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/testdir/test_cmdline.vim b/src/testdir/test_cmdline.vim
index dfebb40..042710c 100644
--- a/src/testdir/test_cmdline.vim
+++ b/src/testdir/test_cmdline.vim
@@ -418,8 +418,8 @@
   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 @@
   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 @@
   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)
diff --git a/src/testdir/test_syntax.vim b/src/testdir/test_syntax.vim
index 748c2bd..49f8833 100644
--- a/src/testdir/test_syntax.vim
+++ b/src/testdir/test_syntax.vim
@@ -209,7 +209,7 @@
 
 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()