patch 9.1.1138: cmdline completion for :hi is too simplistic

Problem:  Existing cmdline completion for :highlight was barebone and
          only completed the highlight group names.

Solution: Implement full completion for the highlight group arguments
          such as guifg and cterm. If the user tries to complete
          immediately after the '=' (e.g. `hi Normal guifg=<Tab>`), the
          completion will fill in the existing value, similar to how
          cmdline completion for options work (Yee Cheng Chin).

closes: #16712

Signed-off-by: Yee Cheng Chin <ychin.git@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/cmdexpand.c b/src/cmdexpand.c
index f707b1d..c14eee2 100644
--- a/src/cmdexpand.c
+++ b/src/cmdexpand.c
@@ -3221,6 +3221,8 @@
 	ret = ExpandMappings(pat, &regmatch, numMatches, matches);
     else if (xp->xp_context == EXPAND_ARGOPT)
 	ret = expand_argopt(pat, xp, &regmatch, matches, numMatches);
+    else if (xp->xp_context == EXPAND_HIGHLIGHT_GROUP)
+	ret = expand_highlight_group(pat, xp, &regmatch, matches, numMatches);
 #if defined(FEAT_TERMINAL)
     else if (xp->xp_context == EXPAND_TERMINALOPT)
 	ret = expand_terminal_opt(pat, xp, &regmatch, matches, numMatches);
@@ -3239,18 +3241,6 @@
     return ret;
 }
 
-/*
- * Expand a list of names.
- *
- * Generic function for command line completion.  It calls a function to
- * obtain strings, one by one.	The strings are matched against a regexp
- * program.  Matching strings are copied into an array, which is returned.
- *
- * If 'fuzzy' is TRUE, then fuzzy matching is used. Otherwise, regex matching
- * is used.
- *
- * Returns OK when no problems encountered, FAIL for error (out of memory).
- */
     int
 ExpandGeneric(
     char_u	*pat,
@@ -3262,6 +3252,38 @@
 					  // returns a string from the list
     int		escaped)
 {
+    return ExpandGenericExt(
+	pat, xp, regmatch, matches, numMatches, func, escaped, 0);
+}
+
+/*
+ * Expand a list of names.
+ *
+ * Generic function for command line completion.  It calls a function to
+ * obtain strings, one by one.	The strings are matched against a regexp
+ * program.  Matching strings are copied into an array, which is returned.
+ *
+ * If 'fuzzy' is TRUE, then fuzzy matching is used. Otherwise, regex matching
+ * is used.
+ *
+ * 'sortStartIdx' allows the caller to control sorting behavior. Items before
+ * the index will not be sorted. Pass 0 to sort all, and -1 to prevent any
+ * sorting.
+ *
+ * Returns OK when no problems encountered, FAIL for error (out of memory).
+ */
+    int
+ExpandGenericExt(
+    char_u	*pat,
+    expand_T	*xp,
+    regmatch_T	*regmatch,
+    char_u	***matches,
+    int		*numMatches,
+    char_u	*((*func)(expand_T *, int)),
+					  // returns a string from the list
+    int		escaped,
+    int		sortStartIdx)
+{
     int		i;
     garray_T	ga;
     char_u	*str;
@@ -3271,6 +3293,7 @@
     int		match;
     int		sort_matches = FALSE;
     int		funcsort = FALSE;
+    int		sortStartMatchIdx = -1;
 
     fuzzy = cmdline_fuzzy_complete(pat);
     *matches = NULL;
@@ -3346,6 +3369,12 @@
 	}
 #endif
 
+	if (sortStartIdx >= 0 && i >= sortStartIdx && sortStartMatchIdx == -1)
+	{
+	    // Found first item to start sorting from. This is usually 0.
+	    sortStartMatchIdx = ga.ga_len;
+	}
+
 	++ga.ga_len;
     }
 
@@ -3371,14 +3400,14 @@
 	funcsort = TRUE;
 
     // Sort the matches.
-    if (sort_matches)
+    if (sort_matches && sortStartMatchIdx != -1)
     {
 	if (funcsort)
 	    // <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);
+	    sort_strings((char_u **)ga.ga_data + sortStartMatchIdx, ga.ga_len - sortStartMatchIdx);
     }
 
     if (!fuzzy)