cmdexpand.c (127257B)
1 // cmdexpand.c: functions for command-line completion 2 3 #include <assert.h> 4 #include <stdbool.h> 5 #include <stdint.h> 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <uv.h> 10 11 #include "nvim/api/private/defs.h" 12 #include "nvim/api/private/helpers.h" 13 #include "nvim/arglist.h" 14 #include "nvim/ascii_defs.h" 15 #include "nvim/autocmd.h" 16 #include "nvim/buffer.h" 17 #include "nvim/buffer_defs.h" 18 #include "nvim/charset.h" 19 #include "nvim/cmdexpand.h" 20 #include "nvim/cmdhist.h" 21 #include "nvim/drawscreen.h" 22 #include "nvim/errors.h" 23 #include "nvim/eval.h" 24 #include "nvim/eval/funcs.h" 25 #include "nvim/eval/typval.h" 26 #include "nvim/eval/typval_defs.h" 27 #include "nvim/eval/userfunc.h" 28 #include "nvim/eval/vars.h" 29 #include "nvim/ex_cmds.h" 30 #include "nvim/ex_cmds_defs.h" 31 #include "nvim/ex_docmd.h" 32 #include "nvim/ex_getln.h" 33 #include "nvim/fuzzy.h" 34 #include "nvim/garray.h" 35 #include "nvim/garray_defs.h" 36 #include "nvim/getchar.h" 37 #include "nvim/gettext_defs.h" 38 #include "nvim/globals.h" 39 #include "nvim/grid.h" 40 #include "nvim/hashtab.h" 41 #include "nvim/hashtab_defs.h" 42 #include "nvim/help.h" 43 #include "nvim/highlight.h" 44 #include "nvim/highlight_defs.h" 45 #include "nvim/highlight_group.h" 46 #include "nvim/insexpand.h" 47 #include "nvim/keycodes.h" 48 #include "nvim/lua/executor.h" 49 #include "nvim/macros_defs.h" 50 #include "nvim/mapping.h" 51 #include "nvim/mbyte.h" 52 #include "nvim/mbyte_defs.h" 53 #include "nvim/memline.h" 54 #include "nvim/memory.h" 55 #include "nvim/menu.h" 56 #include "nvim/message.h" 57 #include "nvim/option.h" 58 #include "nvim/option_vars.h" 59 #include "nvim/os/fs.h" 60 #include "nvim/os/lang.h" 61 #include "nvim/os/os.h" 62 #include "nvim/os/os_defs.h" 63 #include "nvim/path.h" 64 #include "nvim/popupmenu.h" 65 #include "nvim/profile.h" 66 #include "nvim/regexp.h" 67 #include "nvim/runtime.h" 68 #include "nvim/runtime_defs.h" 69 #include "nvim/search.h" 70 #include "nvim/sign.h" 71 #include "nvim/statusline.h" 72 #include "nvim/strings.h" 73 #include "nvim/syntax.h" 74 #include "nvim/tag.h" 75 #include "nvim/types_defs.h" 76 #include "nvim/ui.h" 77 #include "nvim/ui_defs.h" 78 #include "nvim/usercmd.h" 79 #include "nvim/vim_defs.h" 80 #include "nvim/window.h" 81 82 /// Type used by call_user_expand_func 83 typedef void *(*user_expand_func_T)(const char *, int, typval_T *); 84 85 #include "cmdexpand.c.generated.h" 86 87 static bool cmd_showtail; ///< Only show path tail in lists ? 88 static bool may_expand_pattern = false; 89 static pos_T pre_incsearch_pos; ///< Cursor position when incsearch started 90 91 /// "compl_match_array" points the currently displayed list of entries in the 92 /// popup menu. It is NULL when there is no popup menu. 93 static pumitem_T *compl_match_array = NULL; 94 static int compl_match_arraysize; 95 /// First column in cmdline of the matched item for completion. 96 static int compl_startcol; 97 static int compl_selected; 98 /// cmdline before expansion 99 static char *cmdline_orig = NULL; 100 101 #define SHOW_MATCH(m) (showtail ? showmatches_gettail(matches[m], false) : matches[m]) 102 103 /// Returns true if fuzzy completion is supported for a given cmdline completion 104 /// context. 105 static bool cmdline_fuzzy_completion_supported(const expand_T *const xp) 106 FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE 107 { 108 switch (xp->xp_context) { 109 case EXPAND_BOOL_SETTINGS: 110 case EXPAND_COLORS: 111 case EXPAND_COMPILER: 112 case EXPAND_DIRECTORIES: 113 case EXPAND_DIRS_IN_CDPATH: 114 case EXPAND_FILES: 115 case EXPAND_FILES_IN_PATH: 116 case EXPAND_FILETYPE: 117 case EXPAND_FILETYPECMD: 118 case EXPAND_FINDFUNC: 119 case EXPAND_HELP: 120 case EXPAND_KEYMAP: 121 case EXPAND_LUA: 122 case EXPAND_OLD_SETTING: 123 case EXPAND_STRING_SETTING: 124 case EXPAND_SETTING_SUBTRACT: 125 case EXPAND_OWNSYNTAX: 126 case EXPAND_PACKADD: 127 case EXPAND_RUNTIME: 128 case EXPAND_SHELLCMD: 129 case EXPAND_SHELLCMDLINE: 130 case EXPAND_TAGS: 131 case EXPAND_TAGS_LISTFILES: 132 case EXPAND_USER_LIST: 133 case EXPAND_USER_LUA: 134 return false; 135 136 default: 137 break; 138 } 139 140 return wop_flags & kOptWopFlagFuzzy; 141 } 142 143 /// Returns true if fuzzy completion for cmdline completion is enabled and 144 /// "fuzzystr" is not empty. If search pattern is empty, then don't use fuzzy 145 /// matching. 146 bool cmdline_fuzzy_complete(const char *const fuzzystr) 147 FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE 148 { 149 return (wop_flags & kOptWopFlagFuzzy) && *fuzzystr != NUL; 150 } 151 152 /// Sort function for the completion matches. 153 /// <SNR> functions should be sorted to the end. 154 static int sort_func_compare(const void *s1, const void *s2) 155 { 156 char *p1 = *(char **)s1; 157 char *p2 = *(char **)s2; 158 159 if (*p1 != '<' && *p2 == '<') { 160 return -1; 161 } 162 if (*p1 == '<' && *p2 != '<') { 163 return 1; 164 } 165 return strcmp(p1, p2); 166 } 167 168 /// Escape special characters in the cmdline completion matches. 169 static void wildescape(expand_T *xp, const char *str, int numfiles, char **files) 170 { 171 char *p; 172 const int vse_what = xp->xp_context == EXPAND_BUFFERS ? VSE_BUFFER : VSE_NONE; 173 174 if (xp->xp_context == EXPAND_FILES 175 || xp->xp_context == EXPAND_FILES_IN_PATH 176 || xp->xp_context == EXPAND_SHELLCMD 177 || xp->xp_context == EXPAND_BUFFERS 178 || xp->xp_context == EXPAND_DIRECTORIES 179 || xp->xp_context == EXPAND_DIRS_IN_CDPATH) { 180 // Insert a backslash into a file name before a space, \, %, # 181 // and wildmatch characters, except '~'. 182 for (int i = 0; i < numfiles; i++) { 183 // for ":set path=" we need to escape spaces twice 184 if (xp->xp_backslash & XP_BS_THREE) { 185 char *pat = (xp->xp_backslash & XP_BS_COMMA) ? " ," : " "; 186 p = vim_strsave_escaped(files[i], pat); 187 xfree(files[i]); 188 files[i] = p; 189 #if defined(BACKSLASH_IN_FILENAME) 190 p = vim_strsave_escaped(files[i], " "); 191 xfree(files[i]); 192 files[i] = p; 193 #endif 194 } else if (xp->xp_backslash & XP_BS_COMMA) { 195 if (vim_strchr(files[i], ',') != NULL) { 196 p = vim_strsave_escaped(files[i], ","); 197 xfree(files[i]); 198 files[i] = p; 199 } 200 } 201 #ifdef BACKSLASH_IN_FILENAME 202 p = vim_strsave_fnameescape(files[i], vse_what); 203 #else 204 p = vim_strsave_fnameescape(files[i], xp->xp_shell ? VSE_SHELL : vse_what); 205 #endif 206 xfree(files[i]); 207 files[i] = p; 208 209 // If 'str' starts with "\~", replace "~" at start of 210 // files[i] with "\~". 211 if (str[0] == '\\' && str[1] == '~' && files[i][0] == '~') { 212 escape_fname(&files[i]); 213 } 214 } 215 xp->xp_backslash = XP_BS_NONE; 216 217 // If the first file starts with a '+' escape it. Otherwise it 218 // could be seen as "+cmd". 219 if (*files[0] == '+') { 220 escape_fname(&files[0]); 221 } 222 } else if (xp->xp_context == EXPAND_TAGS) { 223 // Insert a backslash before characters in a tag name that 224 // would terminate the ":tag" command. 225 for (int i = 0; i < numfiles; i++) { 226 p = vim_strsave_escaped(files[i], "\\|\""); 227 xfree(files[i]); 228 files[i] = p; 229 } 230 } 231 } 232 233 /// Escape special characters in the cmdline completion matches. 234 static void ExpandEscape(expand_T *xp, char *str, int numfiles, char **files, int options) 235 { 236 // May change home directory back to "~" 237 if (options & WILD_HOME_REPLACE) { 238 tilde_replace(str, numfiles, files); 239 } 240 241 if (options & WILD_ESCAPE) { 242 wildescape(xp, str, numfiles, files); 243 } 244 } 245 246 /// Return FAIL if this is not an appropriate context in which to do 247 /// completion of anything, return OK if it is (even if there are no matches). 248 /// For the caller, this means that the character is just passed through like a 249 /// normal character (instead of being expanded). This allows :s/^I^D etc. 250 /// 251 /// @param options extra options for ExpandOne() 252 /// @param escape if true, escape the returned matches 253 int nextwild(expand_T *xp, int type, int options, bool escape) 254 { 255 CmdlineInfo *const ccline = get_cmdline_info(); 256 char *p; 257 bool from_wildtrigger_func = options & WILD_FUNC_TRIGGER; 258 bool wild_navigate = (type == WILD_NEXT || type == WILD_PREV 259 || type == WILD_PAGEUP || type == WILD_PAGEDOWN 260 || type == WILD_PUM_WANT); 261 262 if (xp->xp_numfiles == -1) { 263 pre_incsearch_pos = xp->xp_pre_incsearch_pos; 264 if (ccline->input_fn && ccline->xp_context == EXPAND_COMMANDS) { 265 // Expand commands typed in input() function 266 set_cmd_context(xp, ccline->cmdbuff, ccline->cmdlen, ccline->cmdpos, false); 267 } else { 268 may_expand_pattern = options & WILD_MAY_EXPAND_PATTERN; 269 set_expand_context(xp); 270 may_expand_pattern = false; 271 } 272 if (xp->xp_context == EXPAND_LUA) { 273 nlua_expand_pat(xp); 274 } 275 cmd_showtail = expand_showtail(xp); 276 } 277 278 if (xp->xp_context == EXPAND_UNSUCCESSFUL) { 279 beep_flush(); 280 return OK; // Something illegal on command line 281 } 282 if (xp->xp_context == EXPAND_NOTHING) { 283 // Caller can use the character as a normal char instead 284 return FAIL; 285 } 286 287 int i = (int)(xp->xp_pattern - ccline->cmdbuff); 288 assert(ccline->cmdpos >= i); 289 xp->xp_pattern_len = (size_t)ccline->cmdpos - (size_t)i; 290 291 // Skip showing matches if prefix is invalid during wildtrigger() 292 if (from_wildtrigger_func && xp->xp_context == EXPAND_COMMANDS 293 && xp->xp_pattern_len == 0) { 294 return FAIL; 295 } 296 297 // If cmd_silent is set then don't show the dots, because redrawcmd() below 298 // won't remove them. 299 if (!cmd_silent && !from_wildtrigger_func && !wild_navigate 300 && !(ui_has(kUICmdline) || ui_has(kUIWildmenu))) { 301 msg_puts("..."); // show that we are busy 302 ui_flush(); 303 } 304 305 if (wild_navigate) { 306 // Get next/previous match for a previous expanded pattern. 307 p = ExpandOne(xp, NULL, NULL, 0, type); 308 } else { 309 char *tmp; 310 if (cmdline_fuzzy_completion_supported(xp) 311 || xp->xp_context == EXPAND_PATTERN_IN_BUF) { 312 // Don't modify the search string 313 tmp = xstrnsave(xp->xp_pattern, xp->xp_pattern_len); 314 } else { 315 tmp = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); 316 } 317 // Translate string into pattern and expand it. 318 const int use_options = (options 319 | WILD_HOME_REPLACE 320 | WILD_ADD_SLASH 321 | WILD_SILENT 322 | (escape ? WILD_ESCAPE : 0) 323 | (p_wic ? WILD_ICASE : 0)); 324 p = ExpandOne(xp, tmp, xstrnsave(&ccline->cmdbuff[i], xp->xp_pattern_len), 325 use_options, type); 326 xfree(tmp); 327 // Longest match: make sure it is not shorter, happens with :help. 328 if (p != NULL && type == WILD_LONGEST) { 329 int j; 330 for (j = 0; (size_t)j < xp->xp_pattern_len; j++) { 331 char c = ccline->cmdbuff[i + j]; 332 if (c == '*' || c == '?') { 333 break; 334 } 335 } 336 if ((int)strlen(p) < j) { 337 XFREE_CLEAR(p); 338 } 339 } 340 } 341 342 // Save cmdline before inserting selected item 343 if (!wild_navigate && ccline->cmdbuff != NULL) { 344 xfree(cmdline_orig); 345 cmdline_orig = xstrnsave(ccline->cmdbuff, (size_t)ccline->cmdlen); 346 } 347 348 if (p != NULL && !got_int && !(options & WILD_NOSELECT)) { 349 size_t plen = strlen(p); 350 int difflen = (int)plen - (int)(xp->xp_pattern_len); 351 if (ccline->cmdlen + difflen + 4 > ccline->cmdbufflen) { 352 realloc_cmdbuff(ccline->cmdlen + difflen + 4); 353 xp->xp_pattern = ccline->cmdbuff + i; 354 } 355 assert(ccline->cmdpos <= ccline->cmdlen); 356 memmove(&ccline->cmdbuff[ccline->cmdpos + difflen], 357 &ccline->cmdbuff[ccline->cmdpos], 358 (size_t)ccline->cmdlen - (size_t)ccline->cmdpos + 1); 359 memmove(&ccline->cmdbuff[i], p, plen); 360 ccline->cmdlen += difflen; 361 ccline->cmdpos += difflen; 362 } 363 364 redrawcmd(); 365 cursorcmd(); 366 367 // When expanding a ":map" command and no matches are found, assume that 368 // the key is supposed to be inserted literally 369 if (xp->xp_context == EXPAND_MAPPINGS && p == NULL) { 370 return FAIL; 371 } 372 373 if (xp->xp_numfiles <= 0 && p == NULL) { 374 beep_flush(); 375 } else if (xp->xp_numfiles == 1 && !(options & WILD_NOSELECT) && !wild_navigate) { 376 // free expanded pattern 377 ExpandOne(xp, NULL, NULL, 0, WILD_FREE); 378 } 379 380 xfree(p); 381 382 return OK; 383 } 384 385 /// Create completion popup menu with items from "matches". 386 static void cmdline_pum_create(CmdlineInfo *ccline, expand_T *xp, char **matches, int numMatches, 387 bool showtail, bool noselect) 388 { 389 assert(numMatches >= 0); 390 // Add all the completion matches 391 compl_match_array = xmalloc(sizeof(pumitem_T) * (size_t)numMatches); 392 compl_match_arraysize = numMatches; 393 for (int i = 0; i < numMatches; i++) { 394 compl_match_array[i] = (pumitem_T){ 395 .pum_text = SHOW_MATCH(i), 396 .pum_info = NULL, 397 .pum_extra = NULL, 398 .pum_kind = NULL, 399 .pum_user_abbr_hlattr = -1, 400 .pum_user_kind_hlattr = -1, 401 }; 402 } 403 404 // Compute the popup menu starting column 405 char *endpos = showtail ? showmatches_gettail(xp->xp_pattern, noselect) : xp->xp_pattern; 406 if (ui_has(kUICmdline) && cmdline_win == NULL) { 407 compl_startcol = (int)(endpos - ccline->cmdbuff); 408 } else { 409 compl_startcol = cmd_screencol((int)(endpos - ccline->cmdbuff)); 410 } 411 } 412 413 void cmdline_pum_display(bool changed_array) 414 { 415 pum_display(compl_match_array, compl_match_arraysize, compl_selected, 416 changed_array, compl_startcol); 417 } 418 419 /// Returns true if the cmdline completion popup menu is being displayed. 420 bool cmdline_pum_active(void) 421 { 422 return pum_visible() && compl_match_array != NULL; 423 } 424 425 /// Remove the cmdline completion popup menu (if present), free the list of items. 426 void cmdline_pum_remove(bool defer_redraw) 427 { 428 pum_undisplay(!defer_redraw); 429 XFREE_CLEAR(compl_match_array); 430 compl_match_arraysize = 0; 431 } 432 433 void cmdline_pum_cleanup(CmdlineInfo *cclp) 434 { 435 cmdline_pum_remove(false); 436 wildmenu_cleanup(cclp); 437 } 438 439 /// Returns the current cmdline completion pattern. 440 char *cmdline_compl_pattern(void) 441 { 442 expand_T *xp = get_cmdline_info()->xpc; 443 return xp == NULL ? NULL : xp->xp_orig; 444 } 445 446 /// Returns true if fuzzy cmdline completion is active, false otherwise. 447 bool cmdline_compl_is_fuzzy(void) 448 { 449 expand_T *xp = get_cmdline_info()->xpc; 450 return xp != NULL && cmdline_fuzzy_completion_supported(xp); 451 } 452 453 /// Checks whether popup menu should be used for cmdline completion wildmenu. 454 /// 455 /// @param wildmenu whether wildmenu is needed by current 'wildmode' part 456 static bool cmdline_compl_use_pum(bool need_wildmenu) 457 { 458 return ((need_wildmenu && (wop_flags & kOptWopFlagPum) 459 && !(ui_has(kUICmdline) && cmdline_win == NULL)) 460 || ui_has(kUIWildmenu) || (ui_has(kUICmdline) && ui_has(kUIPopupmenu))); 461 } 462 463 /// Return the number of characters that should be skipped in the wildmenu 464 /// These are backslashes used for escaping. Do show backslashes in help tags 465 /// and in search pattern completion matches. 466 static int skip_wildmenu_char(expand_T *xp, char *s) 467 { 468 if ((rem_backslash(s) && xp->xp_context != EXPAND_HELP 469 && xp->xp_context != EXPAND_PATTERN_IN_BUF) 470 || ((xp->xp_context == EXPAND_MENUS || xp->xp_context == EXPAND_MENUNAMES) 471 && (s[0] == '\t' || (s[0] == '\\' && s[1] != NUL)))) { 472 #ifndef BACKSLASH_IN_FILENAME 473 // TODO(bfredl): Why in the actual fuck are we special casing the 474 // shell variety deep in the redraw logic? Shell special snowflakiness 475 // should already be eliminated multiple layers before reaching the 476 // screen infracstructure. 477 if (xp->xp_shell && csh_like_shell() && s[1] == '\\' && s[2] == '!') { 478 return 2; 479 } 480 #endif 481 return 1; 482 } 483 return 0; 484 } 485 486 /// Get the length of an item as it will be shown in the status line. 487 static int wildmenu_match_len(expand_T *xp, char *s) 488 { 489 int len = 0; 490 491 int emenu = (xp->xp_context == EXPAND_MENUS 492 || xp->xp_context == EXPAND_MENUNAMES); 493 494 // Check for menu separators - replace with '|'. 495 if (emenu && menu_is_separator(s)) { 496 return 1; 497 } 498 499 while (*s != NUL) { 500 s += skip_wildmenu_char(xp, s); 501 len += ptr2cells(s); 502 MB_PTR_ADV(s); 503 } 504 505 return len; 506 } 507 508 /// Show wildchar matches in the status line. 509 /// Show at least the "match" item. 510 /// We start at item "first_match" in the list and show all matches that fit. 511 /// 512 /// If inversion is possible we use it. Else '=' characters are used. 513 /// 514 /// @param matches list of matches 515 static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int match, bool showtail) 516 { 517 bool highlight = true; 518 char *selstart = NULL; 519 int selstart_col = 0; 520 char *selend = NULL; 521 static int first_match = 0; 522 bool add_left = false; 523 int i, l; 524 525 if (matches == NULL) { // interrupted completion? 526 return; 527 } 528 529 char *buf = xmalloc((size_t)Columns * MB_MAXBYTES + 1); 530 531 if (match == -1) { // don't show match but original text 532 match = 0; 533 highlight = false; 534 } 535 // count 1 for the ending ">" 536 int clen = wildmenu_match_len(xp, SHOW_MATCH(match)) + 3; // length in screen cells 537 if (match == 0) { 538 first_match = 0; 539 } else if (match < first_match) { 540 // jumping left, as far as we can go 541 first_match = match; 542 add_left = true; 543 } else { 544 // check if match fits on the screen 545 for (i = first_match; i < match; i++) { 546 clen += wildmenu_match_len(xp, SHOW_MATCH(i)) + 2; 547 } 548 if (first_match > 0) { 549 clen += 2; 550 } 551 // jumping right, put match at the left 552 if (clen > Columns) { 553 first_match = match; 554 // if showing the last match, we can add some on the left 555 clen = 2; 556 for (i = match; i < num_matches; i++) { 557 clen += wildmenu_match_len(xp, SHOW_MATCH(i)) + 2; 558 if (clen >= Columns) { 559 break; 560 } 561 } 562 if (i == num_matches) { 563 add_left = true; 564 } 565 } 566 } 567 if (add_left) { 568 while (first_match > 0) { 569 clen += wildmenu_match_len(xp, SHOW_MATCH(first_match - 1)) + 2; 570 if (clen >= Columns) { 571 break; 572 } 573 first_match--; 574 } 575 } 576 577 int len; 578 hlf_T group; 579 schar_T fillchar = fillchar_status(&group, curwin); 580 int attr = win_hl_attr(curwin, (int)group); 581 582 if (first_match == 0) { 583 *buf = NUL; 584 len = 0; 585 } else { 586 STRCPY(buf, "< "); 587 len = 2; 588 } 589 clen = len; 590 591 i = first_match; 592 while (clen + wildmenu_match_len(xp, SHOW_MATCH(i)) + 2 < Columns) { 593 if (i == match) { 594 selstart = buf + len; 595 selstart_col = clen; 596 } 597 598 char *s = SHOW_MATCH(i); 599 // Check for menu separators - replace with '|' 600 int emenu = (xp->xp_context == EXPAND_MENUS || xp->xp_context == EXPAND_MENUNAMES); 601 if (emenu && menu_is_separator(s)) { 602 STRCPY(buf + len, transchar('|')); 603 l = (int)strlen(buf + len); 604 len += l; 605 clen += l; 606 } else { 607 for (; *s != NUL; s++) { 608 s += skip_wildmenu_char(xp, s); 609 clen += ptr2cells(s); 610 if ((l = utfc_ptr2len(s)) > 1) { 611 strncpy(buf + len, s, (size_t)l); // NOLINT(runtime/printf) 612 s += l - 1; 613 len += l; 614 } else { 615 STRCPY(buf + len, transchar_byte((uint8_t)(*s))); 616 len += (int)strlen(buf + len); 617 } 618 } 619 } 620 if (i == match) { 621 selend = buf + len; 622 } 623 624 *(buf + len++) = ' '; 625 *(buf + len++) = ' '; 626 clen += 2; 627 if (++i == num_matches) { 628 break; 629 } 630 } 631 632 if (i != num_matches) { 633 *(buf + len++) = '>'; 634 clen++; 635 } 636 637 buf[len] = NUL; 638 639 int row = cmdline_row - 1; 640 if (row >= 0) { 641 if (wild_menu_showing == 0) { 642 if (msg_scrolled > 0) { 643 // Put the wildmenu just above the command line. If there is 644 // no room, scroll the screen one line up. 645 if (cmdline_row == Rows - 1) { 646 msg_scroll_up(false, false); 647 msg_scrolled++; 648 } else { 649 cmdline_row++; 650 row++; 651 } 652 wild_menu_showing = WM_SCROLLED; 653 } else { 654 // Create status line if needed by setting 'laststatus' to 2. 655 // Set 'winminheight' to zero to avoid that the window is 656 // resized. 657 if (lastwin->w_status_height == 0 && global_stl_height() == 0) { 658 save_p_ls = (int)p_ls; 659 save_p_wmh = (int)p_wmh; 660 p_ls = 2; 661 p_wmh = 0; 662 last_status(false); 663 } 664 wild_menu_showing = WM_SHOWN; 665 } 666 } 667 668 // Tricky: wildmenu can be drawn either over a status line, or at empty 669 // scrolled space in the message output 670 grid_line_start((wild_menu_showing == WM_SCROLLED) ? &msg_grid_adj : &default_gridview, row); 671 672 grid_line_puts(0, buf, -1, attr); 673 if (selstart != NULL && highlight) { 674 *selend = NUL; 675 grid_line_puts(selstart_col, selstart, -1, HL_ATTR(HLF_WM)); 676 } 677 678 grid_line_fill(clen, Columns, fillchar, attr); 679 680 grid_line_flush(); 681 } 682 683 win_redraw_last_status(topframe); 684 xfree(buf); 685 } 686 687 /// Get the next or prev cmdline completion match. The index of the match is set 688 /// in "xp->xp_selected" 689 static char *get_next_or_prev_match(int mode, expand_T *xp) 690 { 691 // When no matches found, return NULL 692 if (xp->xp_numfiles <= 0) { 693 return NULL; 694 } 695 696 int findex = xp->xp_selected; 697 698 if (mode == WILD_PREV) { 699 // Select the last entry if at original text 700 if (findex == -1) { 701 findex = xp->xp_numfiles; 702 } 703 // Otherwise select the previous entry 704 findex--; 705 } else if (mode == WILD_NEXT) { 706 // Select the next entry 707 findex++; 708 } else if (mode == WILD_PAGEUP || mode == WILD_PAGEDOWN) { 709 // Get the height of popup menu (used for both PAGEUP and PAGEDOWN) 710 int ht = pum_get_height(); 711 if (ht > 3) { 712 ht -= 2; 713 } 714 715 if (mode == WILD_PAGEUP) { 716 if (findex == 0) { 717 // at the first entry, don't select any entries 718 findex = -1; 719 } else if (findex < 0) { 720 // no entry is selected. select the last entry 721 findex = xp->xp_numfiles - 1; 722 } else { 723 // go up by the pum height 724 findex = MAX(findex - ht, 0); 725 } 726 } else { // mode == WILD_PAGEDOWN 727 if (findex == xp->xp_numfiles - 1) { 728 // at the last entry, don't select any entries 729 findex = -1; 730 } else if (findex < 0) { 731 // no entry is selected. select the first entry 732 findex = 0; 733 } else { 734 // go down by the pum height 735 findex = MIN(findex + ht, xp->xp_numfiles - 1); 736 } 737 } 738 } else { // mode == WILD_PUM_WANT 739 assert(pum_want.active); 740 findex = pum_want.item; 741 } 742 743 // Handle wrapping around 744 if (findex < 0 || findex >= xp->xp_numfiles) { 745 // If original text exists, return to it when wrapping around 746 if (xp->xp_orig != NULL) { 747 findex = -1; 748 } else { 749 // Wrap around to opposite end 750 findex = (findex < 0) ? xp->xp_numfiles - 1 : 0; 751 } 752 } 753 754 // Display matches on screen 755 if (p_wmnu) { 756 if (compl_match_array) { 757 compl_selected = findex; 758 cmdline_pum_display(false); 759 } else if (cmdline_compl_use_pum(true)) { 760 cmdline_pum_create(get_cmdline_info(), xp, xp->xp_files, xp->xp_numfiles, 761 cmd_showtail, false); 762 compl_selected = findex; 763 pum_clear(); 764 cmdline_pum_display(true); 765 } else { 766 redraw_wildmenu(xp, xp->xp_numfiles, xp->xp_files, findex, cmd_showtail); 767 } 768 } 769 770 xp->xp_selected = findex; 771 // Return the original text or the selected match 772 return xstrdup(findex == -1 ? xp->xp_orig : xp->xp_files[findex]); 773 } 774 775 /// Start the command-line expansion and get the matches. 776 static char *ExpandOne_start(int mode, expand_T *xp, char *str, int options) 777 { 778 int non_suf_match; // number without matching suffix 779 char *ss = NULL; 780 781 // Do the expansion. 782 if (ExpandFromContext(xp, str, &xp->xp_files, &xp->xp_numfiles, options) == FAIL) { 783 #ifdef FNAME_ILLEGAL 784 // Illegal file name has been silently skipped. But when there 785 // are wildcards, the real problem is that there was no match, 786 // causing the pattern to be added, which has illegal characters. 787 if (!(options & WILD_SILENT) && (options & WILD_LIST_NOTFOUND)) { 788 semsg(_(e_nomatch2), str); 789 } 790 #endif 791 } else if (xp->xp_numfiles == 0) { 792 if (!(options & WILD_SILENT)) { 793 semsg(_(e_nomatch2), str); 794 } 795 } else { 796 // Escape the matches for use on the command line. 797 ExpandEscape(xp, str, xp->xp_numfiles, xp->xp_files, options); 798 799 // Check for matching suffixes in file names. 800 if (mode != WILD_ALL && mode != WILD_ALL_KEEP && mode != WILD_LONGEST) { 801 if (xp->xp_numfiles) { 802 non_suf_match = xp->xp_numfiles; 803 } else { 804 non_suf_match = 1; 805 } 806 if ((xp->xp_context == EXPAND_FILES 807 || xp->xp_context == EXPAND_DIRECTORIES) 808 && xp->xp_numfiles > 1) { 809 // More than one match; check suffix. 810 // The files will have been sorted on matching suffix in 811 // expand_wildcards, only need to check the first two. 812 non_suf_match = 0; 813 for (int i = 0; i < 2; i++) { 814 if (match_suffix(xp->xp_files[i])) { 815 non_suf_match++; 816 } 817 } 818 } 819 if (non_suf_match != 1) { 820 // Can we ever get here unless it's while expanding 821 // interactively? If not, we can get rid of this all 822 // together. Don't really want to wait for this message 823 // (and possibly have to hit return to continue!). 824 if (!(options & WILD_SILENT)) { 825 emsg(_(e_toomany)); 826 } else if (!(options & WILD_NO_BEEP)) { 827 beep_flush(); 828 } 829 } 830 if (!(non_suf_match != 1 && mode == WILD_EXPAND_FREE)) { 831 ss = xstrdup(xp->xp_files[0]); 832 } 833 } 834 } 835 836 return ss; 837 } 838 839 /// Return the longest common part in the list of cmdline completion matches. 840 static char *find_longest_match(expand_T *xp, int options) 841 { 842 size_t len = 0; 843 844 for (size_t mb_len; xp->xp_files[0][len]; len += mb_len) { 845 mb_len = (size_t)utfc_ptr2len(&xp->xp_files[0][len]); 846 int c0 = utf_ptr2char(&xp->xp_files[0][len]); 847 int i; 848 for (i = 1; i < xp->xp_numfiles; i++) { 849 int ci = utf_ptr2char(&xp->xp_files[i][len]); 850 851 if (p_fic && (xp->xp_context == EXPAND_DIRECTORIES 852 || xp->xp_context == EXPAND_FILES 853 || xp->xp_context == EXPAND_SHELLCMD 854 || xp->xp_context == EXPAND_BUFFERS)) { 855 if (mb_tolower(c0) != mb_tolower(ci)) { 856 break; 857 } 858 } else if (c0 != ci) { 859 break; 860 } 861 } 862 if (i < xp->xp_numfiles) { 863 if (!(options & WILD_NO_BEEP)) { 864 vim_beep(kOptBoFlagWildmode); 865 } 866 break; 867 } 868 } 869 870 return xmemdupz(xp->xp_files[0], len); 871 } 872 873 /// Do wildcard expansion on the string "str". 874 /// Chars that should not be expanded must be preceded with a backslash. 875 /// Return a pointer to allocated memory containing the new string. 876 /// Return NULL for failure. 877 /// 878 /// "orig" is the originally expanded string, copied to allocated memory. It 879 /// should either be kept in "xp->xp_orig" or freed. When "mode" is WILD_NEXT 880 /// or WILD_PREV "orig" should be NULL. 881 /// 882 /// Results are cached in xp->xp_files and xp->xp_numfiles, except when "mode" 883 /// is WILD_EXPAND_FREE or WILD_ALL. 884 /// 885 /// mode = WILD_FREE: just free previously expanded matches 886 /// mode = WILD_EXPAND_FREE: normal expansion, do not keep matches 887 /// mode = WILD_EXPAND_KEEP: normal expansion, keep matches 888 /// mode = WILD_NEXT: use next match in multiple match, wrap to first 889 /// mode = WILD_PREV: use previous match in multiple match, wrap to first 890 /// mode = WILD_ALL: return all matches concatenated 891 /// mode = WILD_LONGEST: return longest matched part 892 /// mode = WILD_ALL_KEEP: get all matches, keep matches 893 /// mode = WILD_APPLY: apply the item selected in the cmdline completion 894 /// popup menu and close the menu. 895 /// mode = WILD_CANCEL: cancel and close the cmdline completion popup and 896 /// use the original text. 897 /// mode = WILD_PUM_WANT: use the match at index pum_want.item 898 /// 899 /// options = WILD_LIST_NOTFOUND: list entries without a match 900 /// options = WILD_HOME_REPLACE: do home_replace() for buffer names 901 /// options = WILD_USE_NL: Use '\n' for WILD_ALL 902 /// options = WILD_NO_BEEP: Don't beep for multiple matches 903 /// options = WILD_ADD_SLASH: add a slash after directory names 904 /// options = WILD_KEEP_ALL: don't remove 'wildignore' entries 905 /// options = WILD_SILENT: don't print warning messages 906 /// options = WILD_ESCAPE: put backslash before special chars 907 /// options = WILD_ICASE: ignore case for files 908 /// 909 /// The variables xp->xp_context and xp->xp_backslash must have been set! 910 /// 911 /// @param orig allocated copy of original of expanded string 912 char *ExpandOne(expand_T *xp, char *str, char *orig, int options, int mode) 913 { 914 char *ss = NULL; 915 bool orig_saved = false; 916 917 // first handle the case of using an old match 918 if (mode == WILD_NEXT || mode == WILD_PREV 919 || mode == WILD_PAGEUP || mode == WILD_PAGEDOWN 920 || mode == WILD_PUM_WANT) { 921 return get_next_or_prev_match(mode, xp); 922 } 923 924 if (mode == WILD_CANCEL) { 925 ss = xstrdup(xp->xp_orig ? xp->xp_orig : ""); 926 } else if (mode == WILD_APPLY) { 927 ss = xstrdup(xp->xp_selected == -1 928 ? (xp->xp_orig ? xp->xp_orig : "") 929 : xp->xp_files[xp->xp_selected]); 930 } 931 932 // free old names 933 if (xp->xp_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) { 934 FreeWild(xp->xp_numfiles, xp->xp_files); 935 xp->xp_numfiles = -1; 936 XFREE_CLEAR(xp->xp_orig); 937 938 // The entries from xp_files may be used in the PUM, remove it. 939 if (compl_match_array != NULL) { 940 cmdline_pum_remove(false); 941 } 942 } 943 xp->xp_selected = (options & WILD_NOSELECT) ? -1 : 0; 944 945 if (mode == WILD_FREE) { // only release file name 946 return NULL; 947 } 948 949 if (xp->xp_numfiles == -1 && mode != WILD_APPLY && mode != WILD_CANCEL) { 950 xfree(xp->xp_orig); 951 xp->xp_orig = orig; 952 orig_saved = true; 953 954 ss = ExpandOne_start(mode, xp, str, options); 955 } 956 957 // Find longest common part 958 if (mode == WILD_LONGEST && xp->xp_numfiles > 0) { 959 ss = find_longest_match(xp, options); 960 xp->xp_selected = -1; // next p_wc gets first one 961 } 962 963 // Concatenate all matching names. Unless interrupted, this can be slow 964 // and the result probably won't be used. 965 if (mode == WILD_ALL && xp->xp_numfiles > 0 && !got_int) { 966 size_t ss_size = 0; 967 char *prefix = ""; 968 char *suffix = (options & WILD_USE_NL) ? "\n" : " "; 969 const int n = xp->xp_numfiles - 1; 970 971 if (xp->xp_prefix == XP_PREFIX_NO) { 972 prefix = "no"; 973 ss_size = STRLEN_LITERAL("no") * (size_t)n; 974 } else if (xp->xp_prefix == XP_PREFIX_INV) { 975 prefix = "inv"; 976 ss_size = STRLEN_LITERAL("inv") * (size_t)n; 977 } 978 979 for (int i = 0; i < xp->xp_numfiles; i++) { 980 ss_size += strlen(xp->xp_files[i]) + 1; // +1 for the suffix 981 } 982 ss_size++; // +1 for the NUL 983 984 ss = xmalloc(ss_size); 985 *ss = NUL; 986 char *ssp = ss; 987 for (int i = 0; i < xp->xp_numfiles; i++) { 988 if (i > 0) { 989 ssp = xstpcpy(ssp, prefix); 990 } 991 ssp = xstpcpy(ssp, xp->xp_files[i]); 992 if (i < n) { 993 ssp = xstpcpy(ssp, suffix); 994 } 995 assert(ssp < ss + ss_size); 996 } 997 } 998 999 if (mode == WILD_EXPAND_FREE || mode == WILD_ALL) { 1000 ExpandCleanup(xp); 1001 } 1002 1003 // Free "orig" if it wasn't stored in "xp->xp_orig". 1004 if (!orig_saved) { 1005 xfree(orig); 1006 } 1007 1008 return ss; 1009 } 1010 1011 /// Prepare an expand structure for use. 1012 void ExpandInit(expand_T *xp) 1013 FUNC_ATTR_NONNULL_ALL 1014 { 1015 CLEAR_POINTER(xp); 1016 xp->xp_backslash = XP_BS_NONE; 1017 xp->xp_prefix = XP_PREFIX_NONE; 1018 xp->xp_numfiles = -1; 1019 } 1020 1021 /// Cleanup an expand structure after use. 1022 void ExpandCleanup(expand_T *xp) 1023 { 1024 if (xp->xp_numfiles >= 0) { 1025 FreeWild(xp->xp_numfiles, xp->xp_files); 1026 xp->xp_numfiles = -1; 1027 } 1028 XFREE_CLEAR(xp->xp_orig); 1029 } 1030 1031 void clear_cmdline_orig(void) 1032 { 1033 XFREE_CLEAR(cmdline_orig); 1034 } 1035 1036 /// Display one line of completion matches. Multiple matches are displayed in 1037 /// each line (used by wildmode=list and CTRL-D) 1038 /// 1039 /// @param matches list of completion match names 1040 /// @param numMatches number of completion matches in "matches" 1041 /// @param lines number of output lines 1042 /// @param linenr line number of matches to display 1043 /// @param maxlen maximum number of characters in each line 1044 /// @param showtail display only the tail of the full path of a file name 1045 static void showmatches_oneline(expand_T *xp, char **matches, int numMatches, int lines, int linenr, 1046 int maxlen, bool showtail) 1047 { 1048 char *p; 1049 int lastlen = 999; 1050 for (int j = linenr; j < numMatches; j += lines) { 1051 if (xp->xp_context == EXPAND_TAGS_LISTFILES) { 1052 msg_outtrans(matches[j], HLF_D, false); 1053 p = matches[j] + strlen(matches[j]) + 1; 1054 msg_advance(maxlen + 1); 1055 msg_puts(p); 1056 msg_advance(maxlen + 3); 1057 msg_outtrans_long(p + 2, HLF_D); 1058 break; 1059 } 1060 for (int i = maxlen - lastlen; --i >= 0;) { 1061 msg_putchar(' '); 1062 } 1063 bool isdir; 1064 if (xp->xp_context == EXPAND_FILES 1065 || xp->xp_context == EXPAND_SHELLCMD 1066 || xp->xp_context == EXPAND_BUFFERS) { 1067 // highlight directories 1068 if (xp->xp_numfiles != -1) { 1069 // Expansion was done before and special characters 1070 // were escaped, need to halve backslashes. Also 1071 // $HOME has been replaced with ~/. 1072 char *exp_path = expand_env_save_opt(matches[j], true); 1073 char *path = exp_path != NULL ? exp_path : matches[j]; 1074 char *halved_slash = backslash_halve_save(path); 1075 isdir = os_isdir(halved_slash); 1076 xfree(exp_path); 1077 if (halved_slash != path) { 1078 xfree(halved_slash); 1079 } 1080 } else { 1081 // Expansion was done here, file names are literal. 1082 isdir = os_isdir(matches[j]); 1083 } 1084 if (showtail) { 1085 p = SHOW_MATCH(j); 1086 } else { 1087 home_replace(NULL, matches[j], NameBuff, MAXPATHL, true); 1088 p = NameBuff; 1089 } 1090 } else { 1091 isdir = false; 1092 p = SHOW_MATCH(j); 1093 } 1094 lastlen = msg_outtrans(p, isdir ? HLF_D : 0, false); 1095 } 1096 if (msg_col > 0) { // when not wrapped around 1097 msg_clr_eos(); 1098 msg_putchar('\n'); 1099 } 1100 } 1101 1102 /// Display completion matches. 1103 /// Returns EXPAND_NOTHING when the character that triggered expansion should be 1104 /// inserted as a normal character. 1105 int showmatches(expand_T *xp, bool display_wildmenu, bool display_list, bool noselect) 1106 { 1107 CmdlineInfo *const ccline = get_cmdline_info(); 1108 int numMatches; 1109 char **matches; 1110 int maxlen; 1111 int lines; 1112 int columns; 1113 bool showtail; 1114 1115 if (xp->xp_numfiles == -1) { 1116 set_expand_context(xp); 1117 if (xp->xp_context == EXPAND_LUA) { 1118 nlua_expand_pat(xp); 1119 } 1120 int retval = expand_cmdline(xp, ccline->cmdbuff, ccline->cmdpos, 1121 &numMatches, &matches); 1122 if (retval != EXPAND_OK) { 1123 return retval; 1124 } 1125 showtail = expand_showtail(xp); 1126 } else { 1127 numMatches = xp->xp_numfiles; 1128 matches = xp->xp_files; 1129 showtail = cmd_showtail; 1130 } 1131 1132 if (cmdline_compl_use_pum(display_wildmenu && !display_list)) { 1133 cmdline_pum_create(ccline, xp, matches, numMatches, showtail, noselect); 1134 compl_selected = noselect ? -1 : 0; 1135 pum_clear(); 1136 cmdline_pum_display(true); 1137 return EXPAND_OK; 1138 } 1139 1140 if (display_list) { 1141 msg_didany = false; // lines_left will be set 1142 msg_start(); // prepare for paging 1143 if (!ui_has(kUIMessages)) { 1144 msg_putchar('\n'); 1145 } 1146 ui_flush(); 1147 cmdline_row = msg_row; 1148 msg_didany = false; // lines_left will be set again 1149 msg_ext_set_kind("wildlist"); 1150 msg_start(); // prepare for paging 1151 } 1152 1153 if (got_int) { 1154 got_int = false; // only interrupt the completion, not the cmd line 1155 } else if (display_wildmenu && !display_list) { 1156 redraw_wildmenu(xp, numMatches, matches, noselect ? -1 : 0, 1157 showtail); // display statusbar menu 1158 } else if (display_list) { 1159 // find the length of the longest file name 1160 maxlen = 0; 1161 for (int i = 0; i < numMatches; i++) { 1162 int len; 1163 if (!showtail && (xp->xp_context == EXPAND_FILES 1164 || xp->xp_context == EXPAND_SHELLCMD 1165 || xp->xp_context == EXPAND_BUFFERS)) { 1166 home_replace(NULL, matches[i], NameBuff, MAXPATHL, true); 1167 len = vim_strsize(NameBuff); 1168 } else { 1169 len = vim_strsize(SHOW_MATCH(i)); 1170 } 1171 maxlen = MAX(maxlen, len); 1172 } 1173 1174 if (xp->xp_context == EXPAND_TAGS_LISTFILES) { 1175 lines = numMatches; 1176 } else { 1177 // compute the number of columns and lines for the listing 1178 maxlen += 2; // two spaces between file names 1179 columns = (Columns + 2) / maxlen; 1180 if (columns < 1) { 1181 columns = 1; 1182 } 1183 lines = (numMatches + columns - 1) / columns; 1184 } 1185 1186 if (xp->xp_context == EXPAND_TAGS_LISTFILES) { 1187 msg_puts_hl(_("tagname"), HLF_T, false); 1188 msg_clr_eos(); 1189 msg_advance(maxlen - 3); 1190 msg_puts_hl(_(" kind file\n"), HLF_T, false); 1191 } 1192 1193 // list the files line by line 1194 for (int i = 0; i < lines; i++) { 1195 showmatches_oneline(xp, matches, numMatches, lines, i, maxlen, showtail); 1196 if (got_int) { 1197 got_int = false; 1198 break; 1199 } 1200 } 1201 1202 // we redraw the command below the lines that we have just listed 1203 // This is a bit tricky, but it saves a lot of screen updating. 1204 cmdline_row = msg_row; // will put it back later 1205 } 1206 1207 if (xp->xp_numfiles == -1) { 1208 FreeWild(numMatches, matches); 1209 } 1210 1211 return EXPAND_OK; 1212 } 1213 1214 /// path_tail() version for showmatches() and redraw_wildmenu(): 1215 /// Return the tail of file name path "s", ignoring a trailing "/". 1216 static char *showmatches_gettail(char *s, bool eager) 1217 { 1218 char *t = s; 1219 bool had_sep = false; 1220 1221 for (char *p = s; *p != NUL;) { 1222 if (vim_ispathsep(*p) 1223 #ifdef BACKSLASH_IN_FILENAME 1224 && !rem_backslash(p) 1225 #endif 1226 ) { 1227 if (eager) { 1228 t = p + 1; 1229 } else { 1230 had_sep = true; 1231 } 1232 } else if (had_sep) { 1233 t = p; 1234 had_sep = false; 1235 } 1236 MB_PTR_ADV(p); 1237 } 1238 return t; 1239 } 1240 1241 /// Return true if we only need to show the tail of completion matches. 1242 /// When not completing file names or there is a wildcard in the path false is 1243 /// returned. 1244 static bool expand_showtail(expand_T *xp) 1245 { 1246 // When not completing file names a "/" may mean something different. 1247 if (xp->xp_context != EXPAND_FILES 1248 && xp->xp_context != EXPAND_SHELLCMD 1249 && xp->xp_context != EXPAND_DIRECTORIES) { 1250 return false; 1251 } 1252 1253 char *end = path_tail(xp->xp_pattern); 1254 if (end == xp->xp_pattern) { // there is no path separator 1255 return false; 1256 } 1257 1258 for (char *s = xp->xp_pattern; s < end; s++) { 1259 // Skip escaped wildcards. Only when the backslash is not a path 1260 // separator, on DOS the '*' "path\*\file" must not be skipped. 1261 if (rem_backslash(s)) { 1262 s++; 1263 } else if (vim_strchr("*?[", (uint8_t)(*s)) != NULL) { 1264 return false; 1265 } 1266 } 1267 return true; 1268 } 1269 1270 /// Prepare a string for expansion. 1271 /// 1272 /// When expanding file names: The string will be used with expand_wildcards(). 1273 /// Copy "fname[len]" into allocated memory and add a '*' at the end. 1274 /// When expanding other names: The string will be used with regcomp(). Copy 1275 /// the name into allocated memory and prepend "^". 1276 /// 1277 /// @param context EXPAND_FILES etc. 1278 char *addstar(char *fname, size_t len, int context) 1279 FUNC_ATTR_NONNULL_RET 1280 { 1281 char *retval; 1282 1283 if (context != EXPAND_FILES 1284 && context != EXPAND_FILES_IN_PATH 1285 && context != EXPAND_SHELLCMD 1286 && context != EXPAND_DIRECTORIES 1287 && context != EXPAND_DIRS_IN_CDPATH) { 1288 // Matching will be done internally (on something other than files). 1289 // So we convert the file-matching-type wildcards into our kind for 1290 // use with vim_regcomp(). First work out how long it will be: 1291 1292 // For help tags the translation is done in find_help_tags(). 1293 // For a tag pattern starting with "/" no translation is needed. 1294 if (context == EXPAND_FINDFUNC 1295 || context == EXPAND_HELP 1296 || context == EXPAND_COLORS 1297 || context == EXPAND_COMPILER 1298 || context == EXPAND_OWNSYNTAX 1299 || context == EXPAND_FILETYPE 1300 || context == EXPAND_KEYMAP 1301 || context == EXPAND_PACKADD 1302 || context == EXPAND_RUNTIME 1303 || ((context == EXPAND_TAGS_LISTFILES || context == EXPAND_TAGS) 1304 && fname[0] == '/') 1305 || context == EXPAND_CHECKHEALTH 1306 || context == EXPAND_LSP 1307 || context == EXPAND_LUA) { 1308 retval = xstrnsave(fname, len); 1309 } else { 1310 size_t new_len = len + 2; // +2 for '^' at start, NUL at end 1311 for (size_t i = 0; i < len; i++) { 1312 if (fname[i] == '*' || fname[i] == '~') { 1313 new_len++; // '*' needs to be replaced by ".*" 1314 // '~' needs to be replaced by "\~" 1315 } 1316 // Buffer names are like file names. "." should be literal 1317 if (context == EXPAND_BUFFERS && fname[i] == '.') { 1318 new_len++; // "." becomes "\." 1319 } 1320 // Custom expansion takes care of special things, match 1321 // backslashes literally (perhaps also for other types?) 1322 if ((context == EXPAND_USER_DEFINED 1323 || context == EXPAND_USER_LIST) && fname[i] == '\\') { 1324 new_len++; // '\' becomes "\\" 1325 } 1326 } 1327 retval = xmalloc(new_len); 1328 { 1329 retval[0] = '^'; 1330 size_t j = 1; 1331 for (size_t i = 0; i < len; i++, j++) { 1332 // Skip backslash. But why? At least keep it for custom 1333 // expansion. 1334 if (context != EXPAND_USER_DEFINED 1335 && context != EXPAND_USER_LIST 1336 && fname[i] == '\\' 1337 && ++i == len) { 1338 break; 1339 } 1340 1341 switch (fname[i]) { 1342 case '*': 1343 retval[j++] = '.'; 1344 break; 1345 case '~': 1346 retval[j++] = '\\'; 1347 break; 1348 case '?': 1349 retval[j] = '.'; 1350 continue; 1351 case '.': 1352 if (context == EXPAND_BUFFERS) { 1353 retval[j++] = '\\'; 1354 } 1355 break; 1356 case '\\': 1357 if (context == EXPAND_USER_DEFINED 1358 || context == EXPAND_USER_LIST) { 1359 retval[j++] = '\\'; 1360 } 1361 break; 1362 } 1363 retval[j] = fname[i]; 1364 } 1365 retval[j] = NUL; 1366 } 1367 } 1368 } else { 1369 retval = xmalloc(len + 4); 1370 xmemcpyz(retval, fname, len); 1371 1372 // Don't add a star to *, ~, ~user, $var or `cmd`. 1373 // * would become **, which walks the whole tree. 1374 // ~ would be at the start of the file name, but not the tail. 1375 // $ could be anywhere in the tail. 1376 // ` could be anywhere in the file name. 1377 // When the name ends in '$' don't add a star, remove the '$'. 1378 char *tail = path_tail(retval); 1379 int ends_in_star = (len > 0 && retval[len - 1] == '*'); 1380 #ifndef BACKSLASH_IN_FILENAME 1381 for (ssize_t k = (ssize_t)len - 2; k >= 0; k--) { 1382 if (retval[k] != '\\') { 1383 break; 1384 } 1385 ends_in_star = !ends_in_star; 1386 } 1387 #endif 1388 if ((*retval != '~' || tail != retval) 1389 && !ends_in_star 1390 && vim_strchr(tail, '$') == NULL 1391 && vim_strchr(retval, '`') == NULL) { 1392 retval[len++] = '*'; 1393 } else if (len > 0 && retval[len - 1] == '$') { 1394 len--; 1395 } 1396 retval[len] = NUL; 1397 } 1398 return retval; 1399 } 1400 1401 /// Must parse the command line so far to work out what context we are in. 1402 /// Completion can then be done based on that context. 1403 /// This routine sets the variables: 1404 /// xp->xp_pattern The start of the pattern to be expanded within 1405 /// the command line (ends at the cursor). 1406 /// xp->xp_context The type of thing to expand. Will be one of: 1407 /// 1408 /// EXPAND_UNSUCCESSFUL Used sometimes when there is something illegal on 1409 /// the command line, like an unknown command. Caller 1410 /// should beep. 1411 /// EXPAND_NOTHING Unrecognised context for completion, use char like 1412 /// a normal char, rather than for completion. eg 1413 /// :s/^I/ 1414 /// EXPAND_COMMANDS Cursor is still touching the command, so complete 1415 /// it. 1416 /// EXPAND_BUFFERS Complete file names for :buf and :sbuf commands. 1417 /// EXPAND_FILES After command with EX_XFILE set, or after setting 1418 /// with kOptFlagExpand set. eg :e ^I, :w>>^I 1419 /// EXPAND_DIRECTORIES In some cases this is used instead of the latter 1420 /// when we know only directories are of interest. 1421 /// E.g. :set dir=^I and :cd ^I 1422 /// EXPAND_SHELLCMD After ":!cmd", ":r !cmd" or ":w !cmd". 1423 /// EXPAND_SETTINGS Complete variable names. eg :set d^I 1424 /// EXPAND_BOOL_SETTINGS Complete boolean variables only, eg :set no^I 1425 /// EXPAND_TAGS Complete tags from the files in p_tags. eg :ta a^I 1426 /// EXPAND_TAGS_LISTFILES As above, but list filenames on ^D, after :tselect 1427 /// EXPAND_HELP Complete tags from the file 'helpfile'/tags 1428 /// EXPAND_EVENTS Complete event names 1429 /// EXPAND_SYNTAX Complete :syntax command arguments 1430 /// EXPAND_HIGHLIGHT Complete highlight (syntax) group names 1431 /// EXPAND_AUGROUP Complete autocommand group names 1432 /// EXPAND_USER_VARS Complete user defined variable names, eg :unlet a^I 1433 /// EXPAND_MAPPINGS Complete mapping and abbreviation names, 1434 /// eg :unmap a^I , :cunab x^I 1435 /// EXPAND_FUNCTIONS Complete internal or user defined function names, 1436 /// eg :call sub^I 1437 /// EXPAND_USER_FUNC Complete user defined function names, eg :delf F^I 1438 /// EXPAND_EXPRESSION Complete internal or user defined function/variable 1439 /// names in expressions, eg :while s^I 1440 /// EXPAND_ENV_VARS Complete environment variable names 1441 /// EXPAND_USER Complete user names 1442 /// EXPAND_PATTERN_IN_BUF Complete pattern in '/', '?', ':s', ':g', etc. 1443 void set_expand_context(expand_T *xp) 1444 { 1445 CmdlineInfo *const ccline = get_cmdline_info(); 1446 1447 // Handle search commands: '/' or '?' 1448 if ((ccline->cmdfirstc == '/' || ccline->cmdfirstc == '?') 1449 && may_expand_pattern) { 1450 xp->xp_context = EXPAND_PATTERN_IN_BUF; 1451 xp->xp_search_dir = (ccline->cmdfirstc == '/') ? FORWARD : BACKWARD; 1452 xp->xp_pattern = ccline->cmdbuff; 1453 xp->xp_pattern_len = (size_t)ccline->cmdpos; 1454 search_first_line = 0; // Search entire buffer 1455 return; 1456 } 1457 1458 // Only handle ':', '>', or '=' command-lines, or expression input 1459 if (ccline->cmdfirstc != ':' 1460 && ccline->cmdfirstc != '>' && ccline->cmdfirstc != '=' 1461 && !ccline->input_fn) { 1462 xp->xp_context = EXPAND_NOTHING; 1463 return; 1464 } 1465 1466 // Fallback to command-line expansion 1467 set_cmd_context(xp, ccline->cmdbuff, ccline->cmdlen, ccline->cmdpos, true); 1468 } 1469 1470 /// Sets the index of a built-in or user defined command "cmd" in eap->cmdidx. 1471 /// For user defined commands, the completion context is set in "xp" and the 1472 /// completion flags in "complp". 1473 /// 1474 /// @return a pointer to the text after the command or NULL for failure. 1475 static const char *set_cmd_index(const char *cmd, exarg_T *eap, expand_T *xp, int *complp) 1476 { 1477 const char *p = NULL; 1478 const bool fuzzy = cmdline_fuzzy_complete(cmd); 1479 1480 // Isolate the command and search for it in the command table. 1481 // Exceptions: 1482 // - the 'k' command can directly be followed by any character, but do 1483 // accept "keepmarks", "keepalt" and "keepjumps". As fuzzy matching can 1484 // find matches anywhere in the command name, do this only for command 1485 // expansion based on regular expression and not for fuzzy matching. 1486 // - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' 1487 if (!fuzzy && (*cmd == 'k' && cmd[1] != 'e')) { 1488 eap->cmdidx = CMD_k; 1489 p = cmd + 1; 1490 } else { 1491 p = cmd; 1492 while (ASCII_ISALPHA(*p) || *p == '*') { // Allow * wild card 1493 p++; 1494 } 1495 // a user command may contain digits 1496 if (ASCII_ISUPPER(cmd[0])) { 1497 while (ASCII_ISALNUM(*p) || *p == '*') { 1498 p++; 1499 } 1500 } 1501 // for python 3.x: ":py3*" commands completion 1502 if (cmd[0] == 'p' && cmd[1] == 'y' && p == cmd + 2 && *p == '3') { 1503 p++; 1504 while (ASCII_ISALPHA(*p) || *p == '*') { 1505 p++; 1506 } 1507 } 1508 // check for non-alpha command 1509 if (p == cmd && vim_strchr("@*!=><&~#", (uint8_t)(*p)) != NULL) { 1510 p++; 1511 } 1512 size_t len = (size_t)(p - cmd); 1513 1514 if (len == 0) { 1515 xp->xp_context = EXPAND_UNSUCCESSFUL; 1516 return NULL; 1517 } 1518 1519 eap->cmdidx = excmd_get_cmdidx(cmd, len); 1520 1521 // User defined commands support alphanumeric characters. 1522 // Also when doing fuzzy expansion for non-shell commands, support 1523 // alphanumeric characters. 1524 if ((cmd[0] >= 'A' && cmd[0] <= 'Z') 1525 || (fuzzy && eap->cmdidx != CMD_bang && *p != NUL)) { 1526 while (ASCII_ISALNUM(*p) || *p == '*') { // Allow * wild card 1527 p++; 1528 } 1529 } 1530 } 1531 1532 // If the cursor is touching the command, and it ends in an alphanumeric 1533 // character, complete the command name. 1534 if (*p == NUL && ASCII_ISALNUM(p[-1])) { 1535 return NULL; 1536 } 1537 1538 if (eap->cmdidx == CMD_SIZE) { 1539 if (*cmd == 's' && vim_strchr("cgriI", (uint8_t)cmd[1]) != NULL) { 1540 eap->cmdidx = CMD_substitute; 1541 p = cmd + 1; 1542 } else if (cmd[0] >= 'A' && cmd[0] <= 'Z') { 1543 eap->cmd = (char *)cmd; 1544 p = find_ucmd(eap, (char *)p, NULL, xp, complp); 1545 if (p == NULL) { 1546 eap->cmdidx = CMD_SIZE; // Ambiguous user command. 1547 } 1548 } 1549 } 1550 if (eap->cmdidx == CMD_SIZE) { 1551 // Not still touching the command and it was an illegal one 1552 xp->xp_context = EXPAND_UNSUCCESSFUL; 1553 return NULL; 1554 } 1555 1556 return p; 1557 } 1558 1559 /// Set the completion context for a command argument with wild card characters. 1560 static void set_context_for_wildcard_arg(exarg_T *eap, const char *arg, bool usefilter, 1561 expand_T *xp, int *complp) 1562 { 1563 bool in_quote = false; 1564 const char *bow = NULL; // Beginning of word. 1565 size_t len = 0; 1566 1567 // Allow spaces within back-quotes to count as part of the argument 1568 // being expanded. 1569 xp->xp_pattern = skipwhite(arg); 1570 const char *p = xp->xp_pattern; 1571 while (*p != NUL) { 1572 int c = utf_ptr2char(p); 1573 if (c == '\\' && p[1] != NUL) { 1574 p++; 1575 } else if (c == '`') { 1576 if (!in_quote) { 1577 xp->xp_pattern = (char *)p; 1578 bow = p + 1; 1579 } 1580 in_quote = !in_quote; 1581 // An argument can contain just about everything, except 1582 // characters that end the command and white space. 1583 } else if (c == '|' || c == '\n' || c == '"' || ascii_iswhite(c)) { 1584 len = 0; // avoid getting stuck when space is in 'isfname' 1585 while (*p != NUL) { 1586 c = utf_ptr2char(p); 1587 if (c == '`' || vim_isfilec_or_wc(c)) { 1588 break; 1589 } 1590 len = (size_t)utfc_ptr2len(p); 1591 MB_PTR_ADV(p); 1592 } 1593 if (in_quote) { 1594 bow = p; 1595 } else { 1596 xp->xp_pattern = (char *)p; 1597 } 1598 p -= len; 1599 } 1600 MB_PTR_ADV(p); 1601 } 1602 1603 // If we are still inside the quotes, and we passed a space, just 1604 // expand from there. 1605 if (bow != NULL && in_quote) { 1606 xp->xp_pattern = (char *)bow; 1607 } 1608 xp->xp_context = EXPAND_FILES; 1609 1610 // For a shell command more chars need to be escaped. 1611 if (usefilter 1612 || (eap != NULL && (eap->cmdidx == CMD_bang || eap->cmdidx == CMD_terminal)) 1613 || *complp == EXPAND_SHELLCMDLINE) { 1614 #ifndef BACKSLASH_IN_FILENAME 1615 xp->xp_shell = true; 1616 #endif 1617 // When still after the command name expand executables. 1618 if (xp->xp_pattern == skipwhite(arg)) { 1619 xp->xp_context = EXPAND_SHELLCMD; 1620 } 1621 } 1622 1623 // Check for environment variable. 1624 if (*xp->xp_pattern == '$') { 1625 for (p = xp->xp_pattern + 1; *p != NUL; p++) { 1626 if (!vim_isIDc((uint8_t)(*p))) { 1627 break; 1628 } 1629 } 1630 if (*p == NUL) { 1631 xp->xp_context = EXPAND_ENV_VARS; 1632 xp->xp_pattern++; 1633 // Avoid that the assignment uses EXPAND_FILES again. 1634 if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST) { 1635 *complp = EXPAND_ENV_VARS; 1636 } 1637 } 1638 } 1639 // Check for user names. 1640 if (*xp->xp_pattern == '~') { 1641 for (p = xp->xp_pattern + 1; *p != NUL && *p != '/'; p++) {} 1642 // Complete ~user only if it partially matches a user name. 1643 // A full match ~user<Tab> will be replaced by user's home 1644 // directory i.e. something like ~user<Tab> -> /home/user/ 1645 if (*p == NUL && p > xp->xp_pattern + 1 && match_user(xp->xp_pattern + 1) >= 1) { 1646 xp->xp_context = EXPAND_USER; 1647 xp->xp_pattern++; 1648 } 1649 } 1650 } 1651 1652 /// Set the completion context for the "++opt=arg" argument. Always returns NULL. 1653 static const char *set_context_in_argopt(expand_T *xp, const char *arg) 1654 { 1655 char *p = vim_strchr(arg, '='); 1656 if (p == NULL) { 1657 xp->xp_pattern = (char *)arg; 1658 } else { 1659 xp->xp_pattern = p + 1; 1660 } 1661 1662 xp->xp_context = EXPAND_ARGOPT; 1663 return NULL; 1664 } 1665 1666 /// Set the completion context for the :filter command. Returns a pointer to the 1667 /// next command after the :filter command. 1668 static const char *set_context_in_filter_cmd(expand_T *xp, const char *arg) 1669 { 1670 if (*arg != NUL) { 1671 arg = skip_vimgrep_pat((char *)arg, NULL, NULL); 1672 } 1673 if (arg == NULL || *arg == NUL) { 1674 xp->xp_context = EXPAND_NOTHING; 1675 return NULL; 1676 } 1677 return skipwhite(arg); 1678 } 1679 1680 /// Set the completion context for the :match command. Returns a pointer to the 1681 /// next command after the :match command. 1682 static const char *set_context_in_match_cmd(expand_T *xp, const char *arg) 1683 { 1684 if (*arg == NUL || !ends_excmd(*arg)) { 1685 // also complete "None" 1686 set_context_in_echohl_cmd(xp, arg); 1687 arg = skipwhite(skiptowhite(arg)); 1688 if (*arg != NUL) { 1689 xp->xp_context = EXPAND_NOTHING; 1690 arg = skip_regexp((char *)arg + 1, (uint8_t)(*arg), magic_isset()); 1691 } 1692 } 1693 return find_nextcmd(arg); 1694 } 1695 1696 /// Returns a pointer to the next command after a :global or a :v command. 1697 /// Returns NULL if there is no next command. 1698 static const char *find_cmd_after_global_cmd(const char *arg) 1699 { 1700 const int delim = (uint8_t)(*arg); // Get the delimiter. 1701 if (delim) { 1702 arg++; // Skip delimiter if there is one. 1703 } 1704 1705 while (arg[0] != NUL && (uint8_t)arg[0] != delim) { 1706 if (arg[0] == '\\' && arg[1] != NUL) { 1707 arg++; 1708 } 1709 arg++; 1710 } 1711 if (arg[0] != NUL) { 1712 return arg + 1; 1713 } 1714 1715 return NULL; 1716 } 1717 1718 /// Returns a pointer to the next command after a :substitute or a :& command. 1719 /// Returns NULL if there is no next command. 1720 static const char *find_cmd_after_substitute_cmd(const char *arg) 1721 { 1722 const int delim = (uint8_t)(*arg); 1723 if (delim) { 1724 // Skip "from" part. 1725 arg++; 1726 arg = skip_regexp((char *)arg, delim, magic_isset()); 1727 1728 if (arg[0] != NUL && arg[0] == delim) { 1729 // Skip "to" part. 1730 arg++; 1731 while (arg[0] != NUL && (uint8_t)arg[0] != delim) { 1732 if (arg[0] == '\\' && arg[1] != NUL) { 1733 arg++; 1734 } 1735 arg++; 1736 } 1737 if (arg[0] != NUL) { // Skip delimiter. 1738 arg++; 1739 } 1740 } 1741 } 1742 while (arg[0] && strchr("|\"#", arg[0]) == NULL) { 1743 arg++; 1744 } 1745 if (arg[0] != NUL) { 1746 return arg; 1747 } 1748 1749 return NULL; 1750 } 1751 1752 /// Returns a pointer to the next command after a :isearch/:dsearch/:ilist 1753 /// :dlist/:ijump/:psearch/:djump/:isplit/:dsplit command. 1754 /// Returns NULL if there is no next command. 1755 static const char *find_cmd_after_isearch_cmd(expand_T *xp, const char *arg) 1756 { 1757 // Skip count. 1758 arg = skipwhite(skipdigits(arg)); 1759 if (*arg != '/') { 1760 return NULL; 1761 } 1762 1763 // Match regexp, not just whole words. 1764 for (++arg; *arg && *arg != '/'; arg++) { 1765 if (*arg == '\\' && arg[1] != NUL) { 1766 arg++; 1767 } 1768 } 1769 if (*arg) { 1770 arg = skipwhite(arg + 1); 1771 1772 // Check for trailing illegal characters. 1773 if (*arg == NUL || strchr("|\"\n", *arg) == NULL) { 1774 xp->xp_context = EXPAND_NOTHING; 1775 } else { 1776 return arg; 1777 } 1778 } 1779 1780 return NULL; 1781 } 1782 1783 /// Set the completion context for the :unlet command. Always returns NULL. 1784 static const char *set_context_in_unlet_cmd(expand_T *xp, const char *arg) 1785 { 1786 while ((xp->xp_pattern = strchr(arg, ' ')) != NULL) { 1787 arg = xp->xp_pattern + 1; 1788 } 1789 1790 xp->xp_context = EXPAND_USER_VARS; 1791 xp->xp_pattern = (char *)arg; 1792 1793 if (*xp->xp_pattern == '$') { 1794 xp->xp_context = EXPAND_ENV_VARS; 1795 xp->xp_pattern++; 1796 } 1797 1798 return NULL; 1799 } 1800 1801 /// Set the completion context for the :language command. Always returns NULL. 1802 static const char *set_context_in_lang_cmd(expand_T *xp, const char *arg) 1803 { 1804 const char *p = skiptowhite(arg); 1805 if (*p == NUL) { 1806 xp->xp_context = EXPAND_LANGUAGE; 1807 xp->xp_pattern = (char *)arg; 1808 } else { 1809 if (strncmp(arg, "messages", (size_t)(p - arg)) == 0 1810 || strncmp(arg, "ctype", (size_t)(p - arg)) == 0 1811 || strncmp(arg, "time", (size_t)(p - arg)) == 0 1812 || strncmp(arg, "collate", (size_t)(p - arg)) == 0) { 1813 xp->xp_context = EXPAND_LOCALES; 1814 xp->xp_pattern = skipwhite(p); 1815 } else { 1816 xp->xp_context = EXPAND_NOTHING; 1817 } 1818 } 1819 1820 return NULL; 1821 } 1822 1823 static enum { 1824 EXP_FILETYPECMD_ALL, ///< expand all :filetype values 1825 EXP_FILETYPECMD_PLUGIN, ///< expand plugin on off 1826 EXP_FILETYPECMD_INDENT, ///< expand indent on off 1827 EXP_FILETYPECMD_ONOFF, ///< expand on off 1828 } filetype_expand_what; 1829 1830 enum { 1831 EXPAND_FILETYPECMD_PLUGIN = 0x01, 1832 EXPAND_FILETYPECMD_INDENT = 0x02, 1833 EXPAND_FILETYPECMD_ONOFF = 0x04, 1834 }; 1835 1836 static enum { 1837 EXP_BREAKPT_ADD, ///< expand ":breakadd" sub-commands 1838 EXP_BREAKPT_DEL, ///< expand ":breakdel" sub-commands 1839 EXP_PROFDEL, ///< expand ":profdel" sub-commands 1840 } breakpt_expand_what; 1841 1842 /// Set the completion context for the :breakadd command. Always returns NULL. 1843 static const char *set_context_in_breakadd_cmd(expand_T *xp, const char *arg, cmdidx_T cmdidx) 1844 { 1845 xp->xp_context = EXPAND_BREAKPOINT; 1846 xp->xp_pattern = (char *)arg; 1847 1848 if (cmdidx == CMD_breakadd) { 1849 breakpt_expand_what = EXP_BREAKPT_ADD; 1850 } else if (cmdidx == CMD_breakdel) { 1851 breakpt_expand_what = EXP_BREAKPT_DEL; 1852 } else { 1853 breakpt_expand_what = EXP_PROFDEL; 1854 } 1855 1856 const char *p = skipwhite(arg); 1857 if (*p == NUL) { 1858 return NULL; 1859 } 1860 const char *subcmd_start = p; 1861 1862 if (strncmp("file ", p, 5) == 0 || strncmp("func ", p, 5) == 0) { 1863 // :breakadd file [lnum] <filename> 1864 // :breakadd func [lnum] <funcname> 1865 p += 4; 1866 p = skipwhite(p); 1867 1868 // skip line number (if specified) 1869 if (ascii_isdigit(*p)) { 1870 p = skipdigits(p); 1871 if (*p != ' ') { 1872 xp->xp_context = EXPAND_NOTHING; 1873 return NULL; 1874 } 1875 p = skipwhite(p); 1876 } 1877 if (strncmp("file", subcmd_start, 4) == 0) { 1878 xp->xp_context = EXPAND_FILES; 1879 } else { 1880 xp->xp_context = EXPAND_USER_FUNC; 1881 } 1882 xp->xp_pattern = (char *)p; 1883 } else if (strncmp("expr ", p, 5) == 0) { 1884 // :breakadd expr <expression> 1885 xp->xp_context = EXPAND_EXPRESSION; 1886 xp->xp_pattern = skipwhite(p + 5); 1887 } 1888 1889 return NULL; 1890 } 1891 1892 static const char *set_context_in_scriptnames_cmd(expand_T *xp, const char *arg) 1893 { 1894 xp->xp_context = EXPAND_NOTHING; 1895 xp->xp_pattern = NULL; 1896 1897 char *p = skipwhite(arg); 1898 if (ascii_isdigit(*p)) { 1899 return NULL; 1900 } 1901 1902 xp->xp_context = EXPAND_SCRIPTNAMES; 1903 xp->xp_pattern = p; 1904 1905 return NULL; 1906 } 1907 1908 /// Set the completion context for the :filetype command. Always returns NULL. 1909 static const char *set_context_in_filetype_cmd(expand_T *xp, const char *arg) 1910 { 1911 xp->xp_context = EXPAND_FILETYPECMD; 1912 xp->xp_pattern = (char *)arg; 1913 filetype_expand_what = EXP_FILETYPECMD_ALL; 1914 1915 char *p = skipwhite(arg); 1916 if (*p == NUL) { 1917 return NULL; 1918 } 1919 1920 int val = 0; 1921 1922 while (true) { 1923 if (strncmp(p, "plugin", 6) == 0) { 1924 val |= EXPAND_FILETYPECMD_PLUGIN; 1925 p = skipwhite(p + 6); 1926 continue; 1927 } 1928 if (strncmp(p, "indent", 6) == 0) { 1929 val |= EXPAND_FILETYPECMD_INDENT; 1930 p = skipwhite(p + 6); 1931 continue; 1932 } 1933 break; 1934 } 1935 1936 if ((val & EXPAND_FILETYPECMD_PLUGIN) && (val & EXPAND_FILETYPECMD_INDENT)) { 1937 filetype_expand_what = EXP_FILETYPECMD_ONOFF; 1938 } else if ((val & EXPAND_FILETYPECMD_PLUGIN)) { 1939 filetype_expand_what = EXP_FILETYPECMD_INDENT; 1940 } else if ((val & EXPAND_FILETYPECMD_INDENT)) { 1941 filetype_expand_what = EXP_FILETYPECMD_PLUGIN; 1942 } 1943 1944 xp->xp_pattern = p; 1945 1946 return NULL; 1947 } 1948 1949 /// Sets the completion context for commands that involve a search pattern 1950 /// and a line range (e.g., :s, :g, :v). 1951 static void set_context_with_pattern(expand_T *xp) 1952 { 1953 CmdlineInfo *ccline = get_cmdline_info(); 1954 1955 emsg_off++; 1956 int skiplen = 0; 1957 int dummy, patlen; 1958 int retval = parse_pattern_and_range(&pre_incsearch_pos, &dummy, &skiplen, &patlen); 1959 emsg_off--; 1960 1961 // Check if cursor is within search pattern 1962 if (!retval || ccline->cmdpos <= skiplen || ccline->cmdpos > skiplen + patlen) { 1963 return; 1964 } 1965 1966 xp->xp_pattern = ccline->cmdbuff + skiplen; 1967 xp->xp_pattern_len = (size_t)(ccline->cmdpos - skiplen); 1968 xp->xp_context = EXPAND_PATTERN_IN_BUF; 1969 xp->xp_search_dir = FORWARD; 1970 } 1971 1972 /// Set the completion context in "xp" for command "cmd" with index "cmdidx". 1973 /// The argument to the command is "arg" and the argument flags is "argt". 1974 /// For user-defined commands and for environment variables, "context" has the 1975 /// completion type. 1976 /// 1977 /// @return a pointer to the next command, or NULL if there is no next command. 1978 static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expand_T *xp, 1979 const char *arg, uint32_t argt, int context, bool forceit) 1980 { 1981 const char *nextcmd; 1982 1983 switch (cmdidx) { 1984 case CMD_find: 1985 case CMD_sfind: 1986 case CMD_tabfind: 1987 if (xp->xp_context == EXPAND_FILES) { 1988 xp->xp_context = *get_findfunc() != NUL ? EXPAND_FINDFUNC : EXPAND_FILES_IN_PATH; 1989 } 1990 break; 1991 case CMD_cd: 1992 case CMD_chdir: 1993 case CMD_lcd: 1994 case CMD_lchdir: 1995 case CMD_tcd: 1996 case CMD_tchdir: 1997 if (xp->xp_context == EXPAND_FILES) { 1998 xp->xp_context = EXPAND_DIRS_IN_CDPATH; 1999 } 2000 break; 2001 case CMD_help: 2002 xp->xp_context = EXPAND_HELP; 2003 xp->xp_pattern = (char *)arg; 2004 break; 2005 2006 // Command modifiers: return the argument. 2007 // Also for commands with an argument that is a command. 2008 case CMD_aboveleft: 2009 case CMD_argdo: 2010 case CMD_belowright: 2011 case CMD_botright: 2012 case CMD_browse: 2013 case CMD_bufdo: 2014 case CMD_cdo: 2015 case CMD_cfdo: 2016 case CMD_confirm: 2017 case CMD_debug: 2018 case CMD_folddoclosed: 2019 case CMD_folddoopen: 2020 case CMD_hide: 2021 case CMD_horizontal: 2022 case CMD_keepalt: 2023 case CMD_keepjumps: 2024 case CMD_keepmarks: 2025 case CMD_keeppatterns: 2026 case CMD_ldo: 2027 case CMD_leftabove: 2028 case CMD_lfdo: 2029 case CMD_lockmarks: 2030 case CMD_noautocmd: 2031 case CMD_noswapfile: 2032 case CMD_restart: 2033 case CMD_rightbelow: 2034 case CMD_sandbox: 2035 case CMD_silent: 2036 case CMD_tab: 2037 case CMD_tabdo: 2038 case CMD_topleft: 2039 case CMD_unsilent: 2040 case CMD_verbose: 2041 case CMD_vertical: 2042 case CMD_windo: 2043 return arg; 2044 2045 case CMD_filter: 2046 return set_context_in_filter_cmd(xp, arg); 2047 2048 case CMD_match: 2049 return set_context_in_match_cmd(xp, arg); 2050 2051 // All completion for the +cmdline_compl feature goes here. 2052 2053 case CMD_command: 2054 return set_context_in_user_cmd(xp, arg); 2055 2056 case CMD_delcommand: 2057 xp->xp_context = EXPAND_USER_COMMANDS; 2058 xp->xp_pattern = (char *)arg; 2059 break; 2060 2061 case CMD_global: 2062 case CMD_vglobal: 2063 nextcmd = find_cmd_after_global_cmd(arg); 2064 if (!nextcmd && may_expand_pattern) { 2065 set_context_with_pattern(xp); 2066 } 2067 return nextcmd; 2068 2069 case CMD_and: 2070 case CMD_substitute: 2071 nextcmd = find_cmd_after_substitute_cmd(arg); 2072 if (!nextcmd && may_expand_pattern) { 2073 set_context_with_pattern(xp); 2074 } 2075 return nextcmd; 2076 2077 case CMD_isearch: 2078 case CMD_dsearch: 2079 case CMD_ilist: 2080 case CMD_dlist: 2081 case CMD_ijump: 2082 case CMD_psearch: 2083 case CMD_djump: 2084 case CMD_isplit: 2085 case CMD_dsplit: 2086 return find_cmd_after_isearch_cmd(xp, arg); 2087 case CMD_autocmd: 2088 return set_context_in_autocmd(xp, (char *)arg, false); 2089 2090 case CMD_doautocmd: 2091 case CMD_doautoall: 2092 return set_context_in_autocmd(xp, (char *)arg, true); 2093 case CMD_set: 2094 set_context_in_set_cmd(xp, (char *)arg, 0); 2095 break; 2096 case CMD_setglobal: 2097 set_context_in_set_cmd(xp, (char *)arg, OPT_GLOBAL); 2098 break; 2099 case CMD_setlocal: 2100 set_context_in_set_cmd(xp, (char *)arg, OPT_LOCAL); 2101 break; 2102 case CMD_tag: 2103 case CMD_stag: 2104 case CMD_ptag: 2105 case CMD_ltag: 2106 case CMD_tselect: 2107 case CMD_stselect: 2108 case CMD_ptselect: 2109 case CMD_tjump: 2110 case CMD_stjump: 2111 case CMD_ptjump: 2112 if (wop_flags & kOptWopFlagTagfile) { 2113 xp->xp_context = EXPAND_TAGS_LISTFILES; 2114 } else { 2115 xp->xp_context = EXPAND_TAGS; 2116 } 2117 xp->xp_pattern = (char *)arg; 2118 break; 2119 case CMD_augroup: 2120 xp->xp_context = EXPAND_AUGROUP; 2121 xp->xp_pattern = (char *)arg; 2122 break; 2123 case CMD_syntax: 2124 set_context_in_syntax_cmd(xp, arg); 2125 break; 2126 case CMD_const: 2127 case CMD_let: 2128 case CMD_if: 2129 case CMD_elseif: 2130 case CMD_while: 2131 case CMD_for: 2132 case CMD_echo: 2133 case CMD_echon: 2134 case CMD_execute: 2135 case CMD_echomsg: 2136 case CMD_echoerr: 2137 case CMD_call: 2138 case CMD_return: 2139 case CMD_cexpr: 2140 case CMD_caddexpr: 2141 case CMD_cgetexpr: 2142 case CMD_lexpr: 2143 case CMD_laddexpr: 2144 case CMD_lgetexpr: 2145 set_context_for_expression(xp, (char *)arg, cmdidx); 2146 break; 2147 2148 case CMD_unlet: 2149 return set_context_in_unlet_cmd(xp, arg); 2150 case CMD_function: 2151 case CMD_delfunction: 2152 xp->xp_context = EXPAND_USER_FUNC; 2153 xp->xp_pattern = (char *)arg; 2154 break; 2155 2156 case CMD_echohl: 2157 set_context_in_echohl_cmd(xp, arg); 2158 break; 2159 case CMD_highlight: 2160 set_context_in_highlight_cmd(xp, arg); 2161 break; 2162 case CMD_sign: 2163 set_context_in_sign_cmd(xp, (char *)arg); 2164 break; 2165 case CMD_bdelete: 2166 case CMD_bwipeout: 2167 case CMD_bunload: 2168 while ((xp->xp_pattern = strchr(arg, ' ')) != NULL) { 2169 arg = xp->xp_pattern + 1; 2170 } 2171 FALLTHROUGH; 2172 case CMD_buffer: 2173 case CMD_sbuffer: 2174 case CMD_pbuffer: 2175 case CMD_checktime: 2176 xp->xp_context = EXPAND_BUFFERS; 2177 xp->xp_pattern = (char *)arg; 2178 break; 2179 case CMD_diffget: 2180 case CMD_diffput: 2181 // If current buffer is in diff mode, complete buffer names 2182 // which are in diff mode, and different than current buffer. 2183 xp->xp_context = EXPAND_DIFF_BUFFERS; 2184 xp->xp_pattern = (char *)arg; 2185 break; 2186 2187 case CMD_USER: 2188 case CMD_USER_BUF: 2189 return set_context_in_user_cmdarg(cmd, arg, argt, context, xp, forceit); 2190 2191 case CMD_map: 2192 case CMD_noremap: 2193 case CMD_nmap: 2194 case CMD_nnoremap: 2195 case CMD_vmap: 2196 case CMD_vnoremap: 2197 case CMD_omap: 2198 case CMD_onoremap: 2199 case CMD_imap: 2200 case CMD_inoremap: 2201 case CMD_cmap: 2202 case CMD_cnoremap: 2203 case CMD_lmap: 2204 case CMD_lnoremap: 2205 case CMD_smap: 2206 case CMD_snoremap: 2207 case CMD_xmap: 2208 case CMD_xnoremap: 2209 return set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, false, 2210 false, cmdidx); 2211 case CMD_unmap: 2212 case CMD_nunmap: 2213 case CMD_vunmap: 2214 case CMD_ounmap: 2215 case CMD_iunmap: 2216 case CMD_cunmap: 2217 case CMD_lunmap: 2218 case CMD_sunmap: 2219 case CMD_xunmap: 2220 return set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, false, 2221 true, cmdidx); 2222 case CMD_mapclear: 2223 case CMD_nmapclear: 2224 case CMD_vmapclear: 2225 case CMD_omapclear: 2226 case CMD_imapclear: 2227 case CMD_cmapclear: 2228 case CMD_lmapclear: 2229 case CMD_smapclear: 2230 case CMD_xmapclear: 2231 xp->xp_context = EXPAND_MAPCLEAR; 2232 xp->xp_pattern = (char *)arg; 2233 break; 2234 2235 case CMD_abbreviate: 2236 case CMD_noreabbrev: 2237 case CMD_cabbrev: 2238 case CMD_cnoreabbrev: 2239 case CMD_iabbrev: 2240 case CMD_inoreabbrev: 2241 return set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, true, 2242 false, cmdidx); 2243 case CMD_unabbreviate: 2244 case CMD_cunabbrev: 2245 case CMD_iunabbrev: 2246 return set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, true, 2247 true, cmdidx); 2248 case CMD_menu: 2249 case CMD_noremenu: 2250 case CMD_unmenu: 2251 case CMD_amenu: 2252 case CMD_anoremenu: 2253 case CMD_aunmenu: 2254 case CMD_nmenu: 2255 case CMD_nnoremenu: 2256 case CMD_nunmenu: 2257 case CMD_vmenu: 2258 case CMD_vnoremenu: 2259 case CMD_vunmenu: 2260 case CMD_omenu: 2261 case CMD_onoremenu: 2262 case CMD_ounmenu: 2263 case CMD_imenu: 2264 case CMD_inoremenu: 2265 case CMD_iunmenu: 2266 case CMD_cmenu: 2267 case CMD_cnoremenu: 2268 case CMD_cunmenu: 2269 case CMD_tlmenu: 2270 case CMD_tlnoremenu: 2271 case CMD_tlunmenu: 2272 case CMD_tmenu: 2273 case CMD_tunmenu: 2274 case CMD_popup: 2275 case CMD_emenu: 2276 return set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit); 2277 2278 case CMD_colorscheme: 2279 xp->xp_context = EXPAND_COLORS; 2280 xp->xp_pattern = (char *)arg; 2281 break; 2282 2283 case CMD_compiler: 2284 xp->xp_context = EXPAND_COMPILER; 2285 xp->xp_pattern = (char *)arg; 2286 break; 2287 2288 case CMD_ownsyntax: 2289 xp->xp_context = EXPAND_OWNSYNTAX; 2290 xp->xp_pattern = (char *)arg; 2291 break; 2292 2293 case CMD_setfiletype: 2294 xp->xp_context = EXPAND_FILETYPE; 2295 xp->xp_pattern = (char *)arg; 2296 break; 2297 2298 case CMD_packadd: 2299 xp->xp_context = EXPAND_PACKADD; 2300 xp->xp_pattern = (char *)arg; 2301 break; 2302 2303 case CMD_runtime: 2304 set_context_in_runtime_cmd(xp, arg); 2305 break; 2306 2307 case CMD_language: 2308 return set_context_in_lang_cmd(xp, arg); 2309 2310 case CMD_profile: 2311 set_context_in_profile_cmd(xp, arg); 2312 break; 2313 case CMD_checkhealth: 2314 xp->xp_context = EXPAND_CHECKHEALTH; 2315 break; 2316 2317 case CMD_lsp: 2318 xp->xp_context = EXPAND_LSP; 2319 break; 2320 2321 case CMD_retab: 2322 xp->xp_context = EXPAND_RETAB; 2323 xp->xp_pattern = (char *)arg; 2324 break; 2325 2326 case CMD_messages: 2327 xp->xp_context = EXPAND_MESSAGES; 2328 xp->xp_pattern = (char *)arg; 2329 break; 2330 2331 case CMD_history: 2332 xp->xp_context = EXPAND_HISTORY; 2333 xp->xp_pattern = (char *)arg; 2334 break; 2335 case CMD_syntime: 2336 xp->xp_context = EXPAND_SYNTIME; 2337 xp->xp_pattern = (char *)arg; 2338 break; 2339 2340 case CMD_argdelete: 2341 while ((xp->xp_pattern = vim_strchr(arg, ' ')) != NULL) { 2342 arg = (xp->xp_pattern + 1); 2343 } 2344 xp->xp_context = EXPAND_ARGLIST; 2345 xp->xp_pattern = (char *)arg; 2346 break; 2347 2348 case CMD_breakadd: 2349 case CMD_profdel: 2350 case CMD_breakdel: 2351 return set_context_in_breakadd_cmd(xp, arg, cmdidx); 2352 2353 case CMD_scriptnames: 2354 return set_context_in_scriptnames_cmd(xp, arg); 2355 2356 case CMD_filetype: 2357 return set_context_in_filetype_cmd(xp, arg); 2358 2359 case CMD_lua: 2360 case CMD_equal: 2361 xp->xp_context = EXPAND_LUA; 2362 break; 2363 2364 default: 2365 break; 2366 } 2367 return NULL; 2368 } 2369 2370 /// This is all pretty much copied from do_one_cmd(), with all the extra stuff 2371 /// we don't need/want deleted. Maybe this could be done better if we didn't 2372 /// repeat all this stuff. The only problem is that they may not stay 2373 /// perfectly compatible with each other, but then the command line syntax 2374 /// probably won't change that much -- webb. 2375 /// 2376 /// @param buff buffer for command string 2377 static const char *set_one_cmd_context(expand_T *xp, const char *buff) 2378 { 2379 size_t len = 0; 2380 exarg_T ea; 2381 int context = EXPAND_NOTHING; 2382 bool forceit = false; 2383 bool usefilter = false; // Filter instead of file name. 2384 2385 ExpandInit(xp); 2386 xp->xp_pattern = (char *)buff; 2387 xp->xp_line = (char *)buff; 2388 xp->xp_context = EXPAND_COMMANDS; // Default until we get past command 2389 ea.argt = 0; 2390 2391 // 1. skip comment lines and leading space, colons or bars 2392 const char *cmd; 2393 for (cmd = buff; vim_strchr(" \t:|", (uint8_t)(*cmd)) != NULL; cmd++) {} 2394 xp->xp_pattern = (char *)cmd; 2395 2396 if (*cmd == NUL) { 2397 return NULL; 2398 } 2399 if (*cmd == '"') { // ignore comment lines 2400 xp->xp_context = EXPAND_NOTHING; 2401 return NULL; 2402 } 2403 2404 // 3. skip over a range specifier of the form: addr [,addr] [;addr] .. 2405 cmd = skip_range(cmd, &xp->xp_context); 2406 xp->xp_pattern = (char *)cmd; 2407 if (*cmd == NUL) { 2408 return NULL; 2409 } 2410 if (*cmd == '"') { 2411 xp->xp_context = EXPAND_NOTHING; 2412 return NULL; 2413 } 2414 2415 if (*cmd == '|' || *cmd == '\n') { 2416 return cmd + 1; // There's another command 2417 } 2418 2419 // Get the command index. 2420 const char *p = set_cmd_index(cmd, &ea, xp, &context); 2421 if (p == NULL) { 2422 return NULL; 2423 } 2424 2425 xp->xp_context = EXPAND_NOTHING; // Default now that we're past command 2426 2427 if (*p == '!') { // forced commands 2428 forceit = true; 2429 p++; 2430 } 2431 2432 // 6. parse arguments 2433 if (!IS_USER_CMDIDX(ea.cmdidx)) { 2434 ea.argt = excmd_get_argt(ea.cmdidx); 2435 } 2436 2437 const char *arg = skipwhite(p); 2438 2439 // Does command allow "++argopt" argument? 2440 if (ea.argt & EX_ARGOPT) { 2441 while (*arg != NUL && strncmp(arg, "++", 2) == 0) { 2442 p = arg + 2; 2443 while (*p && !ascii_isspace(*p)) { 2444 MB_PTR_ADV(p); 2445 } 2446 2447 // Still touching the command after "++"? 2448 if (*p == NUL) { 2449 if (ea.argt & EX_ARGOPT) { 2450 return set_context_in_argopt(xp, arg + 2); 2451 } 2452 } 2453 2454 arg = skipwhite(p); 2455 } 2456 } 2457 2458 if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) { 2459 if (*arg == '>') { // append 2460 if (*++arg == '>') { 2461 arg++; 2462 } 2463 arg = skipwhite(arg); 2464 } else if (*arg == '!' && ea.cmdidx == CMD_write) { // :w !filter 2465 arg++; 2466 usefilter = true; 2467 } 2468 } 2469 2470 if (ea.cmdidx == CMD_read) { 2471 usefilter = forceit; // :r! filter if forced 2472 if (*arg == '!') { // :r !filter 2473 arg++; 2474 usefilter = true; 2475 } 2476 } 2477 2478 if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) { 2479 while (*arg == *cmd) { // allow any number of '>' or '<' 2480 arg++; 2481 } 2482 arg = skipwhite(arg); 2483 } 2484 2485 // Does command allow "+command"? 2486 if ((ea.argt & EX_CMDARG) && !usefilter && *arg == '+') { 2487 // Check if we're in the +command 2488 p = arg + 1; 2489 arg = skip_cmd_arg((char *)arg, false); 2490 2491 // Still touching the command after '+'? 2492 if (*arg == NUL) { 2493 return p; 2494 } 2495 2496 // Skip space(s) after +command to get to the real argument. 2497 arg = skipwhite(arg); 2498 } 2499 2500 // Check for '|' to separate commands and '"' to start comments. 2501 // Don't do this for ":read !cmd" and ":write !cmd". 2502 if ((ea.argt & EX_TRLBAR) && !usefilter) { 2503 p = arg; 2504 // ":redir @" is not the start of a comment 2505 if (ea.cmdidx == CMD_redir && p[0] == '@' && p[1] == '"') { 2506 p += 2; 2507 } 2508 while (*p) { 2509 if (*p == Ctrl_V) { 2510 if (p[1] != NUL) { 2511 p++; 2512 } 2513 } else if ((*p == '"' && !(ea.argt & EX_NOTRLCOM)) 2514 || *p == '|' 2515 || *p == '\n') { 2516 if (*(p - 1) != '\\') { 2517 if (*p == '|' || *p == '\n') { 2518 return p + 1; 2519 } 2520 return NULL; // It's a comment 2521 } 2522 } 2523 MB_PTR_ADV(p); 2524 } 2525 } 2526 2527 if (!(ea.argt & EX_EXTRA) && *arg != NUL && strchr("|\"", *arg) == NULL) { 2528 // no arguments allowed but there is something 2529 return NULL; 2530 } 2531 2532 // Find start of last argument (argument just before cursor): 2533 p = buff; 2534 xp->xp_pattern = (char *)p; 2535 len = strlen(buff); 2536 while (*p && p < buff + len) { 2537 if (*p == ' ' || *p == TAB) { 2538 // argument starts after a space 2539 xp->xp_pattern = (char *)++p; 2540 } else { 2541 if (*p == '\\' && *(p + 1) != NUL) { 2542 p++; // skip over escaped character 2543 } 2544 MB_PTR_ADV(p); 2545 } 2546 } 2547 2548 if (ea.argt & EX_XFILE) { 2549 set_context_for_wildcard_arg(&ea, arg, usefilter, xp, &context); 2550 } 2551 2552 // Switch on command name. 2553 return set_context_by_cmdname(cmd, ea.cmdidx, xp, arg, ea.argt, context, forceit); 2554 } 2555 2556 /// Set the completion context in "xp" for command "str" 2557 /// 2558 /// @param str start of command line 2559 /// @param len length of command line (excl. NUL) 2560 /// @param col position of cursor 2561 /// @param use_ccline use ccline for info 2562 void set_cmd_context(expand_T *xp, char *str, int len, int col, int use_ccline) 2563 { 2564 CmdlineInfo *const ccline = get_cmdline_info(); 2565 char old_char = NUL; 2566 2567 // Avoid a UMR warning from Purify, only save the character if it has been 2568 // written before. 2569 if (col < len) { 2570 old_char = str[col]; 2571 } 2572 str[col] = NUL; 2573 const char *nextcomm = str; 2574 2575 if (use_ccline && ccline->cmdfirstc == '=') { 2576 // pass CMD_SIZE because there is no real command 2577 set_context_for_expression(xp, str, CMD_SIZE); 2578 } else if (use_ccline && ccline->input_fn) { 2579 xp->xp_context = ccline->xp_context; 2580 xp->xp_pattern = ccline->cmdbuff; 2581 xp->xp_arg = ccline->xp_arg; 2582 if (xp->xp_context == EXPAND_SHELLCMDLINE) { 2583 int context = xp->xp_context; 2584 set_context_for_wildcard_arg(NULL, xp->xp_pattern, false, xp, &context); 2585 } 2586 } else { 2587 while (nextcomm != NULL) { 2588 nextcomm = set_one_cmd_context(xp, nextcomm); 2589 } 2590 } 2591 2592 // Store the string here so that call_user_expand_func() can get to them 2593 // easily. 2594 xp->xp_line = str; 2595 xp->xp_col = col; 2596 2597 str[col] = old_char; 2598 } 2599 2600 /// Expand the command line "str" from context "xp". 2601 /// "xp" must have been set by set_cmd_context(). 2602 /// xp->xp_pattern points into "str", to where the text that is to be expanded 2603 /// starts. 2604 /// Returns EXPAND_UNSUCCESSFUL when there is something illegal before the 2605 /// cursor. 2606 /// Returns EXPAND_NOTHING when there is nothing to expand, might insert the 2607 /// key that triggered expansion literally. 2608 /// Returns EXPAND_OK otherwise. 2609 /// 2610 /// @param str start of command line 2611 /// @param col position of cursor 2612 /// @param matchcount return: nr of matches 2613 /// @param matches return: array of pointers to matches 2614 int expand_cmdline(expand_T *xp, const char *str, int col, int *matchcount, char ***matches) 2615 { 2616 char *file_str = NULL; 2617 int options = WILD_ADD_SLASH|WILD_SILENT; 2618 2619 if (xp->xp_context == EXPAND_UNSUCCESSFUL) { 2620 beep_flush(); 2621 return EXPAND_UNSUCCESSFUL; // Something illegal on command line 2622 } 2623 if (xp->xp_context == EXPAND_NOTHING) { 2624 // Caller can use the character as a normal char instead 2625 return EXPAND_NOTHING; 2626 } 2627 2628 // add star to file name, or convert to regexp if not exp. files. 2629 assert((str + col) - xp->xp_pattern >= 0); 2630 xp->xp_pattern_len = (size_t)((str + col) - xp->xp_pattern); 2631 if (cmdline_fuzzy_completion_supported(xp)) { 2632 // If fuzzy matching, don't modify the search string 2633 file_str = xstrdup(xp->xp_pattern); 2634 } else { 2635 file_str = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); 2636 } 2637 2638 if (p_wic) { 2639 options += WILD_ICASE; 2640 } 2641 2642 // find all files that match the description 2643 if (ExpandFromContext(xp, file_str, matches, matchcount, options) == FAIL) { 2644 *matchcount = 0; 2645 *matches = NULL; 2646 } 2647 xfree(file_str); 2648 2649 return EXPAND_OK; 2650 } 2651 2652 /// Expand file or directory names. 2653 static int expand_files_and_dirs(expand_T *xp, char *pat, char ***matches, int *numMatches, 2654 int flags, int options) 2655 { 2656 bool free_pat = false; 2657 2658 // for ":set path=" and ":set tags=" halve backslashes for escaped space 2659 if (xp->xp_backslash != XP_BS_NONE) { 2660 free_pat = true; 2661 size_t pat_len = strlen(pat); 2662 pat = xstrnsave(pat, pat_len); 2663 2664 char *pat_end = pat + pat_len; 2665 for (char *p = pat; *p != NUL; p++) { 2666 if (*p != '\\') { 2667 continue; 2668 } 2669 2670 if (xp->xp_backslash & XP_BS_THREE 2671 && *(p + 1) == '\\' 2672 && *(p + 2) == '\\' 2673 && *(p + 3) == ' ') { 2674 char *from = p + 3; 2675 memmove(p, from, (size_t)(pat_end - from) + 1); // +1 for NUL 2676 pat_end -= 3; 2677 } else if (xp->xp_backslash & XP_BS_ONE 2678 && *(p + 1) == ' ') { 2679 char *from = p + 1; 2680 memmove(p, from, (size_t)(pat_end - from) + 1); // +1 for NUL 2681 pat_end--; 2682 } else if (xp->xp_backslash & XP_BS_COMMA) { 2683 if (*(p + 1) == '\\' && *(p + 2) == ',') { 2684 char *from = p + 2; 2685 memmove(p, from, (size_t)(pat_end - from) + 1); // +1 for NUL 2686 pat_end -= 2; 2687 #ifdef BACKSLASH_IN_FILENAME 2688 } else if (*(p + 1) == ',') { 2689 char *from = p + 1; 2690 memmove(p, from, (size_t)(pat_end - from) + 1); // +1 for NUL 2691 pat_end--; 2692 #endif 2693 } 2694 } 2695 } 2696 } 2697 2698 int ret = FAIL; 2699 if (xp->xp_context == EXPAND_FINDFUNC) { 2700 ret = expand_findfunc(pat, matches, numMatches); 2701 } else { 2702 if (xp->xp_context == EXPAND_FILES) { 2703 flags |= EW_FILE; 2704 } else if (xp->xp_context == EXPAND_FILES_IN_PATH) { 2705 flags |= (EW_FILE | EW_PATH); 2706 } else if (xp->xp_context == EXPAND_DIRS_IN_CDPATH) { 2707 flags = (flags | EW_DIR | EW_CDPATH) & ~EW_FILE; 2708 } else { 2709 flags = (flags | EW_DIR) & ~EW_FILE; 2710 } 2711 if (options & WILD_ICASE) { 2712 flags |= EW_ICASE; 2713 } 2714 // Expand wildcards, supporting %:h and the like. 2715 ret = expand_wildcards_eval(&pat, numMatches, matches, flags); 2716 } 2717 if (free_pat) { 2718 xfree(pat); 2719 } 2720 #ifdef BACKSLASH_IN_FILENAME 2721 if (p_csl[0] != NUL && (options & WILD_IGNORE_COMPLETESLASH) == 0) { 2722 for (int j = 0; j < *numMatches; j++) { 2723 char *ptr = (*matches)[j]; 2724 while (*ptr != NUL) { 2725 if (p_csl[0] == 's' && *ptr == '\\') { 2726 *ptr = '/'; 2727 } else if (p_csl[0] == 'b' && *ptr == '/') { 2728 *ptr = '\\'; 2729 } 2730 ptr += utfc_ptr2len(ptr); 2731 } 2732 } 2733 } 2734 #endif 2735 return ret; 2736 } 2737 2738 /// Function given to ExpandGeneric() to obtain the possible arguments of the 2739 /// ":filetype {plugin,indent}" command. 2740 static char *get_filetypecmd_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) 2741 { 2742 if (idx < 0) { 2743 return NULL; 2744 } 2745 2746 if (filetype_expand_what == EXP_FILETYPECMD_ALL && idx < 4) { 2747 char *opts_all[] = { "indent", "plugin", "on", "off" }; 2748 return opts_all[idx]; 2749 } 2750 if (filetype_expand_what == EXP_FILETYPECMD_PLUGIN && idx < 3) { 2751 char *opts_plugin[] = { "plugin", "on", "off" }; 2752 return opts_plugin[idx]; 2753 } 2754 if (filetype_expand_what == EXP_FILETYPECMD_INDENT && idx < 3) { 2755 char *opts_indent[] = { "indent", "on", "off" }; 2756 return opts_indent[idx]; 2757 } 2758 if (filetype_expand_what == EXP_FILETYPECMD_ONOFF && idx < 2) { 2759 char *opts_onoff[] = { "on", "off" }; 2760 return opts_onoff[idx]; 2761 } 2762 return NULL; 2763 } 2764 2765 /// Function given to ExpandGeneric() to obtain the possible arguments of the 2766 /// ":breakadd {expr, file, func, here}" command. 2767 /// ":breakdel {func, file, here}" command. 2768 static char *get_breakadd_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) 2769 { 2770 if (idx >= 0 && idx <= 3) { 2771 char *opts[] = { "expr", "file", "func", "here" }; 2772 2773 // breakadd {expr, file, func, here} 2774 if (breakpt_expand_what == EXP_BREAKPT_ADD) { 2775 return opts[idx]; 2776 } else if (breakpt_expand_what == EXP_BREAKPT_DEL) { 2777 // breakdel {func, file, here} 2778 if (idx <= 2) { 2779 return opts[idx + 1]; 2780 } 2781 } else { 2782 // profdel {func, file} 2783 if (idx <= 1) { 2784 return opts[idx + 1]; 2785 } 2786 } 2787 } 2788 return NULL; 2789 } 2790 2791 /// Function given to ExpandGeneric() to obtain the possible arguments for the 2792 /// ":scriptnames" command. 2793 static char *get_scriptnames_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) 2794 { 2795 if (!SCRIPT_ID_VALID(idx + 1)) { 2796 return NULL; 2797 } 2798 2799 scriptitem_T *si = SCRIPT_ITEM(idx + 1); 2800 home_replace(NULL, si->sn_name, NameBuff, MAXPATHL, true); 2801 return NameBuff; 2802 } 2803 2804 /// Function given to ExpandGeneric() to obtain the possible arguments of the 2805 /// ":retab {-indentonly}" option. 2806 static char *get_retab_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) 2807 { 2808 if (idx == 0) { 2809 return "-indentonly"; 2810 } 2811 return NULL; 2812 } 2813 2814 /// Function given to ExpandGeneric() to obtain the possible arguments of the 2815 /// ":messages {clear}" command. 2816 static char *get_messages_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) 2817 { 2818 if (idx == 0) { 2819 return "clear"; 2820 } 2821 return NULL; 2822 } 2823 2824 static char *get_mapclear_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) 2825 { 2826 if (idx == 0) { 2827 return "<buffer>"; 2828 } 2829 return NULL; 2830 } 2831 2832 /// Completion for |:checkhealth| command. 2833 /// 2834 /// Given to ExpandGeneric() to obtain all available heathcheck names. 2835 /// @param[in] idx Index of the healthcheck item. 2836 /// @param[in] xp Not used. 2837 static char *get_healthcheck_names(expand_T *xp FUNC_ATTR_UNUSED, int idx) 2838 { 2839 static Object names = OBJECT_INIT; 2840 static unsigned last_gen = 0; 2841 2842 if (last_gen != get_cmdline_last_prompt_id() || last_gen == 0) { 2843 Array a = ARRAY_DICT_INIT; 2844 Error err = ERROR_INIT; 2845 Object res = NLUA_EXEC_STATIC("return vim.health._complete()", a, kRetObject, NULL, &err); 2846 api_clear_error(&err); 2847 api_free_object(names); 2848 names = res; 2849 last_gen = get_cmdline_last_prompt_id(); 2850 } 2851 2852 if (names.type == kObjectTypeArray && idx < (int)names.data.array.size 2853 && names.data.array.items[idx].type == kObjectTypeString) { 2854 return names.data.array.items[idx].data.string.data; 2855 } 2856 return NULL; 2857 } 2858 2859 /// Completion for |:lsp| command. 2860 /// 2861 /// Given to ExpandGeneric() to obtain `:lsp` completion. 2862 /// @param[in] idx Index of the item. 2863 /// @param[in] xp Not used. 2864 static char *get_lsp_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) 2865 { 2866 static Object names = OBJECT_INIT; 2867 static char *last_xp_line = NULL; 2868 static unsigned last_gen = 0; 2869 2870 if (last_xp_line == NULL || strcmp(last_xp_line, 2871 xp->xp_line) != 0 2872 || last_gen != get_cmdline_last_prompt_id()) { 2873 xfree(last_xp_line); 2874 last_xp_line = xstrdup(xp->xp_line); 2875 MAXSIZE_TEMP_ARRAY(args, 1); 2876 Error err = ERROR_INIT; 2877 2878 ADD_C(args, CSTR_AS_OBJ(xp->xp_line)); 2879 // Build the current command line as a Lua string argument 2880 Object res = NLUA_EXEC_STATIC("return require'vim._core.ex_cmd'.lsp_complete(...)", args, 2881 kRetObject, NULL, 2882 &err); 2883 api_clear_error(&err); 2884 api_free_object(names); 2885 names = res; 2886 last_gen = get_cmdline_last_prompt_id(); 2887 } 2888 2889 if (names.type == kObjectTypeArray && idx < (int)names.data.array.size 2890 && names.data.array.items[idx].type == kObjectTypeString) { 2891 return names.data.array.items[idx].data.string.data; 2892 } 2893 return NULL; 2894 } 2895 2896 /// Do the expansion based on xp->xp_context and "rmp". 2897 static int ExpandOther(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches, int *numMatches) 2898 { 2899 typedef CompleteListItemGetter ExpandFunc; 2900 static struct expgen { 2901 int context; 2902 ExpandFunc func; 2903 int ic; 2904 int escaped; 2905 } tab[] = { 2906 { EXPAND_COMMANDS, get_command_name, false, true }, 2907 { EXPAND_FILETYPECMD, get_filetypecmd_arg, true, true }, 2908 { EXPAND_MAPCLEAR, get_mapclear_arg, true, true }, 2909 { EXPAND_MESSAGES, get_messages_arg, true, true }, 2910 { EXPAND_HISTORY, get_history_arg, true, true }, 2911 { EXPAND_USER_COMMANDS, get_user_commands, false, true }, 2912 { EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, false, true }, 2913 { EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, false, true }, 2914 { EXPAND_USER_NARGS, get_user_cmd_nargs, false, true }, 2915 { EXPAND_USER_COMPLETE, get_user_cmd_complete, false, true }, 2916 { EXPAND_USER_VARS, get_user_var_name, false, true }, 2917 { EXPAND_FUNCTIONS, get_function_name, false, true }, 2918 { EXPAND_USER_FUNC, get_user_func_name, false, true }, 2919 { EXPAND_EXPRESSION, get_expr_name, false, true }, 2920 { EXPAND_MENUS, get_menu_name, false, true }, 2921 { EXPAND_MENUNAMES, get_menu_names, false, true }, 2922 { EXPAND_SYNTAX, get_syntax_name, true, true }, 2923 { EXPAND_SYNTIME, get_syntime_arg, true, true }, 2924 { EXPAND_HIGHLIGHT, get_highlight_name, true, false }, 2925 { EXPAND_EVENTS, expand_get_event_name, true, false }, 2926 { EXPAND_AUGROUP, expand_get_augroup_name, true, false }, 2927 { EXPAND_SIGN, get_sign_name, true, true }, 2928 { EXPAND_PROFILE, get_profile_name, true, true }, 2929 { EXPAND_LANGUAGE, get_lang_arg, true, false }, 2930 { EXPAND_LOCALES, get_locales, true, false }, 2931 { EXPAND_ENV_VARS, get_env_name, true, true }, 2932 { EXPAND_USER, get_users, true, false }, 2933 { EXPAND_ARGLIST, get_arglist_name, true, false }, 2934 { EXPAND_BREAKPOINT, get_breakadd_arg, true, true }, 2935 { EXPAND_SCRIPTNAMES, get_scriptnames_arg, true, false }, 2936 { EXPAND_RETAB, get_retab_arg, true, true }, 2937 { EXPAND_CHECKHEALTH, get_healthcheck_names, true, false }, 2938 { EXPAND_LSP, get_lsp_arg, true, false }, 2939 }; 2940 int ret = FAIL; 2941 2942 // Find a context in the table and call the ExpandGeneric() with the 2943 // right function to do the expansion. 2944 for (int i = 0; i < (int)ARRAY_SIZE(tab); i++) { 2945 if (xp->xp_context == tab[i].context) { 2946 if (tab[i].ic) { 2947 rmp->rm_ic = true; 2948 } 2949 ExpandGeneric(pat, xp, rmp, matches, numMatches, tab[i].func, tab[i].escaped); 2950 ret = OK; 2951 break; 2952 } 2953 } 2954 2955 return ret; 2956 } 2957 2958 /// Map wild expand options to flags for expand_wildcards() 2959 static int map_wildopts_to_ewflags(int options) 2960 { 2961 int flags = EW_DIR; // include directories 2962 if (options & WILD_LIST_NOTFOUND) { 2963 flags |= EW_NOTFOUND; 2964 } 2965 if (options & WILD_ADD_SLASH) { 2966 flags |= EW_ADDSLASH; 2967 } 2968 if (options & WILD_KEEP_ALL) { 2969 flags |= EW_KEEPALL; 2970 } 2971 if (options & WILD_SILENT) { 2972 flags |= EW_SILENT; 2973 } 2974 if (options & WILD_NOERROR) { 2975 flags |= EW_NOERROR; 2976 } 2977 if (options & WILD_ALLLINKS) { 2978 flags |= EW_ALLLINKS; 2979 } 2980 2981 return flags; 2982 } 2983 2984 /// Do the expansion based on xp->xp_context and "pat". 2985 /// 2986 /// @param options WILD_ flags 2987 static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numMatches, int options) 2988 { 2989 regmatch_T regmatch = { .rm_ic = false }; 2990 int ret; 2991 int flags = map_wildopts_to_ewflags(options); 2992 const bool fuzzy = cmdline_fuzzy_complete(pat) 2993 && cmdline_fuzzy_completion_supported(xp); 2994 2995 if (xp->xp_context == EXPAND_FILES 2996 || xp->xp_context == EXPAND_DIRECTORIES 2997 || xp->xp_context == EXPAND_FILES_IN_PATH 2998 || xp->xp_context == EXPAND_FINDFUNC 2999 || xp->xp_context == EXPAND_DIRS_IN_CDPATH) { 3000 return expand_files_and_dirs(xp, pat, matches, numMatches, flags, options); 3001 } 3002 3003 *matches = NULL; 3004 *numMatches = 0; 3005 if (xp->xp_context == EXPAND_HELP) { 3006 // With an empty argument we would get all the help tags, which is 3007 // very slow. Get matches for "help" instead. 3008 if (find_help_tags(*pat == NUL ? "help" : pat, 3009 numMatches, matches, false) == OK) { 3010 cleanup_help_tags(*numMatches, *matches); 3011 return OK; 3012 } 3013 return FAIL; 3014 } 3015 3016 if (xp->xp_context == EXPAND_SHELLCMD) { 3017 expand_shellcmd(pat, matches, numMatches, flags); 3018 return OK; 3019 } 3020 if (xp->xp_context == EXPAND_OLD_SETTING) { 3021 return ExpandOldSetting(numMatches, matches); 3022 } 3023 if (xp->xp_context == EXPAND_BUFFERS) { 3024 return ExpandBufnames(pat, numMatches, matches, options); 3025 } 3026 if (xp->xp_context == EXPAND_DIFF_BUFFERS) { 3027 return ExpandBufnames(pat, numMatches, matches, options | BUF_DIFF_FILTER); 3028 } 3029 if (xp->xp_context == EXPAND_TAGS 3030 || xp->xp_context == EXPAND_TAGS_LISTFILES) { 3031 return expand_tags(xp->xp_context == EXPAND_TAGS, pat, numMatches, matches); 3032 } 3033 if (xp->xp_context == EXPAND_COLORS) { 3034 char *directories[] = { "colors", NULL }; 3035 return ExpandRTDir(pat, DIP_START + DIP_OPT, numMatches, matches, directories); 3036 } 3037 if (xp->xp_context == EXPAND_COMPILER) { 3038 char *directories[] = { "compiler", NULL }; 3039 return ExpandRTDir(pat, 0, numMatches, matches, directories); 3040 } 3041 if (xp->xp_context == EXPAND_OWNSYNTAX) { 3042 char *directories[] = { "syntax", NULL }; 3043 return ExpandRTDir(pat, 0, numMatches, matches, directories); 3044 } 3045 if (xp->xp_context == EXPAND_FILETYPE) { 3046 char *directories[] = { "syntax", "indent", "ftplugin", NULL }; 3047 return ExpandRTDir(pat, 0, numMatches, matches, directories); 3048 } 3049 if (xp->xp_context == EXPAND_KEYMAP) { 3050 char *directories[] = { "keymap", NULL }; 3051 return ExpandRTDir(pat, 0, numMatches, matches, directories); 3052 } 3053 if (xp->xp_context == EXPAND_USER_LIST) { 3054 return ExpandUserList(xp, matches, numMatches); 3055 } 3056 if (xp->xp_context == EXPAND_USER_LUA) { 3057 return ExpandUserLua(xp, numMatches, matches); 3058 } 3059 if (xp->xp_context == EXPAND_PACKADD) { 3060 return ExpandPackAddDir(pat, numMatches, matches); 3061 } 3062 if (xp->xp_context == EXPAND_RUNTIME) { 3063 return expand_runtime_cmd(pat, numMatches, matches); 3064 } 3065 if (xp->xp_context == EXPAND_PATTERN_IN_BUF) { 3066 return expand_pattern_in_buf(pat, xp->xp_search_dir, matches, numMatches); 3067 } 3068 3069 // When expanding a function name starting with s:, match the <SNR>nr_ 3070 // prefix. 3071 char *tofree = NULL; 3072 if (xp->xp_context == EXPAND_USER_FUNC && strncmp(pat, "^s:", 3) == 0) { 3073 const size_t len = strlen(pat) + 20; 3074 3075 tofree = xmalloc(len); 3076 snprintf(tofree, len, "^<SNR>\\d\\+_%s", pat + 3); 3077 pat = tofree; 3078 } 3079 3080 if (xp->xp_context == EXPAND_LUA) { 3081 return nlua_expand_get_matches(numMatches, matches); 3082 } 3083 3084 if (!fuzzy) { 3085 regmatch.regprog = vim_regcomp(pat, magic_isset() ? RE_MAGIC : 0); 3086 if (regmatch.regprog == NULL) { 3087 xfree(tofree); 3088 return FAIL; 3089 } 3090 // set ignore-case according to p_ic, p_scs and pat 3091 regmatch.rm_ic = ignorecase(pat); 3092 } 3093 3094 if (xp->xp_context == EXPAND_SETTINGS 3095 || xp->xp_context == EXPAND_BOOL_SETTINGS) { 3096 ret = ExpandSettings(xp, ®match, pat, numMatches, matches, fuzzy); 3097 } else if (xp->xp_context == EXPAND_STRING_SETTING) { 3098 ret = ExpandStringSetting(xp, ®match, numMatches, matches); 3099 } else if (xp->xp_context == EXPAND_SETTING_SUBTRACT) { 3100 ret = ExpandSettingSubtract(xp, ®match, numMatches, matches); 3101 } else if (xp->xp_context == EXPAND_MAPPINGS) { 3102 ret = ExpandMappings(pat, ®match, numMatches, matches); 3103 } else if (xp->xp_context == EXPAND_ARGOPT) { 3104 ret = expand_argopt(pat, xp, ®match, matches, numMatches); 3105 } else if (xp->xp_context == EXPAND_USER_DEFINED) { 3106 ret = ExpandUserDefined(pat, xp, ®match, matches, numMatches); 3107 } else { 3108 ret = ExpandOther(pat, xp, ®match, matches, numMatches); 3109 } 3110 3111 if (!fuzzy) { 3112 vim_regfree(regmatch.regprog); 3113 } 3114 xfree(tofree); 3115 3116 return ret; 3117 } 3118 3119 /// Expand a list of names. 3120 /// 3121 /// Generic function for command line completion. It calls a function to 3122 /// obtain strings, one by one. The strings are matched against a regexp 3123 /// program. Matching strings are copied into an array, which is returned. 3124 /// 3125 /// @param func returns a string from the list 3126 void ExpandGeneric(const char *const pat, expand_T *xp, regmatch_T *regmatch, char ***matches, 3127 int *numMatches, CompleteListItemGetter func, bool escaped) 3128 { 3129 const bool fuzzy = cmdline_fuzzy_complete(pat); 3130 *matches = NULL; 3131 *numMatches = 0; 3132 3133 garray_T ga; 3134 if (!fuzzy) { 3135 ga_init(&ga, sizeof(char *), 30); 3136 } else { 3137 ga_init(&ga, sizeof(fuzmatch_str_T), 30); 3138 } 3139 3140 for (int i = 0;; i++) { 3141 char *str = (*func)(xp, i); 3142 if (str == NULL) { // End of list. 3143 break; 3144 } 3145 if (*str == NUL) { // Skip empty strings. 3146 continue; 3147 } 3148 3149 bool match; 3150 int score = 0; 3151 if (xp->xp_pattern[0] != NUL) { 3152 if (!fuzzy) { 3153 match = vim_regexec(regmatch, str, 0); 3154 } else { 3155 score = fuzzy_match_str(str, pat); 3156 match = (score != FUZZY_SCORE_NONE); 3157 } 3158 } else { 3159 match = true; 3160 } 3161 3162 if (!match) { 3163 continue; 3164 } 3165 3166 if (escaped) { 3167 str = vim_strsave_escaped(str, " \t\\."); 3168 } else { 3169 str = xstrdup(str); 3170 } 3171 3172 if (fuzzy) { 3173 GA_APPEND(fuzmatch_str_T, &ga, ((fuzmatch_str_T){ 3174 .idx = ga.ga_len, 3175 .str = str, 3176 .score = score, 3177 })); 3178 } else { 3179 GA_APPEND(char *, &ga, str); 3180 } 3181 3182 if (func == get_menu_names) { 3183 // Test for separator added by get_menu_names(). 3184 str += strlen(str) - 1; 3185 if (*str == '\001') { 3186 *str = '.'; 3187 } 3188 } 3189 } 3190 3191 if (ga.ga_len == 0) { 3192 return; 3193 } 3194 3195 // Sort the matches when using regular expression matching and sorting 3196 // applies to the completion context. Menus and scriptnames should be kept 3197 // in the specified order. 3198 const bool sort_matches = !fuzzy 3199 && xp->xp_context != EXPAND_MENUNAMES 3200 && xp->xp_context != EXPAND_STRING_SETTING 3201 && xp->xp_context != EXPAND_MENUS 3202 && xp->xp_context != EXPAND_SCRIPTNAMES 3203 && xp->xp_context != EXPAND_ARGOPT; 3204 3205 // <SNR> functions should be sorted to the end. 3206 const bool funcsort = xp->xp_context == EXPAND_EXPRESSION 3207 || xp->xp_context == EXPAND_FUNCTIONS 3208 || xp->xp_context == EXPAND_USER_FUNC; 3209 3210 // Sort the matches. 3211 if (sort_matches) { 3212 if (funcsort) { 3213 // <SNR> functions should be sorted to the end. 3214 qsort(ga.ga_data, (size_t)ga.ga_len, sizeof(char *), sort_func_compare); 3215 } else { 3216 sort_strings(ga.ga_data, ga.ga_len); 3217 } 3218 } 3219 3220 if (!fuzzy) { 3221 *matches = ga.ga_data; 3222 *numMatches = ga.ga_len; 3223 } else { 3224 fuzzymatches_to_strmatches(ga.ga_data, matches, ga.ga_len, funcsort); 3225 *numMatches = ga.ga_len; 3226 } 3227 3228 // Reset the variables used for special highlight names expansion, so that 3229 // they don't show up when getting normal highlight names by ID. 3230 reset_expand_highlight(); 3231 } 3232 3233 /// Expand shell command matches in one directory of $PATH. 3234 /// 3235 /// @param pathed_pattern fully pathed pattern 3236 /// @param pathlen length of the path portion of pathed_pattern (0 if no path) 3237 static void expand_shellcmd_onedir(char *pathed_pattern, size_t pathlen, char ***matches, 3238 int *numMatches, int flags, hashtab_T *ht, garray_T *gap) 3239 { 3240 // Expand matches in one directory of $PATH. 3241 if (expand_wildcards(1, &pathed_pattern, numMatches, matches, flags) != OK) { 3242 return; 3243 } 3244 3245 ga_grow(gap, *numMatches); 3246 3247 for (int i = 0; i < *numMatches; i++) { 3248 char *name = (*matches)[i]; 3249 size_t namelen = strlen(name); 3250 3251 if (namelen > pathlen) { 3252 // Check if this name was already found. 3253 hash_T hash = hash_hash(name + pathlen); 3254 hashitem_T *hi = hash_lookup(ht, name + pathlen, namelen - pathlen, hash); 3255 if (HASHITEM_EMPTY(hi)) { 3256 // Remove the path that was prepended. 3257 memmove(name, name + pathlen, namelen - pathlen + 1); // +1 for NUL 3258 ((char **)gap->ga_data)[gap->ga_len++] = name; 3259 hash_add_item(ht, hi, name, hash); 3260 name = NULL; 3261 } 3262 } 3263 xfree(name); 3264 } 3265 xfree(*matches); 3266 } 3267 3268 /// Complete a shell command. 3269 /// 3270 /// @param filepat is a pattern to match with command names. 3271 /// @param[out] matches is pointer to array of pointers to matches. 3272 /// *matches will either be set to NULL or point to 3273 /// allocated memory. 3274 /// @param[out] numMatches is pointer to number of matches. 3275 /// @param flagsarg is a combination of EW_* flags. 3276 static void expand_shellcmd(char *filepat, char ***matches, int *numMatches, int flagsarg) 3277 FUNC_ATTR_NONNULL_ALL 3278 { 3279 char *path = NULL; 3280 garray_T ga; 3281 char *buf = xmalloc(MAXPATHL); 3282 int flags = flagsarg; 3283 bool did_curdir = false; 3284 3285 // for ":set path=" and ":set tags=" halve backslashes for escaped space 3286 size_t patlen = strlen(filepat); 3287 char *pat = xmemdupz(filepat, patlen); 3288 // Replace "\ " with " ". 3289 char *e = pat + patlen; 3290 for (char *s = pat; *s != NUL; s++) { 3291 if (*s != '\\') { 3292 continue; 3293 } 3294 char *p = s + 1; 3295 if (*p == ' ') { 3296 memmove(s, p, (size_t)(e - p) + 1); // +1 for NUL 3297 e--; 3298 } 3299 } 3300 patlen = (size_t)(e - pat); 3301 3302 flags |= EW_FILE | EW_EXEC | EW_SHELLCMD; 3303 3304 bool mustfree = false; // Track memory allocation for *path. 3305 if (pat[0] == '.' && (vim_ispathsep(pat[1]) 3306 || (pat[1] == '.' && vim_ispathsep(pat[2])))) { 3307 path = "."; 3308 } else { 3309 // For an absolute name we don't use $PATH. 3310 if (!path_is_absolute(pat)) { 3311 path = vim_getenv("PATH"); 3312 } 3313 if (path == NULL) { 3314 path = ""; 3315 } else { 3316 mustfree = true; 3317 } 3318 } 3319 3320 // Go over all directories in $PATH. Expand matches in that directory and 3321 // collect them in "ga". When "." is not in $PATH also expand for the 3322 // current directory, to find "subdir/cmd". 3323 ga_init(&ga, (int)sizeof(char *), 10); 3324 hashtab_T found_ht; 3325 hash_init(&found_ht); 3326 for (char *s = path;; s = e) { 3327 size_t pathlen; // length of the path portion of buf (including trailing slash). 3328 size_t seplen; 3329 3330 if (*s == NUL) { 3331 if (did_curdir) { 3332 break; 3333 } 3334 3335 // Find directories in the current directory, path is empty. 3336 did_curdir = true; 3337 flags |= EW_DIR; 3338 3339 e = s; 3340 pathlen = 0; 3341 seplen = 0; 3342 } else { 3343 e = vim_strchr(s, ENV_SEPCHAR); 3344 if (e == NULL) { 3345 e = s + strlen(s); 3346 } 3347 3348 pathlen = (size_t)(e - s); 3349 if (strncmp(s, ".", pathlen) == 0) { 3350 did_curdir = true; 3351 flags |= EW_DIR; 3352 } else { 3353 // Do not match directories inside a $PATH item. 3354 flags &= ~EW_DIR; 3355 } 3356 3357 seplen = !after_pathsep(s, e) ? STRLEN_LITERAL(PATHSEPSTR) : 0; 3358 } 3359 3360 // Make sure that the pathed pattern (ie the path and pattern concatenated 3361 // together) will fit inside the buffer. If not skip it and move on to the 3362 // next path. 3363 if (pathlen + seplen + patlen + 1 <= MAXPATHL) { 3364 if (pathlen > 0) { 3365 xmemcpyz(buf, s, pathlen); 3366 if (seplen > 0) { 3367 xmemcpyz(buf + pathlen, S_LEN(PATHSEPSTR)); 3368 pathlen += seplen; 3369 } 3370 } 3371 xmemcpyz(buf + pathlen, pat, patlen); 3372 3373 expand_shellcmd_onedir(buf, pathlen, matches, numMatches, flags, &found_ht, &ga); 3374 } 3375 3376 if (*e != NUL) { 3377 e++; 3378 } 3379 } 3380 *matches = ga.ga_data; 3381 *numMatches = ga.ga_len; 3382 3383 xfree(buf); 3384 xfree(pat); 3385 if (mustfree) { 3386 xfree(path); 3387 } 3388 hash_clear(&found_ht); 3389 } 3390 3391 /// Call "user_expand_func()" to invoke a user defined Vim script function and 3392 /// return the result (either a string, a List or NULL). 3393 static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T *xp) 3394 FUNC_ATTR_NONNULL_ALL 3395 { 3396 CmdlineInfo *const ccline = get_cmdline_info(); 3397 char keep = 0; 3398 typval_T args[4]; 3399 const sctx_T save_current_sctx = current_sctx; 3400 3401 if (xp->xp_arg == NULL || xp->xp_arg[0] == NUL || xp->xp_line == NULL) { 3402 return NULL; 3403 } 3404 3405 if (ccline->cmdbuff != NULL) { 3406 keep = ccline->cmdbuff[ccline->cmdlen]; 3407 ccline->cmdbuff[ccline->cmdlen] = 0; 3408 } 3409 3410 char *pat = xstrnsave(xp->xp_pattern, xp->xp_pattern_len); 3411 args[0].v_type = VAR_STRING; 3412 args[1].v_type = VAR_STRING; 3413 args[2].v_type = VAR_NUMBER; 3414 args[3].v_type = VAR_UNKNOWN; 3415 args[0].vval.v_string = pat; 3416 args[1].vval.v_string = xp->xp_line; 3417 args[2].vval.v_number = xp->xp_col; 3418 3419 current_sctx = xp->xp_script_ctx; 3420 3421 void *const ret = user_expand_func(xp->xp_arg, 3, args); 3422 3423 current_sctx = save_current_sctx; 3424 if (ccline->cmdbuff != NULL) { 3425 ccline->cmdbuff[ccline->cmdlen] = keep; 3426 } 3427 3428 xfree(pat); 3429 return ret; 3430 } 3431 3432 /// Expand names with a function defined by the user (EXPAND_USER_DEFINED and 3433 /// EXPAND_USER_LIST). 3434 static int ExpandUserDefined(const char *const pat, expand_T *xp, regmatch_T *regmatch, 3435 char ***matches, int *numMatches) 3436 { 3437 const bool fuzzy = cmdline_fuzzy_complete(pat); 3438 *matches = NULL; 3439 *numMatches = 0; 3440 3441 char *const retstr = call_user_expand_func(call_func_retstr, xp); 3442 if (retstr == NULL) { 3443 return FAIL; 3444 } 3445 3446 garray_T ga; 3447 if (!fuzzy) { 3448 ga_init(&ga, (int)sizeof(char *), 3); 3449 } else { 3450 ga_init(&ga, (int)sizeof(fuzmatch_str_T), 3); 3451 } 3452 3453 for (char *s = retstr, *e; *s != NUL; s = e) { 3454 e = vim_strchr(s, '\n'); 3455 if (e == NULL) { 3456 e = s + strlen(s); 3457 } 3458 const char keep = *e; 3459 *e = NUL; 3460 3461 bool match; 3462 int score = 0; 3463 if (xp->xp_pattern[0] != NUL) { 3464 if (!fuzzy) { 3465 match = vim_regexec(regmatch, s, 0); 3466 } else { 3467 score = fuzzy_match_str(s, pat); 3468 match = (score != FUZZY_SCORE_NONE); 3469 } 3470 } else { 3471 match = true; // match everything 3472 } 3473 3474 *e = keep; 3475 3476 if (match) { 3477 char *p = xmemdupz(s, (size_t)(e - s)); 3478 3479 if (!fuzzy) { 3480 GA_APPEND(char *, &ga, p); 3481 } else { 3482 GA_APPEND(fuzmatch_str_T, &ga, ((fuzmatch_str_T){ 3483 .idx = ga.ga_len, 3484 .str = p, 3485 .score = score, 3486 })); 3487 } 3488 } 3489 3490 if (*e != NUL) { 3491 e++; 3492 } 3493 } 3494 xfree(retstr); 3495 3496 if (ga.ga_len == 0) { 3497 return OK; 3498 } 3499 3500 if (!fuzzy) { 3501 *matches = ga.ga_data; 3502 *numMatches = ga.ga_len; 3503 } else { 3504 fuzzymatches_to_strmatches(ga.ga_data, matches, ga.ga_len, false); 3505 *numMatches = ga.ga_len; 3506 } 3507 return OK; 3508 } 3509 3510 /// Expand names with a list returned by a function defined by the user. 3511 static int ExpandUserList(expand_T *xp, char ***matches, int *numMatches) 3512 { 3513 *matches = NULL; 3514 *numMatches = 0; 3515 list_T *const retlist = call_user_expand_func(call_func_retlist, xp); 3516 if (retlist == NULL) { 3517 return FAIL; 3518 } 3519 3520 garray_T ga; 3521 ga_init(&ga, (int)sizeof(char *), 3); 3522 // Loop over the items in the list. 3523 TV_LIST_ITER_CONST(retlist, li, { 3524 if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING 3525 || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) { 3526 continue; // Skip non-string items and empty strings. 3527 } 3528 char *p = xstrdup(TV_LIST_ITEM_TV(li)->vval.v_string); 3529 3530 GA_APPEND(char *, &ga, p); 3531 }); 3532 tv_list_unref(retlist); 3533 3534 *matches = ga.ga_data; 3535 *numMatches = ga.ga_len; 3536 return OK; 3537 } 3538 3539 static int ExpandUserLua(expand_T *xp, int *num_file, char ***file) 3540 { 3541 typval_T rettv = TV_INITIAL_VALUE; 3542 nlua_call_user_expand_func(xp, &rettv); 3543 if (rettv.v_type != VAR_LIST) { 3544 tv_clear(&rettv); 3545 return FAIL; 3546 } 3547 3548 list_T *const retlist = rettv.vval.v_list; 3549 3550 garray_T ga; 3551 ga_init(&ga, (int)sizeof(char *), 3); 3552 // Loop over the items in the list. 3553 TV_LIST_ITER_CONST(retlist, li, { 3554 if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING 3555 || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) { 3556 continue; // Skip non-string items and empty strings. 3557 } 3558 3559 GA_APPEND(char *, &ga, xstrdup(TV_LIST_ITEM_TV(li)->vval.v_string)); 3560 }); 3561 tv_list_unref(retlist); 3562 3563 *file = ga.ga_data; 3564 *num_file = ga.ga_len; 3565 return OK; 3566 } 3567 3568 /// Expand `file` for all comma-separated directories in `path`. 3569 /// Adds matches to `ga`. 3570 /// If "dirs" is true only expand directory names. 3571 void globpath(char *path, char *file, garray_T *ga, int expand_options, bool dirs) 3572 FUNC_ATTR_NONNULL_ALL 3573 { 3574 char *buf = xmalloc(MAXPATHL); 3575 3576 expand_T xpc; 3577 ExpandInit(&xpc); 3578 xpc.xp_context = dirs ? EXPAND_DIRECTORIES : EXPAND_FILES; 3579 3580 size_t filelen = strlen(file); 3581 3582 #if defined(MSWIN) 3583 // Using the platform's path separator (\) makes vim incorrectly 3584 // treat it as an escape character, use '/' instead. 3585 # define TMP_PATHSEPSTR "/" 3586 #else 3587 # define TMP_PATHSEPSTR PATHSEPSTR 3588 #endif 3589 3590 // Loop over all entries in {path}. 3591 while (*path != NUL) { 3592 // Copy one item of the path to buf[] and concatenate the file name. 3593 3594 // length of the path portion of buf (including trailing slash). 3595 size_t pathlen = copy_option_part(&path, buf, MAXPATHL, ","); 3596 size_t seplen = (*buf != NUL && !after_pathsep(buf, buf + pathlen)) 3597 ? STRLEN_LITERAL(TMP_PATHSEPSTR) : 0; 3598 3599 if (pathlen + seplen + filelen + 1 <= MAXPATHL) { 3600 if (seplen > 0) { 3601 xmemcpyz(buf + pathlen, S_LEN(TMP_PATHSEPSTR)); 3602 pathlen += seplen; 3603 } 3604 xmemcpyz(buf + pathlen, file, filelen); 3605 3606 char **p; 3607 int num_p = 0; 3608 ExpandFromContext(&xpc, buf, &p, &num_p, WILD_SILENT | expand_options); 3609 if (num_p > 0) { 3610 ExpandEscape(&xpc, buf, num_p, p, WILD_SILENT | expand_options); 3611 3612 // Concatenate new results to previous ones. 3613 ga_grow(ga, num_p); 3614 // take over the pointers and put them in "ga" 3615 for (int i = 0; i < num_p; i++) { 3616 ((char **)ga->ga_data)[ga->ga_len] = p[i]; 3617 ga->ga_len++; 3618 } 3619 xfree(p); 3620 } 3621 } 3622 } 3623 3624 xfree(buf); 3625 } 3626 #undef TMP_PATHSEPSTR 3627 3628 /// Translate some keys pressed when 'wildmenu' is used. 3629 int wildmenu_translate_key(CmdlineInfo *cclp, int key, expand_T *xp, bool did_wild_list) 3630 { 3631 int c = key; 3632 3633 if (cmdline_pum_active() || did_wild_list || wild_menu_showing) { 3634 if (c == K_LEFT) { 3635 c = Ctrl_P; 3636 } else if (c == K_RIGHT) { 3637 c = Ctrl_N; 3638 } 3639 } 3640 3641 // Hitting CR after "emenu Name.": complete submenu 3642 if (xp->xp_context == EXPAND_MENUNAMES 3643 && cclp->cmdpos > 1 3644 && cclp->cmdbuff[cclp->cmdpos - 1] == '.' 3645 && cclp->cmdbuff[cclp->cmdpos - 2] != '\\' 3646 && (c == '\n' || c == '\r' || c == K_KENTER)) { 3647 c = K_DOWN; 3648 } 3649 3650 return c; 3651 } 3652 3653 /// Delete characters on the command line, from "from" to the current position. 3654 static void cmdline_del(CmdlineInfo *cclp, int from) 3655 { 3656 assert(cclp->cmdpos <= cclp->cmdlen); 3657 memmove(cclp->cmdbuff + from, cclp->cmdbuff + cclp->cmdpos, 3658 (size_t)cclp->cmdlen - (size_t)cclp->cmdpos + 1); 3659 cclp->cmdlen -= cclp->cmdpos - from; 3660 cclp->cmdpos = from; 3661 } 3662 3663 /// Handle a key pressed when the wild menu for the menu names 3664 /// (EXPAND_MENUNAMES) is displayed. 3665 static int wildmenu_process_key_menunames(CmdlineInfo *cclp, int key, expand_T *xp) 3666 { 3667 // Hitting <Down> after "emenu Name.": complete submenu 3668 if (key == K_DOWN && cclp->cmdpos > 0 3669 && cclp->cmdbuff[cclp->cmdpos - 1] == '.') { 3670 key = (int)p_wc; 3671 KeyTyped = true; // in case the key was mapped 3672 } else if (key == K_UP) { 3673 // Hitting <Up>: Remove one submenu name in front of the 3674 // cursor 3675 bool found = false; 3676 3677 int j = (int)(xp->xp_pattern - cclp->cmdbuff); 3678 int i = 0; 3679 while (--j > 0) { 3680 // check for start of menu name 3681 if (cclp->cmdbuff[j] == ' ' 3682 && cclp->cmdbuff[j - 1] != '\\') { 3683 i = j + 1; 3684 break; 3685 } 3686 // check for start of submenu name 3687 if (cclp->cmdbuff[j] == '.' 3688 && cclp->cmdbuff[j - 1] != '\\') { 3689 if (found) { 3690 i = j + 1; 3691 break; 3692 } else { 3693 found = true; 3694 } 3695 } 3696 } 3697 if (i > 0) { 3698 cmdline_del(cclp, i); 3699 } 3700 key = (int)p_wc; 3701 KeyTyped = true; // in case the key was mapped 3702 xp->xp_context = EXPAND_NOTHING; 3703 } 3704 3705 return key; 3706 } 3707 3708 /// Handle a key pressed when the wild menu for file names (EXPAND_FILES) or 3709 /// directory names (EXPAND_DIRECTORIES) or shell command names 3710 /// (EXPAND_SHELLCMD) is displayed. 3711 static int wildmenu_process_key_filenames(CmdlineInfo *cclp, int key, expand_T *xp) 3712 { 3713 char upseg[5]; 3714 upseg[0] = PATHSEP; 3715 upseg[1] = '.'; 3716 upseg[2] = '.'; 3717 upseg[3] = PATHSEP; 3718 upseg[4] = NUL; 3719 3720 if (key == K_DOWN 3721 && cclp->cmdpos > 0 3722 && cclp->cmdbuff[cclp->cmdpos - 1] == PATHSEP 3723 && (cclp->cmdpos < 3 3724 || cclp->cmdbuff[cclp->cmdpos - 2] != '.' 3725 || cclp->cmdbuff[cclp->cmdpos - 3] != '.')) { 3726 // go down a directory 3727 key = (int)p_wc; 3728 KeyTyped = true; // in case the key was mapped 3729 } else if (strncmp(xp->xp_pattern, upseg + 1, 3) == 0 && key == K_DOWN) { 3730 // If in a direct ancestor, strip off one ../ to go down 3731 bool found = false; 3732 3733 int j = cclp->cmdpos; 3734 int i = (int)(xp->xp_pattern - cclp->cmdbuff); 3735 while (--j > i) { 3736 j -= utf_head_off(cclp->cmdbuff, cclp->cmdbuff + j); 3737 if (vim_ispathsep(cclp->cmdbuff[j])) { 3738 found = true; 3739 break; 3740 } 3741 } 3742 if (found 3743 && cclp->cmdbuff[j - 1] == '.' 3744 && cclp->cmdbuff[j - 2] == '.' 3745 && (vim_ispathsep(cclp->cmdbuff[j - 3]) || j == i + 2)) { 3746 cmdline_del(cclp, j - 2); 3747 key = (int)p_wc; 3748 KeyTyped = true; // in case the key was mapped 3749 } 3750 } else if (key == K_UP) { 3751 // go up a directory 3752 bool found = false; 3753 3754 int j = cclp->cmdpos - 1; 3755 int i = (int)(xp->xp_pattern - cclp->cmdbuff); 3756 while (--j > i) { 3757 j -= utf_head_off(cclp->cmdbuff, cclp->cmdbuff + j); 3758 if (vim_ispathsep(cclp->cmdbuff[j]) 3759 #ifdef BACKSLASH_IN_FILENAME 3760 && vim_strchr(" *?[{`$%#", (uint8_t)cclp->cmdbuff[j + 1]) == NULL 3761 #endif 3762 ) { 3763 if (found) { 3764 i = j + 1; 3765 break; 3766 } else { 3767 found = true; 3768 } 3769 } 3770 } 3771 3772 if (!found) { 3773 j = i; 3774 } else if (strncmp(cclp->cmdbuff + j, upseg, 4) == 0) { 3775 j += 4; 3776 } else if (strncmp(cclp->cmdbuff + j, upseg + 1, 3) == 0 3777 && j == i) { 3778 j += 3; 3779 } else { 3780 j = 0; 3781 } 3782 3783 if (j > 0) { 3784 // TODO(tarruda): this is only for DOS/Unix systems - need to put in 3785 // machine-specific stuff here and in upseg init 3786 cmdline_del(cclp, j); 3787 put_on_cmdline(upseg + 1, 3, false); 3788 } else if (cclp->cmdpos > i) { 3789 cmdline_del(cclp, i); 3790 } 3791 3792 // Now complete in the new directory. Set KeyTyped in case the 3793 // Up key came from a mapping. 3794 key = (int)p_wc; 3795 KeyTyped = true; 3796 } 3797 3798 return key; 3799 } 3800 3801 /// Handle a key pressed when wild menu is displayed 3802 int wildmenu_process_key(CmdlineInfo *cclp, int key, expand_T *xp) 3803 { 3804 // Special translations for 'wildmenu' 3805 if (xp->xp_context == EXPAND_MENUNAMES) { 3806 return wildmenu_process_key_menunames(cclp, key, xp); 3807 } 3808 if (xp->xp_context == EXPAND_FILES 3809 || xp->xp_context == EXPAND_DIRECTORIES 3810 || xp->xp_context == EXPAND_SHELLCMD) { 3811 return wildmenu_process_key_filenames(cclp, key, xp); 3812 } 3813 3814 return key; 3815 } 3816 3817 /// Free expanded names when finished walking through the matches 3818 void wildmenu_cleanup(CmdlineInfo *cclp) 3819 { 3820 if (!p_wmnu || wild_menu_showing == 0) { 3821 return; 3822 } 3823 3824 const bool skt = KeyTyped; 3825 const int old_RedrawingDisabled = RedrawingDisabled; 3826 3827 if (cclp->input_fn) { 3828 RedrawingDisabled = 0; 3829 } 3830 3831 // Clear highlighting applied during wildmenu activity 3832 set_no_hlsearch(true); 3833 3834 if (wild_menu_showing == WM_SCROLLED) { 3835 // Entered command line, move it up 3836 cmdline_row--; 3837 redrawcmd(); 3838 wild_menu_showing = 0; 3839 } else if (save_p_ls != -1) { 3840 // restore 'laststatus' and 'winminheight' 3841 p_ls = save_p_ls; 3842 p_wmh = save_p_wmh; 3843 last_status(false); 3844 update_screen(); // redraw the screen NOW 3845 redrawcmd(); 3846 save_p_ls = -1; 3847 wild_menu_showing = 0; 3848 } else { 3849 win_redraw_last_status(topframe); 3850 wild_menu_showing = 0; // must be before redraw_statuslines #8385 3851 redraw_statuslines(); 3852 } 3853 KeyTyped = skt; 3854 if (cclp->input_fn) { 3855 RedrawingDisabled = old_RedrawingDisabled; 3856 } 3857 } 3858 3859 /// "getcompletion()" function 3860 void f_getcompletion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 3861 { 3862 expand_T xpc; 3863 bool filtered = false; 3864 int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH 3865 | WILD_NO_BEEP | WILD_HOME_REPLACE; 3866 3867 if (tv_check_for_string_arg(argvars, 1) == FAIL) { 3868 return; 3869 } 3870 const char *const type = tv_get_string(&argvars[1]); 3871 3872 if (argvars[2].v_type != VAR_UNKNOWN) { 3873 filtered = (bool)tv_get_number_chk(&argvars[2], NULL); 3874 } 3875 3876 if (p_wic) { 3877 options |= WILD_ICASE; 3878 } 3879 3880 // For filtered results, 'wildignore' is used 3881 if (!filtered) { 3882 options |= WILD_KEEP_ALL; 3883 } 3884 3885 if (argvars[0].v_type != VAR_STRING) { 3886 emsg(_(e_invarg)); 3887 return; 3888 } 3889 const char *const pattern = tv_get_string(&argvars[0]); 3890 const char *pattern_start = pattern; 3891 3892 if (strcmp(type, "cmdline") == 0) { 3893 const int cmdline_len = (int)strlen(pattern); 3894 set_cmd_context(&xpc, (char *)pattern, cmdline_len, cmdline_len, false); 3895 pattern_start = xpc.xp_pattern; 3896 xpc.xp_pattern_len = strlen(xpc.xp_pattern); 3897 xpc.xp_col = cmdline_len; 3898 goto theend; 3899 } 3900 3901 ExpandInit(&xpc); 3902 xpc.xp_pattern = (char *)pattern; 3903 xpc.xp_pattern_len = strlen(xpc.xp_pattern); 3904 xpc.xp_line = (char *)pattern; 3905 3906 xpc.xp_context = cmdcomplete_str_to_type(type); 3907 switch (xpc.xp_context) { 3908 case EXPAND_NOTHING: 3909 semsg(_(e_invarg2), type); 3910 return; 3911 3912 case EXPAND_USER_DEFINED: 3913 // Must be "custom,funcname" pattern 3914 if (strncmp(type, "custom,", 7) != 0) { 3915 semsg(_(e_invarg2), type); 3916 return; 3917 } 3918 3919 xpc.xp_arg = (char *)(type + 7); 3920 break; 3921 3922 case EXPAND_USER_LIST: 3923 // Must be "customlist,funcname" pattern 3924 if (strncmp(type, "customlist,", 11) != 0) { 3925 semsg(_(e_invarg2), type); 3926 return; 3927 } 3928 3929 xpc.xp_arg = (char *)(type + 11); 3930 break; 3931 3932 case EXPAND_MENUS: 3933 set_context_in_menu_cmd(&xpc, "menu", xpc.xp_pattern, false); 3934 xpc.xp_pattern_len -= (size_t)(xpc.xp_pattern - pattern_start); 3935 break; 3936 3937 case EXPAND_SIGN: 3938 set_context_in_sign_cmd(&xpc, xpc.xp_pattern); 3939 xpc.xp_pattern_len -= (size_t)(xpc.xp_pattern - pattern_start); 3940 break; 3941 3942 case EXPAND_RUNTIME: 3943 set_context_in_runtime_cmd(&xpc, xpc.xp_pattern); 3944 xpc.xp_pattern_len -= (size_t)(xpc.xp_pattern - pattern_start); 3945 break; 3946 3947 case EXPAND_SHELLCMDLINE: { 3948 int context = EXPAND_SHELLCMDLINE; 3949 set_context_for_wildcard_arg(NULL, xpc.xp_pattern, false, &xpc, &context); 3950 xpc.xp_pattern_len -= (size_t)(xpc.xp_pattern - pattern_start); 3951 break; 3952 } 3953 3954 case EXPAND_FILETYPECMD: 3955 filetype_expand_what = EXP_FILETYPECMD_ALL; 3956 break; 3957 3958 default: 3959 break; 3960 } 3961 3962 theend: 3963 if (xpc.xp_context == EXPAND_LUA) { 3964 xpc.xp_col = (int)strlen(xpc.xp_line); 3965 nlua_expand_pat(&xpc); 3966 xpc.xp_pattern_len -= (size_t)(xpc.xp_pattern - pattern_start); 3967 } 3968 char *pat; 3969 if (cmdline_fuzzy_completion_supported(&xpc)) { 3970 // when fuzzy matching, don't modify the search string 3971 pat = xmemdupz(xpc.xp_pattern, xpc.xp_pattern_len); 3972 } else { 3973 pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); 3974 } 3975 3976 ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); 3977 tv_list_alloc_ret(rettv, xpc.xp_numfiles); 3978 3979 for (int i = 0; i < xpc.xp_numfiles; i++) { 3980 tv_list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1); 3981 } 3982 xfree(pat); 3983 ExpandCleanup(&xpc); 3984 } 3985 3986 /// "getcompletiontype()" function 3987 void f_getcompletiontype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 3988 { 3989 rettv->v_type = VAR_STRING; 3990 rettv->vval.v_string = NULL; 3991 3992 if (tv_check_for_string_arg(argvars, 0) == FAIL) { 3993 return; 3994 } 3995 3996 const char *pat = tv_get_string(&argvars[0]); 3997 expand_T xpc; 3998 ExpandInit(&xpc); 3999 4000 int cmdline_len = (int)strlen(pat); 4001 set_cmd_context(&xpc, (char *)pat, cmdline_len, cmdline_len, false); 4002 rettv->vval.v_string = cmdcomplete_type_to_str(xpc.xp_context, xpc.xp_arg); 4003 4004 ExpandCleanup(&xpc); 4005 } 4006 4007 /// "cmdcomplete_info()" function 4008 void f_cmdcomplete_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 4009 { 4010 CmdlineInfo *ccline = get_cmdline_info(); 4011 4012 tv_dict_alloc_ret(rettv); 4013 if (ccline == NULL || ccline->xpc == NULL || ccline->xpc->xp_files == NULL) { 4014 return; 4015 } 4016 4017 dict_T *retdict = rettv->vval.v_dict; 4018 int ret = tv_dict_add_str(retdict, S_LEN("cmdline_orig"), cmdline_orig); 4019 if (ret == OK) { 4020 ret = tv_dict_add_nr(retdict, S_LEN("pum_visible"), pum_visible()); 4021 } 4022 if (ret == OK) { 4023 ret = tv_dict_add_nr(retdict, S_LEN("selected"), ccline->xpc->xp_selected); 4024 } 4025 if (ret == OK) { 4026 list_T *li = tv_list_alloc(ccline->xpc->xp_numfiles); 4027 ret = tv_dict_add_list(retdict, S_LEN("matches"), li); 4028 for (int idx = 0; ret == OK && idx < ccline->xpc->xp_numfiles; idx++) { 4029 tv_list_append_string(li, ccline->xpc->xp_files[idx], -1); 4030 } 4031 } 4032 } 4033 4034 /// Copy a substring from the current buffer (curbuf), spanning from the given 4035 /// 'start' position to the word boundary after 'end' position. 4036 /// The copied string is stored in '*match', and the actual end position of the 4037 /// matched text is returned in '*match_end'. 4038 static int copy_substring_from_pos(pos_T *start, pos_T *end, char **match, pos_T *match_end) 4039 { 4040 bool exacttext = wop_flags & kOptWopFlagExacttext; 4041 4042 if (start->lnum > end->lnum 4043 || (start->lnum == end->lnum && start->col >= end->col)) { 4044 return FAIL; // invalid range 4045 } 4046 4047 // Use a growable string (ga) 4048 garray_T ga; 4049 ga_init(&ga, 1, 128); 4050 4051 // Append start line from start->col to end 4052 char *start_line = ml_get(start->lnum); 4053 char *start_ptr = start_line + start->col; 4054 bool is_single_line = start->lnum == end->lnum; 4055 4056 int segment_len = is_single_line ? (int)(end->col - start->col) 4057 : (int)(ml_get_len(start->lnum) - start->col); 4058 ga_grow(&ga, segment_len + 2); 4059 ga_concat_len(&ga, start_ptr, (size_t)segment_len); 4060 if (!is_single_line) { 4061 if (exacttext) { 4062 GA_CONCAT_LITERAL(&ga, "\\n"); 4063 } else { 4064 ga_append(&ga, '\n'); 4065 } 4066 } 4067 4068 // Append full lines between start and end 4069 if (!is_single_line) { 4070 for (linenr_T lnum = start->lnum + 1; lnum < end->lnum; lnum++) { 4071 char *line = ml_get(lnum); 4072 int linelen = ml_get_len(lnum); 4073 ga_grow(&ga, linelen + 2); 4074 ga_concat_len(&ga, line, (size_t)linelen); 4075 if (exacttext) { 4076 GA_CONCAT_LITERAL(&ga, "\\n"); 4077 } else { 4078 ga_append(&ga, '\n'); 4079 } 4080 } 4081 } 4082 4083 // Append partial end line (up to word end) 4084 char *end_line = ml_get(end->lnum); 4085 char *word_end = find_word_end(end_line + end->col); 4086 segment_len = (int)(word_end - end_line); 4087 ga_grow(&ga, segment_len); 4088 ga_concat_len(&ga, end_line + (is_single_line ? end->col : 0), 4089 (size_t)(segment_len - (is_single_line ? end->col : 0))); 4090 4091 // Null-terminate 4092 ga_grow(&ga, 1); 4093 ga_append(&ga, NUL); 4094 4095 *match = (char *)ga.ga_data; 4096 match_end->lnum = end->lnum; 4097 match_end->col = segment_len; 4098 4099 return OK; 4100 } 4101 4102 /// Returns true if the given string `str` matches the regex pattern `pat`. 4103 /// Honors the 'ignorecase' (p_ic) and 'smartcase' (p_scs) settings to determine 4104 /// case sensitivity. 4105 static bool is_regex_match(char *pat, char *str) 4106 { 4107 if (strcmp(pat, str) == 0) { 4108 return true; 4109 } 4110 4111 regmatch_T regmatch; 4112 4113 emsg_off++; 4114 msg_silent++; 4115 regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); 4116 emsg_off--; 4117 msg_silent--; 4118 4119 if (regmatch.regprog == NULL) { 4120 return false; 4121 } 4122 regmatch.rm_ic = p_ic; 4123 if (p_ic && p_scs) { 4124 regmatch.rm_ic = !pat_has_uppercase(pat); 4125 } 4126 4127 emsg_off++; 4128 msg_silent++; 4129 bool result = vim_regexec_nl(®match, str, (colnr_T)0); 4130 emsg_off--; 4131 msg_silent--; 4132 4133 vim_regfree(regmatch.regprog); 4134 return result; 4135 } 4136 4137 /// Constructs a new match string by appending text from the buffer (starting at 4138 /// end_match_pos) to the given pattern `pat`. The result is a concatenation of 4139 /// `pat` and the word following end_match_pos. 4140 /// If 'lowercase' is true, the appended text is converted to lowercase before 4141 /// being combined. Returns the newly allocated match string, or NULL on failure. 4142 static char *concat_pattern_with_buffer_match(char *pat, int pat_len, pos_T *end_match_pos, 4143 bool lowercase) 4144 FUNC_ATTR_NONNULL_RET 4145 { 4146 char *line = ml_get(end_match_pos->lnum); 4147 char *word_end = find_word_end(line + end_match_pos->col); 4148 int match_len = (int)(word_end - (line + end_match_pos->col)); 4149 char *match = xmalloc((size_t)match_len + (size_t)pat_len + 1); // +1 for NUL 4150 4151 memmove(match, pat, (size_t)pat_len); 4152 if (match_len > 0) { 4153 if (lowercase) { 4154 char *mword = xstrnsave(line + end_match_pos->col, (size_t)match_len); 4155 char *lower = strcase_save(mword, false); 4156 xfree(mword); 4157 memmove(match + pat_len, lower, (size_t)match_len); 4158 xfree(lower); 4159 } else { 4160 memmove(match + pat_len, line + end_match_pos->col, (size_t)match_len); 4161 } 4162 } 4163 match[pat_len + match_len] = NUL; 4164 return match; 4165 } 4166 4167 /// Search for strings matching "pat" in the specified range and return them. 4168 /// Returns OK on success, FAIL otherwise. 4169 /// 4170 /// @param pat pattern to match 4171 /// @param dir FORWARD or BACKWARD 4172 /// @param[out] matches array with matched string 4173 /// @param[out] numMatches number of matches 4174 static int expand_pattern_in_buf(char *pat, Direction dir, char ***matches, int *numMatches) 4175 { 4176 bool exacttext = wop_flags & kOptWopFlagExacttext; 4177 bool has_range = search_first_line != 0; 4178 4179 *matches = NULL; 4180 *numMatches = 0; 4181 4182 if (pat == NULL || *pat == NUL) { 4183 return FAIL; 4184 } 4185 4186 int pat_len = (int)strlen(pat); 4187 pos_T cur_match_pos = { 0 }, prev_match_pos = { 0 }; 4188 if (has_range) { 4189 cur_match_pos.lnum = search_first_line; 4190 } else { 4191 cur_match_pos = pre_incsearch_pos; 4192 } 4193 4194 int search_flags = SEARCH_OPT | SEARCH_NOOF | SEARCH_PEEK | SEARCH_NFMSG 4195 | (has_range ? SEARCH_START : 0); 4196 4197 garray_T ga; 4198 ga_init(&ga, sizeof(char *), 10); // Use growable array of char * 4199 4200 pos_T end_match_pos, word_end_pos; 4201 bool looped_around = false; 4202 bool compl_started = false; 4203 char *match, *full_match; 4204 4205 while (true) { 4206 emsg_off++; 4207 msg_silent++; 4208 int found_new_match = searchit(NULL, curbuf, &cur_match_pos, 4209 &end_match_pos, dir, pat, (size_t)pat_len, 1L, 4210 search_flags, RE_LAST, NULL); 4211 msg_silent--; 4212 emsg_off--; 4213 4214 if (found_new_match == FAIL) { 4215 break; 4216 } 4217 4218 // If in range mode, check if match is within the range 4219 if (has_range && (cur_match_pos.lnum < search_first_line 4220 || cur_match_pos.lnum > search_last_line)) { 4221 break; 4222 } 4223 4224 if (compl_started) { 4225 // If we've looped back to an earlier match, stop 4226 if ((dir == FORWARD && ltoreq(cur_match_pos, prev_match_pos)) 4227 || (dir == BACKWARD && ltoreq(prev_match_pos, cur_match_pos))) { 4228 if (looped_around) { 4229 break; 4230 } else { 4231 looped_around = true; 4232 } 4233 } 4234 } 4235 4236 compl_started = true; 4237 prev_match_pos = cur_match_pos; 4238 4239 // Abort if user typed a character or interrupted 4240 if (char_avail() || got_int) { 4241 if (got_int) { 4242 (void)vpeekc(); // Remove <C-C> from input stream 4243 got_int = false; // Don't abandon the command line 4244 } 4245 goto cleanup; 4246 } 4247 4248 // searchit() can return line number +1 past the last line when 4249 // searching for "foo\n" if "foo" is at end of buffer. 4250 if (end_match_pos.lnum > curbuf->b_ml.ml_line_count) { 4251 cur_match_pos.lnum = 1; 4252 cur_match_pos.col = 0; 4253 cur_match_pos.coladd = 0; 4254 continue; 4255 } 4256 4257 // Extract the matching text prepended to completed word 4258 if (!copy_substring_from_pos(&cur_match_pos, &end_match_pos, &full_match, 4259 &word_end_pos)) { 4260 break; 4261 } 4262 4263 if (exacttext) { 4264 match = full_match; 4265 } else { 4266 // Construct a new match from completed word appended to pattern itself 4267 match = concat_pattern_with_buffer_match(pat, pat_len, &end_match_pos, false); 4268 4269 // The regex pattern may include '\C' or '\c'. First, try matching the 4270 // buffer word as-is. If it doesn't match, try again with the lowercase 4271 // version of the word to handle smartcase behavior. 4272 if (!is_regex_match(match, full_match)) { 4273 xfree(match); 4274 match = concat_pattern_with_buffer_match(pat, pat_len, &end_match_pos, true); 4275 if (!is_regex_match(match, full_match)) { 4276 xfree(match); 4277 xfree(full_match); 4278 continue; 4279 } 4280 } 4281 xfree(full_match); 4282 } 4283 4284 // Include this match if it is not a duplicate 4285 for (int i = 0; i < ga.ga_len; i++) { 4286 if (strcmp(match, ((char **)ga.ga_data)[i]) == 0) { 4287 XFREE_CLEAR(match); 4288 break; 4289 } 4290 } 4291 if (match != NULL) { 4292 ga_grow(&ga, 1); 4293 ((char **)ga.ga_data)[ga.ga_len++] = match; 4294 if (ga.ga_len > TAG_MANY) { 4295 break; 4296 } 4297 } 4298 if (has_range) { 4299 cur_match_pos = word_end_pos; 4300 } 4301 } 4302 4303 *matches = (char **)ga.ga_data; 4304 *numMatches = ga.ga_len; 4305 return OK; 4306 4307 cleanup: 4308 ga_clear_strings(&ga); 4309 return FAIL; 4310 }