diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index b3b29d0d8a..8ab0dcc8bd 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1,4 +1,4 @@ -*options.txt* For Vim version 9.1. Last change: 2025 Apr 15 +*options.txt* For Vim version 9.1. Last change: 2025 Apr 16 VIM REFERENCE MANUAL by Bram Moolenaar @@ -2125,6 +2125,13 @@ A jump table for the options with a short description can be found at |Q_op|. based expansion (e.g., dictionary |i_CTRL-X_CTRL-K|, included patterns |i_CTRL-X_CTRL-I|, tags |i_CTRL-X_CTRL-]| and normal expansions). + An optional match limit can be specified for a completion source by + appending a caret ("^") followed by a {count} to the source flag. + For example: ".^9,w,u,t^5" limits matches from the current buffer + to 9 and from tags to 5. Other sources remain unlimited. + The match limit takes effect only during forward completion (CTRL-N) + and is ignored during backward completion (CTRL-P). + *'completefunc'* *'cfu'* 'completefunc' 'cfu' string (default: empty) local to buffer diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt index 27470f0022..a5579f4ab8 100644 --- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -1,4 +1,4 @@ -*version9.txt* For Vim version 9.1. Last change: 2025 Apr 15 +*version9.txt* For Vim version 9.1. Last change: 2025 Apr 16 VIM REFERENCE MANUAL by Bram Moolenaar @@ -41622,6 +41622,8 @@ Completion: ~ "f{func}" - complete using given function "f" - complete using 'completefunc' "o" - complete using 'omnifunc' +- allow to limit matches for the 'complete' sources by using the + "{flag}^" notation Options: ~ - the default for 'commentstring' contains whitespace padding to have diff --git a/src/insexpand.c b/src/insexpand.c index f4449c0bfe..9a82a32bcb 100644 --- a/src/insexpand.c +++ b/src/insexpand.c @@ -109,7 +109,7 @@ struct compl_S int cp_in_match_array; // collected by compl_match_array int cp_user_abbr_hlattr; // highlight attribute for abbr int cp_user_kind_hlattr; // highlight attribute for kind - int cp_cpt_value_idx; // index of this match's source in 'cpt' option + int cp_cpt_source_idx; // index of this match's source in 'cpt' option }; // values for cp_flags @@ -215,9 +215,16 @@ static int compl_selected_item = -1; static int *compl_fuzzy_scores; -static int *cpt_func_refresh_always; // array indicating which 'cpt' functions have 'refresh:always' set -static int cpt_value_count; // total number of completion sources specified in the 'cpt' option -static int cpt_value_idx; // index of the current completion source being expanded +// Define the structure for completion source (in 'cpt' option) information +typedef struct cpt_source_T +{ + int refresh_always; // Flag array to indicate which 'cpt' functions have 'refresh:always' set + int max_matches; // Maximum number of items to display in the menu from the source +} cpt_source_T; + +static cpt_source_T *cpt_sources_array; // Pointer to the array of completion sources +static int cpt_sources_count; // Total number of completion sources specified in the 'cpt' option +static int cpt_sources_index; // Index of the current completion source being expanded // "compl_match_array" points the currently displayed list of entries in the // popup menu. It is NULL when there is no popup menu. @@ -239,12 +246,12 @@ static void ins_compl_fixRedoBufForLeader(char_u *ptr_arg); static void ins_compl_add_list(list_T *list); static void ins_compl_add_dict(dict_T *dict); static int get_userdefined_compl_info(colnr_T curs_col, callback_T *cb, int *startcol); -static callback_T *get_cpt_func_callback(char_u *funcname); static void get_cpt_func_completion_matches(callback_T *cb); +static callback_T *get_cpt_func_callback(char_u *funcname); # endif -static int cpt_compl_src_init(char_u *p_cpt); +static int cpt_sources_init(void); static int is_cpt_func_refresh_always(void); -static void cpt_compl_src_clear(void); +static void cpt_sources_clear(void); static void cpt_compl_refresh(void); static int ins_compl_key2dir(int c); static int ins_compl_pum_key(int c); @@ -980,7 +987,7 @@ ins_compl_add( match->cp_user_abbr_hlattr = user_hl ? user_hl[0] : -1; match->cp_user_kind_hlattr = user_hl ? user_hl[1] : -1; match->cp_score = score; - match->cp_cpt_value_idx = cpt_value_idx; + match->cp_cpt_source_idx = cpt_sources_index; if (cptext != NULL) { @@ -1467,6 +1474,10 @@ ins_compl_build_pum(void) compl_T *match_tail = NULL; compl_T *match_next = NULL; int update_shown_match = fuzzy_filter; + int match_count; + int cur_source = -1; + int max_matches_found = FALSE; + int is_forward = compl_shows_dir_forward() && !fuzzy_filter; // Need to build the popup menu list. compl_match_arraysize = 0; @@ -1495,7 +1506,24 @@ ins_compl_build_pum(void) if (fuzzy_filter && compl_leader.string != NULL && compl_leader.length > 0) compl->cp_score = fuzzy_match_str(compl->cp_str.string, compl_leader.string); + if (is_forward && compl->cp_cpt_source_idx != -1) + { + if (cur_source != compl->cp_cpt_source_idx) + { + cur_source = compl->cp_cpt_source_idx; + match_count = 1; + max_matches_found = FALSE; + } + else if (cpt_sources_array && !max_matches_found) + { + int max_matches = cpt_sources_array[cur_source].max_matches; + if (max_matches > 0 && match_count > max_matches) + max_matches_found = TRUE; + } + } + if (!match_at_original_text(compl) + && !max_matches_found && (compl_leader.string == NULL || ins_compl_equal(compl, compl_leader.string, (int)compl_leader.length) || (fuzzy_filter && compl->cp_score > 0))) @@ -1545,6 +1573,8 @@ ins_compl_build_pum(void) shown_match_ok = TRUE; } } + if (is_forward && compl->cp_cpt_source_idx != -1) + match_count++; i++; } @@ -2116,7 +2146,7 @@ ins_compl_clear(void) edit_submode_extra = NULL; VIM_CLEAR_STRING(compl_orig_text); compl_enter_selects = FALSE; - cpt_compl_src_clear(); + cpt_sources_clear(); #ifdef FEAT_EVAL // clear v:completed_item set_vim_var_dict(VV_COMPLETED_ITEM, dict_alloc_lock(VAR_FIXED)); @@ -2419,7 +2449,7 @@ ins_compl_restart(void) compl_matches = 0; compl_cont_status = 0; compl_cont_mode = 0; - cpt_compl_src_clear(); + cpt_sources_clear(); } /* @@ -3622,6 +3652,7 @@ get_complete_info(list_T *what_list, dict_T *retdict) #define CI_WHAT_MATCHES 0x20 #define CI_WHAT_ALL 0xff int what_flag; + int compl_fuzzy_match = (get_cot_flags() & COT_FUZZY) != 0; if (what_list == NULL) what_flag = CI_WHAT_ALL & ~(CI_WHAT_MATCHES | CI_WHAT_COMPLETED); @@ -3698,7 +3729,8 @@ get_complete_info(list_T *what_list, dict_T *retdict) if (compl_curr_match != NULL && compl_curr_match->cp_number == match->cp_number) selected_idx = list_idx; - list_idx += 1; + if (compl_fuzzy_match || match->cp_in_match_array) + list_idx += 1; } match = match->cp_next; } @@ -4669,6 +4701,39 @@ get_next_completion_match(int type, ins_compl_next_state_T *st, pos_T *ini) return found_new_match; } +/* + * Strips carets followed by numbers. This suffix typically represents the + * max_matches setting. + */ + static void +strip_caret_numbers_in_place(char_u *str) +{ + char_u *read = str, *write = str, *p; + + if (str == NULL) + return; + + while (*read) + { + if (*read == '^') + { + p = read + 1; + while (vim_isdigit(*p)) + p++; + if ((*p == ',' || *p == '\0') && p != read + 1) + { + read = p; + continue; + } + else + *write++ = *read++; + } + else + *write++ = *read++; + } + *write = '\0'; +} + /* * Get the next expansion(s), using "compl_pattern". * The search starts at position "ini" in curbuf and in the direction @@ -4704,11 +4769,12 @@ ins_compl_get_exp(pos_T *ini) // Make a copy of 'complete', in case the buffer is wiped out. st.e_cpt_copy = vim_strsave((compl_cont_status & CONT_LOCAL) ? (char_u *)"." : curbuf->b_p_cpt); + strip_caret_numbers_in_place(st.e_cpt_copy); st.e_cpt = st.e_cpt_copy == NULL ? (char_u *)"" : st.e_cpt_copy; st.last_match_pos = st.first_match_pos = *ini; if ((ctrl_x_mode_normal() || ctrl_x_mode_line_or_eval()) - && !cpt_compl_src_init(st.e_cpt)) + && !cpt_sources_init()) return FAIL; } else if (st.ins_buf != curbuf && !buf_valid(st.ins_buf)) @@ -4719,7 +4785,7 @@ ins_compl_get_exp(pos_T *ini) ? &st.last_match_pos : &st.first_match_pos; // For ^N/^P loop over all the flags/windows/buffers in 'complete'. - for (cpt_value_idx = 0;;) + for (cpt_sources_index = 0;;) { found_new_match = FAIL; st.set_match_pos = FALSE; @@ -4736,7 +4802,7 @@ ins_compl_get_exp(pos_T *ini) break; if (status == INS_COMPL_CPT_CONT) { - cpt_value_idx++; + cpt_sources_index++; continue; } } @@ -4750,7 +4816,7 @@ ins_compl_get_exp(pos_T *ini) found_new_match = get_next_completion_match(type, &st, ini); if (type > 0) - cpt_value_idx++; + cpt_sources_index++; // break the loop for specialized modes (use 'complete' just for the // generic ctrl_x_mode == CTRL_X_NORMAL) or when we've found a new @@ -4778,7 +4844,7 @@ ins_compl_get_exp(pos_T *ini) compl_started = FALSE; } } - cpt_value_idx = -1; + cpt_sources_index = -1; compl_started = TRUE; if ((ctrl_x_mode_normal() || ctrl_x_mode_line_or_eval()) @@ -5058,6 +5124,25 @@ find_comp_when_fuzzy(void) return NULL; } +/* + * Find the appropriate completion item when 'complete' ('cpt') includes + * a 'max_matches' postfix. In this case, we search for a match where + * 'cp_in_match_array' is set, indicating that the match is also present + * in 'compl_match_array'. + */ + static compl_T * +find_comp_when_cpt_sources(void) +{ + int is_forward = compl_shows_dir_forward(); + compl_T *match = compl_shown_match; + + do + match = is_forward ? match->cp_next : match->cp_prev; + while (match->cp_next && !match->cp_in_match_array + && !match_at_original_text(match)); + return match; +} + /* * Find the next set of matches for completion. Repeat the completion "todo" * times. The number of matches found is returned in 'num_matches'. @@ -5083,13 +5168,18 @@ find_next_completion_match( unsigned int cur_cot_flags = get_cot_flags(); int compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0; int compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0; + int cpt_sources_active = compl_match_array && cpt_sources_array; while (--todo >= 0) { if (compl_shows_dir_forward() && compl_shown_match->cp_next != NULL) { - compl_shown_match = compl_fuzzy_match && compl_match_array != NULL - ? find_comp_when_fuzzy() : compl_shown_match->cp_next; + if (compl_match_array != NULL && compl_fuzzy_match) + compl_shown_match = find_comp_when_fuzzy(); + else if (cpt_sources_active) + compl_shown_match = find_comp_when_cpt_sources(); + else + compl_shown_match = compl_shown_match->cp_next; found_end = (compl_first_match != NULL && (is_first_match(compl_shown_match->cp_next) || is_first_match(compl_shown_match))); @@ -5098,8 +5188,12 @@ find_next_completion_match( && compl_shown_match->cp_prev != NULL) { found_end = is_first_match(compl_shown_match); - compl_shown_match = compl_fuzzy_match && compl_match_array != NULL - ? find_comp_when_fuzzy() : compl_shown_match->cp_prev; + if (compl_match_array != NULL && compl_fuzzy_match) + compl_shown_match = find_comp_when_fuzzy(); + else if (cpt_sources_active) + compl_shown_match = find_comp_when_cpt_sources(); + else + compl_shown_match = compl_shown_match->cp_prev; found_end |= is_first_match(compl_shown_match); } else @@ -6358,42 +6452,60 @@ spell_back_to_badword(void) * Reset the info associated with completion sources. */ static void -cpt_compl_src_clear(void) +cpt_sources_clear(void) { - VIM_CLEAR(cpt_func_refresh_always); - cpt_value_idx = -1; - cpt_value_count = 0; + VIM_CLEAR(cpt_sources_array); + cpt_sources_index = -1; + cpt_sources_count = 0; } /* * Initialize the info associated with completion sources. */ static int -cpt_compl_src_init(char_u *cpt_str) +cpt_sources_init(void) { + char_u buf[LSIZE]; + int slen; int count = 0; - char_u *p = cpt_str; + char_u *p; - while (*p) + for (p = curbuf->b_p_cpt; *p;) { while (*p == ',' || *p == ' ') // Skip delimiters p++; if (*p) // If not end of string, count this segment { + (void)copy_option_part(&p, buf, LSIZE, ","); // Advance p count++; - copy_option_part(&p, IObuff, IOSIZE, ","); // Advance p } } - cpt_compl_src_clear(); - cpt_value_count = count; + cpt_sources_clear(); + cpt_sources_count = count; if (count > 0) { - cpt_func_refresh_always = ALLOC_CLEAR_MULT(int, count); - if (cpt_func_refresh_always == NULL) + cpt_sources_array = ALLOC_CLEAR_MULT(cpt_source_T, count); + if (cpt_sources_array == NULL) { - cpt_value_count = 0; + cpt_sources_count = 0; return FAIL; } + count = 0; + for (p = curbuf->b_p_cpt; *p;) + { + while (*p == ',' || *p == ' ') // Skip delimiters + p++; + if (*p) // If not end of string, count this segment + { + char_u *t; + + vim_memset(buf, 0, LSIZE); + slen = copy_option_part(&p, buf, LSIZE, ","); // Advance p + if (slen > 0 && (t = vim_strchr(buf, '^')) != NULL) + cpt_sources_array[count].max_matches = atoi((char *)t + 1); + count++; + } + } } return OK; } @@ -6407,8 +6519,8 @@ is_cpt_func_refresh_always(void) #ifdef FEAT_COMPL_FUNC int i; - for (i = 0; i < cpt_value_count; i++) - if (cpt_func_refresh_always[i]) + for (i = 0; i < cpt_sources_count; i++) + if (cpt_sources_array[i].refresh_always) return TRUE; #endif return FALSE; @@ -6433,7 +6545,7 @@ ins_compl_make_linear(void) /* * Remove the matches linked to the current completion source (as indicated by - * cpt_value_idx) from the completion list. + * cpt_sources_index) from the completion list. */ #ifdef FEAT_COMPL_FUNC static compl_T * @@ -6441,16 +6553,20 @@ remove_old_matches(void) { compl_T *sublist_start = NULL, *sublist_end = NULL, *insert_at = NULL; compl_T *current, *next; - int compl_shown_removed = FALSE; - int forward = compl_dir_forward(); + int compl_shown_removed = FALSE; + int forward = (compl_first_match->cp_cpt_source_idx < 0); + + compl_direction = forward ? FORWARD : BACKWARD; + compl_shows_dir = compl_direction; // Identify the sublist of old matches that needs removal for (current = compl_first_match; current != NULL; current = current->cp_next) { - if (current->cp_cpt_value_idx < cpt_value_idx && (forward || (!forward && !insert_at))) + if (current->cp_cpt_source_idx < cpt_sources_index && + (forward || (!forward && !insert_at))) insert_at = current; - if (current->cp_cpt_value_idx == cpt_value_idx) + if (current->cp_cpt_source_idx == cpt_sources_index) { if (!sublist_start) sublist_start = current; @@ -6459,7 +6575,8 @@ remove_old_matches(void) compl_shown_removed = TRUE; } - if ((forward && current->cp_cpt_value_idx > cpt_value_idx) || (!forward && insert_at)) + if ((forward && current->cp_cpt_source_idx > cpt_sources_index) + || (!forward && insert_at)) break; } @@ -6470,7 +6587,8 @@ remove_old_matches(void) compl_shown_match = compl_first_match; else { // Last node will have the prefix that is being completed - for (current = compl_first_match; current->cp_next != NULL; current = current->cp_next) + for (current = compl_first_match; current->cp_next != NULL; + current = current->cp_next) ; compl_shown_match = current; } @@ -6514,11 +6632,12 @@ get_cpt_func_completion_matches(callback_T *cb UNUSED) VIM_CLEAR_STRING(cpt_compl_pattern); ret = get_userdefined_compl_info(curwin->w_cursor.col, cb, &startcol); if (ret == FAIL && startcol == -3) - cpt_func_refresh_always[cpt_value_idx] = FALSE; + cpt_sources_array[cpt_sources_index].refresh_always = FALSE; else if (ret == OK) { expand_by_function(0, cpt_compl_pattern.string, cb); - cpt_func_refresh_always[cpt_value_idx] = compl_opt_refresh_always; + cpt_sources_array[cpt_sources_index].refresh_always = + compl_opt_refresh_always; compl_opt_refresh_always = FALSE; } } @@ -6540,14 +6659,15 @@ cpt_compl_refresh(void) ins_compl_make_linear(); // Make a copy of 'cpt' in case the buffer gets wiped out cpt = vim_strsave(curbuf->b_p_cpt); + strip_caret_numbers_in_place(cpt); - cpt_value_idx = 0; - for (p = cpt; *p; cpt_value_idx++) + cpt_sources_index = 0; + for (p = cpt; *p; cpt_sources_index++) { while (*p == ',' || *p == ' ') // Skip delimiters p++; - if (cpt_func_refresh_always[cpt_value_idx]) + if (cpt_sources_array[cpt_sources_index].refresh_always) { if (*p == 'o') cb = &curbuf->b_ofu_cb; @@ -6563,7 +6683,7 @@ cpt_compl_refresh(void) copy_option_part(&p, IObuff, IOSIZE, ","); // Advance p } - cpt_value_idx = -1; + cpt_sources_index = -1; vim_free(cpt); // Make the list cyclic diff --git a/src/optionstr.c b/src/optionstr.c index 62a708683e..90b8e52ffb 100644 --- a/src/optionstr.c +++ b/src/optionstr.c @@ -1558,9 +1558,10 @@ did_set_commentstring(optset_T *args) did_set_complete(optset_T *args) { char_u **varp = (char_u **)args->os_varp; - char_u *p = NULL; + char_u *p, *t; char_u buffer[LSIZE]; char_u *buf_ptr; + char_u char_before = NUL; int escape; for (p = *varp; *p; ) @@ -1589,16 +1590,40 @@ did_set_complete(optset_T *args) if (vim_strchr((char_u *)".wbuksid]tUfo", *buffer) == NULL) return illegal_char(args->os_errbuf, args->os_errbuflen, *buffer); - if (!vim_strchr((char_u *)"ksf", *buffer) && *(buffer + 1) != NUL) + if (vim_strchr((char_u *)"ksf", *buffer) == NULL && *(buffer + 1) != NUL + && *(buffer + 1) != '^') + char_before = *buffer; + else + { + // Test for a number after '^' + if ((t = vim_strchr(buffer, '^')) != NULL) + { + *t++ = NUL; + if (!*t) + char_before = '^'; + else + { + for (; *t; t++) + { + if (!vim_isdigit(*t)) + { + char_before = '^'; + break; + } + } + } + } + } + if (char_before != NUL) { if (args->os_errbuf) { vim_snprintf((char *)args->os_errbuf, args->os_errbuflen, - _(e_illegal_character_after_chr), *buffer); + _(e_illegal_character_after_chr), char_before); return args->os_errbuf; } + return NULL; } - // Skip comma and spaces while (*p == ',' || *p == ' ') p++; diff --git a/src/testdir/test_ins_complete.vim b/src/testdir/test_ins_complete.vim index 66beb78e65..2b14bfa760 100644 --- a/src/testdir/test_ins_complete.vim +++ b/src/testdir/test_ins_complete.vim @@ -4038,6 +4038,126 @@ func Test_complete_multiline_marks() delfunc Omni_test endfunc +func Test_complete_match_count() + func PrintMenuWords() + let info = complete_info(["selected", "matches"]) + call map(info.matches, {_, v -> v.word}) + return info + endfunc + + new + set cpt=.^0,w + call setline(1, ["fo", "foo", "foobar", "fobarbaz"]) + exe "normal! Gof\\=PrintMenuWords()\" + call assert_equal('fo{''matches'': [''fo'', ''foo'', ''foobar'', ''fobarbaz''], ''selected'': 0}', getline(5)) + 5d + set cpt=.^0,w + exe "normal! Gof\\=PrintMenuWords()\" + call assert_equal('fobarbaz{''matches'': [''fo'', ''foo'', ''foobar'', ''fobarbaz''], ''selected'': 3}', getline(5)) + 5d + set cpt=.^1,w + exe "normal! Gof\\=PrintMenuWords()\" + call assert_equal('fo{''matches'': [''fo''], ''selected'': 0}', getline(5)) + 5d + " max_matches is ignored for backward search + exe "normal! Gof\\=PrintMenuWords()\" + call assert_equal('fobarbaz{''matches'': [''fo'', ''foo'', ''foobar'', ''fobarbaz''], ''selected'': 3}', getline(5)) + 5d + set cpt=.^2,w + exe "normal! Gof\\=PrintMenuWords()\" + call assert_equal('fo{''matches'': [''fo'', ''foo''], ''selected'': 0}', getline(5)) + 5d + set cot=menuone,noselect + set cpt=.^1,w + exe "normal! Gof\\=PrintMenuWords()\" + call assert_equal('f{''matches'': [''fo''], ''selected'': -1}', getline(5)) + set cot& + + func ComplFunc(findstart, base) + if a:findstart + return col(".") + endif + return ["foo1", "foo2", "foo3", "foo4"] + endfunc + + %d + set completefunc=ComplFunc + set cpt=.^1,f^2 + call setline(1, ["fo", "foo", "foobar", "fobarbaz"]) + exe "normal! Gof\\=PrintMenuWords()\" + call assert_equal('fo{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': 0}', getline(5)) + 5d + exe "normal! Gof\\\=PrintMenuWords()\" + call assert_equal('foo1{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': 1}', getline(5)) + 5d + exe "normal! Gof\\\\=PrintMenuWords()\" + call assert_equal('foo2{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': 2}', getline(5)) + 5d + exe "normal! Gof\\\\\=PrintMenuWords()\" + call assert_equal('f{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': -1}', getline(5)) + 5d + exe "normal! Gof\\\\\\=PrintMenuWords()\" + call assert_equal('fo{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': 0}', getline(5)) + + 5d + exe "normal! Gof\\\=PrintMenuWords()\" + call assert_equal('f{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': -1}', getline(5)) + 5d + exe "normal! Gof\\\\=PrintMenuWords()\" + call assert_equal('foo2{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': 2}', getline(5)) + 5d + exe "normal! Gof\\\\\=PrintMenuWords()\" + call assert_equal('foo1{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': 1}', getline(5)) + 5d + exe "normal! Gof\\\\\\=PrintMenuWords()\" + call assert_equal('fo{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': 0}', getline(5)) + 5d + exe "normal! Gof\\\\\\\=PrintMenuWords()\" + call assert_equal('f{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': -1}', getline(5)) + + %d + call setline(1, ["foo"]) + set cpt=fComplFunc^2,. + exe "normal! Gof\\=PrintMenuWords()\" + call assert_equal('foo1{''matches'': [''foo1'', ''foo2'', ''foo''], ''selected'': 0}', getline(2)) + bw! + + " Test refresh:always with max_items + let g:CallCount = 0 + func! CompleteItemsSelect(findstart, base) + if a:findstart + return col('.') - 1 + endif + let g:CallCount += 1 + let res = [[], ['foobar'], ['foo1', 'foo2', 'foo3'], ['foo4', 'foo5', 'foo6']] + return #{words: res[g:CallCount], refresh: 'always'} + endfunc + + new + set complete=.,ffunction('CompleteItemsSelect')^2 + call setline(1, "foobarbar") + let g:CallCount = 0 + exe "normal! Gof\\\=PrintMenuWords()\" + call assert_equal('foobar{''matches'': [''foobarbar'', ''foobar''], ''selected'': 1}', getline(2)) + call assert_equal(1, g:CallCount) + %d + call setline(1, "foobarbar") + let g:CallCount = 0 + exe "normal! Gof\\o\=PrintMenuWords()\" + call assert_equal('fo{''matches'': [''foobarbar'', ''foo1'', ''foo2''], ''selected'': -1}', getline(2)) + call assert_equal(2, g:CallCount) + %d + call setline(1, "foobarbar") + let g:CallCount = 0 + exe "normal! Gof\\o\\=PrintMenuWords()\" + call assert_equal('f{''matches'': [''foobarbar'', ''foo4'', ''foo5''], ''selected'': -1}', getline(2)) + call assert_equal(3, g:CallCount) + bw! + + set completeopt& complete& + delfunc PrintMenuWords +endfunc + func Test_complete_append_selected_match_default() " when typing a normal character during completion, " completion is ended, see diff --git a/src/testdir/test_options.vim b/src/testdir/test_options.vim index 834ca44df2..d8ad3f210b 100644 --- a/src/testdir/test_options.vim +++ b/src/testdir/test_options.vim @@ -275,11 +275,20 @@ func Test_complete() call assert_fails('set complete=x', 'E539:') call assert_fails('set complete=..', 'E535:') set complete=.,w,b,u,k,\ s,i,d,],t,U,f,o + call assert_fails('set complete=i^-10', 'E535:') + call assert_fails('set complete=i^x', 'E535:') + call assert_fails('set complete=k^2,t^-1,s^', 'E535') + call assert_fails('set complete=t^-1', 'E535') + call assert_fails('set complete=kfoo^foo2', 'E535') + call assert_fails('set complete=kfoo^', 'E535') + call assert_fails('set complete=.^', 'E535') + set complete=.,w,b,u,k,s,i,d,],t,U,f,o set complete=. + set complete=.^10,t^0 set complete+=ffuncref('foo'\\,\ [10]) - set complete=ffuncref('foo'\\,\ [10]) + set complete=ffuncref('foo'\\,\ [10])^10 set complete& - set complete+=ffunction('foo'\\,\ [10\\,\ 20]) + set complete+=ffunction('g:foo'\\,\ [10\\,\ 20]) set complete& endfun diff --git a/src/version.c b/src/version.c index bdd9723119..7aae2b5082 100644 --- a/src/version.c +++ b/src/version.c @@ -704,6 +704,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1311, /**/ 1310, /**/