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
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.
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);
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)
{
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;
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)))
shown_match_ok = TRUE;
}
}
+ if (is_forward && compl->cp_cpt_source_idx != -1)
+ match_count++;
i++;
}
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));
compl_matches = 0;
compl_cont_status = 0;
compl_cont_mode = 0;
- cpt_compl_src_clear();
+ cpt_sources_clear();
}
/*
#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);
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;
}
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
// 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))
? &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;
break;
if (status == INS_COMPL_CPT_CONT)
{
- cpt_value_idx++;
+ cpt_sources_index++;
continue;
}
}
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
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())
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'.
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)));
&& 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
* 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;
}
#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;
/*
* 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 *
{
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;
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;
}
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;
}
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;
}
}
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;
copy_option_part(&p, IObuff, IOSIZE, ","); // Advance p
}
- cpt_value_idx = -1;
+ cpt_sources_index = -1;
vim_free(cpt);
// Make the list cyclic
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\<c-n>\<c-r>=PrintMenuWords()\<cr>"
+ call assert_equal('fo{''matches'': [''fo'', ''foo'', ''foobar'', ''fobarbaz''], ''selected'': 0}', getline(5))
+ 5d
+ set cpt=.^0,w
+ exe "normal! Gof\<c-p>\<c-r>=PrintMenuWords()\<cr>"
+ call assert_equal('fobarbaz{''matches'': [''fo'', ''foo'', ''foobar'', ''fobarbaz''], ''selected'': 3}', getline(5))
+ 5d
+ set cpt=.^1,w
+ exe "normal! Gof\<c-n>\<c-r>=PrintMenuWords()\<cr>"
+ call assert_equal('fo{''matches'': [''fo''], ''selected'': 0}', getline(5))
+ 5d
+ " max_matches is ignored for backward search
+ exe "normal! Gof\<c-p>\<c-r>=PrintMenuWords()\<cr>"
+ call assert_equal('fobarbaz{''matches'': [''fo'', ''foo'', ''foobar'', ''fobarbaz''], ''selected'': 3}', getline(5))
+ 5d
+ set cpt=.^2,w
+ exe "normal! Gof\<c-n>\<c-r>=PrintMenuWords()\<cr>"
+ call assert_equal('fo{''matches'': [''fo'', ''foo''], ''selected'': 0}', getline(5))
+ 5d
+ set cot=menuone,noselect
+ set cpt=.^1,w
+ exe "normal! Gof\<c-n>\<c-r>=PrintMenuWords()\<cr>"
+ 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\<c-n>\<c-r>=PrintMenuWords()\<cr>"
+ call assert_equal('fo{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': 0}', getline(5))
+ 5d
+ exe "normal! Gof\<c-n>\<c-n>\<c-r>=PrintMenuWords()\<cr>"
+ call assert_equal('foo1{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': 1}', getline(5))
+ 5d
+ exe "normal! Gof\<c-n>\<c-n>\<c-n>\<c-r>=PrintMenuWords()\<cr>"
+ call assert_equal('foo2{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': 2}', getline(5))
+ 5d
+ exe "normal! Gof\<c-n>\<c-n>\<c-n>\<c-n>\<c-r>=PrintMenuWords()\<cr>"
+ call assert_equal('f{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': -1}', getline(5))
+ 5d
+ exe "normal! Gof\<c-n>\<c-n>\<c-n>\<c-n>\<c-n>\<c-r>=PrintMenuWords()\<cr>"
+ call assert_equal('fo{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': 0}', getline(5))
+
+ 5d
+ exe "normal! Gof\<c-n>\<c-p>\<c-r>=PrintMenuWords()\<cr>"
+ call assert_equal('f{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': -1}', getline(5))
+ 5d
+ exe "normal! Gof\<c-n>\<c-p>\<c-p>\<c-r>=PrintMenuWords()\<cr>"
+ call assert_equal('foo2{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': 2}', getline(5))
+ 5d
+ exe "normal! Gof\<c-n>\<c-p>\<c-p>\<c-p>\<c-r>=PrintMenuWords()\<cr>"
+ call assert_equal('foo1{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': 1}', getline(5))
+ 5d
+ exe "normal! Gof\<c-n>\<c-p>\<c-p>\<c-p>\<c-p>\<c-r>=PrintMenuWords()\<cr>"
+ call assert_equal('fo{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': 0}', getline(5))
+ 5d
+ exe "normal! Gof\<c-n>\<c-p>\<c-p>\<c-p>\<c-p>\<c-p>\<c-r>=PrintMenuWords()\<cr>"
+ call assert_equal('f{''matches'': [''fo'', ''foo1'', ''foo2''], ''selected'': -1}', getline(5))
+
+ %d
+ call setline(1, ["foo"])
+ set cpt=fComplFunc^2,.
+ exe "normal! Gof\<c-n>\<c-r>=PrintMenuWords()\<cr>"
+ 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\<c-n>\<c-n>\<c-r>=PrintMenuWords()\<cr>"
+ 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\<c-n>\<c-p>o\<c-r>=PrintMenuWords()\<cr>"
+ 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\<c-n>\<c-p>o\<bs>\<c-r>=PrintMenuWords()\<cr>"
+ 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