changeset 27916:6efa2f193c94 v8.2.4483

patch 8.2.4483: command completion makes two rounds to collect matches Commit: https://github.com/vim/vim/commit/5de4c4372d4366bc85cb66efb3e373439b9471c5 Author: Yegappan Lakshmanan <yegappan@yahoo.com> Date: Mon Feb 28 13:28:38 2022 +0000 patch 8.2.4483: command completion makes two rounds to collect matches Problem: Command completion makes two rounds to collect matches. Solution: Use a growarray to collect matches. (Yegappan Lakshmanan, closes #9860)
author Bram Moolenaar <Bram@vim.org>
date Mon, 28 Feb 2022 14:30:04 +0100
parents 4058d89b7a48
children ab7bfb80c2c0
files src/buffer.c src/cmdexpand.c src/map.c src/testdir/test_cmdline.vim src/version.c
diffstat 5 files changed, 262 insertions(+), 227 deletions(-) [+]
line wrap: on
line diff
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -2814,38 +2814,39 @@ ExpandBufnames(
 		    }
 		}
 
-		if (p != NULL)
+		if (p == NULL)
+		    continue;
+
+		if (round == 1)
 		{
-		    if (round == 1)
-			++count;
-		    else
-		    {
-			if (options & WILD_HOME_REPLACE)
-			    p = home_replace_save(buf, p);
-			else
-			    p = vim_strsave(p);
-
-			if (!fuzzy)
-			{
+		    ++count;
+		    continue;
+		}
+
+		if (options & WILD_HOME_REPLACE)
+		    p = home_replace_save(buf, p);
+		else
+		    p = vim_strsave(p);
+
+		if (!fuzzy)
+		{
 #ifdef FEAT_VIMINFO
-			    if (matches != NULL)
-			    {
-				matches[count].buf = buf;
-				matches[count].match = p;
-				count++;
-			    }
-			    else
-#endif
-				(*file)[count++] = p;
-			}
-			else
-			{
-			    fuzmatch[count].idx = count;
-			    fuzmatch[count].str = p;
-			    fuzmatch[count].score = score;
-			    count++;
-			}
+		    if (matches != NULL)
+		    {
+			matches[count].buf = buf;
+			matches[count].match = p;
+			count++;
 		    }
+		    else
+#endif
+			(*file)[count++] = p;
+		}
+		else
+		{
+		    fuzmatch[count].idx = count;
+		    fuzmatch[count].str = p;
+		    fuzmatch[count].score = score;
+		    count++;
 		}
 	    }
 	    if (count == 0)	// no match found, break here
