To: vim_dev@googlegroups.com Subject: Patch 8.2.4463 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.4463 Problem: Completion only uses strict matching. Solution: Add the "fuzzy" item for 'wildoptions'. (Yegappan Lakshmanan, closes #9803) Files: runtime/doc/options.txt, src/buffer.c, src/cmdexpand.c, src/option.c, src/option.h, src/optionstr.c, src/proto/cmdexpand.pro, src/proto/option.pro, src/proto/search.pro, src/search.c, src/structs.h, src/testdir/gen_opt_test.vim, src/testdir/test_cmdline.vim *** ../vim-8.2.4462/runtime/doc/options.txt 2022-02-18 17:50:00.445462060 +0000 --- runtime/doc/options.txt 2022-02-24 13:16:58.937971253 +0000 *************** *** 9058,9063 **** --- 9087,9100 ---- feature} A list of words that change how |cmdline-completion| is done. The following values are supported: + fuzzy Use fuzzy matching to find completion matches. When + this value is specified, wildcard expansion will not + be used for completion. The matches will be sorted by + the "best match" rather than alphabetically sorted. + This will find more matches than the wildcard + expansion. Currently fuzzy matching based completion + is not supported for file and directory names and + instead wildcard expansion is used. pum Display the completion matches using the popupmenu in the same style as the |ins-completion-menu|. tagfile When using CTRL-D to list matching tags, the kind of *** ../vim-8.2.4462/src/buffer.c 2022-02-19 11:44:57.147232377 +0000 --- src/buffer.c 2022-02-24 13:16:58.937971253 +0000 *************** *** 2728,2737 **** int round; char_u *p; int attempt; ! char_u *patc; #ifdef FEAT_VIMINFO bufmatch_T *matches = NULL; #endif *num_file = 0; // return values in case of FAIL *file = NULL; --- 2728,2739 ---- int round; char_u *p; int attempt; ! char_u *patc = NULL; #ifdef FEAT_VIMINFO bufmatch_T *matches = NULL; #endif + int fuzzy; + fuzmatch_str_T *fuzmatch = NULL; *num_file = 0; // return values in case of FAIL *file = NULL; *************** *** 2741,2772 **** return FAIL; #endif ! // Make a copy of "pat" and change "^" to "\(^\|[\/]\)". ! if (*pat == '^') { ! patc = alloc(STRLEN(pat) + 11); ! if (patc == NULL) ! return FAIL; ! STRCPY(patc, "\\(^\\|[\\/]\\)"); ! STRCPY(patc + 11, pat + 1); } - else - patc = pat; // attempt == 0: try match with '\<', match at start of word // attempt == 1: try match without '\<', match anywhere ! for (attempt = 0; attempt <= 1; ++attempt) { regmatch_T regmatch; ! if (attempt > 0 && patc == pat) ! break; // there was no anchor, no need to try again ! regmatch.regprog = vim_regcomp(patc + attempt * 11, RE_MAGIC); ! if (regmatch.regprog == NULL) { ! if (patc != pat) ! vim_free(patc); ! return FAIL; } // round == 1: Count the matches. --- 2743,2784 ---- return FAIL; #endif ! fuzzy = cmdline_fuzzy_complete(pat); ! ! // Make a copy of "pat" and change "^" to "\(^\|[\/]\)" (if doing regular ! // expression matching) ! if (!fuzzy) { ! if (*pat == '^') ! { ! patc = alloc(STRLEN(pat) + 11); ! if (patc == NULL) ! return FAIL; ! STRCPY(patc, "\\(^\\|[\\/]\\)"); ! STRCPY(patc + 11, pat + 1); ! } ! else ! patc = pat; } // attempt == 0: try match with '\<', match at start of word // attempt == 1: try match without '\<', match anywhere ! for (attempt = 0; attempt <= (fuzzy ? 0 : 1); ++attempt) { regmatch_T regmatch; + int score = 0; ! if (!fuzzy) { ! if (attempt > 0 && patc == pat) ! break; // there was no anchor, no need to try again ! regmatch.regprog = vim_regcomp(patc + attempt * 11, RE_MAGIC); ! if (regmatch.regprog == NULL) ! { ! if (patc != pat) ! vim_free(patc); ! return FAIL; ! } } // round == 1: Count the matches. *************** *** 2786,2792 **** continue; #endif ! p = buflist_match(®match, buf, p_wic); if (p != NULL) { if (round == 1) --- 2798,2819 ---- continue; #endif ! if (!fuzzy) ! p = buflist_match(®match, buf, p_wic); ! else ! { ! p = NULL; ! // first try matching with the short file name ! if ((score = fuzzy_match_str(buf->b_sfname, pat)) != 0) ! p = buf->b_sfname; ! if (p == NULL) ! { ! // next try matching with the full path file name ! if ((score = fuzzy_match_str(buf->b_ffname, pat)) != 0) ! p = buf->b_ffname; ! } ! } ! if (p != NULL) { if (round == 1) *************** *** 2797,2812 **** p = home_replace_save(buf, p); else p = vim_strsave(p); #ifdef FEAT_VIMINFO ! if (matches != NULL) { ! matches[count].buf = buf; ! matches[count].match = p; count++; } - else - #endif - (*file)[count++] = p; } } } --- 2824,2850 ---- p = home_replace_save(buf, p); else p = vim_strsave(p); + + if (!fuzzy) + { #ifdef FEAT_VIMINFO ! if (matches != NULL) ! { ! matches[count].buf = buf; ! matches[count].match = p; ! count++; ! } ! else ! #endif ! (*file)[count++] = p; ! } ! else { ! fuzmatch[count].idx = count; ! fuzmatch[count].str = p; ! fuzmatch[count].score = score; count++; } } } } *************** *** 2814,2860 **** break; if (round == 1) { ! *file = ALLOC_MULT(char_u *, count); ! if (*file == NULL) { ! vim_regfree(regmatch.regprog); ! if (patc != pat) ! vim_free(patc); ! return FAIL; ! } #ifdef FEAT_VIMINFO ! if (options & WILD_BUFLASTUSED) ! matches = ALLOC_MULT(bufmatch_T, count); #endif } } ! vim_regfree(regmatch.regprog); ! if (count) // match(es) found, break here ! break; } ! if (patc != pat) vim_free(patc); #ifdef FEAT_VIMINFO ! if (matches != NULL) { ! int i; ! if (count > 1) ! qsort(matches, count, sizeof(bufmatch_T), buf_compare); ! // if the current buffer is first in the list, place it at the end ! if (matches[0].buf == curbuf) { ! for (i = 1; i < count; i++) ! (*file)[i-1] = matches[i].match; ! (*file)[count-1] = matches[0].match; ! } ! else ! { ! for (i = 0; i < count; i++) ! (*file)[i] = matches[i].match; } ! vim_free(matches); } #endif --- 2852,2923 ---- break; if (round == 1) { ! if (!fuzzy) { ! *file = ALLOC_MULT(char_u *, count); ! if (*file == NULL) ! { ! vim_regfree(regmatch.regprog); ! if (patc != pat) ! vim_free(patc); ! return FAIL; ! } #ifdef FEAT_VIMINFO ! if (options & WILD_BUFLASTUSED) ! matches = ALLOC_MULT(bufmatch_T, count); #endif + } + else + { + fuzmatch = ALLOC_MULT(fuzmatch_str_T, count); + if (fuzmatch == NULL) + { + *num_file = 0; + *file = NULL; + return FAIL; + } + } } } ! ! if (!fuzzy) ! { ! vim_regfree(regmatch.regprog); ! if (count) // match(es) found, break here ! break; ! } } ! if (!fuzzy && patc != pat) vim_free(patc); #ifdef FEAT_VIMINFO ! if (!fuzzy) { ! if (matches != NULL) { ! int i; ! if (count > 1) ! qsort(matches, count, sizeof(bufmatch_T), buf_compare); ! // if the current buffer is first in the list, place it at the end ! if (matches[0].buf == curbuf) ! { ! for (i = 1; i < count; i++) ! (*file)[i-1] = matches[i].match; ! (*file)[count-1] = matches[0].match; ! } ! else ! { ! for (i = 0; i < count; i++) ! (*file)[i] = matches[i].match; ! } ! vim_free(matches); } ! } ! else ! { ! if (fuzzymatches_to_strmatches(fuzmatch, file, count, FALSE) == FAIL) ! return FAIL; } #endif *** ../vim-8.2.4462/src/cmdexpand.c 2022-02-17 11:26:38.717059014 +0000 --- src/cmdexpand.c 2022-02-24 13:16:58.937971253 +0000 *************** *** 18,24 **** static void set_expand_context(expand_T *xp); static int ExpandGeneric(expand_T *xp, regmatch_T *regmatch, char_u ***matches, int *numMatches, ! char_u *((*func)(expand_T *, int)), int escaped); static int ExpandFromContext(expand_T *xp, char_u *, char_u ***, int *, int); static int expand_showtail(expand_T *xp); static int expand_shellcmd(char_u *filepat, char_u ***matches, int *numMatches, int flagsarg); --- 18,25 ---- static void set_expand_context(expand_T *xp); static int ExpandGeneric(expand_T *xp, regmatch_T *regmatch, char_u ***matches, int *numMatches, ! char_u *((*func)(expand_T *, int)), int escaped, ! char_u *fuzzystr); static int ExpandFromContext(expand_T *xp, char_u *, char_u ***, int *, int); static int expand_showtail(expand_T *xp); static int expand_shellcmd(char_u *filepat, char_u ***matches, int *numMatches, int flagsarg); *************** *** 40,45 **** --- 41,83 ---- #define SHOW_FILE_TEXT(m) (showtail ? sm_gettail(matches[m]) : matches[m]) /* + * Returns TRUE if fuzzy completion is supported for a given cmdline completion + * context. + */ + static int + cmdline_fuzzy_completion_supported(expand_T *xp) + { + return (vim_strchr(p_wop, WOP_FUZZY) != NULL + && xp->xp_context != EXPAND_BOOL_SETTINGS + && xp->xp_context != EXPAND_COLORS + && xp->xp_context != EXPAND_COMPILER + && xp->xp_context != EXPAND_DIRECTORIES + && xp->xp_context != EXPAND_FILES + && xp->xp_context != EXPAND_FILES_IN_PATH + && xp->xp_context != EXPAND_FILETYPE + && xp->xp_context != EXPAND_HELP + && xp->xp_context != EXPAND_MAPPINGS + && xp->xp_context != EXPAND_OLD_SETTING + && xp->xp_context != EXPAND_OWNSYNTAX + && xp->xp_context != EXPAND_PACKADD + && xp->xp_context != EXPAND_SHELLCMD + && xp->xp_context != EXPAND_TAGS + && xp->xp_context != EXPAND_TAGS_LISTFILES + && xp->xp_context != EXPAND_USER_DEFINED + && xp->xp_context != EXPAND_USER_LIST); + } + + /* + * Returns TRUE if fuzzy completion for cmdline completion is enabled and + * 'fuzzystr' is not empty. + */ + int + cmdline_fuzzy_complete(char_u *fuzzystr) + { + return vim_strchr(p_wop, WOP_FUZZY) != NULL && *fuzzystr != NUL; + } + + /* * sort function for the completion matches. * functions should be sorted to the end. */ *************** *** 195,203 **** } else { // Translate string into pattern and expand it. ! if ((p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, ! xp->xp_context)) == NULL) p2 = NULL; else { --- 233,246 ---- } else { + if (cmdline_fuzzy_completion_supported(xp)) + // If fuzzy matching, don't modify the search string + p1 = vim_strsave(xp->xp_pattern); + else + p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); + // Translate string into pattern and expand it. ! if (p1 == NULL) p2 = NULL; else { *************** *** 2188,2196 **** // add star to file name, or convert to regexp if not exp. files. xp->xp_pattern_len = (int)(str + col - xp->xp_pattern); ! file_str = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); ! if (file_str == NULL) ! return EXPAND_UNSUCCESSFUL; if (p_wic) options += WILD_ICASE; --- 2231,2245 ---- // add star to file name, or convert to regexp if not exp. files. xp->xp_pattern_len = (int)(str + col - xp->xp_pattern); ! if (cmdline_fuzzy_completion_supported(xp)) ! // If fuzzy matching, don't modify the search string ! file_str = vim_strsave(xp->xp_pattern); ! else ! { ! file_str = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); ! if (file_str == NULL) ! return EXPAND_UNSUCCESSFUL; ! } if (p_wic) options += WILD_ICASE; *************** *** 2317,2322 **** --- 2366,2372 ---- */ static int ExpandOther( + char_u *pat, expand_T *xp, regmatch_T *rmp, char_u ***matches, *************** *** 2386,2395 **** { if (xp->xp_context == tab[i].context) { if (tab[i].ic) rmp->rm_ic = TRUE; ret = ExpandGeneric(xp, rmp, matches, numMatches, ! tab[i].func, tab[i].escaped); break; } } --- 2436,2451 ---- { if (xp->xp_context == tab[i].context) { + // Use fuzzy matching if 'wildoptions' has 'fuzzy'. + // If no search pattern is supplied, then don't use fuzzy + // matching and return all the found items. + int fuzzy = cmdline_fuzzy_complete(pat); + if (tab[i].ic) rmp->rm_ic = TRUE; ret = ExpandGeneric(xp, rmp, matches, numMatches, ! tab[i].func, tab[i].escaped, ! fuzzy ? pat : NULL); break; } } *************** *** 2530,2536 **** if (xp->xp_context == EXPAND_SETTINGS || xp->xp_context == EXPAND_BOOL_SETTINGS) ! ret = ExpandSettings(xp, ®match, numMatches, matches); else if (xp->xp_context == EXPAND_MAPPINGS) ret = ExpandMappings(®match, numMatches, matches); # if defined(FEAT_EVAL) --- 2586,2592 ---- if (xp->xp_context == EXPAND_SETTINGS || xp->xp_context == EXPAND_BOOL_SETTINGS) ! ret = ExpandSettings(xp, ®match, pat, numMatches, matches); else if (xp->xp_context == EXPAND_MAPPINGS) ret = ExpandMappings(®match, numMatches, matches); # if defined(FEAT_EVAL) *************** *** 2538,2544 **** ret = ExpandUserDefined(xp, ®match, matches, numMatches); # endif else ! ret = ExpandOther(xp, ®match, matches, numMatches); vim_regfree(regmatch.regprog); vim_free(tofree); --- 2594,2600 ---- ret = ExpandUserDefined(xp, ®match, matches, numMatches); # endif else ! ret = ExpandOther(pat, xp, ®match, matches, numMatches); vim_regfree(regmatch.regprog); vim_free(tofree); *************** *** 2553,2558 **** --- 2609,2617 ---- * obtain strings, one by one. The strings are matched against a regexp * program. Matching strings are copied into an array, which is returned. * + * If 'fuzzy' is TRUE, then fuzzy matching is used. Otherwise, regex matching + * is used. + * * Returns OK when no problems encountered, FAIL for error (out of memory). */ static int *************** *** 2563,2574 **** int *numMatches, char_u *((*func)(expand_T *, int)), // returns a string from the list ! int escaped) { int i; int count = 0; int round; char_u *str; // do this loop twice: // round == 0: count the number of matching names --- 2622,2638 ---- int *numMatches, char_u *((*func)(expand_T *, int)), // returns a string from the list ! int escaped, ! char_u *fuzzystr) { int i; int count = 0; int round; char_u *str; + fuzmatch_str_T *fuzmatch = NULL; + int score = 0; + int fuzzy = (fuzzystr != NULL); + int funcsort = FALSE; // do this loop twice: // round == 0: count the number of matching names *************** *** 2583,2589 **** if (*str == NUL) // skip empty strings continue; ! if (vim_regexec(regmatch, str, (colnr_T)0)) { if (round) { --- 2647,2654 ---- if (*str == NUL) // skip empty strings continue; ! if (vim_regexec(regmatch, str, (colnr_T)0) || ! (fuzzy && ((score = fuzzy_match_str(str, fuzzystr)) != 0))) { if (round) { *************** *** 2594,2604 **** if (str == NULL) { FreeWild(count, *matches); *numMatches = 0; *matches = NULL; return FAIL; } ! (*matches)[count] = str; # ifdef FEAT_MENU if (func == get_menu_names && str != NULL) { --- 2659,2678 ---- if (str == NULL) { FreeWild(count, *matches); + if (fuzzy) + fuzmatch_str_free(fuzmatch, count); *numMatches = 0; *matches = NULL; return FAIL; } ! if (fuzzy) ! { ! fuzmatch[count].idx = count; ! fuzmatch[count].str = str; ! fuzmatch[count].score = score; ! } ! else ! (*matches)[count] = str; # ifdef FEAT_MENU if (func == get_menu_names && str != NULL) { *************** *** 2616,2623 **** { if (count == 0) return OK; ! *matches = ALLOC_MULT(char_u *, count); ! if (*matches == NULL) { *numMatches = 0; *matches = NULL; --- 2690,2700 ---- { if (count == 0) return OK; ! if (fuzzy) ! fuzmatch = ALLOC_MULT(fuzmatch_str_T, count); ! else ! *matches = ALLOC_MULT(char_u *, count); ! if ((fuzzy && (fuzmatch == NULL)) || (*matches == NULL)) { *numMatches = 0; *matches = NULL; *************** *** 2635,2645 **** || xp->xp_context == EXPAND_FUNCTIONS || xp->xp_context == EXPAND_USER_FUNC || xp->xp_context == EXPAND_DISASSEMBLE) // functions should be sorted to the end. ! qsort((void *)*matches, (size_t)*numMatches, sizeof(char_u *), sort_func_compare); else ! sort_strings(*matches, *numMatches); } #if defined(FEAT_SYN_HL) --- 2712,2729 ---- || xp->xp_context == EXPAND_FUNCTIONS || xp->xp_context == EXPAND_USER_FUNC || xp->xp_context == EXPAND_DISASSEMBLE) + { // functions should be sorted to the end. ! funcsort = TRUE; ! if (!fuzzy) ! qsort((void *)*matches, (size_t)*numMatches, sizeof(char_u *), sort_func_compare); + } else ! { ! if (!fuzzy) ! sort_strings(*matches, *numMatches); ! } } #if defined(FEAT_SYN_HL) *************** *** 2647,2652 **** --- 2731,2741 ---- // they don't show up when getting normal highlight names by ID. reset_expand_highlight(); #endif + + if (fuzzy && fuzzymatches_to_strmatches(fuzmatch, matches, count, + funcsort) == FAIL) + return FAIL; + return OK; } *** ../vim-8.2.4462/src/option.c 2022-02-23 12:05:54.482835946 +0000 --- src/option.c 2022-02-24 13:16:58.937971253 +0000 *************** *** 6447,6458 **** } } int ExpandSettings( expand_T *xp, regmatch_T *regmatch, ! int *num_file, ! char_u ***file) { int num_normal = 0; // Nr of matching non-term-code settings int num_term = 0; // Nr of matching terminal code settings --- 6447,6516 ---- } } + /* + * Returns TRUE if 'str' either matches 'regmatch' or fuzzy matches 'pat'. + * + * If 'test_only' is TRUE and 'fuzzy' is FALSE and if 'str' matches the regular + * expression 'regmatch', then returns TRUE. Otherwise returns FALSE. + * + * If 'test_only' is FALSE and 'fuzzy' is FALSE and if 'str' matches the + * regular expression 'regmatch', then stores the match in matches[idx] and + * returns TRUE. + * + * If 'test_only' is TRUE and 'fuzzy' is TRUE and if 'str' fuzzy matches + * 'fuzzystr', then returns TRUE. Otherwise returns FALSE. + * + * If 'test_only' is FALSE and 'fuzzy' is TRUE and if 'str' fuzzy matches + * 'fuzzystr', then stores the match details in fuzmatch[idx] and returns TRUE. + */ + static int + match_str( + char_u *str, + regmatch_T *regmatch, + char_u **matches, + int idx, + int test_only, + int fuzzy, + char_u *fuzzystr, + fuzmatch_str_T *fuzmatch) + { + if (!fuzzy) + { + if (vim_regexec(regmatch, str, (colnr_T)0)) + { + if (!test_only) + matches[idx] = vim_strsave(str); + return TRUE; + } + } + else + { + int score; + + score = fuzzy_match_str(str, fuzzystr); + if (score != 0) + { + if (!test_only) + { + fuzmatch[idx].idx = idx; + fuzmatch[idx].str = vim_strsave(str); + fuzmatch[idx].score = score; + } + + return TRUE; + } + } + + return FALSE; + } + int ExpandSettings( expand_T *xp, regmatch_T *regmatch, ! char_u *fuzzystr, ! int *numMatches, ! char_u ***matches) { int num_normal = 0; // Nr of matching non-term-code settings int num_term = 0; // Nr of matching terminal code settings *************** *** 6465,6470 **** --- 6523,6532 ---- char_u name_buf[MAX_KEY_NAME_LEN]; static char *(names[]) = {"all", "termcap"}; int ic = regmatch->rm_ic; // remember the ignore-case flag + int fuzzy; + fuzmatch_str_T *fuzmatch = NULL; + + fuzzy = cmdline_fuzzy_complete(fuzzystr); // do this loop twice: // loop == 0: count the number of matching options *************** *** 6475,6487 **** if (xp->xp_context != EXPAND_BOOL_SETTINGS) { for (match = 0; match < (int)ARRAY_LENGTH(names); ++match) ! if (vim_regexec(regmatch, (char_u *)names[match], (colnr_T)0)) { if (loop == 0) num_normal++; else ! (*file)[count++] = vim_strsave((char_u *)names[match]); } } for (opt_idx = 0; (str = (char_u *)options[opt_idx].fullname) != NULL; opt_idx++) --- 6537,6552 ---- if (xp->xp_context != EXPAND_BOOL_SETTINGS) { for (match = 0; match < (int)ARRAY_LENGTH(names); ++match) ! { ! if (match_str((char_u *)names[match], regmatch, *matches, ! count, (loop == 0), fuzzy, fuzzystr, fuzmatch)) { if (loop == 0) num_normal++; else ! count++; } + } } for (opt_idx = 0; (str = (char_u *)options[opt_idx].fullname) != NULL; opt_idx++) *************** *** 6494,6505 **** is_term_opt = istermoption_idx(opt_idx); if (is_term_opt && num_normal > 0) continue; ! match = FALSE; ! if (vim_regexec(regmatch, str, (colnr_T)0) ! || (options[opt_idx].shortname != NULL && vim_regexec(regmatch, ! (char_u *)options[opt_idx].shortname, (colnr_T)0))) ! match = TRUE; else if (is_term_opt) { name_buf[0] = '<'; --- 6559,6595 ---- is_term_opt = istermoption_idx(opt_idx); if (is_term_opt && num_normal > 0) continue; ! ! if (match_str(str, regmatch, *matches, count, (loop == 0), ! fuzzy, fuzzystr, fuzmatch)) ! { ! if (loop == 0) ! { ! if (is_term_opt) ! num_term++; ! else ! num_normal++; ! } ! else ! count++; ! } ! else if (!fuzzy && options[opt_idx].shortname != NULL && vim_regexec(regmatch, ! (char_u *)options[opt_idx].shortname, (colnr_T)0)) ! { ! // Compare against the abbreviated option name (for regular ! // expression match). Fuzzy matching (previous if) already ! // matches against both the expanded and abbreviated names. ! if (loop == 0) ! { ! if (is_term_opt) ! num_term++; ! else ! num_normal++; ! } ! else ! (*matches)[count++] = vim_strsave(str); ! } else if (is_term_opt) { name_buf[0] = '<'; *************** *** 6509,6533 **** name_buf[4] = str[3]; name_buf[5] = '>'; name_buf[6] = NUL; ! if (vim_regexec(regmatch, name_buf, (colnr_T)0)) ! { ! match = TRUE; ! str = name_buf; ! } ! } ! if (match) ! { ! if (loop == 0) { ! if (is_term_opt) num_term++; else ! num_normal++; } - else - (*file)[count++] = vim_strsave(str); } } /* * Check terminal key codes, these are not in the option table */ --- 6599,6616 ---- name_buf[4] = str[3]; name_buf[5] = '>'; name_buf[6] = NUL; ! ! if (match_str(name_buf, regmatch, *matches, count, (loop == 0), ! fuzzy, fuzzystr, fuzmatch)) { ! if (loop == 0) num_term++; else ! count++; } } } + /* * Check terminal key codes, these are not in the option table */ *************** *** 6544,6552 **** name_buf[3] = str[1]; name_buf[4] = NUL; ! match = FALSE; ! if (vim_regexec(regmatch, name_buf, (colnr_T)0)) ! match = TRUE; else { name_buf[0] = '<'; --- 6627,6640 ---- name_buf[3] = str[1]; name_buf[4] = NUL; ! if (match_str(name_buf, regmatch, *matches, count, ! (loop == 0), fuzzy, fuzzystr, fuzmatch)) ! { ! if (loop == 0) ! num_term++; ! else ! count++; ! } else { name_buf[0] = '<'; *************** *** 6557,6571 **** name_buf[5] = '>'; name_buf[6] = NUL; ! if (vim_regexec(regmatch, name_buf, (colnr_T)0)) ! match = TRUE; ! } ! if (match) ! { ! if (loop == 0) ! num_term++; ! else ! (*file)[count++] = vim_strsave(name_buf); } } --- 6645,6659 ---- name_buf[5] = '>'; name_buf[6] = NUL; ! if (match_str(name_buf, regmatch, *matches, count, ! (loop == 0), fuzzy, fuzzystr, ! fuzmatch)) ! { ! if (loop == 0) ! num_term++; ! else ! count++; ! } } } *************** *** 6579,6609 **** STRCPY(name_buf + 1, str); STRCAT(name_buf, ">"); ! if (vim_regexec(regmatch, name_buf, (colnr_T)0)) { if (loop == 0) num_term++; else ! (*file)[count++] = vim_strsave(name_buf); } } } if (loop == 0) { if (num_normal > 0) ! *num_file = num_normal; else if (num_term > 0) ! *num_file = num_term; else return OK; ! *file = ALLOC_MULT(char_u *, *num_file); ! if (*file == NULL) { ! *file = (char_u **)""; ! return FAIL; } } } return OK; } --- 6667,6715 ---- STRCPY(name_buf + 1, str); STRCAT(name_buf, ">"); ! if (match_str(name_buf, regmatch, *matches, count, (loop == 0), ! fuzzy, fuzzystr, fuzmatch)) { if (loop == 0) num_term++; else ! count++; } } } if (loop == 0) { if (num_normal > 0) ! *numMatches = num_normal; else if (num_term > 0) ! *numMatches = num_term; else return OK; ! if (!fuzzy) { ! *matches = ALLOC_MULT(char_u *, *numMatches); ! if (*matches == NULL) ! { ! *matches = (char_u **)""; ! return FAIL; ! } ! } ! else ! { ! fuzmatch = ALLOC_MULT(fuzmatch_str_T, *numMatches); ! if (fuzmatch == NULL) ! { ! *matches = (char_u **)""; ! return FAIL; ! } } } } + + if (fuzzy && + fuzzymatches_to_strmatches(fuzmatch, matches, count, FALSE) == FAIL) + return FAIL; + return OK; } *** ../vim-8.2.4462/src/option.h 2022-02-08 12:07:41.835496899 +0000 --- src/option.h 2022-02-24 13:16:58.937971253 +0000 *************** *** 358,363 **** --- 358,364 ---- // flags for the 'wildoptions' option // each defined char should be unique over all values. + #define WOP_FUZZY 'z' #define WOP_TAGFILE 't' #define WOP_PUM 'p' *** ../vim-8.2.4462/src/optionstr.c 2022-02-08 12:07:41.835496899 +0000 --- src/optionstr.c 2022-02-24 13:16:58.937971253 +0000 *************** *** 57,63 **** static char *(p_ttym_values[]) = {"xterm", "xterm2", "dec", "netterm", "jsbterm", "pterm", "urxvt", "sgr", NULL}; #endif static char *(p_ve_values[]) = {"block", "insert", "all", "onemore", "none", "NONE", NULL}; ! static char *(p_wop_values[]) = {"tagfile", "pum", NULL}; #ifdef FEAT_WAK static char *(p_wak_values[]) = {"yes", "menu", "no", NULL}; #endif --- 57,63 ---- static char *(p_ttym_values[]) = {"xterm", "xterm2", "dec", "netterm", "jsbterm", "pterm", "urxvt", "sgr", NULL}; #endif static char *(p_ve_values[]) = {"block", "insert", "all", "onemore", "none", "NONE", NULL}; ! static char *(p_wop_values[]) = {"fuzzy", "tagfile", "pum", NULL}; #ifdef FEAT_WAK static char *(p_wak_values[]) = {"yes", "menu", "no", NULL}; #endif *** ../vim-8.2.4462/src/proto/cmdexpand.pro 2022-02-08 12:07:41.835496899 +0000 --- src/proto/cmdexpand.pro 2022-02-24 13:16:58.937971253 +0000 *************** *** 1,4 **** --- 1,5 ---- /* cmdexpand.c */ + int cmdline_fuzzy_complete(char_u *fuzzystr); int nextwild(expand_T *xp, int type, int options, int escape); char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode); void ExpandInit(expand_T *xp); *** ../vim-8.2.4462/src/proto/option.pro 2022-02-23 12:05:54.482835946 +0000 --- src/proto/option.pro 2022-02-24 13:16:58.937971253 +0000 *************** *** 63,69 **** void set_iminsert_global(void); void set_imsearch_global(void); void set_context_in_set_cmd(expand_T *xp, char_u *arg, int opt_flags); ! int ExpandSettings(expand_T *xp, regmatch_T *regmatch, int *num_file, char_u ***file); int ExpandOldSetting(int *num_file, char_u ***file); int shortmess(int x); void vimrc_found(char_u *fname, char_u *envname); --- 63,69 ---- void set_iminsert_global(void); void set_imsearch_global(void); void set_context_in_set_cmd(expand_T *xp, char_u *arg, int opt_flags); ! int ExpandSettings(expand_T *xp, regmatch_T *regmatch, char_u *pat, int *numMatches, char_u ***matches); int ExpandOldSetting(int *num_file, char_u ***file); int shortmess(int x); void vimrc_found(char_u *fname, char_u *envname); *** ../vim-8.2.4462/src/proto/search.pro 2021-12-12 14:16:34.989862195 +0000 --- src/proto/search.pro 2022-02-24 13:16:58.937971253 +0000 *************** *** 40,43 **** --- 40,46 ---- int fuzzy_match(char_u *str, char_u *pat_arg, int matchseq, int *outScore, int_u *matches, int maxMatches); void f_matchfuzzy(typval_T *argvars, typval_T *rettv); void f_matchfuzzypos(typval_T *argvars, typval_T *rettv); + int fuzzy_match_str(char_u *str, char_u *pat); + int fuzzymatches_to_strmatches(fuzmatch_str_T *fuzmatch, char_u ***matches, int count, int funcsort); + void fuzmatch_str_free(fuzmatch_str_T *fuzmatch, int count); /* vim: set ft=c : */ *** ../vim-8.2.4462/src/search.c 2022-02-16 19:24:03.626162408 +0000 --- src/search.c 2022-02-24 13:16:58.941971243 +0000 *************** *** 1166,1172 **** return submatch + 1; } ! #ifdef FEAT_EVAL void set_search_direction(int cdir) { --- 1166,1172 ---- return submatch + 1; } ! #if defined(FEAT_EVAL) || defined(FEAT_PROTO) void set_search_direction(int cdir) { *************** *** 4107,4113 **** } #endif ! #ifdef FEAT_EVAL /* * "searchcount()" function */ --- 4107,4113 ---- } #endif ! #if defined(FEAT_EVAL) || defined(FEAT_PROTO) /* * "searchcount()" function */ *************** *** 4230,4235 **** --- 4230,4236 ---- restore_incsearch_state(); #endif } + #endif /* * Fuzzy string matching *************** *** 4611,4616 **** --- 4612,4618 ---- return numMatches != 0; } + #if defined(FEAT_EVAL) || defined(FEAT_PROTO) /* * Sort the fuzzy matches in the descending order of the match score. * For items with same score, retain the order using the index (stable sort) *************** *** 4933,4937 **** { do_fuzzymatch(argvars, rettv, TRUE); } - #endif --- 4935,5065 ---- { do_fuzzymatch(argvars, rettv, TRUE); } #endif + + /* + * Same as fuzzy_match_item_compare() except for use with a string match + */ + static int + fuzzy_match_str_compare(const void *s1, const void *s2) + { + int v1 = ((fuzmatch_str_T *)s1)->score; + int v2 = ((fuzmatch_str_T *)s2)->score; + int idx1 = ((fuzmatch_str_T *)s1)->idx; + int idx2 = ((fuzmatch_str_T *)s2)->idx; + + return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1; + } + + /* + * Sort fuzzy matches by score + */ + static void + fuzzy_match_str_sort(fuzmatch_str_T *fm, int sz) + { + // Sort the list by the descending order of the match score + qsort((void *)fm, (size_t)sz, sizeof(fuzmatch_str_T), + fuzzy_match_str_compare); + } + + /* + * Same as fuzzy_match_item_compare() except for use with a function name + * string match. functions should be sorted to the end. + */ + static int + fuzzy_match_func_compare(const void *s1, const void *s2) + { + int v1 = ((fuzmatch_str_T *)s1)->score; + int v2 = ((fuzmatch_str_T *)s2)->score; + int idx1 = ((fuzmatch_str_T *)s1)->idx; + int idx2 = ((fuzmatch_str_T *)s2)->idx; + char_u *str1 = ((fuzmatch_str_T *)s1)->str; + char_u *str2 = ((fuzmatch_str_T *)s2)->str; + + if (*str1 != '<' && *str2 == '<') return -1; + if (*str1 == '<' && *str2 != '<') return 1; + return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1; + } + + /* + * Sort fuzzy matches of function names by score. + * functions should be sorted to the end. + */ + static void + fuzzy_match_func_sort(fuzmatch_str_T *fm, int sz) + { + // Sort the list by the descending order of the match score + qsort((void *)fm, (size_t)sz, sizeof(fuzmatch_str_T), + fuzzy_match_func_compare); + } + + /* + * Fuzzy match 'pat' in 'str'. Returns 0 if there is no match. Otherwise, + * returns the match score. + */ + int + fuzzy_match_str(char_u *str, char_u *pat) + { + int score = 0; + int_u matchpos[256]; + + if (str == NULL || pat == NULL) + return 0; + + fuzzy_match(str, pat, FALSE, &score, matchpos, + sizeof(matchpos) / sizeof(matchpos[0])); + + return score; + } + + /* + * Copy a list of fuzzy matches into a string list after sorting the matches by + * the fuzzy score. Frees the memory allocated for 'fuzmatch'. + * Returns OK on success and FAIL on memory allocation failure. + */ + int + fuzzymatches_to_strmatches( + fuzmatch_str_T *fuzmatch, + char_u ***matches, + int count, + int funcsort) + { + int i; + + if (count <= 0) + return OK; + + *matches = ALLOC_MULT(char_u *, count); + if (*matches == NULL) + { + for (i = 0; i < count; i++) + vim_free(fuzmatch[i].str); + vim_free(fuzmatch); + return FAIL; + } + + // Sort the list by the descending order of the match score + if (funcsort) + fuzzy_match_func_sort((void *)fuzmatch, (size_t)count); + else + fuzzy_match_str_sort((void *)fuzmatch, (size_t)count); + + for (i = 0; i < count; i++) + (*matches)[i] = fuzmatch[i].str; + vim_free(fuzmatch); + + return OK; + } + + /* + * Free a list of fuzzy string matches. + */ + void + fuzmatch_str_free(fuzmatch_str_T *fuzmatch, int count) + { + if (count <= 0 || fuzmatch == NULL) + return; + while (count--) + vim_free(fuzmatch[count].str); + vim_free(fuzmatch); + } *** ../vim-8.2.4462/src/structs.h 2022-02-07 19:56:38.883286149 +0000 --- src/structs.h 2022-02-24 13:16:58.941971243 +0000 *************** *** 4516,4518 **** --- 4516,4526 ---- int sw_same_win; // VIsual_active was not reset int sw_visual_active; } switchwin_T; + + // Fuzzy matched string list item. Used for fuzzy match completion. Items are + // usually sorted by 'score'. The 'idx' member is used for stable-sort. + typedef struct { + int idx; + char_u *str; + int score; + } fuzmatch_str_T; *** ../vim-8.2.4462/src/testdir/gen_opt_test.vim 2020-10-30 18:25:06.829693344 +0000 --- src/testdir/gen_opt_test.vim 2022-02-24 13:26:26.685789402 +0000 *************** *** 11,16 **** --- 11,17 ---- " Clear out t_WS, we don't want to resize the actual terminal. let script = [ \ '" DO NOT EDIT: Generated with gen_opt_test.vim', + \ '" Used by test_options.vim.', \ '', \ 'let save_columns = &columns', \ 'let save_lines = &lines', *************** *** 152,158 **** \ 'virtualedit': [['', 'all', 'all,block'], ['xxx']], \ 'whichwrap': [['', 'b,s', 'bs'], ['xxx']], \ 'wildmode': [['', 'full', 'list:full', 'full,longest'], ['xxx', 'a4', 'full,full,full,full,full']], ! \ 'wildoptions': [['', 'tagfile'], ['xxx']], \ 'winaltkeys': [['menu', 'no'], ['', 'xxx']], \ \ 'luadll': [[], []], --- 153,159 ---- \ 'virtualedit': [['', 'all', 'all,block'], ['xxx']], \ 'whichwrap': [['', 'b,s', 'bs'], ['xxx']], \ 'wildmode': [['', 'full', 'list:full', 'full,longest'], ['xxx', 'a4', 'full,full,full,full,full']], ! \ 'wildoptions': [['', 'tagfile', 'pum', 'fuzzy'], ['xxx']], \ 'winaltkeys': [['menu', 'no'], ['', 'xxx']], \ \ 'luadll': [[], []], *** ../vim-8.2.4462/src/testdir/test_cmdline.vim 2022-02-16 12:44:26.129908861 +0000 --- src/testdir/test_cmdline.vim 2022-02-24 13:16:58.941971243 +0000 *************** *** 1574,1579 **** --- 1574,1585 ---- call assert_equal(1, winnr('$')) endfunc + func Test_cmdwin_tabpage() + tabedit + call assert_fails("silent norm q/g :I\", 'E11:') + tabclose! + endfunc + func Test_cmdwin_interrupted() CheckFeature cmdwin CheckScreendump *************** *** 2438,2441 **** --- 2444,2764 ---- call assert_equal("\"dlist 10 /pat/ | chistory", @:) endfunc + " Test for 'fuzzy' in 'wildoptions' (fuzzy completion) + func Test_wildoptions_fuzzy() + " argument list (only for :argdel) + argadd change.py count.py charge.py + set wildoptions& + call feedkeys(":argdel cge\\\"\", 'tx') + call assert_equal('"argdel cge', @:) + set wildoptions=fuzzy + call feedkeys(":argdel cge\\\"\", 'tx') + call assert_equal('"argdel change.py charge.py', @:) + %argdelete + + " autocmd group name fuzzy completion + set wildoptions& + augroup MyFuzzyGroup + augroup END + call feedkeys(":augroup mfg\\\"\", 'tx') + call assert_equal('"augroup mfg', @:) + call feedkeys(":augroup My*p\\\"\", 'tx') + call assert_equal('"augroup MyFuzzyGroup', @:) + set wildoptions=fuzzy + call feedkeys(":augroup mfg\\\"\", 'tx') + call assert_equal('"augroup MyFuzzyGroup', @:) + call feedkeys(":augroup My*p\\\"\", 'tx') + call assert_equal('"augroup My*p', @:) + augroup! MyFuzzyGroup + + " buffer name fuzzy completion + set wildoptions& + edit SomeFile.txt + enew + call feedkeys(":b SF\\\"\", 'tx') + call assert_equal('"b SF', @:) + call feedkeys(":b S*File.txt\\\"\", 'tx') + call assert_equal('"b SomeFile.txt', @:) + set wildoptions=fuzzy + call feedkeys(":b SF\\\"\", 'tx') + call assert_equal('"b SomeFile.txt', @:) + call feedkeys(":b S*File.txt\\\"\", 'tx') + call assert_equal('"b S*File.txt', @:) + %bw! + + " buffer name (full path) fuzzy completion + if has('unix') + set wildoptions& + call mkdir('Xcmd/Xstate/Xfile.js', 'p') + edit Xcmd/Xstate/Xfile.js + cd Xcmd/Xstate + enew + call feedkeys(":b CmdStateFile\\\"\", 'tx') + call assert_equal('"b CmdStateFile', @:) + set wildoptions=fuzzy + call feedkeys(":b CmdStateFile\\\"\", 'tx') + call assert_match('Xcmd/Xstate/Xfile.js$', @:) + cd - + call delete('Xcmd', 'rf') + endif + + " :behave suboptions fuzzy completion + set wildoptions& + call feedkeys(":behave xm\\\"\", 'tx') + call assert_equal('"behave xm', @:) + call feedkeys(":behave xt*m\\\"\", 'tx') + call assert_equal('"behave xterm', @:) + set wildoptions=fuzzy + call feedkeys(":behave xm\\\"\", 'tx') + call assert_equal('"behave xterm', @:) + call feedkeys(":behave xt*m\\\"\", 'tx') + call assert_equal('"behave xt*m', @:) + let g:Sline = '' + call feedkeys(":behave win\\\\"\", 'tx') + call assert_equal('mswin', g:Sline) + call assert_equal('"behave win', @:) + + " colorscheme name fuzzy completion - NOT supported + + " built-in command name fuzzy completion + set wildoptions& + call feedkeys(":sbwin\\\"\", 'tx') + call assert_equal('"sbwin', @:) + call feedkeys(":sbr*d\\\"\", 'tx') + call assert_equal('"sbrewind', @:) + set wildoptions=fuzzy + call feedkeys(":sbwin\\\"\", 'tx') + call assert_equal('"sbrewind', @:) + call feedkeys(":sbr*d\\\"\", 'tx') + call assert_equal('"sbr*d', @:) + + " compiler name fuzzy completion - NOT supported + + " :cscope suboptions fuzzy completion + if has('cscope') + set wildoptions& + call feedkeys(":cscope ret\\\"\", 'tx') + call assert_equal('"cscope ret', @:) + call feedkeys(":cscope re*t\\\"\", 'tx') + call assert_equal('"cscope reset', @:) + set wildoptions=fuzzy + call feedkeys(":cscope ret\\\"\", 'tx') + call assert_equal('"cscope reset', @:) + call feedkeys(":cscope re*t\\\"\", 'tx') + call assert_equal('"cscope re*t', @:) + endif + + " :diffget/:diffput buffer name fuzzy completion + new SomeBuffer + diffthis + new OtherBuffer + diffthis + set wildoptions& + call feedkeys(":diffget sbuf\\\"\", 'tx') + call assert_equal('"diffget sbuf', @:) + call feedkeys(":diffput sbuf\\\"\", 'tx') + call assert_equal('"diffput sbuf', @:) + set wildoptions=fuzzy + call feedkeys(":diffget sbuf\\\"\", 'tx') + call assert_equal('"diffget SomeBuffer', @:) + call feedkeys(":diffput sbuf\\\"\", 'tx') + call assert_equal('"diffput SomeBuffer', @:) + %bw! + + " directory name fuzzy completion - NOT supported + + " environment variable name fuzzy completion + set wildoptions& + call feedkeys(":echo $VUT\\\"\", 'tx') + call assert_equal('"echo $VUT', @:) + set wildoptions=fuzzy + call feedkeys(":echo $VUT\\\"\", 'tx') + call assert_equal('"echo $VIMRUNTIME', @:) + + " autocmd event fuzzy completion + set wildoptions& + call feedkeys(":autocmd BWout\\\"\", 'tx') + call assert_equal('"autocmd BWout', @:) + set wildoptions=fuzzy + call feedkeys(":autocmd BWout\\\"\", 'tx') + call assert_equal('"autocmd BufWipeout', @:) + + " vim expression fuzzy completion + let g:PerPlaceCount = 10 + set wildoptions& + call feedkeys(":let c = ppc\\\"\", 'tx') + call assert_equal('"let c = ppc', @:) + set wildoptions=fuzzy + call feedkeys(":let c = ppc\\\"\", 'tx') + call assert_equal('"let c = PerPlaceCount', @:) + + " file name fuzzy completion - NOT supported + + " files in path fuzzy completion - NOT supported + + " filetype name fuzzy completion - NOT supported + + " user defined function name completion + set wildoptions& + call feedkeys(":call Test_w_fuz\\\"\", 'tx') + call assert_equal('"call Test_w_fuz', @:) + set wildoptions=fuzzy + call feedkeys(":call Test_w_fuz\\\"\", 'tx') + call assert_equal('"call Test_wildoptions_fuzzy()', @:) + + " user defined command name completion + set wildoptions& + call feedkeys(":MsFeat\\\"\", 'tx') + call assert_equal('"MsFeat', @:) + set wildoptions=fuzzy + call feedkeys(":MsFeat\\\"\", 'tx') + call assert_equal('"MissingFeature', @:) + + " :help tag fuzzy completion - NOT supported + + " highlight group name fuzzy completion + set wildoptions& + call feedkeys(":highlight SKey\\\"\", 'tx') + call assert_equal('"highlight SKey', @:) + call feedkeys(":highlight Sp*Key\\\"\", 'tx') + call assert_equal('"highlight SpecialKey', @:) + set wildoptions=fuzzy + call feedkeys(":highlight SKey\\\"\", 'tx') + call assert_equal('"highlight SpecialKey', @:) + call feedkeys(":highlight Sp*Key\\\"\", 'tx') + call assert_equal('"highlight Sp*Key', @:) + + " :history suboptions fuzzy completion + set wildoptions& + call feedkeys(":history dg\\\"\", 'tx') + call assert_equal('"history dg', @:) + call feedkeys(":history se*h\\\"\", 'tx') + call assert_equal('"history search', @:) + set wildoptions=fuzzy + call feedkeys(":history dg\\\"\", 'tx') + call assert_equal('"history debug', @:) + call feedkeys(":history se*h\\\"\", 'tx') + call assert_equal('"history se*h', @:) + + " :language locale name fuzzy completion + if has('unix') + set wildoptions& + call feedkeys(":lang psx\\\"\", 'tx') + call assert_equal('"lang psx', @:) + set wildoptions=fuzzy + call feedkeys(":lang psx\\\"\", 'tx') + call assert_equal('"lang POSIX', @:) + endif + + " :mapclear buffer argument fuzzy completion + set wildoptions& + call feedkeys(":mapclear buf\\\"\", 'tx') + call assert_equal('"mapclear buf', @:) + set wildoptions=fuzzy + call feedkeys(":mapclear buf\\\"\", 'tx') + call assert_equal('"mapclear ', @:) + + " map name fuzzy completion - NOT supported + + " menu name fuzzy completion + if has('gui_running') + set wildoptions& + call feedkeys(":menu pup\\\"\", 'tx') + call assert_equal('"menu pup', @:) + set wildoptions=fuzzy + call feedkeys(":menu pup\\\"\", 'tx') + call assert_equal('"menu PopUp.', @:) + endif + + " :messages suboptions fuzzy completion + set wildoptions& + call feedkeys(":messages clr\\\"\", 'tx') + call assert_equal('"messages clr', @:) + set wildoptions=fuzzy + call feedkeys(":messages clr\\\"\", 'tx') + call assert_equal('"messages clear', @:) + + " :set option name fuzzy completion + set wildoptions& + call feedkeys(":set brkopt\\\"\", 'tx') + call assert_equal('"set brkopt', @:) + set wildoptions=fuzzy + call feedkeys(":set brkopt\\\"\", 'tx') + call assert_equal('"set breakindentopt', @:) + set wildoptions& + call feedkeys(":set fixeol\\\"\", 'tx') + call assert_equal('"set fixendofline', @:) + set wildoptions=fuzzy + call feedkeys(":set fixeol\\\"\", 'tx') + call assert_equal('"set fixendofline', @:) + + " :set + set wildoptions& + call feedkeys(":set t_E\\\"\", 'tx') + call assert_equal('"set t_EC', @:) + call feedkeys(":set \\"\", 'tx') + call assert_equal('"set ', @:) + set wildoptions=fuzzy + call feedkeys(":set t_E\\\"\", 'tx') + call assert_equal('"set t_EC', @:) + call feedkeys(":set \\"\", 'tx') + call assert_equal('"set ', @:) + + " :packadd directory name fuzzy completion - NOT supported + + " shell command name fuzzy completion - NOT supported + + " :sign suboptions fuzzy completion + set wildoptions& + call feedkeys(":sign ufe\\\"\", 'tx') + call assert_equal('"sign ufe', @:) + set wildoptions=fuzzy + call feedkeys(":sign ufe\\\"\", 'tx') + call assert_equal('"sign undefine', @:) + + " :syntax suboptions fuzzy completion + set wildoptions& + call feedkeys(":syntax kwd\\\"\", 'tx') + call assert_equal('"syntax kwd', @:) + set wildoptions=fuzzy + call feedkeys(":syntax kwd\\\"\", 'tx') + call assert_equal('"syntax keyword', @:) + + " syntax group name fuzzy completion + set wildoptions& + call feedkeys(":syntax list mpar\\\"\", 'tx') + call assert_equal('"syntax list mpar', @:) + set wildoptions=fuzzy + call feedkeys(":syntax list mpar\\\"\", 'tx') + call assert_equal('"syntax list MatchParen', @:) + + " :syntime suboptions fuzzy completion + if has('profile') + set wildoptions& + call feedkeys(":syntime clr\\\"\", 'tx') + call assert_equal('"syntime clr', @:) + set wildoptions=fuzzy + call feedkeys(":syntime clr\\\"\", 'tx') + call assert_equal('"syntime clear', @:) + endif + + " tag name fuzzy completion - NOT supported + + " tag name and file fuzzy completion - NOT supported + + " user names fuzzy completion - how to test this functionality? + + " user defined variable name fuzzy completion + let g:SomeVariable=10 + set wildoptions& + call feedkeys(":let SVar\\\"\", 'tx') + call assert_equal('"let SVar', @:) + set wildoptions=fuzzy + call feedkeys(":let SVar\\\"\", 'tx') + call assert_equal('"let SomeVariable', @:) + + set wildoptions& + %bw! + endfunc + " vim: shiftwidth=2 sts=2 expandtab *** ../vim-8.2.4462/src/version.c 2022-02-24 12:33:13.155233722 +0000 --- src/version.c 2022-02-24 13:19:06.419048552 +0000 *************** *** 756,757 **** --- 756,759 ---- { /* Add new patch number below this line */ + /**/ + 4463, /**/ -- From "know your smileys": [:-) Frankenstein's monster /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///