]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.1.1138: cmdline completion for :hi is too simplistic v9.1.1138
authorYee Cheng Chin <ychin.git@gmail.com>
Sun, 23 Feb 2025 08:32:47 +0000 (09:32 +0100)
committerChristian Brabandt <cb@256bit.org>
Sun, 23 Feb 2025 08:34:50 +0000 (09:34 +0100)
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>
12 files changed:
runtime/doc/eval.txt
runtime/doc/syntax.txt
runtime/doc/version9.txt
src/cmdexpand.c
src/highlight.c
src/proto/cmdexpand.pro
src/proto/highlight.pro
src/syntax.c
src/testdir/test_cmdline.vim
src/testdir/test_syntax.vim
src/version.c
src/vim.h

index 0851aa5c355ac28346f15ab5790e0c35e78460e2..0ada1b2159d2d1d66c3c1e6f2cc4f11416c88f55 100644 (file)
@@ -1,4 +1,4 @@
-*eval.txt*     For Vim version 9.1.  Last change: 2025 Jan 29
+*eval.txt*     For Vim version 9.1.  Last change: 2025 Feb 23
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -2081,7 +2081,7 @@ v:colornames    A dictionary that maps color names to hex color strings. These
 
                You can make changes to that file, but make sure to add new
                keys instead of updating existing ones, otherwise Vim will skip