--- a/src/cmdexpand.c
+++ b/src/cmdexpand.c
@@ -2633,116 +2633,134 @@ ExpandGeneric(
     int		escaped)
 {
     int		i;
-    int		count = 0;
-    int		round;
+    garray_T	ga;
     char_u	*str;
     fuzmatch_str_T	*fuzmatch = NULL;
-    int			score = 0;
+    int		score = 0;
     int		fuzzy;
-    int		funcsort = FALSE;
     int		match;
 
     fuzzy = cmdline_fuzzy_complete(pat);
-
-    // do this loop twice:
-    // round == 0: count the number of matching names
-    // round == 1: copy the matching names into allocated memory
-    for (round = 0; round <= 1; ++round)
+    *matches = NULL;
+    *numMatches = 0;
+
+    if (!fuzzy)
+	ga_init2(&ga, sizeof(char *), 30);
+    else
+	ga_init2(&ga, sizeof(fuzmatch_str_T), 30);
+
+    for (i = 0; ; ++i)
     {
-	for (i = 0; ; ++i)
+	str = (*func)(xp, i);
+	if (str == NULL)	    // end of list
+	    break;
+	if (*str == NUL)	    // skip empty strings
+	    continue;
+
+	if (xp->xp_pattern[0] != NUL)
 	{
-	    str = (*func)(xp, i);
-	    if (str == NULL)	    // end of list
-		break;
-	    if (*str == NUL)	    // skip empty strings
-		continue;
-
 	    if (!fuzzy)
-	       match = vim_regexec(regmatch, str, (colnr_T)0);
+		match = vim_regexec(regmatch, str, (colnr_T)0);
 	    else
 	    {
 		score = fuzzy_match_str(str, pat);
 		match = (score != 0);
 	    }
-
-	    if (!match)
-		continue;
-
-	    if (round)
+	}
+	else
+	    match = TRUE;
+
+	if (!match)
+	    continue;
+
+	if (escaped)
+	    str = vim_strsave_escaped(str, (char_u *)" \t\\.");
+	else
+	    str = vim_strsave(str);
+	if (str == NULL)
+	{
+	    if (!fuzzy)
 	    {
-		if (escaped)
-		    str = vim_strsave_escaped(str, (char_u *)" \t\\.");
-		else
-		    str = vim_strsave(str);
-		if (str == NULL)
-		{
-		    if (fuzzy)
-			fuzmatch_str_free(fuzmatch, count);
-		    else if (count > 0)
-			FreeWild(count, *matches);
-		    *numMatches = 0;
-		    *matches = NULL;
-		    return FAIL;
-		}
-		if (fuzzy)
-		{
-		    fuzmatch[count].idx = count;
-		    fuzmatch[count].str = str;
-		    fuzmatch[count].score = score;
-		}
-		else
-		    (*matches)[count] = str;
-# ifdef FEAT_MENU
-		if (func == get_menu_names && str != NULL)
-		{
-		    // test for separator added by get_menu_names()
-		    str += STRLEN(str) - 1;
-		    if (*str == '\001')
-			*str = '.';
-		}
-# endif
-	    }
-	    ++count;
-	}
-	if (round == 0)
-	{
-	    if (count == 0)
-		return OK;
-	    if (fuzzy)
-		fuzmatch = ALLOC_MULT(fuzmatch_str_T, count);
-	    else
-		*matches = ALLOC_MULT(char_u *, count);
-	    if ((!fuzzy && (*matches == NULL))
-					|| (fuzzy && (fuzmatch == NULL)))
-	    {
-		*numMatches = 0;
-		*matches = NULL;
+		ga_clear_strings(&ga);
 		return FAIL;
 	    }
-	    *numMatches = count;
-	    count = 0;
+
+	    for (i = 0; i < ga.ga_len; ++i)
+	    {
+		fuzmatch = &((fuzmatch_str_T *)ga.ga_data)[i];
+		vim_free(fuzmatch->str);
+	    }
+	    ga_clear(&ga);
+	    return FAIL;
+	}
+
+	if (ga_grow(&ga, 1) == FAIL)
+	{
+	    vim_free(str);
+	    break;
 	}
+
+	if (fuzzy)
+	{
+	    fuzmatch = &((fuzmatch_str_T *)ga.ga_data)[ga.ga_len];
+	    fuzmatch->idx = ga.ga_len;
+	    fuzmatch->str = str;
+	    fuzmatch->score = score;
+	}
+	else
+	    ((char_u **)ga.ga_data)[ga.ga_len] = str;
+
+# ifdef FEAT_MENU
+	if (func == get_menu_names)
+	{
+	    // test for separator added by get_menu_names()
+	    str += STRLEN(str) - 1;
+	    if (*str == '\001')
+		*str = '.';
+	}
+# endif
+
+	++ga.ga_len;
     }
 
+    if (ga.ga_len == 0)
+	return OK;
+
     // Sort the results.  Keep menu's in the specified order.
-    if (xp->xp_context != EXPAND_MENUNAMES && xp->xp_context != EXPAND_MENUS)
+    if (!fuzzy && xp->xp_context != EXPAND_MENUNAMES
+					&& xp->xp_context != EXPAND_MENUS)
     {
 	if (xp->xp_context == EXPAND_EXPRESSION
 		|| xp->xp_context == EXPAND_FUNCTIONS
 		|| xp->xp_context == EXPAND_USER_FUNC
 		|| xp->xp_context == EXPAND_DISASSEMBLE)
-	{
+	    // <SNR> functions should be sorted to the end.
+	    qsort((void *)ga.ga_data, (size_t)ga.ga_len, sizeof(char_u *),
+							   sort_func_compare);
+	else
+	    sort_strings((char_u **)ga.ga_data, ga.ga_len);
+    }
+
+    if (!fuzzy)
+    {
+	*matches = ga.ga_data;
+	*numMatches = ga.ga_len;
+    }
+    else
+    {
+	int	funcsort = FALSE;
+
+	if (xp->xp_context == EXPAND_EXPRESSION
+		|| xp->xp_context == EXPAND_FUNCTIONS
+		|| xp->xp_context == EXPAND_USER_FUNC
+		|| xp->xp_context == EXPAND_DISASSEMBLE)
 	    // <SNR> functions should be sorted to the end.
 	    funcsort = TRUE;
-	    if (!fuzzy)
-		qsort((void *)*matches, (size_t)*numMatches, sizeof(char_u *),
-							   sort_func_compare);
-	}
-	else
-	{
-	    if (!fuzzy)
-		sort_strings(*matches, *numMatches);
-	}
+
+	if (fuzzymatches_to_strmatches(ga.ga_data, matches, ga.ga_len,
+							funcsort) == FAIL)
+	    return FAIL;
+	*numMatches = ga.ga_len;
     }
 
 #if defined(FEAT_SYN_HL)
@@ -2751,10 +2769,6 @@ ExpandGeneric(
     reset_expand_highlight();
 #endif
 
-    if (fuzzy && fuzzymatches_to_strmatches(fuzmatch, matches, count,
-							funcsort) == FAIL)
-	return FAIL;
-
     return OK;
 }
 
@@ -2990,12 +3004,11 @@ ExpandUserDefined(
     int		fuzzy;
     int		match;
     int		score;
-    int		count = 0;
 
     fuzzy = cmdline_fuzzy_complete(pat);
-
     *matches = NULL;
     *numMatches = 0;
+
     retstr = call_user_expand_func(call_func_retstr, xp);
     if (retstr == NULL)
 	return FAIL;
@@ -3013,7 +3026,7 @@ ExpandUserDefined(
 	keep = *e;
 	*e = NUL;
 
-	if (xp->xp_pattern[0] || fuzzy)
+	if (xp->xp_pattern[0] != NUL)
 	{
 	    if (!fuzzy)
 		match = vim_regexec(regmatch, s, (colnr_T)0);
@@ -3038,12 +3051,11 @@ ExpandUserDefined(
 	    {
 		fuzmatch_str_T  *fuzmatch =
 				&((fuzmatch_str_T  *)ga.ga_data)[ga.ga_len];
-		fuzmatch->idx = count;
+		fuzmatch->idx = ga.ga_len;
 		fuzmatch->str = vim_strnsave(s, e - s);
 		fuzmatch->score = score;
 	    }
 	    ++ga.ga_len;
-	    count++;
 	}
 
 	if (*e != NUL)
@@ -3051,6 +3063,9 @@ ExpandUserDefined(
     }
     vim_free(retstr);
 
+    if (ga.ga_len == 0)
+	return OK;
+
     if (!fuzzy)
     {
 	*matches = ga.ga_data;
@@ -3058,10 +3073,10 @@ ExpandUserDefined(
     }
     else
     {
-	if (fuzzymatches_to_strmatches(ga.ga_data, matches, count,
-							FALSE) == FAIL)
+	if (fuzzymatches_to_strmatches(ga.ga_data, matches, ga.ga_len,
+								FALSE) == FAIL)
 	    return FAIL;
-	*numMatches = count;
+	*numMatches = ga.ga_len;
     }
     return OK;
 }
--- a/src/map.c
+++ b/src/map.c
@@ -1263,15 +1263,15 @@ ExpandMappings(
     char_u	***matches)
 {
     mapblock_T	*mp;
+    garray_T	ga;
     int		hash;
     int		count;
-    int		round;
     char_u	*p;
     int		i;
     int		fuzzy;
     int		match;
     int		score;
-    fuzmatch_str_T  *fuzmatch = NULL;
+    fuzmatch_str_T  *fuzmatch;
 
     fuzzy = cmdline_fuzzy_complete(pat);
 
@@ -1280,32 +1280,78 @@ ExpandMappings(
     *numMatches = 0;		    // return values in case of FAIL
     *matches = NULL;
 
-    // round == 1: Count the matches.
-    // round == 2: Build the array to keep the matches.
-    for (round = 1; round <= 2; ++round)
+    if (!fuzzy)
+	ga_init2(&ga, sizeof(char *), 3);
+    else
+	ga_init2(&ga, sizeof(fuzmatch_str_T), 3);
+
+    // First search in map modifier arguments
+    for (i = 0; i < 7; ++i)
     {
-	count = 0;
+	if (i == 0)
+	    p = (char_u *)"<silent>";
+	else if (i == 1)
+	    p = (char_u *)"<unique>";
+#ifdef FEAT_EVAL
+	else if (i == 2)
+	    p = (char_u *)"<script>";
+	else if (i == 3)
+	    p = (char_u *)"<expr>";
+#endif
+	else if (i == 4 && !expand_buffer)
+	    p = (char_u *)"<buffer>";
+	else if (i == 5)
+	    p = (char_u *)"<nowait>";
+	else if (i == 6)
+	    p = (char_u *)"<special>";
+	else
+	    continue;
 
-	// First search in map modifier arguments
-	for (i = 0; i < 7; ++i)
+	if (!fuzzy)
+	    match = vim_regexec(regmatch, p, (colnr_T)0);
+	else
 	{
-	    if (i == 0)
-		p = (char_u *)"<silent>";
-	    else if (i == 1)
-		p = (char_u *)"<unique>";
-#ifdef FEAT_EVAL
-	    else if (i == 2)
-		p = (char_u *)"<script>";
-	    else if (i == 3)
-		p = (char_u *)"<expr>";
-#endif
-	    else if (i == 4 && !expand_buffer)
-		p = (char_u *)"<buffer>";
-	    else if (i == 5)
-		p = (char_u *)"<nowait>";
-	    else if (i == 6)
-		p = (char_u *)"<special>";
-	    else
+	    score = fuzzy_match_str(p, pat);
+	    match = (score != 0);
+	}
+
+	if (!match)
+	    continue;
+
+	if (ga_grow(&ga, 1) == FAIL)
+	    break;
+
+	if (fuzzy)
+	{
+	    fuzmatch = &((fuzmatch_str_T *)ga.ga_data)[ga.ga_len];
+	    fuzmatch->idx = ga.ga_len;
+	    fuzmatch->str = vim_strsave(p);
+	    fuzmatch->score = score;
+	}
+	else
+	    ((char_u **)ga.ga_data)[ga.ga_len] = vim_strsave(p);
+	++ga.ga_len;
+    }
+
+    for (hash = 0; hash < 256; ++hash)
+    {
+	if (expand_isabbrev)
+	{
+	    if (hash > 0)	// only one abbrev list
+		break; // for (hash)
+	    mp = first_abbr;
+	}
+	else if (expand_buffer)
+	    mp = curbuf->b_maphash[hash];
+	else
+	    mp = maphash[hash];
+	for (; mp; mp = mp->m_next)
+	{
+	    if (!(mp->m_mode & expand_mapmodes))
+		continue;
+
+	    p = translate_mapping(mp->m_keys);
+	    if (p == NULL)
 		continue;
 
 	    if (!fuzzy)
@@ -1317,95 +1363,48 @@ ExpandMappings(
 	    }
 
 	    if (!match)
-		continue;
-
-	    if (round == 2)
-	    {
-		if (fuzzy)
-		{
-		    fuzmatch[count].idx = count;
-		    fuzmatch[count].str = vim_strsave(p);
-		    fuzmatch[count].score = score;
-		}
-		else
-		    (*matches)[count] = vim_strsave(p);
-	    }
-	    ++count;
-	}
-
-	for (hash = 0; hash < 256; ++hash)
-	{
-	    if (expand_isabbrev)
-	    {
-		if (hash > 0)	// only one abbrev list
-		    break; // for (hash)
-		mp = first_abbr;
-	    }
-	    else if (expand_buffer)
-		mp = curbuf->b_maphash[hash];
-	    else
-		mp = maphash[hash];
-	    for (; mp; mp = mp->m_next)
 	    {
-		if (mp->m_mode & expand_mapmodes)
-		{
-		    p = translate_mapping(mp->m_keys);
-		    if (p != NULL)
-		    {
-			if (!fuzzy)
-			    match = vim_regexec(regmatch, p, (colnr_T)0);
-			else
-			{
-			    score = fuzzy_match_str(p, pat);
-			    match = (score != 0);
-			}
+		vim_free(p);
+		continue;
+	    }
 
-			if (match)
-			{
-			    if (round == 2)
-			    {
-				if (fuzzy)
-				{
-				    fuzmatch[count].idx = count;
-				    fuzmatch[count].str = p;
-				    fuzmatch[count].score = score;
-				}
-				else
-				    (*matches)[count] = p;
-				p = NULL;
-			    }
-			    ++count;
-			}
-		    }
-		    vim_free(p);
-		}
-	    } // for (mp)
-	} // for (hash)
+	    if (ga_grow(&ga, 1) == FAIL)
+	    {
+		vim_free(p);
+		break;
+	    }
 
-	if (count == 0)			// no match found
-	    break; // for (round)
-
-	if (round == 1)
-	{
 	    if (fuzzy)
 	    {
-		fuzmatch = ALLOC_MULT(fuzmatch_str_T, count);
-		if (fuzmatch == NULL)
-		    return FAIL;
+		fuzmatch = &((fuzmatch_str_T *)ga.ga_data)[ga.ga_len];
+		fuzmatch->idx = ga.ga_len;
+		fuzmatch->str = p;
+		fuzmatch->score = score;
 	    }
 	    else
-	    {
-		*matches = ALLOC_MULT(char_u *, count);
-		if (*matches == NULL)
-		    return FAIL;
-	    }
-	}
-    } // for (round)
+		((char_u **)ga.ga_data)[ga.ga_len] = p;
 
-    if (fuzzy && fuzzymatches_to_strmatches(fuzmatch, matches, count,
-							FALSE) == FAIL)
+	    ++ga.ga_len;
+	} // for (mp)
+    } // for (hash)
+
+    if (ga.ga_len == 0)
 	return FAIL;
 
+    if (!fuzzy)
+    {
+	*matches = ga.ga_data;
+	*numMatches = ga.ga_len;
+    }
+    else
+    {
+	if (fuzzymatches_to_strmatches(ga.ga_data, matches, ga.ga_len,
+							FALSE) == FAIL)
+	    return FAIL;
+	*numMatches = ga.ga_len;
+    }
+
+    count = *numMatches;
     if (count > 1)
     {
 	char_u	**ptr1;
--- a/src/testdir/test_cmdline.vim
+++ b/src/testdir/test_cmdline.vim
@@ -2661,6 +2661,24 @@ func Test_fuzzy_completion_userdefined_f
   set wildoptions&
 endfunc
 
+" <SNR> functions should be sorted to the end
+func Test_fuzzy_completion_userdefined_snr_func()
+  func s:Sendmail()
+  endfunc
+  func SendSomemail()
+  endfunc
+  func S1e2n3dmail()
+  endfunc
+  set wildoptions=fuzzy
+  call feedkeys(":call sendmail\<C-A>\<C-B>\"\<CR>", 'tx')
+  call assert_equal('"call SendSomemail() S1e2n3dmail() '
+        \ .. expand("<SID>") .. 'Sendmail()', @:)
+  set wildoptions&
+  delfunc s:Sendmail
+  delfunc SendSomemail
+  delfunc S1e2n3dmail
+endfunc
+
 " user defined command name completion
 func Test_fuzzy_completion_userdefined_cmd()
   set wildoptions&
--- a/src/version.c
+++ b/src/version.c
@@ -755,6 +755,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    4483,
+/**/
     4482,
 /**/
     4481,