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/highlight.c b/src/highlight.c
index 8c1ad80..2d96566 100644
--- a/src/highlight.c
+++ b/src/highlight.c
@@ -1691,6 +1691,8 @@
break;
}
+ // Note: Keep this in sync with get_highlight_group_key.
+
// Isolate the key ("term", "ctermfg", "ctermbg", "font", "guifg"
// or "guibg").
while (*linep && !VIM_ISWHITE(*linep) && *linep != '=')
@@ -3058,6 +3060,7 @@
if (message_filtered(sgp->sg_name))
return;
+ // Note: Keep this in sync with expand_highlight_group().
didh = highlight_list_arg(id, didh, LIST_ATTR,
sgp->sg_term, NULL, "term");
didh = highlight_list_arg(id, didh, LIST_STRING,
@@ -3108,37 +3111,24 @@
#endif
}
- static int
-highlight_list_arg(
- int id,
- int didh,
+ static char_u*
+highlight_arg_to_string(
int type,
int iarg,
char_u *sarg,
- char *name)
+ char_u *buf)
{
- char_u buf[MAX_ATTR_LEN];
- char_u *ts;
- int i;
-
- if (got_int)
- return FALSE;
-
- if (type == LIST_STRING ? (sarg == NULL) : (iarg == 0))
- return didh;
-
- ts = buf;
if (type == LIST_INT)
sprintf((char *)buf, "%d", iarg - 1);
else if (type == LIST_STRING)
- ts = sarg;
+ return sarg;
else // type == LIST_ATTR
{
size_t buflen;
buf[0] = NUL;
buflen = 0;
- for (i = 0; i < (int)ARRAY_LENGTH(highlight_index_tab); ++i)
+ for (int i = 0; i < (int)ARRAY_LENGTH(highlight_index_tab); ++i)
{
if (iarg & highlight_index_tab[i]->key)
{
@@ -3153,6 +3143,28 @@
}
}
}
+ return buf;
+}
+
+ static int
+highlight_list_arg(
+ int id,
+ int didh,
+ int type,
+ int iarg,
+ char_u *sarg,
+ char *name)
+{
+ char_u buf[MAX_ATTR_LEN];
+ char_u *ts;
+
+ if (got_int)
+ return FALSE;
+
+ if (type == LIST_STRING ? (sarg == NULL) : (iarg == 0))
+ return didh;
+
+ ts = highlight_arg_to_string(type, iarg, sarg, buf);
(void)syn_list_header(didh,
(int)(vim_strsize(ts) + STRLEN(name) + 1), id);
@@ -4078,6 +4090,15 @@
static void highlight_list(void);
static void highlight_list_two(int cnt, int attr);
+// context for :highlight <group> <arg> expansion
+static int expand_hi_synid = 0; // ID for highlight group being completed
+static int expand_hi_equal_col = 0; // column where the '=' is
+static int expand_hi_include_orig = 0; // whether to fill the existing current value or not
+static char_u *expand_hi_curvalue = NULL; // the existing current value
+#if defined(FEAT_EVAL) && (defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS))
+static dict_iterator_T expand_colornames_iter; // iterator for looping through v:colornames
+#endif
+
/*
* Handle command line completion for :highlight command.
*/
@@ -4085,10 +4106,12 @@
set_context_in_highlight_cmd(expand_T *xp, char_u *arg)
{
char_u *p;
+ int expand_group = TRUE;
// Default: expand group names
xp->xp_context = EXPAND_HIGHLIGHT;
xp->xp_pattern = arg;
+ include_none = 0;
include_link = 2;
include_default = 1;
@@ -4114,9 +4137,11 @@
// past group name
include_link = 0;
if (arg[1] == 'i' && arg[0] == 'N')
+ {
highlight_list();
- if (STRNCMP("link", arg, p - arg) == 0
- || STRNCMP("clear", arg, p - arg) == 0)
+ expand_group = FALSE;
+ }
+ if (STRNCMP("link", arg, p - arg) == 0)
{
xp->xp_pattern = skipwhite(p);
p = skiptowhite(xp->xp_pattern);
@@ -4124,10 +4149,67 @@
{
xp->xp_pattern = skipwhite(p);
p = skiptowhite(xp->xp_pattern);
+ include_none = 1;
}
+ expand_group = FALSE;
+ }
+ else if (STRNCMP("clear", arg, p - arg) == 0)
+ {
+ xp->xp_pattern = skipwhite(p);
+ p = skiptowhite(xp->xp_pattern);
+ expand_group = FALSE;
}
if (*p != NUL) // past group name(s)
- xp->xp_context = EXPAND_NOTHING;
+ {
+ if (expand_group)
+ {
+ // expansion will be done in expand_highlight_group()
+ xp->xp_context = EXPAND_HIGHLIGHT_GROUP;
+
+ expand_hi_synid = syn_namen2id(arg, (int)(p - arg));
+
+ while (*p != NUL)
+ {
+ arg = skipwhite(p);
+ p = skiptowhite(arg);
+ }
+
+ p = vim_strchr(arg, '=');
+ if (p == NULL)
+ {
+ // Didn't find a key=<value> pattern
+ xp->xp_pattern = arg;
+ expand_hi_equal_col = -1;
+ expand_hi_include_orig = FALSE;
+ }
+ else
+ {
+ // Found key=<value> pattern, record the exact location
+ expand_hi_equal_col = (int)(p - xp->xp_line);
+
+ // Only include the original value if the pattern is empty
+ if (*(p + 1) == NUL)
+ expand_hi_include_orig = TRUE;
+ else
+ expand_hi_include_orig = FALSE;
+
+ // Account for comma-separated values
+ if (STRNCMP(arg, "term=", 5) == 0 ||
+ STRNCMP(arg, "cterm=", 6) == 0 ||
+ STRNCMP(arg, "gui=", 4) == 0)
+ {
+ char_u *comma = vim_strrchr(p + 1, ',');
+ if (comma != NULL)
+ p = comma;
+ }
+ xp->xp_pattern = p + 1;
+ }
+ }
+ else
+ {
+ xp->xp_context = EXPAND_NOTHING;
+ }
+ }
}
/*
@@ -4178,7 +4260,7 @@
return (char_u *)"";
if (idx == highlight_ga.ga_len && include_none != 0)
- return (char_u *)"none";
+ return (char_u *)"NONE";
if (idx == highlight_ga.ga_len + include_none && include_default != 0)
return (char_u *)"default";
if (idx == highlight_ga.ga_len + include_none + include_default
@@ -4192,6 +4274,300 @@
return HL_TABLE()[idx].sg_name;
}
+ static char_u *
+get_highlight_attr_name(expand_T *xp UNUSED, int idx)
+{
+ if (idx == 0)
+ {
+ // Fill with current value first
+ if (expand_hi_curvalue != NULL)
+ return expand_hi_curvalue;
+ else
+ return (char_u*)"";
+ }
+ if (idx < (int)ARRAY_LENGTH(highlight_index_tab) + 1)
+ {
+ char_u *value = highlight_index_tab[idx-1]->value.string;
+ if (expand_hi_curvalue != NULL && STRCMP(expand_hi_curvalue, value) == 0)
+ {
+ // Already returned the current value above, just skip.
+ return (char_u*)"";
+ }
+ return value;
+ }
+ return NULL;
+}
+
+ static char_u *
+get_highlight_cterm_color(expand_T *xp UNUSED, int idx)
+{
+ if (idx == 0)
+ {
+ // Fill with current value first
+ if (expand_hi_curvalue != NULL)
+ return expand_hi_curvalue;
+ else
+ return (char_u*)"";
+ }
+ // See highlight_set_cterm_color()
+ else if (idx == 1)
+ return (char_u*)"fg";
+ else if (idx == 2)
+ return (char_u*)"bg";
+ if (idx < (int)ARRAY_LENGTH(color_name_tab) + 3)
+ {
+ char_u *value = color_name_tab[idx-3].value.string;
+ return value;
+ }
+ return NULL;
+}
+
+#if defined(FEAT_EVAL) && (defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS))
+ static char_u *
+get_highlight_gui_color(expand_T *xp UNUSED, int idx)
+{
+ if (idx == 0)
+ {
+ // Fill with current value first
+ if (expand_hi_curvalue != NULL)
+ return expand_hi_curvalue;
+ else
+ return (char_u*)"";
+ }
+ // See color_name2handle()
+ else if (idx == 1)
+ return (char_u*)"fg";
+ else if (idx == 2)
+ return (char_u*)"bg";
+ else if (idx == 3)
+ return (char_u*)"NONE";
+
+ // Complete from v:colornames. Don't do platform specific names for now.
+ typval_T *tv_result;
+ char_u *colorname = dict_iterate_next(&expand_colornames_iter, &tv_result);
+ if (colorname != NULL)
+ {
+ // :hi command doesn't allow space, so don't suggest any malformed items
+ if (vim_strchr(colorname, ' ') != NULL)
+ return (char_u*)"";
+
+ if (expand_hi_curvalue != NULL && STRICMP(expand_hi_curvalue, colorname) == 0)
+ {
+ // Already returned the current value above, just skip.
+ return (char_u*)"";
+ }
+ }
+ return colorname;
+}
+#endif
+
+ static char_u *
+get_highlight_group_key(expand_T *xp UNUSED, int idx)
+{
+ // Note: Keep this in sync with do_highlight.
+ static char *(p_hi_group_key_values[]) =
+ {
+ "term=",
+ "start=",
+ "stop=",
+ "cterm=",
+ "ctermfg=",
+ "ctermbg=",
+ "ctermul=",
+ "ctermfont=",
+#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
+ "gui=",
+ "guifg=",
+ "guibg=",
+ "guisp=",
+#endif
+#ifdef FEAT_GUI
+ "font=",
+#endif
+ "NONE",
+ };
+
+ if (idx < (int)ARRAY_LENGTH(p_hi_group_key_values))
+ return (char_u*)p_hi_group_key_values[idx];
+ return NULL;
+}
+
+/*
+ * Command-line expansion for :hi {group-name} <args>...
+ */
+ int
+expand_highlight_group(
+ char_u *pat,
+ expand_T *xp,
+ regmatch_T *rmp,
+ char_u ***matches,
+ int *numMatches)
+{
+ if (expand_hi_equal_col != -1)
+ {
+ // List the values. First fill in the current value, then if possible colors
+ // or attribute names.
+ char_u *(*expandfunc)(expand_T *, int) = NULL;
+ int type = 0;
+ hl_group_T *sgp = NULL;
+ int iarg = 0;
+ char_u *sarg = NULL;
+
+ int unsortedItems = -1; // don't sort by default
+
+ if (expand_hi_synid != 0)
+ sgp = &HL_TABLE()[expand_hi_synid - 1]; // index is ID minus one
+
+ // Note: Keep this in sync with highlight_list_one().
+ char_u *name_end = xp->xp_line + expand_hi_equal_col;
+ if (name_end - xp->xp_line >= 5
+ && STRNCMP(name_end - 5, " term", 5) == 0)
+ {
+ expandfunc = get_highlight_attr_name;
+ if (sgp)
+ {
+ type = LIST_ATTR;
+ iarg = sgp->sg_term;
+ }
+ }
+ else if (name_end - xp->xp_line >= 6
+ && STRNCMP(name_end - 6, " cterm", 6) == 0)
+ {
+ expandfunc = get_highlight_attr_name;
+ if (sgp)
+ {
+ type = LIST_ATTR;
+ iarg = sgp->sg_cterm;
+ }
+ }
+#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
+ else if (name_end - xp->xp_line >= 4
+ && STRNCMP(name_end - 4, " gui", 4) == 0)
+ {
+ expandfunc = get_highlight_attr_name;
+ if (sgp)
+ {
+ type = LIST_ATTR;
+ iarg = sgp->sg_gui;
+ }
+ }
+#endif
+ else if (name_end - xp->xp_line >= 8
+ && STRNCMP(name_end - 8, " ctermfg", 8) == 0)
+ {
+ expandfunc = get_highlight_cterm_color;
+ if (sgp)
+ {
+ type = LIST_INT;
+ iarg = sgp->sg_cterm_fg;
+ }
+ }
+ else if (name_end - xp->xp_line >= 8
+ && STRNCMP(name_end - 8, " ctermbg", 8) == 0)
+ {
+ expandfunc = get_highlight_cterm_color;
+ if (sgp)
+ {
+ type = LIST_INT;
+ iarg = sgp->sg_cterm_bg;
+ }
+ }
+ else if (name_end - xp->xp_line >= 8
+ && STRNCMP(name_end - 8, " ctermul", 8) == 0)
+ {
+ expandfunc = get_highlight_cterm_color;
+ if (sgp)
+ {
+ type = LIST_INT;
+ iarg = sgp->sg_cterm_ul;
+ }
+ }
+#if defined(FEAT_EVAL) && (defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS))
+ else if (name_end - xp->xp_line >= 6
+ && STRNCMP(name_end - 6, " guifg", 6) == 0)
+ {
+ expandfunc = get_highlight_gui_color;
+ if (sgp)
+ {
+ type = LIST_STRING;
+ sarg = sgp->sg_gui_fg_name;
+ }
+ }
+ else if (name_end - xp->xp_line >= 6
+ && STRNCMP(name_end - 6, " guibg", 6) == 0)
+ {
+ expandfunc = get_highlight_gui_color;
+ if (sgp)
+ {
+ type = LIST_STRING;
+ sarg = sgp->sg_gui_bg_name;
+ }
+ }
+ else if (name_end - xp->xp_line >= 6
+ && STRNCMP(name_end - 6, " guisp", 6) == 0)
+ {
+ expandfunc = get_highlight_gui_color;
+ if (sgp)
+ {
+ type = LIST_STRING;
+ sarg = sgp->sg_gui_sp_name;
+ }
+ }
+#endif
+
+#if defined(FEAT_EVAL) && (defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS))
+ if (expandfunc == get_highlight_gui_color)
+ {
+ // Top 4 items are special, after that sort all the color names
+ unsortedItems = 4;
+
+ dict_T *colornames_table = get_vim_var_dict(VV_COLORNAMES);
+ typval_T colornames_val;
+ colornames_val.v_type = VAR_DICT;
+ colornames_val.vval.v_dict = colornames_table;
+ dict_iterate_start(&colornames_val, &expand_colornames_iter);
+ }
+#endif
+
+ char_u buf[MAX_ATTR_LEN];
+
+ if (expand_hi_synid != 0 && type != 0 && expand_hi_include_orig)
+ {
+ // Retrieve the current value to go first in completion
+ expand_hi_curvalue = highlight_arg_to_string(
+ type, iarg, sarg, buf);
+ }
+ else
+ expand_hi_curvalue = NULL;
+
+ if (expandfunc != NULL)
+ {
+ return ExpandGenericExt(
+ pat,
+ xp,
+ rmp,
+ matches,
+ numMatches,
+ expandfunc,
+ FALSE,
+ unsortedItems);
+ }
+
+ return FAIL;
+ }
+
+ // List all the key names
+ return ExpandGenericExt(
+ pat,
+ xp,
+ rmp,
+ matches,
+ numMatches,
+ get_highlight_group_key,
+ FALSE,
+ -1);
+}
+
#if defined(FEAT_GUI) || defined(PROTO)
/*
* Free all the highlight group fonts.