-               loading the file (thinking is hasn't been changed).
+               loading the file (thinking it hasn't been changed).
 
                                *v:completed_item* *completed_item-variable*
 v:completed_item
index cb6704cd335ad1a1654987899ba0de9356ece55b..d8c4b08ea648278a63ff98a116f9d370cc440de3 100644 (file)
@@ -1,4 +1,4 @@
-*syntax.txt*   For Vim version 9.1.  Last change: 2025 Feb 20
+*syntax.txt*   For Vim version 9.1.  Last change: 2025 Feb 23
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -5432,6 +5432,10 @@ in their own color.
                        See |:highlight-default| for the optional [default]
                        argument.
 
+:hi[ghlight][!] [default] link {from-group} {to-group}
+:hi[ghlight][!] [default] link {from-group} NONE
+                       See |:hi-link|.
+
 Normally a highlight group is added once when starting up.  This sets the
 default values for the highlighting.  After that, you can use additional
 highlight commands to change the arguments that you want to set to non-default
index de38d08e2e63afac826170ee262a7ab1aa5c9d0e..99a4002c12ba14bfba2dbb63496eb6e21e1ddabf 100644 (file)
@@ -1,4 +1,4 @@
-*version9.txt*  For Vim version 9.1.  Last change: 2025 Feb 11
+*version9.txt*  For Vim version 9.1.  Last change: 2025 Feb 23
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -41573,8 +41573,11 @@ Include the "linematch" algorithm for the 'diffopt' setting.  This aligns
 changes between buffers on similar lines improving the diff highlighting in
 Vim
 
-Adjusted default values ~
------------------------
+                                                       *changed-9.2*
+Changed~
+-------
+
+Default values: ~
 - the default 'history' option value has been increased to 200 and removed
   from |defaults.vim|
 - the default 'backspace' option for Vim has been set to "indent,eol,start"
@@ -41584,61 +41587,69 @@ Adjusted default values ~
 - the default value of the 'keyprotocol' option has been updated and support
   for the ghostty terminal emulator (using kitty protocol) has been added
 
-                                                       *changed-9.2*
-Changed~
--------
-- use 'smoothscroll' logic for CTRL-F and CTRL-B for pagewise scrolling
-- use 'smoothscroll' logic for CTRL-D and CTRL-U for half-pagewise scrolling
+
+Completion: ~
+- allow to complete directories from 'cdpath' for |:cd| and similar commands,
+  add the "cd_in_path" completion type for e.g. |:command-complete| and
+  |getcompletion()|
+- allow to complete shell commands and files using the new shellcmdline
+  completion type using |:command-complete| and |getcmdcomplpat()|
+- allow to specify additional attributes in the completion menu (allows to
+  mark deprecated attributes from LSP server) |complete-items|
+- the completed word and completion type are provided when handling the
+  |CompleteDone| autocommand in the |v:event| dictionary
+- |complete_info()| returns the list of matches shown in the poppu menu via
+  the "matches" key
+- New option value for 'completeopt':
+       "nosort"        - do not sort completion results
+       "preinsert"     - highlight to be inserted values
+- handle multi-line completion as expected
+- improved commandline completion for the |:hi| command
+
+Options: ~
 - the default for 'commentstring' contains whitespace padding to have
   automatic comments look nicer |comment-install|
 - 'completeopt' is now a |global-local| option.
 - 'nrformats' accepts the new "blank" suboption, to determine a signed or
   unsigned number based on whitespace in front of a minus sign.
+- add 'cpoptions' flag "z" |cpo-z|, to disable some (traditional) vi
+  behaviour/inconsistency (see |d-special| and |cw|).
+- 'rulerformat' now supports the |stl-%!| item
+- use 'smoothscroll' logic for CTRL-F / CTRL-B for pagewise scrolling
+  and CTRL-D / CTRL-U for half-pagewise scrolling
+
+Ex commands: ~
 - allow to specify a priority when defining a new sign |:sign-define|
-- provide information about function arguments using the get(func, "arity")
-  function |get()-func|
 - |:bwipe| also wipes jumplist and tagstack data
 - moving in the buffer list using |:bnext| and similar commands, behaves as
   documented and skips help buffers (if not run from a help buffer, else
   moves to the next/previous help buffer).
-- allow to complete directories from 'cdpath' for |:cd| and similar commands,
-  add the "cd_in_path" completion type for e.g. |:command-complete| and
-  |getcompletion()|
-- allow to complete shell commands and files using the new shellcmdline
-  completion type using |:command-complete| and |getcmdcomplpat()|
-- add 'cpoptions' flag "z" |cpo-z|, to disable some (traditional) vi
-  behaviour/inconsistency (see |d-special| and |cw|).
-- allow to specify additional attributes in the completion menu (allows to
-  mark deprecated attributes from LSP server) |complete-items|
-- the regex engines match correctly case-insensitive multi-byte characters
-  (and apply proper case folding)
 - |:keeppatterns| preserves the last substitute pattern when used with |:s|
+
+Functions: ~
+- provide information about function arguments using the get(func, "arity")
+  function |get()-func|
 - |setqflist()| and |setloclist()| can optionally try to preserve the current
   selection in the quickfix list with the "u" action.
+- allow to pass local Vim script variables to python interpreter |py3eval()|
+- |getwininfo()| now also returns the "leftcol" property for a window
+- |v:stacktrace| The stack trace of the exception most recently caught and
+  not finished
+- Add the optional {opts} |Dict| argument to |getchar()| to control: cursor
+  behaviour, return type and whether or not to simplify the returned key
+
+Others: ~
+- the regex engines match correctly case-insensitive multi-byte characters
+  (and apply proper case folding)
 - the putty terminal is detected using an |TermResponse| autocommand in
   |defaults.vim| and Vim switches to a dark background
 - the |help-TOC| package is included to ease navigating the documentation.
 - an interactive tutor plugin has been included |vim-tutor-mode|, can be
   started via |:Tutor|
 - improve the |vimtutor| and add a second chapter for more advanced tips
-- allow to pass local Vim script variables to python interpreter |py3eval()|
-- |getwininfo()| now also returns the "leftcol" property for a window
-- 'rulerformat' now supports the |stl-%!| item
-- the completed word and completion type are provided when handling the
-  |CompleteDone| autocommand in the |v:event| dictionary
-- |complete_info()| returns the list of matches shown in the poppu menu via
-  the "matches" key
-- |v:stacktrace| The stack trace of the exception most recently caught and
-  not finished
-- New option value for 'completeopt':
-       "nosort"        - do not sort completion results
-       "preinsert"     - highlight to be inserted values
 - add |dist#vim9#Launch()| and |dist#vim9#Open()| to the |vim-script-library|
   and decouple it from |netrw|
 - new digraph "APPROACHES THE LIMIT" using ".="
-- Add the optional {opts} |Dict| argument to |getchar()| to control: cursor
-  behaviour, return type and whether or not to simplify the returned key
-- handle multi-line completion as expected
 
                                                        *added-9.2*
 Added ~
index f707b1d5866c4c41a82e76c64a16228eedd61a25..c14eee2c93581cd7b5ce21e487744eb405d6af52 100644 (file)
@@ -3221,6 +3221,8 @@ ExpandFromContext(
        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,6 +3241,21 @@ ExpandFromContext(
     return ret;
 }
 
+    int
+ExpandGeneric(
+    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)
+{
+    return ExpandGenericExt(
+       pat, xp, regmatch, matches, numMatches, func, escaped, 0);
+}
+
 /*
  * Expand a list of names.
  *
@@ -3249,10 +3266,14 @@ ExpandFromContext(
  * 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
-ExpandGeneric(
+ExpandGenericExt(
     char_u     *pat,
     expand_T   *xp,
     regmatch_T *regmatch,
@@ -3260,7 +3281,8 @@ ExpandGeneric(
     int                *numMatches,
     char_u     *((*func)(expand_T *, int)),
                                          // returns a string from the list
-    int                escaped)
+    int                escaped,
+    int                sortStartIdx)
 {
     int                i;
     garray_T   ga;
@@ -3271,6 +3293,7 @@ ExpandGeneric(
     int                match;
     int                sort_matches = FALSE;
     int                funcsort = FALSE;
+    int                sortStartMatchIdx = -1;
 
     fuzzy = cmdline_fuzzy_complete(pat);
     *matches = NULL;
@@ -3346,6 +3369,12 @@ ExpandGeneric(
        }
 #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 @@ ExpandGeneric(
        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)
index 8c1ad8049ef05b12cf451b2f10c9ee89ecb8d448..2d965660e20f6704fce6f788080e69c595c25795 100644 (file)
@@ -1691,6 +1691,8 @@ do_highlight(
                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 @@ highlight_list_one(int id)
     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 @@ highlight_list_one(int id)
 #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 @@ highlight_list_arg(
            }
        }
     }
+    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 @@ highlight_changed(void)
 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 @@ static void highlight_list_two(int cnt, int attr);
 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 @@ set_context_in_highlight_cmd(expand_T *xp, char_u *arg)
     // 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 @@ set_context_in_highlight_cmd(expand_T *xp, char_u *arg)
        {
            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 @@ get_highlight_name_ext(expand_T *xp UNUSED, int idx, int skip_cleared)
        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 @@ get_highlight_name_ext(expand_T *xp UNUSED, int idx, int skip_cleared)
     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.
index dfa6d1b96b6f0616d9b0738101e89f1fe76cb4a7..bfcc5c8386cd3bca09ef849e0dacf17d0898525a 100644 (file)
@@ -17,6 +17,7 @@ void set_expand_context(expand_T *xp);
 void set_cmd_context(expand_T *xp, char_u *str, int len, int col, int use_ccline);
 int expand_cmdline(expand_T *xp, char_u *str, int col, int *matchcount, char_u ***matches);
 int ExpandGeneric(char_u *pat, expand_T *xp, regmatch_T *regmatch, char_u ***matches, int *numMatches, char_u *((*func)(expand_T *, int)), int escaped);
+int ExpandGenericExt(char_u *pat, expand_T *xp, regmatch_T *regmatch, char_u ***matches, int *numMatches, char_u *((*func)(expand_T *, int)), int escaped, int sortStartIdx);
 void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options, int dirs);
 int wildmenu_translate_key(cmdline_info_T *cclp, int key, expand_T *xp, int did_wild_list);
 int wildmenu_process_key(cmdline_info_T *cclp, int key, expand_T *xp);
index 33f90c65253f7b7d42f73b0389d49641c995efb6..531825836aef3b622060256f5d956c55806e11c8 100644 (file)
@@ -43,6 +43,7 @@ int highlight_changed(void);
 void set_context_in_highlight_cmd(expand_T *xp, char_u *arg);
 char_u *get_highlight_name(expand_T *xp, int idx);
 char_u *get_highlight_name_ext(expand_T *xp, int idx, int skip_cleared);
+int expand_highlight_group( char_u *pat, expand_T *xp, regmatch_T *rmp, char_u ***matches, int *numMatches);
 void free_highlight_fonts(void);
 void f_hlget(typval_T *argvars, typval_T *rettv);
 void f_hlset(typval_T *argvars, typval_T *rettv);
index 34563aad9dbe643e783fe10c38a69a7f34fe1f14..bbcf02b052510617e0d29d8944f117dc91445153 100644 (file)
@@ -6372,7 +6372,7 @@ reset_expand_highlight(void)
 }
 
 /*
- * Handle command line completion for :match and :echohl command: Add "None"
+ * Handle command line completion for :match and :echohl command: Add "NONE"
  * as highlight group.
  */
     void
index dfebb400b53644b91aaf85c20289097b570399a3..042710c2aae77a22d63be3c12b5f249fbbf51419 100644 (file)
@@ -418,8 +418,8 @@ func Test_match_completion()
   hi Aardig ctermfg=green
   call feedkeys(":match \<Tab>\<Home>\"\<CR>", 'xt')
   call assert_equal('"match Aardig', @:)
-  call feedkeys(":match \<S-Tab>\<Home>\"\<CR>", 'xt')
-  call assert_equal('"match none', @:)
+  call feedkeys(":match NON\<Tab>\<Home>\"\<CR>", 'xt')
+  call assert_equal('"match NONE', @:)
   call feedkeys(":match | chist\<Tab>\<C-B>\"\<CR>", 'xt')
   call assert_equal('"match | chistory', @:)
 endfunc
@@ -428,20 +428,37 @@ func Test_highlight_completion()
   hi Aardig ctermfg=green
   call feedkeys(":hi \<Tab>\<Home>\"\<CR>", 'xt')
   call assert_equal('"hi Aardig', getreg(':'))
+
+  " hi default
   call feedkeys(":hi default \<Tab>\<Home>\"\<CR>", 'xt')
   call assert_equal('"hi default Aardig', getreg(':'))
-  call feedkeys(":hi clear Aa\<Tab>\<Home>\"\<CR>", 'xt')
-  call assert_equal('"hi clear Aardig', getreg(':'))
-  call feedkeys(":hi li\<S-Tab>\<Home>\"\<CR>", 'xt')
-  call assert_equal('"hi link', getreg(':'))
   call feedkeys(":hi d\<S-Tab>\<Home>\"\<CR>", 'xt')
   call assert_equal('"hi default', getreg(':'))
+  call feedkeys(":hi default link Aa\<Tab>\<Home>\"\<CR>", 'xt')
+  call assert_equal('"hi default link Aardig', getreg(':'))
+
+  " hi clear only accepts one parameter
   call feedkeys(":hi c\<S-Tab>\<Home>\"\<CR>", 'xt')
   call assert_equal('"hi clear', getreg(':'))
+  call feedkeys(":hi clear Aa\<Tab>\<Home>\"\<CR>", 'xt')
+  call assert_equal('"hi clear Aardig', getreg(':'))
   call feedkeys(":hi clear Aardig Aard\<Tab>\<C-B>\"\<CR>", 'xt')
-  call assert_equal('"hi clear Aardig Aardig', getreg(':'))
-  call feedkeys(":hi Aardig \<Tab>\<C-B>\"\<CR>", 'xt')
-  call assert_equal("\"hi Aardig \t", getreg(':'))
+  call assert_equal("\"hi clear Aardig Aard\<Tab>", getreg(':'))
+  " hi link accepts up to two parameters
+  call feedkeys(":hi li\<S-Tab>\<Home>\"\<CR>", 'xt')
+  call assert_equal('"hi link', getreg(':'))
+  call assert_equal('"hi link', getreg(':'))
+  call feedkeys(":hi link Aa\<Tab>\<Home>\"\<CR>", 'xt')
+  call assert_equal('"hi link Aardig', getreg(':'))
+  call feedkeys(":hi link Aardig Aard\<Tab>\<C-B>\"\<CR>", 'xt')
+  call assert_equal("\"hi link Aardig Aardig", getreg(':'))
+  call feedkeys(":hi link Aardig Aardig Aard\<Tab>\<C-B>\"\<CR>", 'xt')
+  call assert_equal("\"hi link Aardig Aardig Aard\<Tab>", getreg(':'))
+  " hi link will complete to "NONE" for second parameter
+  call feedkeys(":hi link NON\<Tab>\<Home>\"\<CR>", 'xt')
+  call assert_equal("\"hi link NonText", getreg(':'))
+  call feedkeys(":hi link Aardig NON\<Tab>\<Home>\"\<CR>", 'xt')
+  call assert_equal("\"hi link Aardig NONE", getreg(':'))
 
   " A cleared group does not show up in completions.
   hi Anders ctermfg=green
@@ -460,6 +477,102 @@ func Test_highlight_easter_egg()
   call test_override('ALL', 0)
 endfunc
 
+func Test_highlight_group_completion()
+  " Test completing keys
+  call assert_equal('term=', getcompletion('hi Foo ', 'cmdline')[0])
+  call assert_equal('ctermfg=', getcompletion('hi Foo c*fg', 'cmdline')[0])
+  call assert_equal('NONE', getcompletion('hi Foo NON', 'cmdline')[0])
+  set wildoptions+=fuzzy
+  call assert_equal('ctermbg=', getcompletion('hi Foo cmbg', 'cmdline')[0])
+  set wildoptions-=fuzzy
+
+  " Test completing the current value
+  hi FooBar term=bold,underline cterm=undercurl ctermfg=lightgray ctermbg=12 ctermul=34
+  call assert_equal('bold,underline', getcompletion('hi FooBar term=', 'cmdline')[0])
+  call assert_equal('undercurl', getcompletion('hi FooBar cterm=', 'cmdline')[0])
+  call assert_equal('7', getcompletion('hi FooBar ctermfg=', 'cmdline')[0])
+  call assert_equal('12', getcompletion('hi FooBar ctermbg=', 'cmdline')[0])
+  call assert_equal('34', getcompletion('hi FooBar ctermul=', 'cmdline')[0])
+
+  " "bold,underline" is unique and creates an extra item. "undercurl" and
+  " should be de-duplicated
+  call assert_equal(len(getcompletion('hi FooBar term=', 'cmdline')),
+        \ 1 + len(getcompletion('hi FooBar cterm=', 'cmdline')))
+
+  " don't complete original value if we have user input already, similar to
+  " behavior in :set <option>=<pattern>
+  call assert_equal(['bold'], getcompletion('hi FooBar term=bol', 'cmdline'))
+  call assert_equal([], getcompletion('hi FooBar ctermfg=1', 'cmdline'))
+
+  " start/stop do not fill their current value now as they are more
+  " complicated
+  hi FooBar start=123 stop=234
+  call assert_equal([], getcompletion('hi FooBar start=', 'cmdline'))
+  call assert_equal([], getcompletion('hi FooBar stop=', 'cmdline'))
+
+  if has("gui") || has("termguicolors")
+    hi FooBar gui=italic guifg=#112233 guibg=brown1 guisp=green
+    call assert_equal('italic', getcompletion('hi FooBar gui=', 'cmdline')[0])
+    call assert_equal('#112233', getcompletion('hi FooBar guifg=', 'cmdline')[0])
+    call assert_equal('brown1', getcompletion('hi FooBar guibg=', 'cmdline')[0])
+    call assert_equal('green', getcompletion('hi FooBar guisp=', 'cmdline')[0])
+
+    " Check that existing value is de-duplicated and doesn't show up later
+    call assert_equal(1, count(getcompletion('hi FooBar guibg=', 'cmdline'), 'brown1'))
+  endif
+
+  " Test completing attributes
+  call assert_equal(['underdouble', 'underdotted'], getcompletion('hi DoesNotExist term=un*erdo*', 'cmdline'))
+  call assert_equal('NONE', getcompletion('hi DoesNotExist cterm=NON', 'cmdline')[0])
+  call assert_equal('NONE', getcompletion('hi DoesNotExist cterm=', 'cmdline')[-1]) " NONE should be at the end and not sorted
+  call assert_equal('bold', getcompletion('hi DoesNotExist cterm=underline,bo', 'cmdline')[0]) " complete after comma
+  if has("gui") || has("termguicolors")
+    set wildoptions+=fuzzy
+    call assert_equal('italic', getcompletion('hi DoesNotExist gui=itic', 'cmdline')[0])
+    set wildoptions-=fuzzy
+  endif
+
+  " Test completing cterm colors
+  call assert_equal('fg', getcompletion('hi FooBar ctermbg=f*g', 'cmdline')[0])
+  call assert_equal('fg', getcompletion('hi DoesNotExist ctermbg=f*g', 'cmdline')[0])
+  call assert_equal('NONE', getcompletion('hi FooBar ctermfg=NON', 'cmdline')[0])
+  call assert_equal('NONE', getcompletion('hi DoesNotExist ctermfg=NON', 'cmdline')[0])
+  set wildoptions+=fuzzy
+  call assert_equal('Black', getcompletion('hi FooBar ctermul=blck', 'cmdline')[0])
+  call assert_equal('Black', getcompletion('hi DoesNotExist ctermul=blck', 'cmdline')[0])
+  set wildoptions-=fuzzy
+
+  " Test completing gui colors
+  if has("gui") || has("termguicolors")
+    call assert_equal('fg', getcompletion('hi FooBar guibg=f*g', 'cmdline')[0])
+    call assert_equal('fg', getcompletion('hi DoesNotExist guibg=f*g', 'cmdline')[0])
+    call assert_equal('NONE', getcompletion('hi FooBar guifg=NON', 'cmdline')[0])
+    call assert_equal('NONE', getcompletion('hi DoesNotExist guifg=NON', 'cmdline')[0])
+    set wildoptions=fuzzy
+    call assert_equal('limegreen', getcompletion('hi FooBar guisp=limgrn', 'cmdline')[0])
+    call assert_equal('limegreen', getcompletion('hi DoesNotExist guisp=limgrn', 'cmdline')[0])
+    set wildoptions-=fuzzy
+
+    " Test pruning bad color names with space. Vim doesn't support them.
+    let v:colornames['foobar with space'] = '#123456'
+    let v:colornames['foobarwithoutspace'] = '#234567'
+    call assert_equal(['foobarwithoutspace'], getcompletion('hi FooBar guibg=foobarw', 'cmdline'))
+
+    " Test specialized sorting. First few items are special values that
+    " go first, after that it's a sorted list of color names.
+    call assert_equal(['fg','bg','NONE'], getcompletion('hi DoesNotExist guifg=', 'cmdline')[0:2])
+    let completed_colors=getcompletion('hi DoesNotExist guifg=', 'cmdline')[3:]
+    let gui_colors_no_space=filter(copy(v:colornames), {key,val -> match(key, ' ')==-1})
+    call assert_equal(len(gui_colors_no_space), len(completed_colors))
+    call assert_equal(sort(copy(completed_colors)), completed_colors)
+
+    " Test that the specialized sorting still works if we have some pattern matches
+    let completed_colors=getcompletion('hi DoesNotExist guifg=*blue*', 'cmdline')
+    call assert_equal(sort(copy(completed_colors)), completed_colors)
+    call assert_equal('aliceblue', completed_colors[0])
+  endif
+endfunc
+
 func Test_getcompletion()
   let groupcount = len(getcompletion('', 'event'))
   call assert_true(groupcount > 0)
index 748c2bd7b1a095d6aa98294edb579322c669ace1..49f883303aecbf7673b74ae9961ee3a62bd884d5 100644 (file)
@@ -209,7 +209,7 @@ endfunc
 
 func Test_echohl_completion()
   call feedkeys(":echohl no\<C-A>\<C-B>\"\<CR>", 'tx')
-  call assert_equal('"echohl NonText Normal none', @:)
+  call assert_equal('"echohl NONE NonText Normal', @:)
 endfunc
 
 func Test_syntax_arg_skipped()
index 948abdf784e742c2ad05c45840f45abc9e692bc4..87ed0591ccfbd3df0e4d4db2a4dbdf8cc48b98ce 100644 (file)
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1138,
 /**/
     1137,
 /**/
index e0c40c9785b9e637646d8e83686c72e4101cdc77..2ebc8fe0228472f9d7a98db354a3d1ae725a15e6 100644 (file)
--- a/src/vim.h
+++ b/src/vim.h
@@ -846,6 +846,7 @@ extern int (*dyn_libintl_wputenv)(const wchar_t *envstring);
 #define EXPAND_DIRS_IN_CDPATH  59
 #define EXPAND_SHELLCMDLINE    60
 #define EXPAND_FINDFUNC                61
+#define EXPAND_HIGHLIGHT_GROUP  62
 
 
 // Values for exmode_active (0 is no exmode)