patch 9.1.1311: completion: not possible to limit number of matches

Commit: 
https://github.com/vim/vim/commit/0ac1eb3555445f4c458c06cef7c411de1c8d1020
Author: Girish Palya <giris...@gmail.com>
Date:   Wed Apr 16 20:18:33 2025 +0200

    patch 9.1.1311: completion: not possible to limit number of matches
    
    Problem:  completion: not possible to limit number of matches
    Solution: allow to limit the matches for 'complete' sources by using the
              "{flag}^{limit}" notation (Girish Palya)
    
    This change extends the 'complete'  option to support limiting the
    number of matches returned from individual completion sources.
    
    **Rationale:** In large files, certain sources (such as the current
    buffer) can generate an overwhelming number of matches, which may cause
    more relevant results from other sources (e.g., LSP or tags) to be
    pushed out of view. By specifying per-source match limits, the
    completion menu remains balanced and diverse, improving visibility and
    relevance of suggestions.
    
    A caret (`^`) followed by a number can be appended to a source flag to
    specify the maximum number of matches for that source. For example:
    ```
      :set complete=.^9,w,u,t^5
    ```
    In this configuration:
    - The current buffer (`.`) will return up to 9 matches.
    - The tag completion (`t`) will return up to 5 matches.
    - Other sources (`w`, `u`) are not limited.
    
    This feature is fully backward-compatible and does not affect behavior
    when the `^count` suffix is not used.
    
    The caret (`^`) was chosen as the delimiter because it is least likely
    to appear in file names.
    
    closes: #17087
    
    Signed-off-by: Girish Palya <giris...@gmail.com>
    Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index b3b29d0d8..8ab0dcc8b 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 27470f002..a5579f4ab 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}^<limit>" notation
 
 Options: ~
 - the default for 'commentstring' contains whitespace padding to have
diff --git a/src/insexpand.c b/src/insexpand.c
index f4449c0bf..9a82a32bc 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 == '
+           {
+               read = p;
+               continue;
+           }
+           else
+               *write++ = *read++;
+       }
+       else
+           *write++ = *read++;
+    }
+    *write = '
+}
+
 /*
  * 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 62a708683..90b8e52ff 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 66beb78e6..2b14bfa76 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\<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
diff --git a/src/testdir/test_options.vim b/src/testdir/test_options.vim
index 834ca44df..d8ad3f210 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 bdd972311..7aae2b508 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,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to vim_dev+unsubscr...@googlegroups.com.
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1u57WM-006xhM-Vp%40256bit.org.

Raspunde prin e-mail lui