From d5a6040967d8dd32b6cdeceb907ca4d50f56e4c2 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 30 Aug 2024 15:37:24 +0800 Subject: vim-patch:9.1.0598: fuzzy completion does not work with default completion Problem: fuzzy completion does not work with default completion Solution: Make it work (glepnir) closes: vim/vim#15193 https://github.com/vim/vim/commit/8159fb18a92e9a9f5e35201bd92bf651f4d5835c Cherry-pick insexpand.c changes from patch 9.1.0608. N/A patch: vim-patch:9.1.0632: MS-Windows: Compiler Warnings Co-authored-by: glepnir --- src/nvim/insexpand.c | 91 ++++++++++++++++++++++++++++++---- src/nvim/search.c | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 1be4f601bc..7a123898a5 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -284,6 +284,8 @@ static size_t spell_bad_len = 0; // length of located bad word static int compl_selected_item = -1; +static int *compl_fuzzy_scores; + // "compl_match_array" points the currently displayed list of entries in the // popup menu. It is NULL when there is no popup menu. static pumitem_T *compl_match_array = NULL; @@ -3141,7 +3143,7 @@ enum { /// the "st->e_cpt" option value and process the next matching source. /// INS_COMPL_CPT_END if all the values in "st->e_cpt" are processed. static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_arg, - pos_T *start_match_pos) + pos_T *start_match_pos, bool in_fuzzy) { int compl_type = -1; int status = INS_COMPL_CPT_OK; @@ -3157,7 +3159,7 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar st->first_match_pos = *start_match_pos; // Move the cursor back one character so that ^N can match the // word immediately after the cursor. - if (ctrl_x_mode_normal() && dec(&st->first_match_pos) < 0) { + if (ctrl_x_mode_normal() && (!in_fuzzy && dec(&st->first_match_pos) < 0)) { // Move the cursor to after the last character in the // buffer, so that word at start of buffer is found // correctly. @@ -3297,11 +3299,30 @@ static void get_next_tag_completion(void) p_ic = save_p_ic; } +/// Compare function for qsort +static int compare_scores(const void *a, const void *b) +{ + int idx_a = *(const int *)a; + int idx_b = *(const int *)b; + int score_a = compl_fuzzy_scores[idx_a]; + int score_b = compl_fuzzy_scores[idx_b]; + return (score_a > score_b) ? -1 : (score_a < score_b) ? 1 : 0; +} + /// Get the next set of filename matching "compl_pattern". static void get_next_filename_completion(void) { char **matches; int num_matches; + char *leader = ins_compl_leader(); + size_t leader_len = strlen(leader); + bool in_fuzzy = ((get_cot_flags() & kOptCotFlagFuzzy) != 0 && leader_len > 0); + + if (in_fuzzy) { + API_CLEAR_STRING(compl_pattern); + compl_pattern = cbuf_to_string("*", 1); + } + if (expand_wildcards(1, &compl_pattern.data, &num_matches, &matches, EW_FILE|EW_DIR|EW_ADDSLASH|EW_SILENT) != OK) { return; @@ -3324,6 +3345,40 @@ static void get_next_filename_completion(void) } } #endif + + if (in_fuzzy) { + garray_T fuzzy_indices; + ga_init(&fuzzy_indices, sizeof(int), 10); + compl_fuzzy_scores = (int *)xmalloc(sizeof(int) * (size_t)num_matches); + + for (int i = 0; i < num_matches; i++) { + char *ptr = matches[i]; + int score = fuzzy_match_str(ptr, leader); + if (score > 0) { + GA_APPEND(int, &fuzzy_indices, i); + compl_fuzzy_scores[i] = score; + } + } + + // prevent qsort from deref NULL pointer + if (fuzzy_indices.ga_len > 0) { + int *fuzzy_indices_data = (int *)fuzzy_indices.ga_data; + qsort(fuzzy_indices_data, (size_t)fuzzy_indices.ga_len, sizeof(int), compare_scores); + + char **sorted_matches = (char **)xmalloc(sizeof(char *) * (size_t)fuzzy_indices.ga_len); + for (int i = 0; i < fuzzy_indices.ga_len; i++) { + sorted_matches[i] = xstrdup(matches[fuzzy_indices_data[i]]); + } + + FreeWild(num_matches, matches); + matches = sorted_matches; + num_matches = fuzzy_indices.ga_len; + } + + xfree(compl_fuzzy_scores); + ga_clear(&fuzzy_indices); + } + ins_compl_add_matches(num_matches, matches, p_fic || p_wic); } @@ -3447,6 +3502,11 @@ static char *ins_compl_get_next_word_or_line(buf_T *ins_buf, pos_T *cur_match_po /// @return OK if a new next match is found, otherwise FAIL. static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_pos) { + char *ptr = NULL; + int len = 0; + bool in_fuzzy = (get_cot_flags() & kOptCotFlagFuzzy) != 0 && compl_length > 0; + char *leader = ins_compl_leader(); + // If 'infercase' is set, don't use 'smartcase' here const int save_p_scs = p_scs; assert(st->ins_buf); @@ -3461,7 +3521,7 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_ const int save_p_ws = p_ws; if (st->ins_buf != curbuf) { p_ws = false; - } else if (*st->e_cpt == '.') { + } else if (*st->e_cpt == '.' && !in_fuzzy) { p_ws = true; } bool looped_around = false; @@ -3472,9 +3532,14 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_ msg_silent++; // Don't want messages for wrapscan. // ctrl_x_mode_line_or_eval() || word-wise search that // has added a word that was at the beginning of the line. - if (ctrl_x_mode_line_or_eval() || (compl_cont_status & CONT_SOL)) { + if ((ctrl_x_mode_whole_line() && !in_fuzzy) || ctrl_x_mode_eval() + || (compl_cont_status & CONT_SOL)) { found_new_match = search_for_exact_line(st->ins_buf, st->cur_match_pos, compl_direction, compl_pattern.data); + } else if (in_fuzzy) { + found_new_match = search_for_fuzzy_match(st->ins_buf, + st->cur_match_pos, leader, compl_direction, + start_pos, &len, &ptr, ctrl_x_mode_whole_line()); } else { found_new_match = searchit(NULL, st->ins_buf, st->cur_match_pos, NULL, compl_direction, compl_pattern.data, @@ -3521,13 +3586,16 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_ && start_pos->col == st->cur_match_pos->col) { continue; } - int len; - char *ptr = ins_compl_get_next_word_or_line(st->ins_buf, st->cur_match_pos, - &len, &cont_s_ipos); + + if (!in_fuzzy) { + ptr = ins_compl_get_next_word_or_line(st->ins_buf, st->cur_match_pos, + &len, &cont_s_ipos); + } if (ptr == NULL || (ins_compl_has_preinsert() && strcmp(ptr, compl_pattern.data) == 0)) { continue; } + if (ins_compl_add_infercase(ptr, len, p_ic, st->ins_buf == curbuf ? NULL : st->ins_buf->b_sfname, 0, cont_s_ipos) != NOTDONE) { @@ -3628,6 +3696,7 @@ static int ins_compl_get_exp(pos_T *ini) static bool st_cleared = false; int found_new_match; int type = ctrl_x_mode; + bool in_fuzzy = (get_cot_flags() & kOptCotFlagFuzzy) != 0; assert(curbuf != NULL); @@ -3652,7 +3721,11 @@ static int ins_compl_get_exp(pos_T *ini) assert(st.ins_buf != NULL); compl_old_match = compl_curr_match; // remember the last current match - st.cur_match_pos = compl_dir_forward() ? &st.last_match_pos : &st.first_match_pos; + if (in_fuzzy) { + st.cur_match_pos = compl_dir_forward() ? &st.last_match_pos : &st.first_match_pos; + } else { + st.cur_match_pos = &st.last_match_pos; + } // For ^N/^P loop over all the flags/windows/buffers in 'complete' while (true) { @@ -3664,7 +3737,7 @@ static int ins_compl_get_exp(pos_T *ini) // entries from 'complete' that look in loaded buffers. if ((ctrl_x_mode_normal() || ctrl_x_mode_line_or_eval()) && (!compl_started || st.found_all)) { - int status = process_next_cpt_value(&st, &type, ini); + int status = process_next_cpt_value(&st, &type, ini, in_fuzzy); if (status == INS_COMPL_CPT_END) { break; } diff --git a/src/nvim/search.c b/src/nvim/search.c index 04f33b9445..30653cbe63 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3620,6 +3620,141 @@ garray_T *fuzzy_match_str_with_pos(char *const str, const char *const pat) return match_positions; } +/// This function searches for a fuzzy match of the pattern `pat` within the +/// line pointed to by `*ptr`. It splits the line into words, performs fuzzy +/// matching on each word, and returns the length and position of the first +/// matched word. +static bool fuzzy_match_str_in_line(char **ptr, char *pat, int *len, pos_T *current_pos) +{ + char *str = *ptr; + char *strBegin = str; + char *end = NULL; + char *start = NULL; + bool found = false; + + if (str == NULL || pat == NULL) { + return found; + } + + while (*str != NUL) { + // Skip non-word characters + start = find_word_start(str); + if (*start == NUL) { + break; + } + end = find_word_end(start); + + // Extract the word from start to end + char save_end = *end; + *end = NUL; + + // Perform fuzzy match + int result = fuzzy_match_str(start, pat); + *end = save_end; + + if (result > 0) { + *len = (int)(end - start); + current_pos->col += (int)(end - strBegin); + found = true; + *ptr = start; + break; + } + + // Move to the end of the current word for the next iteration + str = end; + // Ensure we continue searching after the current word + while (*str != NUL && !vim_iswordp(str)) { + MB_PTR_ADV(str); + } + } + + return found; +} + +/// Search for the next fuzzy match in the specified buffer. +/// This function attempts to find the next occurrence of the given pattern +/// in the buffer, starting from the current position. It handles line wrapping +/// and direction of search. +/// +/// Return true if a match is found, otherwise false. +bool search_for_fuzzy_match(buf_T *buf, pos_T *pos, char *pattern, int dir, pos_T *start_pos, + int *len, char **ptr, bool whole_line) +{ + pos_T current_pos = *pos; + pos_T circly_end; + bool found_new_match = false; + bool looped_around = false; + + if (whole_line) { + current_pos.lnum += dir; + } + + while (true) { + if (buf == curbuf) { + circly_end = *start_pos; + } else { + circly_end.lnum = buf->b_ml.ml_line_count; + circly_end.col = 0; + circly_end.coladd = 0; + } + + // Check if looped around and back to start position + if (looped_around && equalpos(current_pos, circly_end)) { + break; + } + + // Ensure current_pos is valid + if (current_pos.lnum >= 1 && current_pos.lnum <= buf->b_ml.ml_line_count) { + // Get the current line buffer + *ptr = ml_get_buf(buf, current_pos.lnum); + // If ptr is end of line is reached, move to next line + // or previous line based on direction + if (**ptr != NUL) { + if (!whole_line) { + *ptr += current_pos.col; + // Try to find a fuzzy match in the current line starting from current position + found_new_match = fuzzy_match_str_in_line(ptr, pattern, len, ¤t_pos); + if (found_new_match) { + *pos = current_pos; + break; + } + } else { + if (fuzzy_match_str(*ptr, pattern) > 0) { + found_new_match = true; + *pos = current_pos; + *len = (int)strlen(*ptr); + break; + } + } + } + } + + // Move to the next line or previous line based on direction + if (dir == FORWARD) { + if (++current_pos.lnum > buf->b_ml.ml_line_count) { + if (p_ws) { + current_pos.lnum = 1; + looped_around = true; + } else { + break; + } + } + } else { + if (--current_pos.lnum < 1) { + if (p_ws) { + current_pos.lnum = buf->b_ml.ml_line_count; + looped_around = true; + } else { + break; + } + } + } + current_pos.col = 0; + } + + return found_new_match; +} + /// Copy a list of fuzzy matches into a string list after sorting the matches by /// the fuzzy score. Frees the memory allocated for "fuzmatch". void fuzzymatches_to_strmatches(fuzmatch_str_T *const fuzmatch, char ***const matches, -- cgit From a9aedfbc5880b9ac25f7b0ac1dc49fa318dbe89b Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 30 Aug 2024 16:22:08 +0800 Subject: vim-patch:9.1.0605: internal error with fuzzy completion Problem: internal error with fuzzy completion (techntools) Solution: only fuzzy complete the pattern after directory separator (glepnir) fixes: vim/vim#15287 closes: vim/vim#15291 https://github.com/vim/vim/commit/0be03e14b9a2899f5e96720e3b21935bd9d2d34e Co-authored-by: glepnir --- src/nvim/insexpand.c | 25 ++++++++++++++++++++++--- src/nvim/search.c | 18 ++++++++++-------- 2 files changed, 32 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 7a123898a5..a02d7a62d2 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -3315,12 +3315,31 @@ static void get_next_filename_completion(void) char **matches; int num_matches; char *leader = ins_compl_leader(); - size_t leader_len = strlen(leader); + size_t leader_len = ins_compl_leader_len(); bool in_fuzzy = ((get_cot_flags() & kOptCotFlagFuzzy) != 0 && leader_len > 0); if (in_fuzzy) { - API_CLEAR_STRING(compl_pattern); - compl_pattern = cbuf_to_string("*", 1); + char *last_sep = strrchr(leader, PATHSEP); + if (last_sep == NULL) { + // No path separator or separator is the last character, + // fuzzy match the whole leader + API_CLEAR_STRING(compl_pattern); + compl_pattern = cbuf_to_string("*", 1); + } else if (*(last_sep + 1) == NUL) { + in_fuzzy = false; + } else { + // Split leader into path and file parts + size_t path_len = (size_t)(last_sep - leader) + 1; + char *path_with_wildcard = xmalloc(path_len + 2); + vim_snprintf(path_with_wildcard, path_len + 2, "%*.*s*", + (int)path_len, (int)path_len, leader); + API_CLEAR_STRING(compl_pattern); + compl_pattern.data = path_with_wildcard; + compl_pattern.size = path_len + 1; + + // Move leader to the file part + leader = last_sep + 1; + } } if (expand_wildcards(1, &compl_pattern.data, &num_matches, &matches, diff --git a/src/nvim/search.c b/src/nvim/search.c index 30653cbe63..756a4c5255 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3689,15 +3689,15 @@ bool search_for_fuzzy_match(buf_T *buf, pos_T *pos, char *pattern, int dir, pos_ current_pos.lnum += dir; } - while (true) { - if (buf == curbuf) { - circly_end = *start_pos; - } else { - circly_end.lnum = buf->b_ml.ml_line_count; - circly_end.col = 0; - circly_end.coladd = 0; - } + if (buf == curbuf) { + circly_end = *start_pos; + } else { + circly_end.lnum = buf->b_ml.ml_line_count; + circly_end.col = 0; + circly_end.coladd = 0; + } + while (true) { // Check if looped around and back to start position if (looped_around && equalpos(current_pos, circly_end)) { break; @@ -3717,6 +3717,8 @@ bool search_for_fuzzy_match(buf_T *buf, pos_T *pos, char *pattern, int dir, pos_ if (found_new_match) { *pos = current_pos; break; + } else if (looped_around && current_pos.lnum == circly_end.lnum) { + break; } } else { if (fuzzy_match_str(*ptr, pattern) > 0) { -- cgit From 9757b11aaf58cf49b96daa3abe31af2212badbe1 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 30 Aug 2024 16:34:01 +0800 Subject: vim-patch:9.1.0631: wrong completion list displayed with non-existing dir + fuzzy completion Problem: wrong completion list displayed with non-existing dir + fuzzy completion (kawarimidoll) Solution: clear list of matches, if leader did not use fuzzy match (glepnir) fixes: vim/vim#15357 closes: vim/vim#15365 https://github.com/vim/vim/commit/6b6280c4a270547f84f01c0e0d9be1b7d6bb9e20 Co-authored-by: glepnir --- src/nvim/insexpand.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index a02d7a62d2..4cddebf3f9 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -3339,6 +3339,7 @@ static void get_next_filename_completion(void) // Move leader to the file part leader = last_sep + 1; + leader_len -= path_len; } } @@ -3392,13 +3393,18 @@ static void get_next_filename_completion(void) FreeWild(num_matches, matches); matches = sorted_matches; num_matches = fuzzy_indices.ga_len; + } else if (leader_len > 0) { + FreeWild(num_matches, matches); + num_matches = 0; } xfree(compl_fuzzy_scores); ga_clear(&fuzzy_indices); } - ins_compl_add_matches(num_matches, matches, p_fic || p_wic); + if (num_matches > 0) { + ins_compl_add_matches(num_matches, matches, p_fic || p_wic); + } } /// Get the next set of command-line completions matching "compl_pattern". -- cgit From d9f4c1db23bff997ec2f568b378c2666fa580ad7 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 30 Aug 2024 16:42:38 +0800 Subject: vim-patch:9.1.0634: Ctrl-P not working by default Problem: Ctrl-P not working by default (Jesse Pavel, after v9.1.0598) Solution: Revert part of v9.1.0598 and set cur_match_pos correctly according to compl_dir_forward() fixes: vim/vim#15370 closes: vim/vim#15379 https://github.com/vim/vim/commit/13032a49b7d2a45e7c774cf23ee8f58f56b03781 Co-authored-by: Christian Brabandt --- src/nvim/insexpand.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 4cddebf3f9..15ece1cab8 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -3746,11 +3746,7 @@ static int ins_compl_get_exp(pos_T *ini) assert(st.ins_buf != NULL); compl_old_match = compl_curr_match; // remember the last current match - if (in_fuzzy) { - st.cur_match_pos = compl_dir_forward() ? &st.last_match_pos : &st.first_match_pos; - } else { - st.cur_match_pos = &st.last_match_pos; - } + st.cur_match_pos = compl_dir_forward() ? &st.last_match_pos : &st.first_match_pos; // For ^N/^P loop over all the flags/windows/buffers in 'complete' while (true) { -- cgit From f4ddbaeb9e3dd68f8d6b90e805246a24b7960019 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 30 Aug 2024 16:43:31 +0800 Subject: vim-patch:9.1.0654: completion does not respect completeslash with fuzzy Problem: completion does not respect completeslash with fuzzy (egesip) Solution: Change path separator on Windows, depending on 'completeslash' option value (glepnir) fixes: vim/vim#15392 closes: vim/vim#15418 https://github.com/vim/vim/commit/b9de1a057f9a0b6de6f64a9c1b2078c7069cdd7d Co-authored-by: glepnir --- src/nvim/insexpand.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 15ece1cab8..af26e882dc 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -3318,8 +3318,30 @@ static void get_next_filename_completion(void) size_t leader_len = ins_compl_leader_len(); bool in_fuzzy = ((get_cot_flags() & kOptCotFlagFuzzy) != 0 && leader_len > 0); +#ifdef BACKSLASH_IN_FILENAME + char pathsep = (curbuf->b_p_csl[0] == 's') + ? '/' : (curbuf->b_p_csl[0] == 'b') ? '\\' : PATHSEP; +#else + char pathsep = PATHSEP; +#endif + if (in_fuzzy) { - char *last_sep = strrchr(leader, PATHSEP); +#ifdef BACKSLASH_IN_FILENAME + if (curbuf->b_p_csl[0] == 's') { + for (size_t i = 0; i < leader_len; i++) { + if (leader[i] == '\\') { + leader[i] = '/'; + } + } + } else if (curbuf->b_p_csl[0] == 'b') { + for (size_t i = 0; i < leader_len; i++) { + if (leader[i] == '/') { + leader[i] = '\\'; + } + } + } +#endif + char *last_sep = strrchr(leader, pathsep); if (last_sep == NULL) { // No path separator or separator is the last character, // fuzzy match the whole leader -- cgit From 767a52ba308655f72ac4d255b4f13fd2c635f6d1 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 1 Sep 2024 06:22:37 +0800 Subject: vim-patch:9.1.0705: Sorting of fuzzy filename completion is not stable Problem: Sorting of fuzzy filename completion is not stable Solution: Compare indexes when scores are equal. Fix some typos. (zeertzjq) closes: vim/vim#15593 https://github.com/vim/vim/commit/58d705238c0794ee3baa4173831ab157e709a48a --- src/nvim/insexpand.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index af26e882dc..f2ad4c0757 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -3306,7 +3306,8 @@ static int compare_scores(const void *a, const void *b) int idx_b = *(const int *)b; int score_a = compl_fuzzy_scores[idx_a]; int score_b = compl_fuzzy_scores[idx_b]; - return (score_a > score_b) ? -1 : (score_a < score_b) ? 1 : 0; + return score_a == score_b ? (idx_a == idx_b ? 0 : (idx_a < idx_b ? -1 : 1)) + : (score_a > score_b ? -1 : 1); } /// Get the next set of filename matching "compl_pattern". -- cgit From bf62672d59299cc548103bdf7d8d162043acd011 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 11 Sep 2024 06:04:57 +0800 Subject: vim-patch:26e4b00: runtime(doc): Revert outdated comment in completeopt's fuzzy documentation Originally, `:set completeopt+=fuzzy` did not affect how the candidate list is collected in default keyword completion. A comment was added to documentation as part of vim/vim#14912 to clarify it. vim/vim#15193 later changed the fuzzy behavior to now change the candidate collection behavior as well so the clarification in docs is now wrong. Remove them here. closes: vim/vim#15656 https://github.com/vim/vim/commit/26e4b000025ea0e25ea7877314d9095737431bae Co-authored-by: Yee Cheng Chin --- src/nvim/options.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/options.lua b/src/nvim/options.lua index bb63ff638b..c028999d61 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1505,10 +1505,7 @@ local options = { fuzzy Enable |fuzzy-matching| for completion candidates. This allows for more flexible and intuitive matching, where characters can be skipped and matches can be found even - if the exact sequence is not typed. Only makes a - difference how completion candidates are reduced from the - list of alternatives, but not how the candidates are - collected (using different completion types). + if the exact sequence is not typed. longest Only insert the longest common text of the matches. If the menu is displayed you can use CTRL-L to add more -- cgit From fbac2545110bec653a0cc984b04e3636593ec214 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 16 Sep 2024 09:10:51 +0800 Subject: vim-patch:9.1.0733: keyword completion does not work with fuzzy Problem: keyword completion does not work with fuzzy (egesip) Solution: handle ctrl_x_mode_normal() specifically (glepnir) fixes: vim/vim#15412 closes: vim/vim#15424 https://github.com/vim/vim/commit/7cfe693f9bfa74690867e4d96c25f2205d0d13e4 Co-authored-by: glepnir --- src/nvim/insexpand.c | 4 ++++ src/nvim/search.c | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) (limited to 'src') diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index f2ad4c0757..226cfeb322 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -4822,6 +4822,7 @@ static int ins_compl_start(void) line = ml_get(curwin->w_cursor.lnum); } + bool in_fuzzy = get_cot_flags() & kOptCotFlagFuzzy; if (compl_status_adding()) { edit_submode_pre = _(" Adding"); if (ctrl_x_mode_line_or_eval()) { @@ -4836,6 +4837,9 @@ static int ins_compl_start(void) compl_length = 0; compl_col = curwin->w_cursor.col; compl_lnum = curwin->w_cursor.lnum; + } else if (ctrl_x_mode_normal() && in_fuzzy) { + compl_startpos = curwin->w_cursor; + compl_cont_status &= CONT_S_IPOS; } } else { edit_submode_pre = NULL; diff --git a/src/nvim/search.c b/src/nvim/search.c index 756a4c5255..19de55666f 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3684,6 +3684,8 @@ bool search_for_fuzzy_match(buf_T *buf, pos_T *pos, char *pattern, int dir, pos_ pos_T circly_end; bool found_new_match = false; bool looped_around = false; + char *next_word_end = NULL; + char *match_word = NULL; if (whole_line) { current_pos.lnum += dir; @@ -3715,6 +3717,27 @@ bool search_for_fuzzy_match(buf_T *buf, pos_T *pos, char *pattern, int dir, pos_ // Try to find a fuzzy match in the current line starting from current position found_new_match = fuzzy_match_str_in_line(ptr, pattern, len, ¤t_pos); if (found_new_match) { + if (ctrl_x_mode_normal()) { + match_word = xstrnsave(*ptr, (size_t)(*len)); + if (strcmp(match_word, pattern) == 0) { + next_word_end = find_word_start(*ptr + *len); + if (*next_word_end != NUL && *next_word_end != NL) { + // Find end of the word. + while (*next_word_end != NUL) { + int l = utfc_ptr2len(next_word_end); + if (l < 2 && !vim_iswordc(*next_word_end)) { + break; + } + next_word_end += l; + } + } else if (looped_around) { + found_new_match = false; + } + *len = (int)(next_word_end - *ptr); + current_pos.col = *len; + } + xfree(match_word); + } *pos = current_pos; break; } else if (looped_around && current_pos.lnum == circly_end.lnum) { -- cgit From 30293575204bc6b1cdc8a7e06af2710921d46da2 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 22 Feb 2025 07:32:40 +0800 Subject: vim-patch:9.1.1131: potential out-of-memory issue in search.c Problem: potential out-of-memory issue in search.c Solution: improve situation and refactor search.c slightly (John Marriott) - In function update_search_stat(): add a check for a theoretical null pointer reference, set and remember the length of lastpat, remove the three calls to STRLEN() and use the various string's associated lengths instead, add a check for an out-of-memory condition. - In function search_for_fuzz_match(): remove a call to strnsave() and thus avoid having to add a check for an out-of-memory condition, also replace the call to STRLEN() by ml_get_buf_len(). closes: vim/vim#16689 https://github.com/vim/vim/commit/b79fa3d9c8a08f15267797511d779e33bd33e68e Co-authored-by: John Marriott --- src/nvim/search.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/nvim/search.c b/src/nvim/search.c index 19de55666f..07bc84ba84 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3684,8 +3684,6 @@ bool search_for_fuzzy_match(buf_T *buf, pos_T *pos, char *pattern, int dir, pos_ pos_T circly_end; bool found_new_match = false; bool looped_around = false; - char *next_word_end = NULL; - char *match_word = NULL; if (whole_line) { current_pos.lnum += dir; @@ -3718,9 +3716,8 @@ bool search_for_fuzzy_match(buf_T *buf, pos_T *pos, char *pattern, int dir, pos_ found_new_match = fuzzy_match_str_in_line(ptr, pattern, len, ¤t_pos); if (found_new_match) { if (ctrl_x_mode_normal()) { - match_word = xstrnsave(*ptr, (size_t)(*len)); - if (strcmp(match_word, pattern) == 0) { - next_word_end = find_word_start(*ptr + *len); + if (strncmp(*ptr, pattern, (size_t)(*len)) == 0 && pattern[*len] == NUL) { + char *next_word_end = find_word_start(*ptr + *len); if (*next_word_end != NUL && *next_word_end != NL) { // Find end of the word. while (*next_word_end != NUL) { @@ -3736,7 +3733,6 @@ bool search_for_fuzzy_match(buf_T *buf, pos_T *pos, char *pattern, int dir, pos_ *len = (int)(next_word_end - *ptr); current_pos.col = *len; } - xfree(match_word); } *pos = current_pos; break; @@ -3747,7 +3743,7 @@ bool search_for_fuzzy_match(buf_T *buf, pos_T *pos, char *pattern, int dir, pos_ if (fuzzy_match_str(*ptr, pattern) > 0) { found_new_match = true; *pos = current_pos; - *len = (int)strlen(*ptr); + *len = ml_get_buf_len(buf, current_pos.lnum); break; } } -- cgit From 90d59e6c8aa6141c54f81586df423e5a76e009f9 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 7 Mar 2025 08:18:33 +0800 Subject: vim-patch:9.1.1178: not possible to generate completion candidates using fuzzy matching Problem: not possible to generate completion candidates using fuzzy matching Solution: add the 'completefuzzycollect' option for (some) ins-completion modes (glepnir) fixes vim/vim#15296 fixes vim/vim#15295 fixes vim/vim#15294 closes: vim/vim#16032 https://github.com/vim/vim/commit/f31cfa29bf72b0cdf6fa1b60346ea4e187bcafd1 Co-authored-by: glepnir --- src/nvim/insexpand.c | 302 +++++++++++++++++++++++++++++++++++++++---------- src/nvim/option_vars.h | 4 +- src/nvim/options.lua | 31 ++++- src/nvim/optionstr.c | 1 + src/nvim/search.c | 38 ++++--- src/nvim/spell.c | 2 +- 6 files changed, 304 insertions(+), 74 deletions(-) (limited to 'src') diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 226cfeb322..fc4c9c9807 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -215,6 +215,13 @@ static compl_T *compl_curr_match = NULL; static compl_T *compl_shown_match = NULL; static compl_T *compl_old_match = NULL; +/// list used to store the compl_T which have the max score +/// used for completefuzzycollect +static compl_T **compl_best_matches = NULL; +static int compl_num_bests = 0; +/// inserted a longest when completefuzzycollect enabled +static bool compl_cfc_longest_ins = false; + /// After using a cursor key selects a match in the popup menu, /// otherwise it inserts a line break. static bool compl_enter_selects = false; @@ -732,7 +739,7 @@ static char *ins_compl_infercase_gettext(const char *str, int char_len, int comp /// /// @param[in] cont_s_ipos next ^X<> will set initial_pos int ins_compl_add_infercase(char *str_arg, int len, bool icase, char *fname, Direction dir, - bool cont_s_ipos) + bool cont_s_ipos, int score) FUNC_ATTR_NONNULL_ARG(1) { char *str = str_arg; @@ -777,11 +784,26 @@ int ins_compl_add_infercase(char *str_arg, int len, bool icase, char *fname, Dir flags |= CP_ICASE; } - int res = ins_compl_add(str, len, fname, NULL, false, NULL, dir, flags, false, NULL); + int res = ins_compl_add(str, len, fname, NULL, false, NULL, dir, flags, false, NULL, score); xfree(tofree); return res; } +/// Check if ctrl_x_mode has been configured in 'completefuzzycollect' +static bool cfc_has_mode(void) +{ + switch (ctrl_x_mode) { + case CTRL_X_NORMAL: + return (cfc_flags & kOptCfcFlagKeyword) != 0; + case CTRL_X_FILES: + return (cfc_flags & kOptCfcFlagFiles) != 0; + case CTRL_X_WHOLE_LINE: + return (cfc_flags & kOptCfcFlagWholeLine) != 0; + default: + return false; + } +} + /// free cptext static inline void free_cptext(char *const *const cptext) { @@ -818,12 +840,13 @@ static inline void free_cptext(char *const *const cptext) /// returned in case of error. static int ins_compl_add(char *const str, int len, char *const fname, char *const *const cptext, const bool cptext_allocated, typval_T *user_data, const Direction cdir, - int flags_arg, const bool adup, const int *user_hl) + int flags_arg, const bool adup, const int *user_hl, const int score) FUNC_ATTR_NONNULL_ARG(1) { compl_T *match; const Direction dir = (cdir == kDirectionNotSet ? compl_direction : cdir); int flags = flags_arg; + bool inserted = false; if (flags & CP_FAST) { fast_breakcheck(); @@ -864,6 +887,7 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons match = xcalloc(1, sizeof(compl_T)); match->cp_number = flags & CP_ORIGINAL_TEXT ? 0 : -1; match->cp_str = cbuf_to_string(str, (size_t)len); + match->cp_score = score; // match-fname is: // - compl_curr_match->cp_fname if it is a string equal to fname. @@ -907,6 +931,33 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons // current match in the list of matches . if (compl_first_match == NULL) { match->cp_next = match->cp_prev = NULL; + } else if (cfc_has_mode() && score > 0 && compl_get_longest) { + compl_T *current = compl_first_match->cp_next; + compl_T *prev = compl_first_match; + inserted = false; + // The direction is ignored when using longest and + // completefuzzycollect, because matches are inserted + // and sorted by score. + while (current != NULL && current != compl_first_match) { + if (current->cp_score < score) { + match->cp_next = current; + match->cp_prev = current->cp_prev; + if (current->cp_prev) { + current->cp_prev->cp_next = match; + } + current->cp_prev = match; + inserted = true; + break; + } + prev = current; + current = current->cp_next; + } + if (!inserted) { + prev->cp_next = match; + match->cp_prev = prev; + match->cp_next = compl_first_match; + compl_first_match->cp_prev = match; + } } else if (dir == FORWARD) { match->cp_next = compl_curr_match->cp_next; match->cp_prev = compl_curr_match; @@ -925,7 +976,7 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons compl_curr_match = match; // Find the longest common string if still doing that. - if (compl_get_longest && (flags & CP_ORIGINAL_TEXT) == 0) { + if (compl_get_longest && (flags & CP_ORIGINAL_TEXT) == 0 && !cfc_has_mode()) { ins_compl_longest_match(match); } @@ -1013,9 +1064,7 @@ static void ins_compl_longest_match(compl_T *match) compl_leader = copy_string(match->cp_str, NULL); bool had_match = (curwin->w_cursor.col > compl_col); - ins_compl_delete(false); - ins_compl_insert_bytes(compl_leader.data + get_compl_len(), -1); - ins_redraw(false); + ins_compl_longest_insert(compl_leader.data); // When the match isn't there (to avoid matching itself) remove it // again after redrawing. @@ -1049,9 +1098,7 @@ static void ins_compl_longest_match(compl_T *match) compl_leader.size = (size_t)(p - compl_leader.data); bool had_match = (curwin->w_cursor.col > compl_col); - ins_compl_delete(false); - ins_compl_insert_bytes(compl_leader.data + get_compl_len(), -1); - ins_redraw(false); + ins_compl_longest_insert(compl_leader.data); // When the match isn't there (to avoid matching itself) remove it // again after redrawing. @@ -1072,7 +1119,7 @@ static void ins_compl_add_matches(int num_matches, char **matches, int icase) for (int i = 0; i < num_matches && add_r != FAIL; i++) { add_r = ins_compl_add(matches[i], -1, NULL, NULL, false, NULL, dir, - CP_FAST | (icase ? CP_ICASE : 0), false, NULL); + CP_FAST | (icase ? CP_ICASE : 0), false, NULL, 0); if (add_r == OK) { // If dir was BACKWARD then honor it just once. dir = FORWARD; @@ -1238,6 +1285,7 @@ static int ins_compl_build_pum(void) bool compl_no_select = (cur_cot_flags & kOptCotFlagNoselect) != 0; bool fuzzy_filter = (cur_cot_flags & kOptCotFlagFuzzy) != 0; bool fuzzy_sort = fuzzy_filter && !(cur_cot_flags & kOptCotFlagNosort); + compl_T *match_head = NULL, *match_tail = NULL; // If the current match is the original text don't find the first @@ -1554,7 +1602,7 @@ static void ins_compl_dictionaries(char *dict_start, char *pat, int flags, bool spell_dump_compl(ptr, regmatch.rm_ic, &dir, 0); } else if (count > 0) { // avoid warning for using "files" uninit ins_compl_files(count, files, thesaurus, flags, - ®match, buf, &dir); + (cfc_has_mode() ? NULL : ®match), buf, &dir); if (flags != DICT_EXACT) { FreeWild(count, files); } @@ -1605,7 +1653,7 @@ static int thesaurus_add_words_in_line(char *fname, char **buf_arg, int dir, con // Add the word. Skip the regexp match. if (wstart != skip_word) { status = ins_compl_add_infercase(wstart, (int)(ptr - wstart), p_ic, - fname, dir, false); + fname, dir, false, 0); if (status == FAIL) { break; } @@ -1622,6 +1670,11 @@ static void ins_compl_files(int count, char **files, bool thesaurus, int flags, regmatch_T *regmatch, char *buf, Direction *dir) FUNC_ATTR_NONNULL_ARG(2, 7) { + bool in_fuzzy_collect = cfc_has_mode() && ctrl_x_mode_normal(); + + char *leader = in_fuzzy_collect ? ins_compl_leader() : NULL; + int leader_len = in_fuzzy_collect ? (int)ins_compl_leader_len() : 0; + for (int i = 0; i < count && !got_int && !compl_interrupted; i++) { FILE *fp = os_fopen(files[i], "r"); // open dictionary file if (flags != DICT_EXACT && !shortmess(SHM_COMPLETIONSCAN)) { @@ -1640,27 +1693,51 @@ static void ins_compl_files(int count, char **files, bool thesaurus, int flags, // Check each line for a match. while (!got_int && !compl_interrupted && !vim_fgets(buf, LSIZE, fp)) { char *ptr = buf; - while (vim_regexec(regmatch, buf, (colnr_T)(ptr - buf))) { - ptr = regmatch->startp[0]; - ptr = ctrl_x_mode_line_or_eval() ? find_line_end(ptr) : find_word_end(ptr); - int add_r = ins_compl_add_infercase(regmatch->startp[0], - (int)(ptr - regmatch->startp[0]), - p_ic, files[i], *dir, false); - if (thesaurus) { - // For a thesaurus, add all the words in the line - ptr = buf; - add_r = thesaurus_add_words_in_line(files[i], &ptr, *dir, regmatch->startp[0]); - } - if (add_r == OK) { - // if dir was BACKWARD then honor it just once - *dir = FORWARD; - } else if (add_r == FAIL) { - break; + if (regmatch != NULL) { + while (vim_regexec(regmatch, buf, (colnr_T)(ptr - buf))) { + ptr = regmatch->startp[0]; + ptr = ctrl_x_mode_line_or_eval() ? find_line_end(ptr) : find_word_end(ptr); + int add_r = ins_compl_add_infercase(regmatch->startp[0], + (int)(ptr - regmatch->startp[0]), + p_ic, files[i], *dir, false, 0); + if (thesaurus) { + // For a thesaurus, add all the words in the line + ptr = buf; + add_r = thesaurus_add_words_in_line(files[i], &ptr, *dir, regmatch->startp[0]); + } + if (add_r == OK) { + // if dir was BACKWARD then honor it just once + *dir = FORWARD; + } else if (add_r == FAIL) { + break; + } + // avoid expensive call to vim_regexec() when at end + // of line + if (*ptr == '\n' || got_int) { + break; + } } - // avoid expensive call to vim_regexec() when at end - // of line - if (*ptr == '\n' || got_int) { - break; + } else if (in_fuzzy_collect && leader_len > 0) { + char *line_end = find_line_end(ptr); + while (ptr < line_end) { + int score = 0; + int len = 0; + if (fuzzy_match_str_in_line(&ptr, leader, &len, NULL, &score)) { + char *end_ptr = ctrl_x_mode_line_or_eval() ? find_line_end(ptr) : find_word_end(ptr); + int add_r = ins_compl_add_infercase(ptr, (int)(end_ptr - ptr), + p_ic, files[i], *dir, false, score); + if (add_r == FAIL) { + break; + } + ptr = end_ptr; // start from next word + if (compl_get_longest && ctrl_x_mode_normal() + && compl_first_match->cp_next + && score == compl_first_match->cp_next->cp_score) { + compl_num_bests++; + } + } else if (find_word_end(ptr) == line_end) { + break; + } } } line_breakcheck(); @@ -1746,6 +1823,7 @@ void ins_compl_clear(void) { compl_cont_status = 0; compl_started = false; + compl_cfc_longest_ins = false; compl_matches = 0; compl_selected_item = -1; compl_ins_end_col = 0; @@ -2744,7 +2822,7 @@ static int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast) return FAIL; } int status = ins_compl_add((char *)word, -1, NULL, cptext, true, - &user_data, dir, flags, dup, user_hl); + &user_data, dir, flags, dup, user_hl, 0); if (status != OK) { tv_clear(&user_data); } @@ -2840,7 +2918,7 @@ static void set_completion(colnr_T startcol, list_T *list) } if (ins_compl_add(compl_orig_text.data, (int)compl_orig_text.size, NULL, NULL, false, NULL, 0, - flags | CP_FAST, false, NULL) != OK) { + flags | CP_FAST, false, NULL, 0) != OK) { return; } @@ -3310,6 +3388,87 @@ static int compare_scores(const void *a, const void *b) : (score_a > score_b ? -1 : 1); } +/// insert prefix with redraw +static void ins_compl_longest_insert(char *prefix) +{ + ins_compl_delete(false); + ins_compl_insert_bytes(prefix + get_compl_len(), -1); + ins_redraw(false); +} + +/// Calculate the longest common prefix among the best fuzzy matches +/// stored in compl_best_matches, and insert it as the longest. +static void fuzzy_longest_match(void) +{ + if (compl_num_bests == 0) { + return; + } + + compl_T *nn_compl = compl_first_match->cp_next->cp_next; + bool more_candidates = nn_compl && nn_compl != compl_first_match; + + compl_T *compl = ctrl_x_mode_whole_line() ? compl_first_match + : compl_first_match->cp_next; + if (compl_num_bests == 1) { + // no more candidates insert the match str + if (!more_candidates) { + ins_compl_longest_insert(compl->cp_str.data); + compl_num_bests = 0; + } + compl_num_bests = 0; + return; + } + + compl_best_matches = (compl_T **)xmalloc((size_t)compl_num_bests * sizeof(compl_T *)); + + for (int i = 0; compl != NULL && i < compl_num_bests; i++) { + compl_best_matches[i] = compl; + compl = compl->cp_next; + } + + char *prefix = compl_best_matches[0]->cp_str.data; + int prefix_len = (int)strlen(prefix); + + for (int i = 1; i < compl_num_bests; i++) { + char *match_str = compl_best_matches[i]->cp_str.data; + char *prefix_ptr = prefix; + char *match_ptr = match_str; + int j = 0; + + while (j < prefix_len && *match_ptr != NUL && *prefix_ptr != NUL) { + if (strncmp(prefix_ptr, match_ptr, (size_t)utfc_ptr2len(prefix_ptr)) != 0) { + break; + } + + MB_PTR_ADV(prefix_ptr); + MB_PTR_ADV(match_ptr); + j++; + } + + if (j > 0) { + prefix_len = j; + } + } + + char *leader = ins_compl_leader(); + size_t leader_len = leader != NULL ? strlen(leader) : 0; + + // skip non-consecutive prefixes + if (strncmp(prefix, leader, leader_len) != 0) { + goto end; + } + + prefix = xmemdupz(compl_best_matches[0]->cp_str.data, (size_t)prefix_len); + ins_compl_longest_insert(prefix); + compl_cfc_longest_ins = true; + xfree(prefix); + +end: + xfree(compl_best_matches); + compl_best_matches = NULL; + compl_num_bests = 0; +} + /// Get the next set of filename matching "compl_pattern". static void get_next_filename_completion(void) { @@ -3317,7 +3476,10 @@ static void get_next_filename_completion(void) int num_matches; char *leader = ins_compl_leader(); size_t leader_len = ins_compl_leader_len(); - bool in_fuzzy = ((get_cot_flags() & kOptCotFlagFuzzy) != 0 && leader_len > 0); + bool in_fuzzy_collect = (cfc_has_mode() && leader_len > 0); + bool need_collect_bests = in_fuzzy_collect && compl_get_longest; + int max_score = 0; + Direction dir = compl_direction; #ifdef BACKSLASH_IN_FILENAME char pathsep = (curbuf->b_p_csl[0] == 's') @@ -3326,7 +3488,7 @@ static void get_next_filename_completion(void) char pathsep = PATHSEP; #endif - if (in_fuzzy) { + if (in_fuzzy_collect) { #ifdef BACKSLASH_IN_FILENAME if (curbuf->b_p_csl[0] == 's') { for (size_t i = 0; i < leader_len; i++) { @@ -3349,7 +3511,7 @@ static void get_next_filename_completion(void) API_CLEAR_STRING(compl_pattern); compl_pattern = cbuf_to_string("*", 1); } else if (*(last_sep + 1) == NUL) { - in_fuzzy = false; + in_fuzzy_collect = false; } else { // Split leader into path and file parts size_t path_len = (size_t)(last_sep - leader) + 1; @@ -3389,7 +3551,7 @@ static void get_next_filename_completion(void) } #endif - if (in_fuzzy) { + if (in_fuzzy_collect) { garray_T fuzzy_indices; ga_init(&fuzzy_indices, sizeof(int), 10); compl_fuzzy_scores = (int *)xmalloc(sizeof(int) * (size_t)num_matches); @@ -3408,14 +3570,24 @@ static void get_next_filename_completion(void) int *fuzzy_indices_data = (int *)fuzzy_indices.ga_data; qsort(fuzzy_indices_data, (size_t)fuzzy_indices.ga_len, sizeof(int), compare_scores); - char **sorted_matches = (char **)xmalloc(sizeof(char *) * (size_t)fuzzy_indices.ga_len); for (int i = 0; i < fuzzy_indices.ga_len; i++) { - sorted_matches[i] = xstrdup(matches[fuzzy_indices_data[i]]); + char *match = matches[fuzzy_indices_data[i]]; + int current_score = compl_fuzzy_scores[fuzzy_indices_data[i]]; + if (ins_compl_add(match, -1, NULL, NULL, false, NULL, dir, + CP_FAST | ((p_fic || p_wic) ? CP_ICASE : 0), + false, NULL, current_score) == OK) { + dir = FORWARD; + } + + if (need_collect_bests) { + if (i == 0 || current_score == max_score) { + compl_num_bests++; + max_score = current_score; + } + } } FreeWild(num_matches, matches); - matches = sorted_matches; - num_matches = fuzzy_indices.ga_len; } else if (leader_len > 0) { FreeWild(num_matches, matches); num_matches = 0; @@ -3423,6 +3595,11 @@ static void get_next_filename_completion(void) xfree(compl_fuzzy_scores); ga_clear(&fuzzy_indices); + + if (compl_num_bests > 0 && compl_get_longest) { + fuzzy_longest_match(); + } + return; } if (num_matches > 0) { @@ -3552,8 +3729,9 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_ { char *ptr = NULL; int len = 0; - bool in_fuzzy = (get_cot_flags() & kOptCotFlagFuzzy) != 0 && compl_length > 0; + bool in_collect = (cfc_has_mode() && compl_length > 0); char *leader = ins_compl_leader(); + int score = 0; // If 'infercase' is set, don't use 'smartcase' here const int save_p_scs = p_scs; @@ -3569,7 +3747,7 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_ const int save_p_ws = p_ws; if (st->ins_buf != curbuf) { p_ws = false; - } else if (*st->e_cpt == '.' && !in_fuzzy) { + } else if (*st->e_cpt == '.') { p_ws = true; } bool looped_around = false; @@ -3578,16 +3756,17 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_ bool cont_s_ipos = false; msg_silent++; // Don't want messages for wrapscan. - // ctrl_x_mode_line_or_eval() || word-wise search that - // has added a word that was at the beginning of the line. - if ((ctrl_x_mode_whole_line() && !in_fuzzy) || ctrl_x_mode_eval() - || (compl_cont_status & CONT_SOL)) { - found_new_match = search_for_exact_line(st->ins_buf, st->cur_match_pos, - compl_direction, compl_pattern.data); - } else if (in_fuzzy) { + + if (in_collect) { found_new_match = search_for_fuzzy_match(st->ins_buf, st->cur_match_pos, leader, compl_direction, - start_pos, &len, &ptr, ctrl_x_mode_whole_line()); + start_pos, &len, &ptr, &score); + // ctrl_x_mode_line_or_eval() || word-wise search that + // has added a word that was at the beginning of the line. + } else if (ctrl_x_mode_whole_line() || ctrl_x_mode_eval() + || (compl_cont_status & CONT_SOL)) { + found_new_match = search_for_exact_line(st->ins_buf, st->cur_match_pos, + compl_direction, compl_pattern.data); } else { found_new_match = searchit(NULL, st->ins_buf, st->cur_match_pos, NULL, compl_direction, compl_pattern.data, @@ -3635,7 +3814,7 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_ continue; } - if (!in_fuzzy) { + if (!in_collect) { ptr = ins_compl_get_next_word_or_line(st->ins_buf, st->cur_match_pos, &len, &cont_s_ipos); } @@ -3646,7 +3825,10 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_ if (ins_compl_add_infercase(ptr, len, p_ic, st->ins_buf == curbuf ? NULL : st->ins_buf->b_sfname, - 0, cont_s_ipos) != NOTDONE) { + 0, cont_s_ipos, score) != NOTDONE) { + if (in_collect && score == compl_first_match->cp_next->cp_score) { + compl_num_bests++; + } found_new_match = OK; break; } @@ -3725,7 +3907,7 @@ static void get_next_bufname_token(void) char *tail = path_tail(b->b_sfname); if (strncmp(tail, compl_orig_text.data, compl_orig_text.size) == 0) { ins_compl_add(tail, (int)strlen(tail), NULL, NULL, false, NULL, 0, - p_ic ? CP_ICASE : 0, false, NULL); + p_ic ? CP_ICASE : 0, false, NULL, 0); } } } @@ -3839,6 +4021,10 @@ static int ins_compl_get_exp(pos_T *ini) i = ins_compl_make_cyclic(); } + if (cfc_has_mode() && compl_get_longest && compl_num_bests > 0) { + fuzzy_longest_match(); + } + if (compl_old_match != NULL) { // If several matches were added (FORWARD) or the search failed and has // just been made cyclic then we have to move compl_curr_match to the @@ -4865,9 +5051,9 @@ static int ins_compl_start(void) if (p_ic) { flags |= CP_ICASE; } - if (ins_compl_add(compl_orig_text.data, (int)compl_orig_text.size, + if (ins_compl_add(compl_orig_text.data, -1, NULL, NULL, false, NULL, 0, - flags, false, NULL) != OK) { + flags, false, NULL, 0) != OK) { API_CLEAR_STRING(compl_pattern); API_CLEAR_STRING(compl_orig_text); kv_destroy(compl_orig_extmarks); diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h index 3f0445675d..2e5698870f 100644 --- a/src/nvim/option_vars.h +++ b/src/nvim/option_vars.h @@ -294,8 +294,10 @@ EXTERN char *p_cms; ///< 'commentstring' EXTERN char *p_cpt; ///< 'complete' EXTERN OptInt p_columns; ///< 'columns' EXTERN int p_confirm; ///< 'confirm' +EXTERN char *p_cfc; ///< 'completefuzzycollect' +EXTERN unsigned cfc_flags; ///< flags from 'completefuzzycollect' EXTERN char *p_cia; ///< 'completeitemalign' -EXTERN unsigned cia_flags; ///< order flags of 'completeitemalign' +EXTERN unsigned cia_flags; ///< order flags of 'completeitemalign' EXTERN char *p_cot; ///< 'completeopt' EXTERN unsigned cot_flags; ///< flags from 'completeopt' #ifdef BACKSLASH_IN_FILENAME diff --git a/src/nvim/options.lua b/src/nvim/options.lua index c028999d61..8ee9434a60 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1459,6 +1459,30 @@ local options = { type = 'string', varname = 'p_cfu', }, + { + abbreviation = 'cfc', + defaults = '', + values = { 'keyword', 'files', 'whole_line' }, + flags = true, + deny_duplicates = true, + desc = [=[ + This option enables fuzzy collection for (only some) specific + |ins-completion| modes, adjusting how items are gathered for fuzzy + matching based on input. + The option can contain the following values (separated by commas), + each enabling fuzzy collection for a specific completion mode: + files file names + keyword keyword completion in 'complete' and current file + whole_line whole lines + ]=], + full_name = 'completefuzzycollect', + list = 'onecomma', + scope = { 'global' }, + short_desc = N_('use fuzzy collection for specific completion modes'), + type = 'string', + varname = 'p_cfc', + flags_varname = 'cfc_flags', + }, { abbreviation = 'cia', cb = 'did_set_completeitemalign', @@ -1505,7 +1529,12 @@ local options = { fuzzy Enable |fuzzy-matching| for completion candidates. This allows for more flexible and intuitive matching, where characters can be skipped and matches can be found even - if the exact sequence is not typed. + if the exact sequence is not typed. Note: This option + does not affect the collection of candidate list, it only + controls how completion candidates are reduced from the + list of alternatives. If you want to use |fuzzy-matching| + to gather more alternatives for your candidate list, + see |'completefuzzycollect'|. longest Only insert the longest common text of the matches. If the menu is displayed you can use CTRL-L to add more diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 3a6b4c9936..c6cc7af8cd 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -84,6 +84,7 @@ void didset_string_options(void) check_str_opt(kOptCasemap, NULL); check_str_opt(kOptBackupcopy, NULL); check_str_opt(kOptBelloff, NULL); + check_str_opt(kOptCompletefuzzycollect, NULL); check_str_opt(kOptCompleteopt, NULL); check_str_opt(kOptSessionoptions, NULL); check_str_opt(kOptViewoptions, NULL); diff --git a/src/nvim/search.c b/src/nvim/search.c index 07bc84ba84..b31263e127 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3620,11 +3620,18 @@ garray_T *fuzzy_match_str_with_pos(char *const str, const char *const pat) return match_positions; } -/// This function searches for a fuzzy match of the pattern `pat` within the -/// line pointed to by `*ptr`. It splits the line into words, performs fuzzy -/// matching on each word, and returns the length and position of the first -/// matched word. -static bool fuzzy_match_str_in_line(char **ptr, char *pat, int *len, pos_T *current_pos) +/// This function splits the line pointed to by `*ptr` into words and performs +/// a fuzzy match for the pattern `pat` on each word. It iterates through the +/// line, moving `*ptr` to the start of each word during the process. +/// +/// If a match is found: +/// - `*ptr` points to the start of the matched word. +/// - `*len` is set to the length of the matched word. +/// - `*score` contains the match score. +/// +/// If no match is found, `*ptr` is updated to point beyond the last word +/// or to the end of the line. +bool fuzzy_match_str_in_line(char **ptr, char *pat, int *len, pos_T *current_pos, int *score) { char *str = *ptr; char *strBegin = str; @@ -3649,14 +3656,16 @@ static bool fuzzy_match_str_in_line(char **ptr, char *pat, int *len, pos_T *curr *end = NUL; // Perform fuzzy match - int result = fuzzy_match_str(start, pat); + *score = fuzzy_match_str(start, pat); *end = save_end; - if (result > 0) { + if (*score > 0) { *len = (int)(end - start); - current_pos->col += (int)(end - strBegin); found = true; *ptr = start; + if (current_pos) { + current_pos->col += (int)(end - strBegin); + } break; } @@ -3678,13 +3687,14 @@ static bool fuzzy_match_str_in_line(char **ptr, char *pat, int *len, pos_T *curr /// /// Return true if a match is found, otherwise false. bool search_for_fuzzy_match(buf_T *buf, pos_T *pos, char *pattern, int dir, pos_T *start_pos, - int *len, char **ptr, bool whole_line) + int *len, char **ptr, int *score) { pos_T current_pos = *pos; pos_T circly_end; bool found_new_match = false; bool looped_around = false; + bool whole_line = ctrl_x_mode_whole_line(); if (whole_line) { current_pos.lnum += dir; } @@ -3709,11 +3719,13 @@ bool search_for_fuzzy_match(buf_T *buf, pos_T *pos, char *pattern, int dir, pos_ *ptr = ml_get_buf(buf, current_pos.lnum); // If ptr is end of line is reached, move to next line // or previous line based on direction - if (**ptr != NUL) { + if (*ptr != NULL && **ptr != NUL) { if (!whole_line) { *ptr += current_pos.col; - // Try to find a fuzzy match in the current line starting from current position - found_new_match = fuzzy_match_str_in_line(ptr, pattern, len, ¤t_pos); + // Try to find a fuzzy match in the current line starting + // from current position + found_new_match = fuzzy_match_str_in_line(ptr, pattern, + len, ¤t_pos, score); if (found_new_match) { if (ctrl_x_mode_normal()) { if (strncmp(*ptr, pattern, (size_t)(*len)) == 0 && pattern[*len] == NUL) { @@ -4227,7 +4239,7 @@ search_line: const int add_r = ins_compl_add_infercase(aux, i, p_ic, curr_fname == curbuf->b_fname ? NULL : curr_fname, - dir, cont_s_ipos); + dir, cont_s_ipos, 0); if (add_r == OK) { // if dir was BACKWARD then honor it just once dir = FORWARD; diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 7437a7a32f..c8c3622347 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -3483,7 +3483,7 @@ static void dump_word(slang_T *slang, char *word, char *pat, Direction *dir, int ? mb_strnicmp(p, pat, strlen(pat)) == 0 : strncmp(p, pat, strlen(pat)) == 0) && ins_compl_add_infercase(p, (int)strlen(p), - p_ic, NULL, *dir, false) == OK) { + p_ic, NULL, *dir, false, 0) == OK) { // if dir was BACKWARD then honor it just once *dir = FORWARD; } -- cgit From 17db70f3af382211b82574d03b227164f4db510b Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 8 Mar 2025 05:55:28 +0800 Subject: vim-patch:9.1.1181: Unnecessary STRLEN() calls in insexpand.c Problem: Unnecessary STRLEN() calls in insexpand.c (after 9.1.1178). Solution: Use the already available length (zeertzjq). closes: vim/vim#16814 https://github.com/vim/vim/commit/4422de6316b544c282e6c74afd3df3ee3a4b1cfd --- src/nvim/insexpand.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index fc4c9c9807..0b85fc23e8 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -3427,7 +3427,7 @@ static void fuzzy_longest_match(void) } char *prefix = compl_best_matches[0]->cp_str.data; - int prefix_len = (int)strlen(prefix); + int prefix_len = (int)compl_best_matches[0]->cp_str.size; for (int i = 1; i < compl_num_bests; i++) { char *match_str = compl_best_matches[i]->cp_str.data; @@ -3451,14 +3451,14 @@ static void fuzzy_longest_match(void) } char *leader = ins_compl_leader(); - size_t leader_len = leader != NULL ? strlen(leader) : 0; + size_t leader_len = ins_compl_leader_len(); // skip non-consecutive prefixes - if (strncmp(prefix, leader, leader_len) != 0) { + if (leader_len > 0 && strncmp(prefix, leader, leader_len) != 0) { goto end; } - prefix = xmemdupz(compl_best_matches[0]->cp_str.data, (size_t)prefix_len); + prefix = xmemdupz(prefix, (size_t)prefix_len); ins_compl_longest_insert(prefix); compl_cfc_longest_ins = true; xfree(prefix); @@ -5051,7 +5051,7 @@ static int ins_compl_start(void) if (p_ic) { flags |= CP_ICASE; } - if (ins_compl_add(compl_orig_text.data, -1, + if (ins_compl_add(compl_orig_text.data, (int)compl_orig_text.size, NULL, NULL, false, NULL, 0, flags, false, NULL, 0) != OK) { API_CLEAR_STRING(compl_pattern); -- cgit From 10fde593f177fb7655c5f1c2b9774509e90c6106 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 8 Mar 2025 05:58:00 +0800 Subject: vim-patch:9.1.1182: No cmdline completion for 'completefuzzycollect' Problem: No cmdline completion for the 'completefuzzycollect' option (after v9.1.1178) Solution: Add cmdline completion for the 'completefuzzycollect' option, improve its description in optwin.vim (zeertzjq). closes: vim/vim#16813 https://github.com/vim/vim/commit/53d59ecc1d93ce3a3f6d0182479d825852018ceb No code change is needed in Nvim as Nvim uses expand_set_str_generic() by default. --- src/nvim/options.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 8ee9434a60..71c96b6770 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1529,10 +1529,10 @@ local options = { fuzzy Enable |fuzzy-matching| for completion candidates. This allows for more flexible and intuitive matching, where characters can be skipped and matches can be found even - if the exact sequence is not typed. Note: This option + if the exact sequence is not typed. Note: This option does not affect the collection of candidate list, it only controls how completion candidates are reduced from the - list of alternatives. If you want to use |fuzzy-matching| + list of alternatives. If you want to use |fuzzy-matching| to gather more alternatives for your candidate list, see |'completefuzzycollect'|. -- cgit From cd95ea5d48daa271e323b1eee862e25b49d379d1 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 9 Mar 2025 06:23:25 +0800 Subject: vim-patch:9.1.1185: endless loop with completefuzzycollect and no match found Problem: endless loop with completefuzzycollect and no match found Solution: move pointer to line end and break loop closes: vim/vim#16820 https://github.com/vim/vim/commit/dd42b05f8a37df03a9b77a16a47c08ab33af2b1f Co-authored-by: glepnir --- src/nvim/insexpand.c | 4 +--- src/nvim/search.c | 10 +++++++--- 2 files changed, 8 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 0b85fc23e8..0e8b135812 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -1735,8 +1735,6 @@ static void ins_compl_files(int count, char **files, bool thesaurus, int flags, && score == compl_first_match->cp_next->cp_score) { compl_num_bests++; } - } else if (find_word_end(ptr) == line_end) { - break; } } } @@ -1778,7 +1776,7 @@ char *find_word_end(char *ptr) /// Find the end of the line, omitting CR and NL at the end. /// /// @return a pointer to just after the line. -static char *find_line_end(char *ptr) +char *find_line_end(char *ptr) { char *s = ptr + strlen(ptr); while (s > ptr && (s[-1] == CAR || s[-1] == NL)) { diff --git a/src/nvim/search.c b/src/nvim/search.c index b31263e127..2b7739292b 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3629,8 +3629,7 @@ garray_T *fuzzy_match_str_with_pos(char *const str, const char *const pat) /// - `*len` is set to the length of the matched word. /// - `*score` contains the match score. /// -/// If no match is found, `*ptr` is updated to point beyond the last word -/// or to the end of the line. +/// If no match is found, `*ptr` is updated to to the end of the line. bool fuzzy_match_str_in_line(char **ptr, char *pat, int *len, pos_T *current_pos, int *score) { char *str = *ptr; @@ -3642,8 +3641,9 @@ bool fuzzy_match_str_in_line(char **ptr, char *pat, int *len, pos_T *current_pos if (str == NULL || pat == NULL) { return found; } + char *line_end = find_line_end(str); - while (*str != NUL) { + while (str < line_end) { // Skip non-word characters start = find_word_start(str); if (*start == NUL) { @@ -3677,6 +3677,10 @@ bool fuzzy_match_str_in_line(char **ptr, char *pat, int *len, pos_T *current_pos } } + if (!found) { + *ptr = line_end; + } + return found; } -- cgit From 28f61994748e1edd5fb1755ddf2e5ca1324b5635 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 13 Mar 2025 07:08:25 +0800 Subject: vim-patch:9.1.1197: process_next_cpt_value() uses wrong condition Problem: process_next_cpt_value() uses wrong condition Solution: use cfc_has_mode() instead and remove redundant else if branch (glepnir) closes: vim/vim#16833 https://github.com/vim/vim/commit/53b14578e03f93a53fd6eb21c00caf96484742ed Co-authored-by: glepnir --- src/nvim/insexpand.c | 7 +++---- src/nvim/options.lua | 25 +++++++++++++++++-------- src/nvim/search.c | 16 +++++++++------- 3 files changed, 29 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 0e8b135812..47238468db 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -3219,7 +3219,7 @@ enum { /// the "st->e_cpt" option value and process the next matching source. /// INS_COMPL_CPT_END if all the values in "st->e_cpt" are processed. static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_arg, - pos_T *start_match_pos, bool in_fuzzy) + pos_T *start_match_pos, bool fuzzy_collect) { int compl_type = -1; int status = INS_COMPL_CPT_OK; @@ -3235,7 +3235,7 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar st->first_match_pos = *start_match_pos; // Move the cursor back one character so that ^N can match the // word immediately after the cursor. - if (ctrl_x_mode_normal() && (!in_fuzzy && dec(&st->first_match_pos) < 0)) { + if (ctrl_x_mode_normal() && (!fuzzy_collect && dec(&st->first_match_pos) < 0)) { // Move the cursor to after the last character in the // buffer, so that word at start of buffer is found // correctly. @@ -3924,7 +3924,6 @@ static int ins_compl_get_exp(pos_T *ini) static bool st_cleared = false; int found_new_match; int type = ctrl_x_mode; - bool in_fuzzy = (get_cot_flags() & kOptCotFlagFuzzy) != 0; assert(curbuf != NULL); @@ -3961,7 +3960,7 @@ static int ins_compl_get_exp(pos_T *ini) // entries from 'complete' that look in loaded buffers. if ((ctrl_x_mode_normal() || ctrl_x_mode_line_or_eval()) && (!compl_started || st.found_all)) { - int status = process_next_cpt_value(&st, &type, ini, in_fuzzy); + int status = process_next_cpt_value(&st, &type, ini, cfc_has_mode()); if (status == INS_COMPL_CPT_END) { break; } diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 71c96b6770..1a66b32ccc 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1466,14 +1466,23 @@ local options = { flags = true, deny_duplicates = true, desc = [=[ - This option enables fuzzy collection for (only some) specific - |ins-completion| modes, adjusting how items are gathered for fuzzy - matching based on input. - The option can contain the following values (separated by commas), - each enabling fuzzy collection for a specific completion mode: - files file names - keyword keyword completion in 'complete' and current file - whole_line whole lines + A comma-separated list of option enables fuzzy collection for specific + |ins-completion| modes, affecting how items are gathered during + completion. When set, fuzzy matching is used to find completion + candidates instead of the standard prefix-based matching. This option + can contain the following values are: + + keyword keywords in the current file |i_CTRL-X_CTRL-N| + keywords with the ".", "w", "b", "u", "U" and + "k{dict}" flags in 'complete'. |i_CTRL-N| |i_CTRL-P| + + files file names |i_CTRL-X_CTRL-F| + + whole_line whole lines |i_CTRL-X_CTRL-L| + + When used with 'completeopt' "longest" option, fuzzy collection can + identify the longest common string among the best fuzzy matches and + automatically insert it. ]=], full_name = 'completefuzzycollect', list = 'onecomma', diff --git a/src/nvim/search.c b/src/nvim/search.c index 2b7739292b..6db5fc159b 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3629,7 +3629,7 @@ garray_T *fuzzy_match_str_with_pos(char *const str, const char *const pat) /// - `*len` is set to the length of the matched word. /// - `*score` contains the match score. /// -/// If no match is found, `*ptr` is updated to to the end of the line. +/// If no match is found, `*ptr` is updated to the end of the line. bool fuzzy_match_str_in_line(char **ptr, char *pat, int *len, pos_T *current_pos, int *score) { char *str = *ptr; @@ -3699,9 +3699,6 @@ bool search_for_fuzzy_match(buf_T *buf, pos_T *pos, char *pattern, int dir, pos_ bool looped_around = false; bool whole_line = ctrl_x_mode_whole_line(); - if (whole_line) { - current_pos.lnum += dir; - } if (buf == curbuf) { circly_end = *start_pos; @@ -3711,6 +3708,10 @@ bool search_for_fuzzy_match(buf_T *buf, pos_T *pos, char *pattern, int dir, pos_ circly_end.coladd = 0; } + if (whole_line && start_pos->lnum != pos->lnum) { + current_pos.lnum += dir; + } + while (true) { // Check if looped around and back to start position if (looped_around && equalpos(current_pos, circly_end)) { @@ -3721,11 +3722,14 @@ bool search_for_fuzzy_match(buf_T *buf, pos_T *pos, char *pattern, int dir, pos_ if (current_pos.lnum >= 1 && current_pos.lnum <= buf->b_ml.ml_line_count) { // Get the current line buffer *ptr = ml_get_buf(buf, current_pos.lnum); + if (!whole_line) { + *ptr += current_pos.col; + } + // If ptr is end of line is reached, move to next line // or previous line based on direction if (*ptr != NULL && **ptr != NUL) { if (!whole_line) { - *ptr += current_pos.col; // Try to find a fuzzy match in the current line starting // from current position found_new_match = fuzzy_match_str_in_line(ptr, pattern, @@ -3743,8 +3747,6 @@ bool search_for_fuzzy_match(buf_T *buf, pos_T *pos, char *pattern, int dir, pos_ } next_word_end += l; } - } else if (looped_around) { - found_new_match = false; } *len = (int)(next_word_end - *ptr); current_pos.col = *len; -- cgit From 5975ddbdb85073ce055a24915de7f6960e201dc4 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 14 Mar 2025 06:33:35 +0800 Subject: vim-patch:1dc731a: runtime(doc): make :h 'completefuzzycollect' a bit clearer - Fix grammar - Use "matches" instead of "items" ("completion candidates" is used in some other places, but it's a bit verbose) - "When set" is a bit vague, instead use "For specified modes" closes: vim/vim#16871 https://github.com/vim/vim/commit/1dc731a49ff5d06ead4b506afa04288a5baafc1a --- src/nvim/options.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 1a66b32ccc..96548580ba 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1466,11 +1466,11 @@ local options = { flags = true, deny_duplicates = true, desc = [=[ - A comma-separated list of option enables fuzzy collection for specific - |ins-completion| modes, affecting how items are gathered during - completion. When set, fuzzy matching is used to find completion - candidates instead of the standard prefix-based matching. This option - can contain the following values are: + A comma-separated list of strings to enable fuzzy collection for + specific |ins-completion| modes, affecting how matches are gathered + during completion. For specified modes, fuzzy matching is used to + find completion candidates instead of the standard prefix-based + matching. This option can contain the following values: keyword keywords in the current file |i_CTRL-X_CTRL-N| keywords with the ".", "w", "b", "u", "U" and -- cgit From e39cdafed96813567128c931a6b784b6858dcc13 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 14 Mar 2025 07:42:54 +0800 Subject: vim-patch:9.1.1201: 'completefuzzycollect' does not handle dictionary correctly Problem: 'completefuzzycollect' does not handle dictionary correctly Solution: check for ctrl_x_mode_dictionary (glepnir) closes: vim/vim#16867 https://github.com/vim/vim/commit/587601671cd06ddc4d78f907d98578cdab96003f Cherry-pick a documentation fix from later. Co-authored-by: glepnir --- src/nvim/insexpand.c | 11 +++++------ src/nvim/options.lua | 17 +++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 47238468db..422eb409fe 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -792,14 +792,13 @@ int ins_compl_add_infercase(char *str_arg, int len, bool icase, char *fname, Dir /// Check if ctrl_x_mode has been configured in 'completefuzzycollect' static bool cfc_has_mode(void) { - switch (ctrl_x_mode) { - case CTRL_X_NORMAL: + if (ctrl_x_mode_normal() || ctrl_x_mode_dictionary()) { return (cfc_flags & kOptCfcFlagKeyword) != 0; - case CTRL_X_FILES: + } else if (ctrl_x_mode_files()) { return (cfc_flags & kOptCfcFlagFiles) != 0; - case CTRL_X_WHOLE_LINE: + } else if (ctrl_x_mode_whole_line()) { return (cfc_flags & kOptCfcFlagWholeLine) != 0; - default: + } else { return false; } } @@ -1670,7 +1669,7 @@ static void ins_compl_files(int count, char **files, bool thesaurus, int flags, regmatch_T *regmatch, char *buf, Direction *dir) FUNC_ATTR_NONNULL_ARG(2, 7) { - bool in_fuzzy_collect = cfc_has_mode() && ctrl_x_mode_normal(); + bool in_fuzzy_collect = cfc_has_mode(); char *leader = in_fuzzy_collect ? ins_compl_leader() : NULL; int leader_len = in_fuzzy_collect ? (int)ins_compl_leader_len() : 0; diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 96548580ba..f566582c0c 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1472,17 +1472,18 @@ local options = { find completion candidates instead of the standard prefix-based matching. This option can contain the following values: - keyword keywords in the current file |i_CTRL-X_CTRL-N| - keywords with the ".", "w", "b", "u", "U" and - "k{dict}" flags in 'complete'. |i_CTRL-N| |i_CTRL-P| + keyword keywords in the current file |i_CTRL-X_CTRL-N| + keywords with flags ".", "w", |i_CTRL-N| |i_CTRL-P| + "b", "u", "U" and "k{dict}" in 'complete' + keywords in 'dictionary' |i_CTRL-X_CTRL-K| - files file names |i_CTRL-X_CTRL-F| + files file names |i_CTRL-X_CTRL-F| - whole_line whole lines |i_CTRL-X_CTRL-L| + whole_line whole lines |i_CTRL-X_CTRL-L| - When used with 'completeopt' "longest" option, fuzzy collection can - identify the longest common string among the best fuzzy matches and - automatically insert it. + When using the 'completeopt' "longest" option value, fuzzy collection + can identify the longest common string among the best fuzzy matches + and insert it automatically. ]=], full_name = 'completefuzzycollect', list = 'onecomma', -- cgit From 0af780e8df849f6d393873124afaa31681ab43ec Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 22 Mar 2025 06:32:49 +0800 Subject: vim-patch:9.1.1228: completion: current position column wrong after got a match Problem: The current_pos.col was incorrectly updated to the length of the matching text. This will cause the next search to start from the wrong position. Solution: current_pos has already been updated in search_str_in_line and does not need to be changed (glepnir) closes: vim/vim#16941 https://github.com/vim/vim/commit/5753084042e17d794627d77e4300def031ce5498 Co-authored-by: glepnir --- src/nvim/search.c | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/nvim/search.c b/src/nvim/search.c index 6db5fc159b..b38296ac5c 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3749,7 +3749,6 @@ bool search_for_fuzzy_match(buf_T *buf, pos_T *pos, char *pattern, int dir, pos_ } } *len = (int)(next_word_end - *ptr); - current_pos.col = *len; } } *pos = current_pos; -- cgit