xref: /vim-8.2.3635/src/highlight.c (revision 1a928c20)
1 /* vi:set ts=8 sts=4 sw=4 noet:
2  *
3  * VIM - Vi IMproved	by Bram Moolenaar
4  *
5  * Do ":help uganda"  in Vim to read copying and usage conditions.
6  * Do ":help credits" in Vim to see a list of people who contributed.
7  * See README.txt for an overview of the Vim source code.
8  */
9 
10 /*
11  * Highlighting stuff.
12  * Includes highlighting matches.
13  */
14 
15 #include "vim.h"
16 
17 #define SG_TERM		1	// term has been set
18 #define SG_CTERM	2	// cterm has been set
19 #define SG_GUI		4	// gui has been set
20 #define SG_LINK		8	// link has been set
21 
22 /*
23  * The "term", "cterm" and "gui" arguments can be any combination of the
24  * following names, separated by commas (but no spaces!).
25  */
26 static char *(hl_name_table[]) =
27     {"bold", "standout", "underline", "undercurl",
28       "italic", "reverse", "inverse", "nocombine", "strikethrough", "NONE"};
29 static int hl_attr_table[] =
30     {HL_BOLD, HL_STANDOUT, HL_UNDERLINE, HL_UNDERCURL, HL_ITALIC, HL_INVERSE, HL_INVERSE, HL_NOCOMBINE, HL_STRIKETHROUGH, 0};
31 #define ATTR_COMBINE(attr_a, attr_b) ((((attr_b) & HL_NOCOMBINE) ? attr_b : (attr_a)) | (attr_b))
32 
33 /*
34  * Structure that stores information about a highlight group.
35  * The ID of a highlight group is also called group ID.  It is the index in
36  * the highlight_ga array PLUS ONE.
37  */
38 typedef struct
39 {
40     char_u	*sg_name;	// highlight group name
41     char_u	*sg_name_u;	// uppercase of sg_name
42     int		sg_cleared;	// "hi clear" was used
43 // for normal terminals
44     int		sg_term;	// "term=" highlighting attributes
45     char_u	*sg_start;	// terminal string for start highl
46     char_u	*sg_stop;	// terminal string for stop highl
47     int		sg_term_attr;	// Screen attr for term mode
48 // for color terminals
49     int		sg_cterm;	// "cterm=" highlighting attr
50     int		sg_cterm_bold;	// bold attr was set for light color
51     int		sg_cterm_fg;	// terminal fg color number + 1
52     int		sg_cterm_bg;	// terminal bg color number + 1
53     int		sg_cterm_attr;	// Screen attr for color term mode
54 // for when using the GUI
55 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
56     guicolor_T	sg_gui_fg;	// GUI foreground color handle
57     guicolor_T	sg_gui_bg;	// GUI background color handle
58 #endif
59 #ifdef FEAT_GUI
60     guicolor_T	sg_gui_sp;	// GUI special color handle
61     GuiFont	sg_font;	// GUI font handle
62 #ifdef FEAT_XFONTSET
63     GuiFontset	sg_fontset;	// GUI fontset handle
64 #endif
65     char_u	*sg_font_name;  // GUI font or fontset name
66     int		sg_gui_attr;    // Screen attr for GUI mode
67 #endif
68 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
69 // Store the sp color name for the GUI or synIDattr()
70     int		sg_gui;		// "gui=" highlighting attributes
71     char_u	*sg_gui_fg_name;// GUI foreground color name
72     char_u	*sg_gui_bg_name;// GUI background color name
73     char_u	*sg_gui_sp_name;// GUI special color name
74 #endif
75     int		sg_link;	// link to this highlight group ID
76     int		sg_set;		// combination of SG_* flags
77 #ifdef FEAT_EVAL
78     sctx_T	sg_script_ctx;	// script in which the group was last set
79 #endif
80 } hl_group_T;
81 
82 // highlight groups for 'highlight' option
83 static garray_T highlight_ga;
84 #define HL_TABLE()	((hl_group_T *)((highlight_ga.ga_data)))
85 
86 /*
87  * An attribute number is the index in attr_table plus ATTR_OFF.
88  */
89 #define ATTR_OFF (HL_ALL + 1)
90 
91 static void syn_unadd_group(void);
92 static void set_hl_attr(int idx);
93 static void highlight_list_one(int id);
94 static int highlight_list_arg(int id, int didh, int type, int iarg, char_u *sarg, char *name);
95 static int syn_add_group(char_u *name);
96 static int hl_has_settings(int idx, int check_link);
97 static void highlight_clear(int idx);
98 
99 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
100 static void gui_do_one_color(int idx, int do_menu, int do_tooltip);
101 #endif
102 #ifdef FEAT_GUI
103 static int  set_group_colors(char_u *name, guicolor_T *fgp, guicolor_T *bgp, int do_menu, int use_norm, int do_tooltip);
104 static void hl_do_font(int idx, char_u *arg, int do_normal, int do_menu, int do_tooltip, int free_font);
105 #endif
106 
107 /*
108  * The default highlight groups.  These are compiled-in for fast startup and
109  * they still work when the runtime files can't be found.
110  * When making changes here, also change runtime/colors/default.vim!
111  * The #ifdefs are needed to reduce the amount of static data.  Helps to make
112  * the 16 bit DOS (museum) version compile.
113  */
114 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
115 # define CENT(a, b) b
116 #else
117 # define CENT(a, b) a
118 #endif
119 static char *(highlight_init_both[]) = {
120     CENT("ErrorMsg term=standout ctermbg=DarkRed ctermfg=White",
121 	 "ErrorMsg term=standout ctermbg=DarkRed ctermfg=White guibg=Red guifg=White"),
122     CENT("IncSearch term=reverse cterm=reverse",
123 	 "IncSearch term=reverse cterm=reverse gui=reverse"),
124     CENT("ModeMsg term=bold cterm=bold",
125 	 "ModeMsg term=bold cterm=bold gui=bold"),
126     CENT("NonText term=bold ctermfg=Blue",
127 	 "NonText term=bold ctermfg=Blue gui=bold guifg=Blue"),
128     CENT("StatusLine term=reverse,bold cterm=reverse,bold",
129 	 "StatusLine term=reverse,bold cterm=reverse,bold gui=reverse,bold"),
130     CENT("StatusLineNC term=reverse cterm=reverse",
131 	 "StatusLineNC term=reverse cterm=reverse gui=reverse"),
132     "default link EndOfBuffer NonText",
133     CENT("VertSplit term=reverse cterm=reverse",
134 	 "VertSplit term=reverse cterm=reverse gui=reverse"),
135 #ifdef FEAT_CLIPBOARD
136     CENT("VisualNOS term=underline,bold cterm=underline,bold",
137 	 "VisualNOS term=underline,bold cterm=underline,bold gui=underline,bold"),
138 #endif
139 #ifdef FEAT_DIFF
140     CENT("DiffText term=reverse cterm=bold ctermbg=Red",
141 	 "DiffText term=reverse cterm=bold ctermbg=Red gui=bold guibg=Red"),
142 #endif
143     CENT("PmenuSbar ctermbg=Grey",
144 	 "PmenuSbar ctermbg=Grey guibg=Grey"),
145     CENT("TabLineSel term=bold cterm=bold",
146 	 "TabLineSel term=bold cterm=bold gui=bold"),
147     CENT("TabLineFill term=reverse cterm=reverse",
148 	 "TabLineFill term=reverse cterm=reverse gui=reverse"),
149 #ifdef FEAT_GUI
150     "Cursor guibg=fg guifg=bg",
151     "lCursor guibg=fg guifg=bg", // should be different, but what?
152 #endif
153     "default link QuickFixLine Search",
154     CENT("Normal cterm=NONE", "Normal gui=NONE"),
155     NULL
156 };
157 
158 // Default colors only used with a light background.
159 static char *(highlight_init_light[]) = {
160     CENT("Directory term=bold ctermfg=DarkBlue",
161 	 "Directory term=bold ctermfg=DarkBlue guifg=Blue"),
162     CENT("LineNr term=underline ctermfg=Brown",
163 	 "LineNr term=underline ctermfg=Brown guifg=Brown"),
164     CENT("CursorLineNr term=bold cterm=underline ctermfg=Brown",
165 	 "CursorLineNr term=bold cterm=underline ctermfg=Brown gui=bold guifg=Brown"),
166     CENT("MoreMsg term=bold ctermfg=DarkGreen",
167 	 "MoreMsg term=bold ctermfg=DarkGreen gui=bold guifg=SeaGreen"),
168     CENT("Question term=standout ctermfg=DarkGreen",
169 	 "Question term=standout ctermfg=DarkGreen gui=bold guifg=SeaGreen"),
170     CENT("Search term=reverse ctermbg=Yellow ctermfg=NONE",
171 	 "Search term=reverse ctermbg=Yellow ctermfg=NONE guibg=Yellow guifg=NONE"),
172 #ifdef FEAT_SPELL
173     CENT("SpellBad term=reverse ctermbg=LightRed",
174 	 "SpellBad term=reverse ctermbg=LightRed guisp=Red gui=undercurl"),
175     CENT("SpellCap term=reverse ctermbg=LightBlue",
176 	 "SpellCap term=reverse ctermbg=LightBlue guisp=Blue gui=undercurl"),
177     CENT("SpellRare term=reverse ctermbg=LightMagenta",
178 	 "SpellRare term=reverse ctermbg=LightMagenta guisp=Magenta gui=undercurl"),
179     CENT("SpellLocal term=underline ctermbg=Cyan",
180 	 "SpellLocal term=underline ctermbg=Cyan guisp=DarkCyan gui=undercurl"),
181 #endif
182     CENT("PmenuThumb ctermbg=Black",
183 	 "PmenuThumb ctermbg=Black guibg=Black"),
184     CENT("Pmenu ctermbg=LightMagenta ctermfg=Black",
185 	 "Pmenu ctermbg=LightMagenta ctermfg=Black guibg=LightMagenta"),
186     CENT("PmenuSel ctermbg=LightGrey ctermfg=Black",
187 	 "PmenuSel ctermbg=LightGrey ctermfg=Black guibg=Grey"),
188     CENT("SpecialKey term=bold ctermfg=DarkBlue",
189 	 "SpecialKey term=bold ctermfg=DarkBlue guifg=Blue"),
190     CENT("Title term=bold ctermfg=DarkMagenta",
191 	 "Title term=bold ctermfg=DarkMagenta gui=bold guifg=Magenta"),
192     CENT("WarningMsg term=standout ctermfg=DarkRed",
193 	 "WarningMsg term=standout ctermfg=DarkRed guifg=Red"),
194 #ifdef FEAT_WILDMENU
195     CENT("WildMenu term=standout ctermbg=Yellow ctermfg=Black",
196 	 "WildMenu term=standout ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black"),
197 #endif
198 #ifdef FEAT_FOLDING
199     CENT("Folded term=standout ctermbg=Grey ctermfg=DarkBlue",
200 	 "Folded term=standout ctermbg=Grey ctermfg=DarkBlue guibg=LightGrey guifg=DarkBlue"),
201     CENT("FoldColumn term=standout ctermbg=Grey ctermfg=DarkBlue",
202 	 "FoldColumn term=standout ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue"),
203 #endif
204 #ifdef FEAT_SIGNS
205     CENT("SignColumn term=standout ctermbg=Grey ctermfg=DarkBlue",
206 	 "SignColumn term=standout ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue"),
207 #endif
208     CENT("Visual term=reverse",
209 	 "Visual term=reverse guibg=LightGrey"),
210 #ifdef FEAT_DIFF
211     CENT("DiffAdd term=bold ctermbg=LightBlue",
212 	 "DiffAdd term=bold ctermbg=LightBlue guibg=LightBlue"),
213     CENT("DiffChange term=bold ctermbg=LightMagenta",
214 	 "DiffChange term=bold ctermbg=LightMagenta guibg=LightMagenta"),
215     CENT("DiffDelete term=bold ctermfg=Blue ctermbg=LightCyan",
216 	 "DiffDelete term=bold ctermfg=Blue ctermbg=LightCyan gui=bold guifg=Blue guibg=LightCyan"),
217 #endif
218     CENT("TabLine term=underline cterm=underline ctermfg=black ctermbg=LightGrey",
219 	 "TabLine term=underline cterm=underline ctermfg=black ctermbg=LightGrey gui=underline guibg=LightGrey"),
220 #ifdef FEAT_SYN_HL
221     CENT("CursorColumn term=reverse ctermbg=LightGrey",
222 	 "CursorColumn term=reverse ctermbg=LightGrey guibg=Grey90"),
223     CENT("CursorLine term=underline cterm=underline",
224 	 "CursorLine term=underline cterm=underline guibg=Grey90"),
225     CENT("ColorColumn term=reverse ctermbg=LightRed",
226 	 "ColorColumn term=reverse ctermbg=LightRed guibg=LightRed"),
227 #endif
228 #ifdef FEAT_CONCEAL
229     CENT("Conceal ctermbg=DarkGrey ctermfg=LightGrey",
230 	 "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey"),
231 #endif
232     CENT("MatchParen term=reverse ctermbg=Cyan",
233 	 "MatchParen term=reverse ctermbg=Cyan guibg=Cyan"),
234 #ifdef FEAT_TERMINAL
235     CENT("StatusLineTerm term=reverse,bold cterm=bold ctermfg=White ctermbg=DarkGreen",
236 	 "StatusLineTerm term=reverse,bold cterm=bold ctermfg=White ctermbg=DarkGreen gui=bold guifg=bg guibg=DarkGreen"),
237     CENT("StatusLineTermNC term=reverse ctermfg=White ctermbg=DarkGreen",
238 	 "StatusLineTermNC term=reverse ctermfg=White ctermbg=DarkGreen guifg=bg guibg=DarkGreen"),
239 #endif
240 #ifdef FEAT_MENU
241     CENT("ToolbarLine term=underline ctermbg=LightGrey",
242 	 "ToolbarLine term=underline ctermbg=LightGrey guibg=LightGrey"),
243     CENT("ToolbarButton cterm=bold ctermfg=White ctermbg=DarkGrey",
244 	 "ToolbarButton cterm=bold ctermfg=White ctermbg=DarkGrey gui=bold guifg=White guibg=Grey40"),
245 #endif
246     NULL
247 };
248 
249 // Default colors only used with a dark background.
250 static char *(highlight_init_dark[]) = {
251     CENT("Directory term=bold ctermfg=LightCyan",
252 	 "Directory term=bold ctermfg=LightCyan guifg=Cyan"),
253     CENT("LineNr term=underline ctermfg=Yellow",
254 	 "LineNr term=underline ctermfg=Yellow guifg=Yellow"),
255     CENT("CursorLineNr term=bold cterm=underline ctermfg=Yellow",
256 	 "CursorLineNr term=bold cterm=underline ctermfg=Yellow gui=bold guifg=Yellow"),
257     CENT("MoreMsg term=bold ctermfg=LightGreen",
258 	 "MoreMsg term=bold ctermfg=LightGreen gui=bold guifg=SeaGreen"),
259     CENT("Question term=standout ctermfg=LightGreen",
260 	 "Question term=standout ctermfg=LightGreen gui=bold guifg=Green"),
261     CENT("Search term=reverse ctermbg=Yellow ctermfg=Black",
262 	 "Search term=reverse ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black"),
263     CENT("SpecialKey term=bold ctermfg=LightBlue",
264 	 "SpecialKey term=bold ctermfg=LightBlue guifg=Cyan"),
265 #ifdef FEAT_SPELL
266     CENT("SpellBad term=reverse ctermbg=Red",
267 	 "SpellBad term=reverse ctermbg=Red guisp=Red gui=undercurl"),
268     CENT("SpellCap term=reverse ctermbg=Blue",
269 	 "SpellCap term=reverse ctermbg=Blue guisp=Blue gui=undercurl"),
270     CENT("SpellRare term=reverse ctermbg=Magenta",
271 	 "SpellRare term=reverse ctermbg=Magenta guisp=Magenta gui=undercurl"),
272     CENT("SpellLocal term=underline ctermbg=Cyan",
273 	 "SpellLocal term=underline ctermbg=Cyan guisp=Cyan gui=undercurl"),
274 #endif
275     CENT("PmenuThumb ctermbg=White",
276 	 "PmenuThumb ctermbg=White guibg=White"),
277     CENT("Pmenu ctermbg=Magenta ctermfg=Black",
278 	 "Pmenu ctermbg=Magenta ctermfg=Black guibg=Magenta"),
279     CENT("PmenuSel ctermbg=Black ctermfg=DarkGrey",
280 	 "PmenuSel ctermbg=Black ctermfg=DarkGrey guibg=DarkGrey"),
281     CENT("Title term=bold ctermfg=LightMagenta",
282 	 "Title term=bold ctermfg=LightMagenta gui=bold guifg=Magenta"),
283     CENT("WarningMsg term=standout ctermfg=LightRed",
284 	 "WarningMsg term=standout ctermfg=LightRed guifg=Red"),
285 #ifdef FEAT_WILDMENU
286     CENT("WildMenu term=standout ctermbg=Yellow ctermfg=Black",
287 	 "WildMenu term=standout ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black"),
288 #endif
289 #ifdef FEAT_FOLDING
290     CENT("Folded term=standout ctermbg=DarkGrey ctermfg=Cyan",
291 	 "Folded term=standout ctermbg=DarkGrey ctermfg=Cyan guibg=DarkGrey guifg=Cyan"),
292     CENT("FoldColumn term=standout ctermbg=DarkGrey ctermfg=Cyan",
293 	 "FoldColumn term=standout ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan"),
294 #endif
295 #ifdef FEAT_SIGNS
296     CENT("SignColumn term=standout ctermbg=DarkGrey ctermfg=Cyan",
297 	 "SignColumn term=standout ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan"),
298 #endif
299     CENT("Visual term=reverse",
300 	 "Visual term=reverse guibg=DarkGrey"),
301 #ifdef FEAT_DIFF
302     CENT("DiffAdd term=bold ctermbg=DarkBlue",
303 	 "DiffAdd term=bold ctermbg=DarkBlue guibg=DarkBlue"),
304     CENT("DiffChange term=bold ctermbg=DarkMagenta",
305 	 "DiffChange term=bold ctermbg=DarkMagenta guibg=DarkMagenta"),
306     CENT("DiffDelete term=bold ctermfg=Blue ctermbg=DarkCyan",
307 	 "DiffDelete term=bold ctermfg=Blue ctermbg=DarkCyan gui=bold guifg=Blue guibg=DarkCyan"),
308 #endif
309     CENT("TabLine term=underline cterm=underline ctermfg=white ctermbg=DarkGrey",
310 	 "TabLine term=underline cterm=underline ctermfg=white ctermbg=DarkGrey gui=underline guibg=DarkGrey"),
311 #ifdef FEAT_SYN_HL
312     CENT("CursorColumn term=reverse ctermbg=DarkGrey",
313 	 "CursorColumn term=reverse ctermbg=DarkGrey guibg=Grey40"),
314     CENT("CursorLine term=underline cterm=underline",
315 	 "CursorLine term=underline cterm=underline guibg=Grey40"),
316     CENT("ColorColumn term=reverse ctermbg=DarkRed",
317 	 "ColorColumn term=reverse ctermbg=DarkRed guibg=DarkRed"),
318 #endif
319     CENT("MatchParen term=reverse ctermbg=DarkCyan",
320 	 "MatchParen term=reverse ctermbg=DarkCyan guibg=DarkCyan"),
321 #ifdef FEAT_CONCEAL
322     CENT("Conceal ctermbg=DarkGrey ctermfg=LightGrey",
323 	 "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey"),
324 #endif
325 #ifdef FEAT_TERMINAL
326     CENT("StatusLineTerm term=reverse,bold cterm=bold ctermfg=Black ctermbg=LightGreen",
327 	 "StatusLineTerm term=reverse,bold cterm=bold ctermfg=Black ctermbg=LightGreen gui=bold guifg=bg guibg=LightGreen"),
328     CENT("StatusLineTermNC term=reverse ctermfg=Black ctermbg=LightGreen",
329 	 "StatusLineTermNC term=reverse ctermfg=Black ctermbg=LightGreen guifg=bg guibg=LightGreen"),
330 #endif
331 #ifdef FEAT_MENU
332     CENT("ToolbarLine term=underline ctermbg=DarkGrey",
333 	 "ToolbarLine term=underline ctermbg=DarkGrey guibg=Grey50"),
334     CENT("ToolbarButton cterm=bold ctermfg=Black ctermbg=LightGrey",
335 	 "ToolbarButton cterm=bold ctermfg=Black ctermbg=LightGrey gui=bold guifg=Black guibg=LightGrey"),
336 #endif
337     NULL
338 };
339 
340 /*
341  * Returns the number of highlight groups.
342  */
343     int
344 highlight_num_groups(void)
345 {
346     return highlight_ga.ga_len;
347 }
348 
349 /*
350  * Returns the name of a highlight group.
351  */
352     char_u *
353 highlight_group_name(int id)
354 {
355     return HL_TABLE()[id].sg_name;
356 }
357 
358 /*
359  * Returns the ID of the link to a highlight group.
360  */
361     int
362 highlight_link_id(int id)
363 {
364     return HL_TABLE()[id].sg_link;
365 }
366 
367     void
368 init_highlight(
369     int		both,	    // include groups where 'bg' doesn't matter
370     int		reset)	    // clear group first
371 {
372     int		i;
373     char	**pp;
374     static int	had_both = FALSE;
375 #ifdef FEAT_EVAL
376     char_u	*p;
377 
378     /*
379      * Try finding the color scheme file.  Used when a color file was loaded
380      * and 'background' or 't_Co' is changed.
381      */
382     p = get_var_value((char_u *)"g:colors_name");
383     if (p != NULL)
384     {
385 	// The value of g:colors_name could be freed when sourcing the script,
386 	// making "p" invalid, so copy it.
387 	char_u *copy_p = vim_strsave(p);
388 	int    r;
389 
390 	if (copy_p != NULL)
391 	{
392 	    r = load_colors(copy_p);
393 	    vim_free(copy_p);
394 	    if (r == OK)
395 		return;
396 	}
397     }
398 
399 #endif
400 
401     /*
402      * Didn't use a color file, use the compiled-in colors.
403      */
404     if (both)
405     {
406 	had_both = TRUE;
407 	pp = highlight_init_both;
408 	for (i = 0; pp[i] != NULL; ++i)
409 	    do_highlight((char_u *)pp[i], reset, TRUE);
410     }
411     else if (!had_both)
412 	// Don't do anything before the call with both == TRUE from main().
413 	// Not everything has been setup then, and that call will overrule
414 	// everything anyway.
415 	return;
416 
417     if (*p_bg == 'l')
418 	pp = highlight_init_light;
419     else
420 	pp = highlight_init_dark;
421     for (i = 0; pp[i] != NULL; ++i)
422 	do_highlight((char_u *)pp[i], reset, TRUE);
423 
424     // Reverse looks ugly, but grey may not work for 8 colors.  Thus let it
425     // depend on the number of colors available.
426     // With 8 colors brown is equal to yellow, need to use black for Search fg
427     // to avoid Statement highlighted text disappears.
428     // Clear the attributes, needed when changing the t_Co value.
429     if (t_colors > 8)
430 	do_highlight((char_u *)(*p_bg == 'l'
431 		    ? "Visual cterm=NONE ctermbg=LightGrey"
432 		    : "Visual cterm=NONE ctermbg=DarkGrey"), FALSE, TRUE);
433     else
434     {
435 	do_highlight((char_u *)"Visual cterm=reverse ctermbg=NONE",
436 								 FALSE, TRUE);
437 	if (*p_bg == 'l')
438 	    do_highlight((char_u *)"Search ctermfg=black", FALSE, TRUE);
439     }
440 
441 #ifdef FEAT_SYN_HL
442     /*
443      * If syntax highlighting is enabled load the highlighting for it.
444      */
445     if (get_var_value((char_u *)"g:syntax_on") != NULL)
446     {
447 	static int	recursive = 0;
448 
449 	if (recursive >= 5)
450 	    emsg(_("E679: recursive loop loading syncolor.vim"));
451 	else
452 	{
453 	    ++recursive;
454 	    (void)source_runtime((char_u *)"syntax/syncolor.vim", DIP_ALL);
455 	    --recursive;
456 	}
457     }
458 #endif
459 }
460 
461 /*
462  * Load color file "name".
463  * Return OK for success, FAIL for failure.
464  */
465     int
466 load_colors(char_u *name)
467 {
468     char_u	*buf;
469     int		retval = FAIL;
470     static int	recursive = FALSE;
471 
472     // When being called recursively, this is probably because setting
473     // 'background' caused the highlighting to be reloaded.  This means it is
474     // working, thus we should return OK.
475     if (recursive)
476 	return OK;
477 
478     recursive = TRUE;
479     buf = alloc(STRLEN(name) + 12);
480     if (buf != NULL)
481     {
482 	apply_autocmds(EVENT_COLORSCHEMEPRE, name,
483 					       curbuf->b_fname, FALSE, curbuf);
484 	sprintf((char *)buf, "colors/%s.vim", name);
485 	retval = source_runtime(buf, DIP_START + DIP_OPT);
486 	vim_free(buf);
487 	apply_autocmds(EVENT_COLORSCHEME, name, curbuf->b_fname, FALSE, curbuf);
488     }
489     recursive = FALSE;
490 
491     return retval;
492 }
493 
494 static char *(color_names[28]) = {
495 	    "Black", "DarkBlue", "DarkGreen", "DarkCyan",
496 	    "DarkRed", "DarkMagenta", "Brown", "DarkYellow",
497 	    "Gray", "Grey", "LightGray", "LightGrey",
498 	    "DarkGray", "DarkGrey",
499 	    "Blue", "LightBlue", "Green", "LightGreen",
500 	    "Cyan", "LightCyan", "Red", "LightRed", "Magenta",
501 	    "LightMagenta", "Yellow", "LightYellow", "White", "NONE"};
502 	    // indices:
503 	    // 0, 1, 2, 3,
504 	    // 4, 5, 6, 7,
505 	    // 8, 9, 10, 11,
506 	    // 12, 13,
507 	    // 14, 15, 16, 17,
508 	    // 18, 19, 20, 21, 22,
509 	    // 23, 24, 25, 26, 27
510 static int color_numbers_16[28] = {0, 1, 2, 3,
511 				 4, 5, 6, 6,
512 				 7, 7, 7, 7,
513 				 8, 8,
514 				 9, 9, 10, 10,
515 				 11, 11, 12, 12, 13,
516 				 13, 14, 14, 15, -1};
517 // for xterm with 88 colors...
518 static int color_numbers_88[28] = {0, 4, 2, 6,
519 				 1, 5, 32, 72,
520 				 84, 84, 7, 7,
521 				 82, 82,
522 				 12, 43, 10, 61,
523 				 14, 63, 9, 74, 13,
524 				 75, 11, 78, 15, -1};
525 // for xterm with 256 colors...
526 static int color_numbers_256[28] = {0, 4, 2, 6,
527 				 1, 5, 130, 130,
528 				 248, 248, 7, 7,
529 				 242, 242,
530 				 12, 81, 10, 121,
531 				 14, 159, 9, 224, 13,
532 				 225, 11, 229, 15, -1};
533 // for terminals with less than 16 colors...
534 static int color_numbers_8[28] = {0, 4, 2, 6,
535 				 1, 5, 3, 3,
536 				 7, 7, 7, 7,
537 				 0+8, 0+8,
538 				 4+8, 4+8, 2+8, 2+8,
539 				 6+8, 6+8, 1+8, 1+8, 5+8,
540 				 5+8, 3+8, 3+8, 7+8, -1};
541 
542 /*
543  * Lookup the "cterm" value to be used for color with index "idx" in
544  * color_names[].
545  * "boldp" will be set to TRUE or FALSE for a foreground color when using 8
546  * colors, otherwise it will be unchanged.
547  */
548     int
549 lookup_color(int idx, int foreground, int *boldp)
550 {
551     int		color = color_numbers_16[idx];
552     char_u	*p;
553 
554     // Use the _16 table to check if it's a valid color name.
555     if (color < 0)
556 	return -1;
557 
558     if (t_colors == 8)
559     {
560 	// t_Co is 8: use the 8 colors table
561 #if defined(__QNXNTO__)
562 	// On qnx, the 8 & 16 color arrays are the same
563 	if (STRNCMP(T_NAME, "qansi", 5) == 0)
564 	    color = color_numbers_16[idx];
565 	else
566 #endif
567 	    color = color_numbers_8[idx];
568 	if (foreground)
569 	{
570 	    // set/reset bold attribute to get light foreground
571 	    // colors (on some terminals, e.g. "linux")
572 	    if (color & 8)
573 		*boldp = TRUE;
574 	    else
575 		*boldp = FALSE;
576 	}
577 	color &= 7;	// truncate to 8 colors
578     }
579     else if (t_colors == 16 || t_colors == 88
580 					   || t_colors >= 256)
581     {
582 	/*
583 	 * Guess: if the termcap entry ends in 'm', it is
584 	 * probably an xterm-like terminal.  Use the changed
585 	 * order for colors.
586 	 */
587 	if (*T_CAF != NUL)
588 	    p = T_CAF;
589 	else
590 	    p = T_CSF;
591 	if (*p != NUL && (t_colors > 256
592 			      || *(p + STRLEN(p) - 1) == 'm'))
593 	{
594 	    if (t_colors == 88)
595 		color = color_numbers_88[idx];
596 	    else if (t_colors >= 256)
597 		color = color_numbers_256[idx];
598 	    else
599 		color = color_numbers_8[idx];
600 	}
601 #ifdef FEAT_TERMRESPONSE
602 	if (t_colors >= 256 && color == 15 && is_mac_terminal)
603 	    // Terminal.app has a bug: 15 is light grey. Use white
604 	    // from the color cube instead.
605 	    color = 231;
606 #endif
607     }
608     return color;
609 }
610 
611 /*
612  * Handle the ":highlight .." command.
613  * When using ":hi clear" this is called recursively for each group with
614  * "forceit" and "init" both TRUE.
615  */
616     void
617 do_highlight(
618     char_u	*line,
619     int		forceit,
620     int		init)	    // TRUE when called for initializing
621 {
622     char_u	*name_end;
623     char_u	*p;
624     char_u	*linep;
625     char_u	*key_start;
626     char_u	*arg_start;
627     char_u	*key = NULL, *arg = NULL;
628     long	i;
629     int		off;
630     int		len;
631     int		attr;
632     int		id;
633     int		idx;
634     hl_group_T	item_before;
635     int		did_change = FALSE;
636     int		dodefault = FALSE;
637     int		doclear = FALSE;
638     int		dolink = FALSE;
639     int		error = FALSE;
640     int		color;
641     int		is_normal_group = FALSE;	// "Normal" group
642 #ifdef FEAT_TERMINAL
643     int		is_terminal_group = FALSE;	// "Terminal" group
644 #endif
645 #ifdef FEAT_GUI_X11
646     int		is_menu_group = FALSE;		// "Menu" group
647     int		is_scrollbar_group = FALSE;	// "Scrollbar" group
648     int		is_tooltip_group = FALSE;	// "Tooltip" group
649     int		do_colors = FALSE;		// need to update colors?
650 #else
651 # define is_menu_group 0
652 # define is_tooltip_group 0
653 #endif
654 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
655     int		did_highlight_changed = FALSE;
656 #endif
657 
658     /*
659      * If no argument, list current highlighting.
660      */
661     if (ends_excmd(*line))
662     {
663 	for (i = 1; i <= highlight_ga.ga_len && !got_int; ++i)
664 	    // TODO: only call when the group has attributes set
665 	    highlight_list_one((int)i);
666 	return;
667     }
668 
669     /*
670      * Isolate the name.
671      */
672     name_end = skiptowhite(line);
673     linep = skipwhite(name_end);
674 
675     /*
676      * Check for "default" argument.
677      */
678     if (STRNCMP(line, "default", name_end - line) == 0)
679     {
680 	dodefault = TRUE;
681 	line = linep;
682 	name_end = skiptowhite(line);
683 	linep = skipwhite(name_end);
684     }
685 
686     /*
687      * Check for "clear" or "link" argument.
688      */
689     if (STRNCMP(line, "clear", name_end - line) == 0)
690 	doclear = TRUE;
691     if (STRNCMP(line, "link", name_end - line) == 0)
692 	dolink = TRUE;
693 
694     /*
695      * ":highlight {group-name}": list highlighting for one group.
696      */
697     if (!doclear && !dolink && ends_excmd(*linep))
698     {
699 	id = syn_namen2id(line, (int)(name_end - line));
700 	if (id == 0)
701 	    semsg(_("E411: highlight group not found: %s"), line);
702 	else
703 	    highlight_list_one(id);
704 	return;
705     }
706 
707     /*
708      * Handle ":highlight link {from} {to}" command.
709      */
710     if (dolink)
711     {
712 	char_u	    *from_start = linep;
713 	char_u	    *from_end;
714 	char_u	    *to_start;
715 	char_u	    *to_end;
716 	int	    from_id;
717 	int	    to_id;
718 
719 	from_end = skiptowhite(from_start);
720 	to_start = skipwhite(from_end);
721 	to_end	 = skiptowhite(to_start);
722 
723 	if (ends_excmd(*from_start) || ends_excmd(*to_start))
724 	{
725 	    semsg(_("E412: Not enough arguments: \":highlight link %s\""),
726 								  from_start);
727 	    return;
728 	}
729 
730 	if (!ends_excmd(*skipwhite(to_end)))
731 	{
732 	    semsg(_("E413: Too many arguments: \":highlight link %s\""), from_start);
733 	    return;
734 	}
735 
736 	from_id = syn_check_group(from_start, (int)(from_end - from_start));
737 	if (STRNCMP(to_start, "NONE", 4) == 0)
738 	    to_id = 0;
739 	else
740 	    to_id = syn_check_group(to_start, (int)(to_end - to_start));
741 
742 	if (from_id > 0 && (!init || HL_TABLE()[from_id - 1].sg_set == 0))
743 	{
744 	    /*
745 	     * Don't allow a link when there already is some highlighting
746 	     * for the group, unless '!' is used
747 	     */
748 	    if (to_id > 0 && !forceit && !init
749 				   && hl_has_settings(from_id - 1, dodefault))
750 	    {
751 		if (SOURCING_NAME == NULL && !dodefault)
752 		    emsg(_("E414: group has settings, highlight link ignored"));
753 	    }
754 	    else if (HL_TABLE()[from_id - 1].sg_link != to_id
755 #ifdef FEAT_EVAL
756 		    || HL_TABLE()[from_id - 1].sg_script_ctx.sc_sid
757 							 != current_sctx.sc_sid
758 #endif
759 		    || HL_TABLE()[from_id - 1].sg_cleared)
760 	    {
761 		if (!init)
762 		    HL_TABLE()[from_id - 1].sg_set |= SG_LINK;
763 		HL_TABLE()[from_id - 1].sg_link = to_id;
764 #ifdef FEAT_EVAL
765 		HL_TABLE()[from_id - 1].sg_script_ctx = current_sctx;
766 		HL_TABLE()[from_id - 1].sg_script_ctx.sc_lnum += SOURCING_LNUM;
767 #endif
768 		HL_TABLE()[from_id - 1].sg_cleared = FALSE;
769 		redraw_all_later(SOME_VALID);
770 
771 		// Only call highlight_changed() once after multiple changes.
772 		need_highlight_changed = TRUE;
773 	    }
774 	}
775 
776 	return;
777     }
778 
779     if (doclear)
780     {
781 	/*
782 	 * ":highlight clear [group]" command.
783 	 */
784 	line = linep;
785 	if (ends_excmd(*line))
786 	{
787 #ifdef FEAT_GUI
788 	    // First, we do not destroy the old values, but allocate the new
789 	    // ones and update the display. THEN we destroy the old values.
790 	    // If we destroy the old values first, then the old values
791 	    // (such as GuiFont's or GuiFontset's) will still be displayed but
792 	    // invalid because they were free'd.
793 	    if (gui.in_use)
794 	    {
795 # ifdef FEAT_BEVAL_TIP
796 		gui_init_tooltip_font();
797 # endif
798 # if defined(FEAT_MENU) && (defined(FEAT_GUI_ATHENA) || defined(FEAT_GUI_MOTIF))
799 		gui_init_menu_font();
800 # endif
801 	    }
802 # if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_X11)
803 	    gui_mch_def_colors();
804 # endif
805 # ifdef FEAT_GUI_X11
806 #  ifdef FEAT_MENU
807 
808 	    // This only needs to be done when there is no Menu highlight
809 	    // group defined by default, which IS currently the case.
810 	    gui_mch_new_menu_colors();
811 #  endif
812 	    if (gui.in_use)
813 	    {
814 		gui_new_scrollbar_colors();
815 #  ifdef FEAT_BEVAL_GUI
816 		gui_mch_new_tooltip_colors();
817 #  endif
818 #  ifdef FEAT_MENU
819 		gui_mch_new_menu_font();
820 #  endif
821 	    }
822 # endif
823 
824 	    // Ok, we're done allocating the new default graphics items.
825 	    // The screen should already be refreshed at this point.
826 	    // It is now Ok to clear out the old data.
827 #endif
828 #ifdef FEAT_EVAL
829 	    do_unlet((char_u *)"colors_name", TRUE);
830 #endif
831 	    restore_cterm_colors();
832 
833 	    /*
834 	     * Clear all default highlight groups and load the defaults.
835 	     */
836 	    for (idx = 0; idx < highlight_ga.ga_len; ++idx)
837 		highlight_clear(idx);
838 	    init_highlight(TRUE, TRUE);
839 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
840 	    if (USE_24BIT)
841 		highlight_gui_started();
842 	    else
843 #endif
844 		highlight_changed();
845 	    redraw_later_clear();
846 	    return;
847 	}
848 	name_end = skiptowhite(line);
849 	linep = skipwhite(name_end);
850     }
851 
852     /*
853      * Find the group name in the table.  If it does not exist yet, add it.
854      */
855     id = syn_check_group(line, (int)(name_end - line));
856     if (id == 0)			// failed (out of memory)
857 	return;
858     idx = id - 1;			// index is ID minus one
859 
860     // Return if "default" was used and the group already has settings.
861     if (dodefault && hl_has_settings(idx, TRUE))
862 	return;
863 
864     // Make a copy so we can check if any attribute actually changed.
865     item_before = HL_TABLE()[idx];
866 
867     if (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0)
868 	is_normal_group = TRUE;
869 #ifdef FEAT_TERMINAL
870     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "TERMINAL") == 0)
871 	is_terminal_group = TRUE;
872 #endif
873 #ifdef FEAT_GUI_X11
874     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "MENU") == 0)
875 	is_menu_group = TRUE;
876     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "SCROLLBAR") == 0)
877 	is_scrollbar_group = TRUE;
878     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "TOOLTIP") == 0)
879 	is_tooltip_group = TRUE;
880 #endif
881 
882     // Clear the highlighting for ":hi clear {group}" and ":hi clear".
883     if (doclear || (forceit && init))
884     {
885 	highlight_clear(idx);
886 	if (!doclear)
887 	    HL_TABLE()[idx].sg_set = 0;
888     }
889 
890     if (!doclear)
891       while (!ends_excmd(*linep))
892       {
893 	key_start = linep;
894 	if (*linep == '=')
895 	{
896 	    semsg(_("E415: unexpected equal sign: %s"), key_start);
897 	    error = TRUE;
898 	    break;
899 	}
900 
901 	/*
902 	 * Isolate the key ("term", "ctermfg", "ctermbg", "font", "guifg" or
903 	 * "guibg").
904 	 */
905 	while (*linep && !VIM_ISWHITE(*linep) && *linep != '=')
906 	    ++linep;
907 	vim_free(key);
908 	key = vim_strnsave_up(key_start, (int)(linep - key_start));
909 	if (key == NULL)
910 	{
911 	    error = TRUE;
912 	    break;
913 	}
914 	linep = skipwhite(linep);
915 
916 	if (STRCMP(key, "NONE") == 0)
917 	{
918 	    if (!init || HL_TABLE()[idx].sg_set == 0)
919 	    {
920 		if (!init)
921 		    HL_TABLE()[idx].sg_set |= SG_TERM+SG_CTERM+SG_GUI;
922 		highlight_clear(idx);
923 	    }
924 	    continue;
925 	}
926 
927 	/*
928 	 * Check for the equal sign.
929 	 */
930 	if (*linep != '=')
931 	{
932 	    semsg(_("E416: missing equal sign: %s"), key_start);
933 	    error = TRUE;
934 	    break;
935 	}
936 	++linep;
937 
938 	/*
939 	 * Isolate the argument.
940 	 */
941 	linep = skipwhite(linep);
942 	if (*linep == '\'')		// guifg='color name'
943 	{
944 	    arg_start = ++linep;
945 	    linep = vim_strchr(linep, '\'');
946 	    if (linep == NULL)
947 	    {
948 		semsg(_(e_invarg2), key_start);
949 		error = TRUE;
950 		break;
951 	    }
952 	}
953 	else
954 	{
955 	    arg_start = linep;
956 	    linep = skiptowhite(linep);
957 	}
958 	if (linep == arg_start)
959 	{
960 	    semsg(_("E417: missing argument: %s"), key_start);
961 	    error = TRUE;
962 	    break;
963 	}
964 	vim_free(arg);
965 	arg = vim_strnsave(arg_start, (int)(linep - arg_start));
966 	if (arg == NULL)
967 	{
968 	    error = TRUE;
969 	    break;
970 	}
971 	if (*linep == '\'')
972 	    ++linep;
973 
974 	/*
975 	 * Store the argument.
976 	 */
977 	if (  STRCMP(key, "TERM") == 0
978 		|| STRCMP(key, "CTERM") == 0
979 		|| STRCMP(key, "GUI") == 0)
980 	{
981 	    attr = 0;
982 	    off = 0;
983 	    while (arg[off] != NUL)
984 	    {
985 		for (i = sizeof(hl_attr_table) / sizeof(int); --i >= 0; )
986 		{
987 		    len = (int)STRLEN(hl_name_table[i]);
988 		    if (STRNICMP(arg + off, hl_name_table[i], len) == 0)
989 		    {
990 			attr |= hl_attr_table[i];
991 			off += len;
992 			break;
993 		    }
994 		}
995 		if (i < 0)
996 		{
997 		    semsg(_("E418: Illegal value: %s"), arg);
998 		    error = TRUE;
999 		    break;
1000 		}
1001 		if (arg[off] == ',')		// another one follows
1002 		    ++off;
1003 	    }
1004 	    if (error)
1005 		break;
1006 	    if (*key == 'T')
1007 	    {
1008 		if (!init || !(HL_TABLE()[idx].sg_set & SG_TERM))
1009 		{
1010 		    if (!init)
1011 			HL_TABLE()[idx].sg_set |= SG_TERM;
1012 		    HL_TABLE()[idx].sg_term = attr;
1013 		}
1014 	    }
1015 	    else if (*key == 'C')
1016 	    {
1017 		if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM))
1018 		{
1019 		    if (!init)
1020 			HL_TABLE()[idx].sg_set |= SG_CTERM;
1021 		    HL_TABLE()[idx].sg_cterm = attr;
1022 		    HL_TABLE()[idx].sg_cterm_bold = FALSE;
1023 		}
1024 	    }
1025 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
1026 	    else
1027 	    {
1028 		if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
1029 		{
1030 		    if (!init)
1031 			HL_TABLE()[idx].sg_set |= SG_GUI;
1032 		    HL_TABLE()[idx].sg_gui = attr;
1033 		}
1034 	    }
1035 #endif
1036 	}
1037 	else if (STRCMP(key, "FONT") == 0)
1038 	{
1039 	    // in non-GUI fonts are simply ignored
1040 #ifdef FEAT_GUI
1041 	    if (HL_TABLE()[idx].sg_font_name != NULL
1042 			     && STRCMP(HL_TABLE()[idx].sg_font_name, arg) == 0)
1043 	    {
1044 		// Font name didn't change, ignore.
1045 	    }
1046 	    else if (!gui.shell_created)
1047 	    {
1048 		// GUI not started yet, always accept the name.
1049 		vim_free(HL_TABLE()[idx].sg_font_name);
1050 		HL_TABLE()[idx].sg_font_name = vim_strsave(arg);
1051 		did_change = TRUE;
1052 	    }
1053 	    else
1054 	    {
1055 		GuiFont temp_sg_font = HL_TABLE()[idx].sg_font;
1056 # ifdef FEAT_XFONTSET
1057 		GuiFontset temp_sg_fontset = HL_TABLE()[idx].sg_fontset;
1058 # endif
1059 		// First, save the current font/fontset.
1060 		// Then try to allocate the font/fontset.
1061 		// If the allocation fails, HL_TABLE()[idx].sg_font OR
1062 		// sg_fontset will be set to NOFONT or NOFONTSET respectively.
1063 
1064 		HL_TABLE()[idx].sg_font = NOFONT;
1065 # ifdef FEAT_XFONTSET
1066 		HL_TABLE()[idx].sg_fontset = NOFONTSET;
1067 # endif
1068 		hl_do_font(idx, arg, is_normal_group, is_menu_group,
1069 						     is_tooltip_group, FALSE);
1070 
1071 # ifdef FEAT_XFONTSET
1072 		if (HL_TABLE()[idx].sg_fontset != NOFONTSET)
1073 		{
1074 		    // New fontset was accepted. Free the old one, if there
1075 		    // was one.
1076 		    gui_mch_free_fontset(temp_sg_fontset);
1077 		    vim_free(HL_TABLE()[idx].sg_font_name);
1078 		    HL_TABLE()[idx].sg_font_name = vim_strsave(arg);
1079 		    did_change = TRUE;
1080 		}
1081 		else
1082 		    HL_TABLE()[idx].sg_fontset = temp_sg_fontset;
1083 # endif
1084 		if (HL_TABLE()[idx].sg_font != NOFONT)
1085 		{
1086 		    // New font was accepted. Free the old one, if there was
1087 		    // one.
1088 		    gui_mch_free_font(temp_sg_font);
1089 		    vim_free(HL_TABLE()[idx].sg_font_name);
1090 		    HL_TABLE()[idx].sg_font_name = vim_strsave(arg);
1091 		    did_change = TRUE;
1092 		}
1093 		else
1094 		    HL_TABLE()[idx].sg_font = temp_sg_font;
1095 	    }
1096 #endif
1097 	}
1098 	else if (STRCMP(key, "CTERMFG") == 0 || STRCMP(key, "CTERMBG") == 0)
1099 	{
1100 	  if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM))
1101 	  {
1102 	    if (!init)
1103 		HL_TABLE()[idx].sg_set |= SG_CTERM;
1104 
1105 	    // When setting the foreground color, and previously the "bold"
1106 	    // flag was set for a light color, reset it now
1107 	    if (key[5] == 'F' && HL_TABLE()[idx].sg_cterm_bold)
1108 	    {
1109 		HL_TABLE()[idx].sg_cterm &= ~HL_BOLD;
1110 		HL_TABLE()[idx].sg_cterm_bold = FALSE;
1111 	    }
1112 
1113 	    if (VIM_ISDIGIT(*arg))
1114 		color = atoi((char *)arg);
1115 	    else if (STRICMP(arg, "fg") == 0)
1116 	    {
1117 		if (cterm_normal_fg_color)
1118 		    color = cterm_normal_fg_color - 1;
1119 		else
1120 		{
1121 		    emsg(_("E419: FG color unknown"));
1122 		    error = TRUE;
1123 		    break;
1124 		}
1125 	    }
1126 	    else if (STRICMP(arg, "bg") == 0)
1127 	    {
1128 		if (cterm_normal_bg_color > 0)
1129 		    color = cterm_normal_bg_color - 1;
1130 		else
1131 		{
1132 		    emsg(_("E420: BG color unknown"));
1133 		    error = TRUE;
1134 		    break;
1135 		}
1136 	    }
1137 	    else
1138 	    {
1139 		int bold = MAYBE;
1140 
1141 		// reduce calls to STRICMP a bit, it can be slow
1142 		off = TOUPPER_ASC(*arg);
1143 		for (i = (sizeof(color_names) / sizeof(char *)); --i >= 0; )
1144 		    if (off == color_names[i][0]
1145 				 && STRICMP(arg + 1, color_names[i] + 1) == 0)
1146 			break;
1147 		if (i < 0)
1148 		{
1149 		    semsg(_("E421: Color name or number not recognized: %s"), key_start);
1150 		    error = TRUE;
1151 		    break;
1152 		}
1153 
1154 		color = lookup_color(i, key[5] == 'F', &bold);
1155 
1156 		// set/reset bold attribute to get light foreground
1157 		// colors (on some terminals, e.g. "linux")
1158 		if (bold == TRUE)
1159 		{
1160 		    HL_TABLE()[idx].sg_cterm |= HL_BOLD;
1161 		    HL_TABLE()[idx].sg_cterm_bold = TRUE;
1162 		}
1163 		else if (bold == FALSE)
1164 		    HL_TABLE()[idx].sg_cterm &= ~HL_BOLD;
1165 	    }
1166 
1167 	    // Add one to the argument, to avoid zero.  Zero is used for
1168 	    // "NONE", then "color" is -1.
1169 	    if (key[5] == 'F')
1170 	    {
1171 		HL_TABLE()[idx].sg_cterm_fg = color + 1;
1172 		if (is_normal_group)
1173 		{
1174 		    cterm_normal_fg_color = color + 1;
1175 		    cterm_normal_fg_bold = (HL_TABLE()[idx].sg_cterm & HL_BOLD);
1176 #ifdef FEAT_GUI
1177 		    // Don't do this if the GUI is used.
1178 		    if (!gui.in_use && !gui.starting)
1179 #endif
1180 		    {
1181 			must_redraw = CLEAR;
1182 			if (termcap_active && color >= 0)
1183 			    term_fg_color(color);
1184 		    }
1185 		}
1186 	    }
1187 	    else
1188 	    {
1189 		HL_TABLE()[idx].sg_cterm_bg = color + 1;
1190 		if (is_normal_group)
1191 		{
1192 		    cterm_normal_bg_color = color + 1;
1193 #ifdef FEAT_GUI
1194 		    // Don't mess with 'background' if the GUI is used.
1195 		    if (!gui.in_use && !gui.starting)
1196 #endif
1197 		    {
1198 			must_redraw = CLEAR;
1199 			if (color >= 0)
1200 			{
1201 			    int dark = -1;
1202 
1203 			    if (termcap_active)
1204 				term_bg_color(color);
1205 			    if (t_colors < 16)
1206 				dark = (color == 0 || color == 4);
1207 			    // Limit the heuristic to the standard 16 colors
1208 			    else if (color < 16)
1209 				dark = (color < 7 || color == 8);
1210 			    // Set the 'background' option if the value is
1211 			    // wrong.
1212 			    if (dark != -1
1213 				    && dark != (*p_bg == 'd')
1214 				    && !option_was_set((char_u *)"bg"))
1215 			    {
1216 				set_option_value((char_u *)"bg", 0L,
1217 				       (char_u *)(dark ? "dark" : "light"), 0);
1218 				reset_option_was_set((char_u *)"bg");
1219 			    }
1220 			}
1221 		    }
1222 		}
1223 	    }
1224 	  }
1225 	}
1226 	else if (STRCMP(key, "GUIFG") == 0)
1227 	{
1228 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
1229 	    char_u **namep = &HL_TABLE()[idx].sg_gui_fg_name;
1230 
1231 	    if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
1232 	    {
1233 		if (!init)
1234 		    HL_TABLE()[idx].sg_set |= SG_GUI;
1235 
1236 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
1237 		// In GUI guifg colors are only used when recognized
1238 		i = color_name2handle(arg);
1239 		if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !USE_24BIT)
1240 		{
1241 		    HL_TABLE()[idx].sg_gui_fg = i;
1242 # endif
1243 		    if (*namep == NULL || STRCMP(*namep, arg) != 0)
1244 		    {
1245 			vim_free(*namep);
1246 			if (STRCMP(arg, "NONE") != 0)
1247 			    *namep = vim_strsave(arg);
1248 			else
1249 			    *namep = NULL;
1250 			did_change = TRUE;
1251 		    }
1252 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
1253 #  ifdef FEAT_GUI_X11
1254 		    if (is_menu_group && gui.menu_fg_pixel != i)
1255 		    {
1256 			gui.menu_fg_pixel = i;
1257 			do_colors = TRUE;
1258 		    }
1259 		    if (is_scrollbar_group && gui.scroll_fg_pixel != i)
1260 		    {
1261 			gui.scroll_fg_pixel = i;
1262 			do_colors = TRUE;
1263 		    }
1264 #   ifdef FEAT_BEVAL_GUI
1265 		    if (is_tooltip_group && gui.tooltip_fg_pixel != i)
1266 		    {
1267 			gui.tooltip_fg_pixel = i;
1268 			do_colors = TRUE;
1269 		    }
1270 #   endif
1271 #  endif
1272 		}
1273 # endif
1274 	    }
1275 #endif
1276 	}
1277 	else if (STRCMP(key, "GUIBG") == 0)
1278 	{
1279 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
1280 	    char_u **namep = &HL_TABLE()[idx].sg_gui_bg_name;
1281 
1282 	    if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
1283 	    {
1284 		if (!init)
1285 		    HL_TABLE()[idx].sg_set |= SG_GUI;
1286 
1287 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
1288 		// In GUI guifg colors are only used when recognized
1289 		i = color_name2handle(arg);
1290 		if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !USE_24BIT)
1291 		{
1292 		    HL_TABLE()[idx].sg_gui_bg = i;
1293 # endif
1294 		    if (*namep == NULL || STRCMP(*namep, arg) != 0)
1295 		    {
1296 			vim_free(*namep);
1297 			if (STRCMP(arg, "NONE") != 0)
1298 			    *namep = vim_strsave(arg);
1299 			else
1300 			    *namep = NULL;
1301 			did_change = TRUE;
1302 		    }
1303 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
1304 #  ifdef FEAT_GUI_X11
1305 		    if (is_menu_group && gui.menu_bg_pixel != i)
1306 		    {
1307 			gui.menu_bg_pixel = i;
1308 			do_colors = TRUE;
1309 		    }
1310 		    if (is_scrollbar_group && gui.scroll_bg_pixel != i)
1311 		    {
1312 			gui.scroll_bg_pixel = i;
1313 			do_colors = TRUE;
1314 		    }
1315 #   ifdef FEAT_BEVAL_GUI
1316 		    if (is_tooltip_group && gui.tooltip_bg_pixel != i)
1317 		    {
1318 			gui.tooltip_bg_pixel = i;
1319 			do_colors = TRUE;
1320 		    }
1321 #   endif
1322 #  endif
1323 		}
1324 # endif
1325 	    }
1326 #endif
1327 	}
1328 	else if (STRCMP(key, "GUISP") == 0)
1329 	{
1330 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
1331 	    char_u **namep = &HL_TABLE()[idx].sg_gui_sp_name;
1332 
1333 	    if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
1334 	    {
1335 		if (!init)
1336 		    HL_TABLE()[idx].sg_set |= SG_GUI;
1337 
1338 # ifdef FEAT_GUI
1339 		i = color_name2handle(arg);
1340 		if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !gui.in_use)
1341 		{
1342 		    HL_TABLE()[idx].sg_gui_sp = i;
1343 # endif
1344 		    if (*namep == NULL || STRCMP(*namep, arg) != 0)
1345 		    {
1346 			vim_free(*namep);
1347 			if (STRCMP(arg, "NONE") != 0)
1348 			    *namep = vim_strsave(arg);
1349 			else
1350 			    *namep = NULL;
1351 			did_change = TRUE;
1352 		    }
1353 # ifdef FEAT_GUI
1354 		}
1355 # endif
1356 	    }
1357 #endif
1358 	}
1359 	else if (STRCMP(key, "START") == 0 || STRCMP(key, "STOP") == 0)
1360 	{
1361 	    char_u	buf[100];
1362 	    char_u	*tname;
1363 
1364 	    if (!init)
1365 		HL_TABLE()[idx].sg_set |= SG_TERM;
1366 
1367 	    /*
1368 	     * The "start" and "stop"  arguments can be a literal escape
1369 	     * sequence, or a comma separated list of terminal codes.
1370 	     */
1371 	    if (STRNCMP(arg, "t_", 2) == 0)
1372 	    {
1373 		off = 0;
1374 		buf[0] = 0;
1375 		while (arg[off] != NUL)
1376 		{
1377 		    // Isolate one termcap name
1378 		    for (len = 0; arg[off + len] &&
1379 						 arg[off + len] != ','; ++len)
1380 			;
1381 		    tname = vim_strnsave(arg + off, len);
1382 		    if (tname == NULL)		// out of memory
1383 		    {
1384 			error = TRUE;
1385 			break;
1386 		    }
1387 		    // lookup the escape sequence for the item
1388 		    p = get_term_code(tname);
1389 		    vim_free(tname);
1390 		    if (p == NULL)	    // ignore non-existing things
1391 			p = (char_u *)"";
1392 
1393 		    // Append it to the already found stuff
1394 		    if ((int)(STRLEN(buf) + STRLEN(p)) >= 99)
1395 		    {
1396 			semsg(_("E422: terminal code too long: %s"), arg);
1397 			error = TRUE;
1398 			break;
1399 		    }
1400 		    STRCAT(buf, p);
1401 
1402 		    // Advance to the next item
1403 		    off += len;
1404 		    if (arg[off] == ',')	    // another one follows
1405 			++off;
1406 		}
1407 	    }
1408 	    else
1409 	    {
1410 		/*
1411 		 * Copy characters from arg[] to buf[], translating <> codes.
1412 		 */
1413 		for (p = arg, off = 0; off < 100 - 6 && *p; )
1414 		{
1415 		    len = trans_special(&p, buf + off, FALSE, FALSE,
1416 								   TRUE, NULL);
1417 		    if (len > 0)	    // recognized special char
1418 			off += len;
1419 		    else		    // copy as normal char
1420 			buf[off++] = *p++;
1421 		}
1422 		buf[off] = NUL;
1423 	    }
1424 	    if (error)
1425 		break;
1426 
1427 	    if (STRCMP(buf, "NONE") == 0)	// resetting the value
1428 		p = NULL;
1429 	    else
1430 		p = vim_strsave(buf);
1431 	    if (key[2] == 'A')
1432 	    {
1433 		vim_free(HL_TABLE()[idx].sg_start);
1434 		HL_TABLE()[idx].sg_start = p;
1435 	    }
1436 	    else
1437 	    {
1438 		vim_free(HL_TABLE()[idx].sg_stop);
1439 		HL_TABLE()[idx].sg_stop = p;
1440 	    }
1441 	}
1442 	else
1443 	{
1444 	    semsg(_("E423: Illegal argument: %s"), key_start);
1445 	    error = TRUE;
1446 	    break;
1447 	}
1448 	HL_TABLE()[idx].sg_cleared = FALSE;
1449 
1450 	/*
1451 	 * When highlighting has been given for a group, don't link it.
1452 	 */
1453 	if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK))
1454 	    HL_TABLE()[idx].sg_link = 0;
1455 
1456 	/*
1457 	 * Continue with next argument.
1458 	 */
1459 	linep = skipwhite(linep);
1460       }
1461 
1462     /*
1463      * If there is an error, and it's a new entry, remove it from the table.
1464      */
1465     if (error && idx == highlight_ga.ga_len)
1466 	syn_unadd_group();
1467     else
1468     {
1469 	if (is_normal_group)
1470 	{
1471 	    HL_TABLE()[idx].sg_term_attr = 0;
1472 	    HL_TABLE()[idx].sg_cterm_attr = 0;
1473 #ifdef FEAT_GUI
1474 	    HL_TABLE()[idx].sg_gui_attr = 0;
1475 	    /*
1476 	     * Need to update all groups, because they might be using "bg"
1477 	     * and/or "fg", which have been changed now.
1478 	     */
1479 #endif
1480 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
1481 	    if (USE_24BIT)
1482 	    {
1483 		highlight_gui_started();
1484 		did_highlight_changed = TRUE;
1485 		redraw_all_later(NOT_VALID);
1486 	    }
1487 #endif
1488 	}
1489 #ifdef FEAT_TERMINAL
1490 	else if (is_terminal_group)
1491 	    set_terminal_default_colors(
1492 		    HL_TABLE()[idx].sg_cterm_fg, HL_TABLE()[idx].sg_cterm_bg);
1493 #endif
1494 #ifdef FEAT_GUI_X11
1495 # ifdef FEAT_MENU
1496 	else if (is_menu_group)
1497 	{
1498 	    if (gui.in_use && do_colors)
1499 		gui_mch_new_menu_colors();
1500 	}
1501 # endif
1502 	else if (is_scrollbar_group)
1503 	{
1504 	    if (gui.in_use && do_colors)
1505 		gui_new_scrollbar_colors();
1506 	    else
1507 		set_hl_attr(idx);
1508 	}
1509 # ifdef FEAT_BEVAL_GUI
1510 	else if (is_tooltip_group)
1511 	{
1512 	    if (gui.in_use && do_colors)
1513 		gui_mch_new_tooltip_colors();
1514 	}
1515 # endif
1516 #endif
1517 	else
1518 	    set_hl_attr(idx);
1519 #ifdef FEAT_EVAL
1520 	HL_TABLE()[idx].sg_script_ctx = current_sctx;
1521 	HL_TABLE()[idx].sg_script_ctx.sc_lnum += SOURCING_LNUM;
1522 #endif
1523     }
1524 
1525     vim_free(key);
1526     vim_free(arg);
1527 
1528     // Only call highlight_changed() once, after a sequence of highlight
1529     // commands, and only if an attribute actually changed.
1530     if ((did_change
1531 	   || memcmp(&HL_TABLE()[idx], &item_before, sizeof(item_before)) != 0)
1532 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
1533 	    && !did_highlight_changed
1534 #endif
1535        )
1536     {
1537 	// Do not trigger a redraw when highlighting is changed while
1538 	// redrawing.  This may happen when evaluating 'statusline' changes the
1539 	// StatusLine group.
1540 	if (!updating_screen)
1541 	    redraw_all_later(NOT_VALID);
1542 	need_highlight_changed = TRUE;
1543     }
1544 }
1545 
1546 #if defined(EXITFREE) || defined(PROTO)
1547     void
1548 free_highlight(void)
1549 {
1550     int	    i;
1551 
1552     for (i = 0; i < highlight_ga.ga_len; ++i)
1553     {
1554 	highlight_clear(i);
1555 	vim_free(HL_TABLE()[i].sg_name);
1556 	vim_free(HL_TABLE()[i].sg_name_u);
1557     }
1558     ga_clear(&highlight_ga);
1559 }
1560 #endif
1561 
1562 /*
1563  * Reset the cterm colors to what they were before Vim was started, if
1564  * possible.  Otherwise reset them to zero.
1565  */
1566     void
1567 restore_cterm_colors(void)
1568 {
1569 #if defined(MSWIN) && !defined(FEAT_GUI_MSWIN)
1570     // Since t_me has been set, this probably means that the user
1571     // wants to use this as default colors.  Need to reset default
1572     // background/foreground colors.
1573     mch_set_normal_colors();
1574 #else
1575 # ifdef VIMDLL
1576     if (!gui.in_use)
1577     {
1578 	mch_set_normal_colors();
1579 	return;
1580     }
1581 # endif
1582     cterm_normal_fg_color = 0;
1583     cterm_normal_fg_bold = 0;
1584     cterm_normal_bg_color = 0;
1585 # ifdef FEAT_TERMGUICOLORS
1586     cterm_normal_fg_gui_color = INVALCOLOR;
1587     cterm_normal_bg_gui_color = INVALCOLOR;
1588 # endif
1589 #endif
1590 }
1591 
1592 /*
1593  * Return TRUE if highlight group "idx" has any settings.
1594  * When "check_link" is TRUE also check for an existing link.
1595  */
1596     static int
1597 hl_has_settings(int idx, int check_link)
1598 {
1599     return (   HL_TABLE()[idx].sg_term_attr != 0
1600 	    || HL_TABLE()[idx].sg_cterm_attr != 0
1601 	    || HL_TABLE()[idx].sg_cterm_fg != 0
1602 	    || HL_TABLE()[idx].sg_cterm_bg != 0
1603 #ifdef FEAT_GUI
1604 	    || HL_TABLE()[idx].sg_gui_attr != 0
1605 	    || HL_TABLE()[idx].sg_gui_fg_name != NULL
1606 	    || HL_TABLE()[idx].sg_gui_bg_name != NULL
1607 	    || HL_TABLE()[idx].sg_gui_sp_name != NULL
1608 	    || HL_TABLE()[idx].sg_font_name != NULL
1609 #endif
1610 	    || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK)));
1611 }
1612 
1613 /*
1614  * Clear highlighting for one group.
1615  */
1616     static void
1617 highlight_clear(int idx)
1618 {
1619     HL_TABLE()[idx].sg_cleared = TRUE;
1620 
1621     HL_TABLE()[idx].sg_term = 0;
1622     VIM_CLEAR(HL_TABLE()[idx].sg_start);
1623     VIM_CLEAR(HL_TABLE()[idx].sg_stop);
1624     HL_TABLE()[idx].sg_term_attr = 0;
1625     HL_TABLE()[idx].sg_cterm = 0;
1626     HL_TABLE()[idx].sg_cterm_bold = FALSE;
1627     HL_TABLE()[idx].sg_cterm_fg = 0;
1628     HL_TABLE()[idx].sg_cterm_bg = 0;
1629     HL_TABLE()[idx].sg_cterm_attr = 0;
1630 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
1631     HL_TABLE()[idx].sg_gui = 0;
1632     VIM_CLEAR(HL_TABLE()[idx].sg_gui_fg_name);
1633     VIM_CLEAR(HL_TABLE()[idx].sg_gui_bg_name);
1634     VIM_CLEAR(HL_TABLE()[idx].sg_gui_sp_name);
1635 #endif
1636 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
1637     HL_TABLE()[idx].sg_gui_fg = INVALCOLOR;
1638     HL_TABLE()[idx].sg_gui_bg = INVALCOLOR;
1639 #endif
1640 #ifdef FEAT_GUI
1641     HL_TABLE()[idx].sg_gui_sp = INVALCOLOR;
1642     gui_mch_free_font(HL_TABLE()[idx].sg_font);
1643     HL_TABLE()[idx].sg_font = NOFONT;
1644 # ifdef FEAT_XFONTSET
1645     gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
1646     HL_TABLE()[idx].sg_fontset = NOFONTSET;
1647 # endif
1648     VIM_CLEAR(HL_TABLE()[idx].sg_font_name);
1649     HL_TABLE()[idx].sg_gui_attr = 0;
1650 #endif
1651 #ifdef FEAT_EVAL
1652     // Clear the script ID only when there is no link, since that is not
1653     // cleared.
1654     if (HL_TABLE()[idx].sg_link == 0)
1655     {
1656 	HL_TABLE()[idx].sg_script_ctx.sc_sid = 0;
1657 	HL_TABLE()[idx].sg_script_ctx.sc_lnum = 0;
1658     }
1659 #endif
1660 }
1661 
1662 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
1663 /*
1664  * Set the normal foreground and background colors according to the "Normal"
1665  * highlighting group.  For X11 also set "Menu", "Scrollbar", and
1666  * "Tooltip" colors.
1667  */
1668     void
1669 set_normal_colors(void)
1670 {
1671 # ifdef FEAT_GUI
1672 #  ifdef FEAT_TERMGUICOLORS
1673     if (gui.in_use)
1674 #  endif
1675     {
1676 	if (set_group_colors((char_u *)"Normal",
1677 				 &gui.norm_pixel, &gui.back_pixel,
1678 				 FALSE, TRUE, FALSE))
1679 	{
1680 	    gui_mch_new_colors();
1681 	    must_redraw = CLEAR;
1682 	}
1683 #  ifdef FEAT_GUI_X11
1684 	if (set_group_colors((char_u *)"Menu",
1685 			     &gui.menu_fg_pixel, &gui.menu_bg_pixel,
1686 			     TRUE, FALSE, FALSE))
1687 	{
1688 #   ifdef FEAT_MENU
1689 	    gui_mch_new_menu_colors();
1690 #   endif
1691 	    must_redraw = CLEAR;
1692 	}
1693 #   ifdef FEAT_BEVAL_GUI
1694 	if (set_group_colors((char_u *)"Tooltip",
1695 			     &gui.tooltip_fg_pixel, &gui.tooltip_bg_pixel,
1696 			     FALSE, FALSE, TRUE))
1697 	{
1698 #    ifdef FEAT_TOOLBAR
1699 	    gui_mch_new_tooltip_colors();
1700 #    endif
1701 	    must_redraw = CLEAR;
1702 	}
1703 #   endif
1704 	if (set_group_colors((char_u *)"Scrollbar",
1705 			&gui.scroll_fg_pixel, &gui.scroll_bg_pixel,
1706 			FALSE, FALSE, FALSE))
1707 	{
1708 	    gui_new_scrollbar_colors();
1709 	    must_redraw = CLEAR;
1710 	}
1711 #  endif
1712     }
1713 # endif
1714 # ifdef FEAT_TERMGUICOLORS
1715 #  ifdef FEAT_GUI
1716     else
1717 #  endif
1718     {
1719 	int		idx;
1720 
1721 	idx = syn_name2id((char_u *)"Normal") - 1;
1722 	if (idx >= 0)
1723 	{
1724 	    gui_do_one_color(idx, FALSE, FALSE);
1725 
1726 	    // If the normal fg or bg color changed a complete redraw is
1727 	    // required.
1728 	    if (cterm_normal_fg_gui_color != HL_TABLE()[idx].sg_gui_fg
1729 		    || cterm_normal_bg_gui_color != HL_TABLE()[idx].sg_gui_bg)
1730 	    {
1731 		// if the GUI color is INVALCOLOR then we use the default cterm
1732 		// color
1733 		cterm_normal_fg_gui_color = HL_TABLE()[idx].sg_gui_fg;
1734 		cterm_normal_bg_gui_color = HL_TABLE()[idx].sg_gui_bg;
1735 		must_redraw = CLEAR;
1736 	    }
1737 	}
1738     }
1739 # endif
1740 }
1741 #endif
1742 
1743 #if defined(FEAT_GUI) || defined(PROTO)
1744 /*
1745  * Set the colors for "Normal", "Menu", "Tooltip" or "Scrollbar".
1746  */
1747     static int
1748 set_group_colors(
1749     char_u	*name,
1750     guicolor_T	*fgp,
1751     guicolor_T	*bgp,
1752     int		do_menu,
1753     int		use_norm,
1754     int		do_tooltip)
1755 {
1756     int		idx;
1757 
1758     idx = syn_name2id(name) - 1;
1759     if (idx >= 0)
1760     {
1761 	gui_do_one_color(idx, do_menu, do_tooltip);
1762 
1763 	if (HL_TABLE()[idx].sg_gui_fg != INVALCOLOR)
1764 	    *fgp = HL_TABLE()[idx].sg_gui_fg;
1765 	else if (use_norm)
1766 	    *fgp = gui.def_norm_pixel;
1767 	if (HL_TABLE()[idx].sg_gui_bg != INVALCOLOR)
1768 	    *bgp = HL_TABLE()[idx].sg_gui_bg;
1769 	else if (use_norm)
1770 	    *bgp = gui.def_back_pixel;
1771 	return TRUE;
1772     }
1773     return FALSE;
1774 }
1775 
1776 /*
1777  * Get the font of the "Normal" group.
1778  * Returns "" when it's not found or not set.
1779  */
1780     char_u *
1781 hl_get_font_name(void)
1782 {
1783     int		id;
1784     char_u	*s;
1785 
1786     id = syn_name2id((char_u *)"Normal");
1787     if (id > 0)
1788     {
1789 	s = HL_TABLE()[id - 1].sg_font_name;
1790 	if (s != NULL)
1791 	    return s;
1792     }
1793     return (char_u *)"";
1794 }
1795 
1796 /*
1797  * Set font for "Normal" group.  Called by gui_mch_init_font() when a font has
1798  * actually chosen to be used.
1799  */
1800     void
1801 hl_set_font_name(char_u *font_name)
1802 {
1803     int	    id;
1804 
1805     id = syn_name2id((char_u *)"Normal");
1806     if (id > 0)
1807     {
1808 	vim_free(HL_TABLE()[id - 1].sg_font_name);
1809 	HL_TABLE()[id - 1].sg_font_name = vim_strsave(font_name);
1810     }
1811 }
1812 
1813 /*
1814  * Set background color for "Normal" group.  Called by gui_set_bg_color()
1815  * when the color is known.
1816  */
1817     void
1818 hl_set_bg_color_name(
1819     char_u  *name)	    // must have been allocated
1820 {
1821     int	    id;
1822 
1823     if (name != NULL)
1824     {
1825 	id = syn_name2id((char_u *)"Normal");
1826 	if (id > 0)
1827 	{
1828 	    vim_free(HL_TABLE()[id - 1].sg_gui_bg_name);
1829 	    HL_TABLE()[id - 1].sg_gui_bg_name = name;
1830 	}
1831     }
1832 }
1833 
1834 /*
1835  * Set foreground color for "Normal" group.  Called by gui_set_fg_color()
1836  * when the color is known.
1837  */
1838     void
1839 hl_set_fg_color_name(
1840     char_u  *name)	    // must have been allocated
1841 {
1842     int	    id;
1843 
1844     if (name != NULL)
1845     {
1846 	id = syn_name2id((char_u *)"Normal");
1847 	if (id > 0)
1848 	{
1849 	    vim_free(HL_TABLE()[id - 1].sg_gui_fg_name);
1850 	    HL_TABLE()[id - 1].sg_gui_fg_name = name;
1851 	}
1852     }
1853 }
1854 
1855 /*
1856  * Return the handle for a font name.
1857  * Returns NOFONT when failed.
1858  */
1859     static GuiFont
1860 font_name2handle(char_u *name)
1861 {
1862     if (STRCMP(name, "NONE") == 0)
1863 	return NOFONT;
1864 
1865     return gui_mch_get_font(name, TRUE);
1866 }
1867 
1868 # ifdef FEAT_XFONTSET
1869 /*
1870  * Return the handle for a fontset name.
1871  * Returns NOFONTSET when failed.
1872  */
1873     static GuiFontset
1874 fontset_name2handle(char_u *name, int fixed_width)
1875 {
1876     if (STRCMP(name, "NONE") == 0)
1877 	return NOFONTSET;
1878 
1879     return gui_mch_get_fontset(name, TRUE, fixed_width);
1880 }
1881 # endif
1882 
1883 /*
1884  * Get the font or fontset for one highlight group.
1885  */
1886     static void
1887 hl_do_font(
1888     int		idx,
1889     char_u	*arg,
1890     int		do_normal,		// set normal font
1891     int		do_menu UNUSED,		// set menu font
1892     int		do_tooltip UNUSED,	// set tooltip font
1893     int		free_font)		// free current font/fontset
1894 {
1895 # ifdef FEAT_XFONTSET
1896     // If 'guifontset' is not empty, first try using the name as a
1897     // fontset.  If that doesn't work, use it as a font name.
1898     if (*p_guifontset != NUL
1899 #  ifdef FONTSET_ALWAYS
1900 	|| do_menu
1901 #  endif
1902 #  ifdef FEAT_BEVAL_TIP
1903 	// In Athena & Motif, the Tooltip highlight group is always a fontset
1904 	|| do_tooltip
1905 #  endif
1906 	    )
1907     {
1908 	if (free_font)
1909 	    gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
1910 	HL_TABLE()[idx].sg_fontset = fontset_name2handle(arg, 0
1911 #  ifdef FONTSET_ALWAYS
1912 		|| do_menu
1913 #  endif
1914 #  ifdef FEAT_BEVAL_TIP
1915 		|| do_tooltip
1916 #  endif
1917 		);
1918     }
1919     if (HL_TABLE()[idx].sg_fontset != NOFONTSET)
1920     {
1921 	// If it worked and it's the Normal group, use it as the normal
1922 	// fontset.  Same for the Menu group.
1923 	if (do_normal)
1924 	    gui_init_font(arg, TRUE);
1925 #   if (defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_ATHENA)) && defined(FEAT_MENU)
1926 	if (do_menu)
1927 	{
1928 #    ifdef FONTSET_ALWAYS
1929 	    gui.menu_fontset = HL_TABLE()[idx].sg_fontset;
1930 #    else
1931 	    // YIKES!  This is a bug waiting to crash the program
1932 	    gui.menu_font = HL_TABLE()[idx].sg_fontset;
1933 #    endif
1934 	    gui_mch_new_menu_font();
1935 	}
1936 #    ifdef FEAT_BEVAL_GUI
1937 	if (do_tooltip)
1938 	{
1939 	    // The Athena widget set cannot currently handle switching between
1940 	    // displaying a single font and a fontset.
1941 	    // If the XtNinternational resource is set to True at widget
1942 	    // creation, then a fontset is always used, otherwise an
1943 	    // XFontStruct is used.
1944 	    gui.tooltip_fontset = (XFontSet)HL_TABLE()[idx].sg_fontset;
1945 	    gui_mch_new_tooltip_font();
1946 	}
1947 #    endif
1948 #   endif
1949     }
1950     else
1951 # endif
1952     {
1953 	if (free_font)
1954 	    gui_mch_free_font(HL_TABLE()[idx].sg_font);
1955 	HL_TABLE()[idx].sg_font = font_name2handle(arg);
1956 	// If it worked and it's the Normal group, use it as the
1957 	// normal font.  Same for the Menu group.
1958 	if (HL_TABLE()[idx].sg_font != NOFONT)
1959 	{
1960 	    if (do_normal)
1961 		gui_init_font(arg, FALSE);
1962 #ifndef FONTSET_ALWAYS
1963 # if (defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_ATHENA)) && defined(FEAT_MENU)
1964 	    if (do_menu)
1965 	    {
1966 		gui.menu_font = HL_TABLE()[idx].sg_font;
1967 		gui_mch_new_menu_font();
1968 	    }
1969 # endif
1970 #endif
1971 	}
1972     }
1973 }
1974 
1975 #endif // FEAT_GUI
1976 
1977 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
1978 /*
1979  * Return the handle for a color name.
1980  * Returns INVALCOLOR when failed.
1981  */
1982     guicolor_T
1983 color_name2handle(char_u *name)
1984 {
1985     if (STRCMP(name, "NONE") == 0)
1986 	return INVALCOLOR;
1987 
1988     if (STRICMP(name, "fg") == 0 || STRICMP(name, "foreground") == 0)
1989     {
1990 #if defined(FEAT_TERMGUICOLORS) && defined(FEAT_GUI)
1991 	if (gui.in_use)
1992 #endif
1993 #ifdef FEAT_GUI
1994 	    return gui.norm_pixel;
1995 #endif
1996 #ifdef FEAT_TERMGUICOLORS
1997 	if (cterm_normal_fg_gui_color != INVALCOLOR)
1998 	    return cterm_normal_fg_gui_color;
1999 	// Guess that the foreground is black or white.
2000 	return GUI_GET_COLOR((char_u *)(*p_bg == 'l' ? "black" : "white"));
2001 #endif
2002     }
2003     if (STRICMP(name, "bg") == 0 || STRICMP(name, "background") == 0)
2004     {
2005 #if defined(FEAT_TERMGUICOLORS) && defined(FEAT_GUI)
2006 	if (gui.in_use)
2007 #endif
2008 #ifdef FEAT_GUI
2009 	    return gui.back_pixel;
2010 #endif
2011 #ifdef FEAT_TERMGUICOLORS
2012 	if (cterm_normal_bg_gui_color != INVALCOLOR)
2013 	    return cterm_normal_bg_gui_color;
2014 	// Guess that the background is white or black.
2015 	return GUI_GET_COLOR((char_u *)(*p_bg == 'l' ? "white" : "black"));
2016 #endif
2017     }
2018 
2019     return GUI_GET_COLOR(name);
2020 }
2021 #endif
2022 
2023 /*
2024  * Table with the specifications for an attribute number.
2025  * Note that this table is used by ALL buffers.  This is required because the
2026  * GUI can redraw at any time for any buffer.
2027  */
2028 static garray_T	term_attr_table = {0, 0, 0, 0, NULL};
2029 
2030 #define TERM_ATTR_ENTRY(idx) ((attrentry_T *)term_attr_table.ga_data)[idx]
2031 
2032 static garray_T	cterm_attr_table = {0, 0, 0, 0, NULL};
2033 
2034 #define CTERM_ATTR_ENTRY(idx) ((attrentry_T *)cterm_attr_table.ga_data)[idx]
2035 
2036 #ifdef FEAT_GUI
2037 static garray_T	gui_attr_table = {0, 0, 0, 0, NULL};
2038 
2039 #define GUI_ATTR_ENTRY(idx) ((attrentry_T *)gui_attr_table.ga_data)[idx]
2040 #endif
2041 
2042 /*
2043  * Return the attr number for a set of colors and font.
2044  * Add a new entry to the term_attr_table, cterm_attr_table or gui_attr_table
2045  * if the combination is new.
2046  * Return 0 for error (no more room).
2047  */
2048     static int
2049 get_attr_entry(garray_T *table, attrentry_T *aep)
2050 {
2051     int		i;
2052     attrentry_T	*taep;
2053     static int	recursive = FALSE;
2054 
2055     /*
2056      * Init the table, in case it wasn't done yet.
2057      */
2058     table->ga_itemsize = sizeof(attrentry_T);
2059     table->ga_growsize = 7;
2060 
2061     /*
2062      * Try to find an entry with the same specifications.
2063      */
2064     for (i = 0; i < table->ga_len; ++i)
2065     {
2066 	taep = &(((attrentry_T *)table->ga_data)[i]);
2067 	if (	   aep->ae_attr == taep->ae_attr
2068 		&& (
2069 #ifdef FEAT_GUI
2070 		       (table == &gui_attr_table
2071 			&& (aep->ae_u.gui.fg_color == taep->ae_u.gui.fg_color
2072 			    && aep->ae_u.gui.bg_color
2073 						    == taep->ae_u.gui.bg_color
2074 			    && aep->ae_u.gui.sp_color
2075 						    == taep->ae_u.gui.sp_color
2076 			    && aep->ae_u.gui.font == taep->ae_u.gui.font
2077 #  ifdef FEAT_XFONTSET
2078 			    && aep->ae_u.gui.fontset == taep->ae_u.gui.fontset
2079 #  endif
2080 			    ))
2081 		    ||
2082 #endif
2083 		       (table == &term_attr_table
2084 			&& (aep->ae_u.term.start == NULL)
2085 					    == (taep->ae_u.term.start == NULL)
2086 			&& (aep->ae_u.term.start == NULL
2087 			    || STRCMP(aep->ae_u.term.start,
2088 						  taep->ae_u.term.start) == 0)
2089 			&& (aep->ae_u.term.stop == NULL)
2090 					     == (taep->ae_u.term.stop == NULL)
2091 			&& (aep->ae_u.term.stop == NULL
2092 			    || STRCMP(aep->ae_u.term.stop,
2093 						  taep->ae_u.term.stop) == 0))
2094 		    || (table == &cterm_attr_table
2095 			    && aep->ae_u.cterm.fg_color
2096 						  == taep->ae_u.cterm.fg_color
2097 			    && aep->ae_u.cterm.bg_color
2098 						  == taep->ae_u.cterm.bg_color
2099 #ifdef FEAT_TERMGUICOLORS
2100 			    && aep->ae_u.cterm.fg_rgb
2101 						    == taep->ae_u.cterm.fg_rgb
2102 			    && aep->ae_u.cterm.bg_rgb
2103 						    == taep->ae_u.cterm.bg_rgb
2104 #endif
2105 		       )))
2106 
2107 	return i + ATTR_OFF;
2108     }
2109 
2110     if (table->ga_len + ATTR_OFF > MAX_TYPENR)
2111     {
2112 	/*
2113 	 * Running out of attribute entries!  remove all attributes, and
2114 	 * compute new ones for all groups.
2115 	 * When called recursively, we are really out of numbers.
2116 	 */
2117 	if (recursive)
2118 	{
2119 	    emsg(_("E424: Too many different highlighting attributes in use"));
2120 	    return 0;
2121 	}
2122 	recursive = TRUE;
2123 
2124 	clear_hl_tables();
2125 
2126 	must_redraw = CLEAR;
2127 
2128 	for (i = 0; i < highlight_ga.ga_len; ++i)
2129 	    set_hl_attr(i);
2130 
2131 	recursive = FALSE;
2132     }
2133 
2134     /*
2135      * This is a new combination of colors and font, add an entry.
2136      */
2137     if (ga_grow(table, 1) == FAIL)
2138 	return 0;
2139 
2140     taep = &(((attrentry_T *)table->ga_data)[table->ga_len]);
2141     vim_memset(taep, 0, sizeof(attrentry_T));
2142     taep->ae_attr = aep->ae_attr;
2143 #ifdef FEAT_GUI
2144     if (table == &gui_attr_table)
2145     {
2146 	taep->ae_u.gui.fg_color = aep->ae_u.gui.fg_color;
2147 	taep->ae_u.gui.bg_color = aep->ae_u.gui.bg_color;
2148 	taep->ae_u.gui.sp_color = aep->ae_u.gui.sp_color;
2149 	taep->ae_u.gui.font = aep->ae_u.gui.font;
2150 # ifdef FEAT_XFONTSET
2151 	taep->ae_u.gui.fontset = aep->ae_u.gui.fontset;
2152 # endif
2153     }
2154 #endif
2155     if (table == &term_attr_table)
2156     {
2157 	if (aep->ae_u.term.start == NULL)
2158 	    taep->ae_u.term.start = NULL;
2159 	else
2160 	    taep->ae_u.term.start = vim_strsave(aep->ae_u.term.start);
2161 	if (aep->ae_u.term.stop == NULL)
2162 	    taep->ae_u.term.stop = NULL;
2163 	else
2164 	    taep->ae_u.term.stop = vim_strsave(aep->ae_u.term.stop);
2165     }
2166     else if (table == &cterm_attr_table)
2167     {
2168 	taep->ae_u.cterm.fg_color = aep->ae_u.cterm.fg_color;
2169 	taep->ae_u.cterm.bg_color = aep->ae_u.cterm.bg_color;
2170 #ifdef FEAT_TERMGUICOLORS
2171 	taep->ae_u.cterm.fg_rgb = aep->ae_u.cterm.fg_rgb;
2172 	taep->ae_u.cterm.bg_rgb = aep->ae_u.cterm.bg_rgb;
2173 #endif
2174     }
2175     ++table->ga_len;
2176     return (table->ga_len - 1 + ATTR_OFF);
2177 }
2178 
2179 #if defined(FEAT_TERMINAL) || defined(PROTO)
2180 /*
2181  * Get an attribute index for a cterm entry.
2182  * Uses an existing entry when possible or adds one when needed.
2183  */
2184     int
2185 get_cterm_attr_idx(int attr, int fg, int bg)
2186 {
2187     attrentry_T		at_en;
2188 
2189     vim_memset(&at_en, 0, sizeof(attrentry_T));
2190 #ifdef FEAT_TERMGUICOLORS
2191     at_en.ae_u.cterm.fg_rgb = INVALCOLOR;
2192     at_en.ae_u.cterm.bg_rgb = INVALCOLOR;
2193 #endif
2194     at_en.ae_attr = attr;
2195     at_en.ae_u.cterm.fg_color = fg;
2196     at_en.ae_u.cterm.bg_color = bg;
2197     return get_attr_entry(&cterm_attr_table, &at_en);
2198 }
2199 #endif
2200 
2201 #if (defined(FEAT_TERMINAL) && defined(FEAT_TERMGUICOLORS)) || defined(PROTO)
2202 /*
2203  * Get an attribute index for a 'termguicolors' entry.
2204  * Uses an existing entry when possible or adds one when needed.
2205  */
2206     int
2207 get_tgc_attr_idx(int attr, guicolor_T fg, guicolor_T bg)
2208 {
2209     attrentry_T		at_en;
2210 
2211     vim_memset(&at_en, 0, sizeof(attrentry_T));
2212     at_en.ae_attr = attr;
2213     if (fg == INVALCOLOR && bg == INVALCOLOR)
2214     {
2215 	// If both GUI colors are not set fall back to the cterm colors.  Helps
2216 	// if the GUI only has an attribute, such as undercurl.
2217 	at_en.ae_u.cterm.fg_rgb = CTERMCOLOR;
2218 	at_en.ae_u.cterm.bg_rgb = CTERMCOLOR;
2219     }
2220     else
2221     {
2222 	at_en.ae_u.cterm.fg_rgb = fg;
2223 	at_en.ae_u.cterm.bg_rgb = bg;
2224     }
2225     return get_attr_entry(&cterm_attr_table, &at_en);
2226 }
2227 #endif
2228 
2229 #if (defined(FEAT_TERMINAL) && defined(FEAT_GUI)) || defined(PROTO)
2230 /*
2231  * Get an attribute index for a cterm entry.
2232  * Uses an existing entry when possible or adds one when needed.
2233  */
2234     int
2235 get_gui_attr_idx(int attr, guicolor_T fg, guicolor_T bg)
2236 {
2237     attrentry_T		at_en;
2238 
2239     vim_memset(&at_en, 0, sizeof(attrentry_T));
2240     at_en.ae_attr = attr;
2241     at_en.ae_u.gui.fg_color = fg;
2242     at_en.ae_u.gui.bg_color = bg;
2243     return get_attr_entry(&gui_attr_table, &at_en);
2244 }
2245 #endif
2246 
2247 /*
2248  * Clear all highlight tables.
2249  */
2250     void
2251 clear_hl_tables(void)
2252 {
2253     int		i;
2254     attrentry_T	*taep;
2255 
2256 #ifdef FEAT_GUI
2257     ga_clear(&gui_attr_table);
2258 #endif
2259     for (i = 0; i < term_attr_table.ga_len; ++i)
2260     {
2261 	taep = &(((attrentry_T *)term_attr_table.ga_data)[i]);
2262 	vim_free(taep->ae_u.term.start);
2263 	vim_free(taep->ae_u.term.stop);
2264     }
2265     ga_clear(&term_attr_table);
2266     ga_clear(&cterm_attr_table);
2267 }
2268 
2269 /*
2270  * Combine special attributes (e.g., for spelling) with other attributes
2271  * (e.g., for syntax highlighting).
2272  * "prim_attr" overrules "char_attr".
2273  * This creates a new group when required.
2274  * Since we expect there to be few spelling mistakes we don't cache the
2275  * result.
2276  * Return the resulting attributes.
2277  */
2278     int
2279 hl_combine_attr(int char_attr, int prim_attr)
2280 {
2281     attrentry_T *char_aep = NULL;
2282     attrentry_T *spell_aep;
2283     attrentry_T new_en;
2284 
2285     if (char_attr == 0)
2286 	return prim_attr;
2287     if (char_attr <= HL_ALL && prim_attr <= HL_ALL)
2288 	return ATTR_COMBINE(char_attr, prim_attr);
2289 #ifdef FEAT_GUI
2290     if (gui.in_use)
2291     {
2292 	if (char_attr > HL_ALL)
2293 	    char_aep = syn_gui_attr2entry(char_attr);
2294 	if (char_aep != NULL)
2295 	    new_en = *char_aep;
2296 	else
2297 	{
2298 	    vim_memset(&new_en, 0, sizeof(new_en));
2299 	    new_en.ae_u.gui.fg_color = INVALCOLOR;
2300 	    new_en.ae_u.gui.bg_color = INVALCOLOR;
2301 	    new_en.ae_u.gui.sp_color = INVALCOLOR;
2302 	    if (char_attr <= HL_ALL)
2303 		new_en.ae_attr = char_attr;
2304 	}
2305 
2306 	if (prim_attr <= HL_ALL)
2307 	    new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, prim_attr);
2308 	else
2309 	{
2310 	    spell_aep = syn_gui_attr2entry(prim_attr);
2311 	    if (spell_aep != NULL)
2312 	    {
2313 		new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr,
2314 							   spell_aep->ae_attr);
2315 		if (spell_aep->ae_u.gui.fg_color != INVALCOLOR)
2316 		    new_en.ae_u.gui.fg_color = spell_aep->ae_u.gui.fg_color;
2317 		if (spell_aep->ae_u.gui.bg_color != INVALCOLOR)
2318 		    new_en.ae_u.gui.bg_color = spell_aep->ae_u.gui.bg_color;
2319 		if (spell_aep->ae_u.gui.sp_color != INVALCOLOR)
2320 		    new_en.ae_u.gui.sp_color = spell_aep->ae_u.gui.sp_color;
2321 		if (spell_aep->ae_u.gui.font != NOFONT)
2322 		    new_en.ae_u.gui.font = spell_aep->ae_u.gui.font;
2323 # ifdef FEAT_XFONTSET
2324 		if (spell_aep->ae_u.gui.fontset != NOFONTSET)
2325 		    new_en.ae_u.gui.fontset = spell_aep->ae_u.gui.fontset;
2326 # endif
2327 	    }
2328 	}
2329 	return get_attr_entry(&gui_attr_table, &new_en);
2330     }
2331 #endif
2332 
2333     if (IS_CTERM)
2334     {
2335 	if (char_attr > HL_ALL)
2336 	    char_aep = syn_cterm_attr2entry(char_attr);
2337 	if (char_aep != NULL)
2338 	    new_en = *char_aep;
2339 	else
2340 	{
2341 	    vim_memset(&new_en, 0, sizeof(new_en));
2342 #ifdef FEAT_TERMGUICOLORS
2343 	    new_en.ae_u.cterm.bg_rgb = INVALCOLOR;
2344 	    new_en.ae_u.cterm.fg_rgb = INVALCOLOR;
2345 #endif
2346 	    if (char_attr <= HL_ALL)
2347 		new_en.ae_attr = char_attr;
2348 	}
2349 
2350 	if (prim_attr <= HL_ALL)
2351 		new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, prim_attr);
2352 	else
2353 	{
2354 	    spell_aep = syn_cterm_attr2entry(prim_attr);
2355 	    if (spell_aep != NULL)
2356 	    {
2357 		new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr,
2358 							   spell_aep->ae_attr);
2359 		if (spell_aep->ae_u.cterm.fg_color > 0)
2360 		    new_en.ae_u.cterm.fg_color = spell_aep->ae_u.cterm.fg_color;
2361 		if (spell_aep->ae_u.cterm.bg_color > 0)
2362 		    new_en.ae_u.cterm.bg_color = spell_aep->ae_u.cterm.bg_color;
2363 #ifdef FEAT_TERMGUICOLORS
2364 		// If both fg and bg are not set fall back to cterm colors.
2365 		// Helps for SpellBad which uses undercurl in the GUI.
2366 		if (COLOR_INVALID(spell_aep->ae_u.cterm.fg_rgb)
2367 			&& COLOR_INVALID(spell_aep->ae_u.cterm.bg_rgb))
2368 		{
2369 		    if (spell_aep->ae_u.cterm.fg_color > 0)
2370 			new_en.ae_u.cterm.fg_rgb = CTERMCOLOR;
2371 		    if (spell_aep->ae_u.cterm.bg_color > 0)
2372 			new_en.ae_u.cterm.bg_rgb = CTERMCOLOR;
2373 		}
2374 		else
2375 		{
2376 		    if (spell_aep->ae_u.cterm.fg_rgb != INVALCOLOR)
2377 			new_en.ae_u.cterm.fg_rgb = spell_aep->ae_u.cterm.fg_rgb;
2378 		    if (spell_aep->ae_u.cterm.bg_rgb != INVALCOLOR)
2379 			new_en.ae_u.cterm.bg_rgb = spell_aep->ae_u.cterm.bg_rgb;
2380 		}
2381 #endif
2382 	    }
2383 	}
2384 	return get_attr_entry(&cterm_attr_table, &new_en);
2385     }
2386 
2387     if (char_attr > HL_ALL)
2388 	char_aep = syn_term_attr2entry(char_attr);
2389     if (char_aep != NULL)
2390 	new_en = *char_aep;
2391     else
2392     {
2393 	vim_memset(&new_en, 0, sizeof(new_en));
2394 	if (char_attr <= HL_ALL)
2395 	    new_en.ae_attr = char_attr;
2396     }
2397 
2398     if (prim_attr <= HL_ALL)
2399 	new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, prim_attr);
2400     else
2401     {
2402 	spell_aep = syn_term_attr2entry(prim_attr);
2403 	if (spell_aep != NULL)
2404 	{
2405 	    new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, spell_aep->ae_attr);
2406 	    if (spell_aep->ae_u.term.start != NULL)
2407 	    {
2408 		new_en.ae_u.term.start = spell_aep->ae_u.term.start;
2409 		new_en.ae_u.term.stop = spell_aep->ae_u.term.stop;
2410 	    }
2411 	}
2412     }
2413     return get_attr_entry(&term_attr_table, &new_en);
2414 }
2415 
2416 #ifdef FEAT_GUI
2417     attrentry_T *
2418 syn_gui_attr2entry(int attr)
2419 {
2420     attr -= ATTR_OFF;
2421     if (attr >= gui_attr_table.ga_len)	    // did ":syntax clear"
2422 	return NULL;
2423     return &(GUI_ATTR_ENTRY(attr));
2424 }
2425 #endif
2426 
2427 /*
2428  * Get the highlight attributes (HL_BOLD etc.) from an attribute nr.
2429  * Only to be used when "attr" > HL_ALL.
2430  */
2431     int
2432 syn_attr2attr(int attr)
2433 {
2434     attrentry_T	*aep;
2435 
2436 #ifdef FEAT_GUI
2437     if (gui.in_use)
2438 	aep = syn_gui_attr2entry(attr);
2439     else
2440 #endif
2441 	if (IS_CTERM)
2442 	    aep = syn_cterm_attr2entry(attr);
2443 	else
2444 	    aep = syn_term_attr2entry(attr);
2445 
2446     if (aep == NULL)	    // highlighting not set
2447 	return 0;
2448     return aep->ae_attr;
2449 }
2450 
2451 
2452     attrentry_T *
2453 syn_term_attr2entry(int attr)
2454 {
2455     attr -= ATTR_OFF;
2456     if (attr >= term_attr_table.ga_len)	    // did ":syntax clear"
2457 	return NULL;
2458     return &(TERM_ATTR_ENTRY(attr));
2459 }
2460 
2461     attrentry_T *
2462 syn_cterm_attr2entry(int attr)
2463 {
2464     attr -= ATTR_OFF;
2465     if (attr >= cterm_attr_table.ga_len)	// did ":syntax clear"
2466 	return NULL;
2467     return &(CTERM_ATTR_ENTRY(attr));
2468 }
2469 
2470 #define LIST_ATTR   1
2471 #define LIST_STRING 2
2472 #define LIST_INT    3
2473 
2474     static void
2475 highlight_list_one(int id)
2476 {
2477     hl_group_T	    *sgp;
2478     int		    didh = FALSE;
2479 
2480     sgp = &HL_TABLE()[id - 1];	    // index is ID minus one
2481 
2482     if (message_filtered(sgp->sg_name))
2483 	return;
2484 
2485     didh = highlight_list_arg(id, didh, LIST_ATTR,
2486 				    sgp->sg_term, NULL, "term");
2487     didh = highlight_list_arg(id, didh, LIST_STRING,
2488 				    0, sgp->sg_start, "start");
2489     didh = highlight_list_arg(id, didh, LIST_STRING,
2490 				    0, sgp->sg_stop, "stop");
2491 
2492     didh = highlight_list_arg(id, didh, LIST_ATTR,
2493 				    sgp->sg_cterm, NULL, "cterm");
2494     didh = highlight_list_arg(id, didh, LIST_INT,
2495 				    sgp->sg_cterm_fg, NULL, "ctermfg");
2496     didh = highlight_list_arg(id, didh, LIST_INT,
2497 				    sgp->sg_cterm_bg, NULL, "ctermbg");
2498 
2499 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
2500     didh = highlight_list_arg(id, didh, LIST_ATTR,
2501 				    sgp->sg_gui, NULL, "gui");
2502     didh = highlight_list_arg(id, didh, LIST_STRING,
2503 				    0, sgp->sg_gui_fg_name, "guifg");
2504     didh = highlight_list_arg(id, didh, LIST_STRING,
2505 				    0, sgp->sg_gui_bg_name, "guibg");
2506     didh = highlight_list_arg(id, didh, LIST_STRING,
2507 				    0, sgp->sg_gui_sp_name, "guisp");
2508 #endif
2509 #ifdef FEAT_GUI
2510     didh = highlight_list_arg(id, didh, LIST_STRING,
2511 				    0, sgp->sg_font_name, "font");
2512 #endif
2513 
2514     if (sgp->sg_link && !got_int)
2515     {
2516 	(void)syn_list_header(didh, 9999, id);
2517 	didh = TRUE;
2518 	msg_puts_attr("links to", HL_ATTR(HLF_D));
2519 	msg_putchar(' ');
2520 	msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name);
2521     }
2522 
2523     if (!didh)
2524 	highlight_list_arg(id, didh, LIST_STRING, 0, (char_u *)"cleared", "");
2525 #ifdef FEAT_EVAL
2526     if (p_verbose > 0)
2527 	last_set_msg(sgp->sg_script_ctx);
2528 #endif
2529 }
2530 
2531     static int
2532 highlight_list_arg(
2533     int		id,
2534     int		didh,
2535     int		type,
2536     int		iarg,
2537     char_u	*sarg,
2538     char	*name)
2539 {
2540     char_u	buf[100];
2541     char_u	*ts;
2542     int		i;
2543 
2544     if (got_int)
2545 	return FALSE;
2546     if (type == LIST_STRING ? (sarg != NULL) : (iarg != 0))
2547     {
2548 	ts = buf;
2549 	if (type == LIST_INT)
2550 	    sprintf((char *)buf, "%d", iarg - 1);
2551 	else if (type == LIST_STRING)
2552 	    ts = sarg;
2553 	else // type == LIST_ATTR
2554 	{
2555 	    buf[0] = NUL;
2556 	    for (i = 0; hl_attr_table[i] != 0; ++i)
2557 	    {
2558 		if (iarg & hl_attr_table[i])
2559 		{
2560 		    if (buf[0] != NUL)
2561 			vim_strcat(buf, (char_u *)",", 100);
2562 		    vim_strcat(buf, (char_u *)hl_name_table[i], 100);
2563 		    iarg &= ~hl_attr_table[i];	    // don't want "inverse"
2564 		}
2565 	    }
2566 	}
2567 
2568 	(void)syn_list_header(didh,
2569 			       (int)(vim_strsize(ts) + STRLEN(name) + 1), id);
2570 	didh = TRUE;
2571 	if (!got_int)
2572 	{
2573 	    if (*name != NUL)
2574 	    {
2575 		msg_puts_attr(name, HL_ATTR(HLF_D));
2576 		msg_puts_attr("=", HL_ATTR(HLF_D));
2577 	    }
2578 	    msg_outtrans(ts);
2579 	}
2580     }
2581     return didh;
2582 }
2583 
2584 #if (((defined(FEAT_EVAL) || defined(FEAT_PRINTER))) && defined(FEAT_SYN_HL)) || defined(PROTO)
2585 /*
2586  * Return "1" if highlight group "id" has attribute "flag".
2587  * Return NULL otherwise.
2588  */
2589     char_u *
2590 highlight_has_attr(
2591     int		id,
2592     int		flag,
2593     int		modec)	// 'g' for GUI, 'c' for cterm, 't' for term
2594 {
2595     int		attr;
2596 
2597     if (id <= 0 || id > highlight_ga.ga_len)
2598 	return NULL;
2599 
2600 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
2601     if (modec == 'g')
2602 	attr = HL_TABLE()[id - 1].sg_gui;
2603     else
2604 #endif
2605 	 if (modec == 'c')
2606 	attr = HL_TABLE()[id - 1].sg_cterm;
2607     else
2608 	attr = HL_TABLE()[id - 1].sg_term;
2609 
2610     if (attr & flag)
2611 	return (char_u *)"1";
2612     return NULL;
2613 }
2614 #endif
2615 
2616 #if (defined(FEAT_SYN_HL) && defined(FEAT_EVAL)) || defined(PROTO)
2617 /*
2618  * Return color name of highlight group "id".
2619  */
2620     char_u *
2621 highlight_color(
2622     int		id,
2623     char_u	*what,	// "font", "fg", "bg", "sp", "fg#", "bg#" or "sp#"
2624     int		modec)	// 'g' for GUI, 'c' for cterm, 't' for term
2625 {
2626     static char_u	name[20];
2627     int			n;
2628     int			fg = FALSE;
2629     int			sp = FALSE;
2630     int			font = FALSE;
2631 
2632     if (id <= 0 || id > highlight_ga.ga_len)
2633 	return NULL;
2634 
2635     if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'g')
2636 	fg = TRUE;
2637     else if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'o'
2638 	     && TOLOWER_ASC(what[2]) == 'n' && TOLOWER_ASC(what[3]) == 't')
2639 	font = TRUE;
2640     else if (TOLOWER_ASC(what[0]) == 's' && TOLOWER_ASC(what[1]) == 'p')
2641 	sp = TRUE;
2642     else if (!(TOLOWER_ASC(what[0]) == 'b' && TOLOWER_ASC(what[1]) == 'g'))
2643 	return NULL;
2644     if (modec == 'g')
2645     {
2646 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
2647 #  ifdef FEAT_GUI
2648 	// return font name
2649 	if (font)
2650 	    return HL_TABLE()[id - 1].sg_font_name;
2651 #  endif
2652 
2653 	// return #RRGGBB form (only possible when GUI is running)
2654 	if ((USE_24BIT) && what[2] == '#')
2655 	{
2656 	    guicolor_T		color;
2657 	    long_u		rgb;
2658 	    static char_u	buf[10];
2659 
2660 	    if (fg)
2661 		color = HL_TABLE()[id - 1].sg_gui_fg;
2662 	    else if (sp)
2663 #  ifdef FEAT_GUI
2664 		color = HL_TABLE()[id - 1].sg_gui_sp;
2665 #  else
2666 		color = INVALCOLOR;
2667 #  endif
2668 	    else
2669 		color = HL_TABLE()[id - 1].sg_gui_bg;
2670 	    if (color == INVALCOLOR)
2671 		return NULL;
2672 	    rgb = (long_u)GUI_MCH_GET_RGB(color);
2673 	    sprintf((char *)buf, "#%02x%02x%02x",
2674 				      (unsigned)(rgb >> 16),
2675 				      (unsigned)(rgb >> 8) & 255,
2676 				      (unsigned)rgb & 255);
2677 	    return buf;
2678 	}
2679 # endif
2680 	if (fg)
2681 	    return (HL_TABLE()[id - 1].sg_gui_fg_name);
2682 	if (sp)
2683 	    return (HL_TABLE()[id - 1].sg_gui_sp_name);
2684 	return (HL_TABLE()[id - 1].sg_gui_bg_name);
2685     }
2686     if (font || sp)
2687 	return NULL;
2688     if (modec == 'c')
2689     {
2690 	if (fg)
2691 	    n = HL_TABLE()[id - 1].sg_cterm_fg - 1;
2692 	else
2693 	    n = HL_TABLE()[id - 1].sg_cterm_bg - 1;
2694 	if (n < 0)
2695 	    return NULL;
2696 	sprintf((char *)name, "%d", n);
2697 	return name;
2698     }
2699     // term doesn't have color
2700     return NULL;
2701 }
2702 #endif
2703 
2704 #if (defined(FEAT_SYN_HL) \
2705 	    && (defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)) \
2706 	&& defined(FEAT_PRINTER)) || defined(PROTO)
2707 /*
2708  * Return color name of highlight group "id" as RGB value.
2709  */
2710     long_u
2711 highlight_gui_color_rgb(
2712     int		id,
2713     int		fg)	// TRUE = fg, FALSE = bg
2714 {
2715     guicolor_T	color;
2716 
2717     if (id <= 0 || id > highlight_ga.ga_len)
2718 	return 0L;
2719 
2720     if (fg)
2721 	color = HL_TABLE()[id - 1].sg_gui_fg;
2722     else
2723 	color = HL_TABLE()[id - 1].sg_gui_bg;
2724 
2725     if (color == INVALCOLOR)
2726 	return 0L;
2727 
2728     return GUI_MCH_GET_RGB(color);
2729 }
2730 #endif
2731 
2732 /*
2733  * Output the syntax list header.
2734  * Return TRUE when started a new line.
2735  */
2736     int
2737 syn_list_header(
2738     int	    did_header,		// did header already
2739     int	    outlen,		// length of string that comes
2740     int	    id)			// highlight group id
2741 {
2742     int	    endcol = 19;
2743     int	    newline = TRUE;
2744     int	    name_col = 0;
2745 
2746     if (!did_header)
2747     {
2748 	msg_putchar('\n');
2749 	if (got_int)
2750 	    return TRUE;
2751 	msg_outtrans(HL_TABLE()[id - 1].sg_name);
2752 	name_col = msg_col;
2753 	endcol = 15;
2754     }
2755     else if (msg_col + outlen + 1 >= Columns)
2756     {
2757 	msg_putchar('\n');
2758 	if (got_int)
2759 	    return TRUE;
2760     }
2761     else
2762     {
2763 	if (msg_col >= endcol)	// wrap around is like starting a new line
2764 	    newline = FALSE;
2765     }
2766 
2767     if (msg_col >= endcol)	// output at least one space
2768 	endcol = msg_col + 1;
2769     if (Columns <= endcol)	// avoid hang for tiny window
2770 	endcol = Columns - 1;
2771 
2772     msg_advance(endcol);
2773 
2774     // Show "xxx" with the attributes.
2775     if (!did_header)
2776     {
2777 	if (endcol == Columns - 1 && endcol <= name_col)
2778 	    msg_putchar(' ');
2779 	msg_puts_attr("xxx", syn_id2attr(id));
2780 	msg_putchar(' ');
2781     }
2782 
2783     return newline;
2784 }
2785 
2786 /*
2787  * Set the attribute numbers for a highlight group.
2788  * Called after one of the attributes has changed.
2789  */
2790     static void
2791 set_hl_attr(
2792     int		idx)	    // index in array
2793 {
2794     attrentry_T	    at_en;
2795     hl_group_T	    *sgp = HL_TABLE() + idx;
2796 
2797     // The "Normal" group doesn't need an attribute number
2798     if (sgp->sg_name_u != NULL && STRCMP(sgp->sg_name_u, "NORMAL") == 0)
2799 	return;
2800 
2801 #ifdef FEAT_GUI
2802     /*
2803      * For the GUI mode: If there are other than "normal" highlighting
2804      * attributes, need to allocate an attr number.
2805      */
2806     if (sgp->sg_gui_fg == INVALCOLOR
2807 	    && sgp->sg_gui_bg == INVALCOLOR
2808 	    && sgp->sg_gui_sp == INVALCOLOR
2809 	    && sgp->sg_font == NOFONT
2810 # ifdef FEAT_XFONTSET
2811 	    && sgp->sg_fontset == NOFONTSET
2812 # endif
2813 	    )
2814     {
2815 	sgp->sg_gui_attr = sgp->sg_gui;
2816     }
2817     else
2818     {
2819 	at_en.ae_attr = sgp->sg_gui;
2820 	at_en.ae_u.gui.fg_color = sgp->sg_gui_fg;
2821 	at_en.ae_u.gui.bg_color = sgp->sg_gui_bg;
2822 	at_en.ae_u.gui.sp_color = sgp->sg_gui_sp;
2823 	at_en.ae_u.gui.font = sgp->sg_font;
2824 # ifdef FEAT_XFONTSET
2825 	at_en.ae_u.gui.fontset = sgp->sg_fontset;
2826 # endif
2827 	sgp->sg_gui_attr = get_attr_entry(&gui_attr_table, &at_en);
2828     }
2829 #endif
2830     /*
2831      * For the term mode: If there are other than "normal" highlighting
2832      * attributes, need to allocate an attr number.
2833      */
2834     if (sgp->sg_start == NULL && sgp->sg_stop == NULL)
2835 	sgp->sg_term_attr = sgp->sg_term;
2836     else
2837     {
2838 	at_en.ae_attr = sgp->sg_term;
2839 	at_en.ae_u.term.start = sgp->sg_start;
2840 	at_en.ae_u.term.stop = sgp->sg_stop;
2841 	sgp->sg_term_attr = get_attr_entry(&term_attr_table, &at_en);
2842     }
2843 
2844     /*
2845      * For the color term mode: If there are other than "normal"
2846      * highlighting attributes, need to allocate an attr number.
2847      */
2848     if (sgp->sg_cterm_fg == 0 && sgp->sg_cterm_bg == 0
2849 # ifdef FEAT_TERMGUICOLORS
2850 	    && sgp->sg_gui_fg == INVALCOLOR
2851 	    && sgp->sg_gui_bg == INVALCOLOR
2852 # endif
2853 	    )
2854 	sgp->sg_cterm_attr = sgp->sg_cterm;
2855     else
2856     {
2857 	at_en.ae_attr = sgp->sg_cterm;
2858 	at_en.ae_u.cterm.fg_color = sgp->sg_cterm_fg;
2859 	at_en.ae_u.cterm.bg_color = sgp->sg_cterm_bg;
2860 # ifdef FEAT_TERMGUICOLORS
2861 #  ifdef MSWIN
2862 #   ifdef VIMDLL
2863 	// Only when not using the GUI.
2864 	if (!gui.in_use && !gui.starting)
2865 #   endif
2866 	{
2867 	    int id;
2868 	    guicolor_T fg, bg;
2869 
2870 	    id = syn_name2id((char_u *)"Normal");
2871 	    if (id > 0)
2872 	    {
2873 		syn_id2colors(id, &fg, &bg);
2874 		if (sgp->sg_gui_fg == INVALCOLOR)
2875 		    sgp->sg_gui_fg = fg;
2876 		if (sgp->sg_gui_bg == INVALCOLOR)
2877 		    sgp->sg_gui_bg = bg;
2878 	    }
2879 
2880 	}
2881 #  endif
2882 	at_en.ae_u.cterm.fg_rgb = GUI_MCH_GET_RGB2(sgp->sg_gui_fg);
2883 	at_en.ae_u.cterm.bg_rgb = GUI_MCH_GET_RGB2(sgp->sg_gui_bg);
2884 	if (at_en.ae_u.cterm.fg_rgb == INVALCOLOR
2885 		&& at_en.ae_u.cterm.bg_rgb == INVALCOLOR)
2886 	{
2887 	    // If both fg and bg are invalid fall back to the cterm colors.
2888 	    // Helps when the GUI only uses an attribute, e.g. undercurl.
2889 	    at_en.ae_u.cterm.fg_rgb = CTERMCOLOR;
2890 	    at_en.ae_u.cterm.bg_rgb = CTERMCOLOR;
2891 	}
2892 # endif
2893 	sgp->sg_cterm_attr = get_attr_entry(&cterm_attr_table, &at_en);
2894     }
2895 }
2896 
2897 /*
2898  * Lookup a highlight group name and return its ID.
2899  * If it is not found, 0 is returned.
2900  */
2901     int
2902 syn_name2id(char_u *name)
2903 {
2904     int		i;
2905     char_u	name_u[200];
2906 
2907     // Avoid using stricmp() too much, it's slow on some systems
2908     // Avoid alloc()/free(), these are slow too.  ID names over 200 chars
2909     // don't deserve to be found!
2910     vim_strncpy(name_u, name, 199);
2911     vim_strup(name_u);
2912     for (i = highlight_ga.ga_len; --i >= 0; )
2913 	if (HL_TABLE()[i].sg_name_u != NULL
2914 		&& STRCMP(name_u, HL_TABLE()[i].sg_name_u) == 0)
2915 	    break;
2916     return i + 1;
2917 }
2918 
2919 /*
2920  * Lookup a highlight group name and return its attributes.
2921  * Return zero if not found.
2922  */
2923     int
2924 syn_name2attr(char_u *name)
2925 {
2926     int id = syn_name2id(name);
2927 
2928     if (id != 0)
2929 	return syn_id2attr(id);
2930     return 0;
2931 }
2932 
2933 #if defined(FEAT_EVAL) || defined(PROTO)
2934 /*
2935  * Return TRUE if highlight group "name" exists.
2936  */
2937     int
2938 highlight_exists(char_u *name)
2939 {
2940     return (syn_name2id(name) > 0);
2941 }
2942 
2943 # if defined(FEAT_SEARCH_EXTRA) || defined(PROTO)
2944 /*
2945  * Return the name of highlight group "id".
2946  * When not a valid ID return an empty string.
2947  */
2948     char_u *
2949 syn_id2name(int id)
2950 {
2951     if (id <= 0 || id > highlight_ga.ga_len)
2952 	return (char_u *)"";
2953     return HL_TABLE()[id - 1].sg_name;
2954 }
2955 # endif
2956 #endif
2957 
2958 /*
2959  * Like syn_name2id(), but take a pointer + length argument.
2960  */
2961     int
2962 syn_namen2id(char_u *linep, int len)
2963 {
2964     char_u  *name;
2965     int	    id = 0;
2966 
2967     name = vim_strnsave(linep, len);
2968     if (name != NULL)
2969     {
2970 	id = syn_name2id(name);
2971 	vim_free(name);
2972     }
2973     return id;
2974 }
2975 
2976 /*
2977  * Find highlight group name in the table and return its ID.
2978  * The argument is a pointer to the name and the length of the name.
2979  * If it doesn't exist yet, a new entry is created.
2980  * Return 0 for failure.
2981  */
2982     int
2983 syn_check_group(char_u *pp, int len)
2984 {
2985     int	    id;
2986     char_u  *name;
2987 
2988     name = vim_strnsave(pp, len);
2989     if (name == NULL)
2990 	return 0;
2991 
2992     id = syn_name2id(name);
2993     if (id == 0)			// doesn't exist yet
2994 	id = syn_add_group(name);
2995     else
2996 	vim_free(name);
2997     return id;
2998 }
2999 
3000 /*
3001  * Add new highlight group and return its ID.
3002  * "name" must be an allocated string, it will be consumed.
3003  * Return 0 for failure.
3004  */
3005     static int
3006 syn_add_group(char_u *name)
3007 {
3008     char_u	*p;
3009     char_u	*name_up;
3010 
3011     // Check that the name is ASCII letters, digits and underscore.
3012     for (p = name; *p != NUL; ++p)
3013     {
3014 	if (!vim_isprintc(*p))
3015 	{
3016 	    emsg(_("E669: Unprintable character in group name"));
3017 	    vim_free(name);
3018 	    return 0;
3019 	}
3020 	else if (!ASCII_ISALNUM(*p) && *p != '_')
3021 	{
3022 	    // This is an error, but since there previously was no check only
3023 	    // give a warning.
3024 	    msg_source(HL_ATTR(HLF_W));
3025 	    msg(_("W18: Invalid character in group name"));
3026 	    break;
3027 	}
3028     }
3029 
3030     /*
3031      * First call for this growarray: init growing array.
3032      */
3033     if (highlight_ga.ga_data == NULL)
3034     {
3035 	highlight_ga.ga_itemsize = sizeof(hl_group_T);
3036 	highlight_ga.ga_growsize = 10;
3037     }
3038 
3039     if (highlight_ga.ga_len >= MAX_HL_ID)
3040     {
3041 	emsg(_("E849: Too many highlight and syntax groups"));
3042 	vim_free(name);
3043 	return 0;
3044     }
3045 
3046     /*
3047      * Make room for at least one other syntax_highlight entry.
3048      */
3049     if (ga_grow(&highlight_ga, 1) == FAIL)
3050     {
3051 	vim_free(name);
3052 	return 0;
3053     }
3054 
3055     name_up = vim_strsave_up(name);
3056     if (name_up == NULL)
3057     {
3058 	vim_free(name);
3059 	return 0;
3060     }
3061 
3062     vim_memset(&(HL_TABLE()[highlight_ga.ga_len]), 0, sizeof(hl_group_T));
3063     HL_TABLE()[highlight_ga.ga_len].sg_name = name;
3064     HL_TABLE()[highlight_ga.ga_len].sg_name_u = name_up;
3065 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3066     HL_TABLE()[highlight_ga.ga_len].sg_gui_bg = INVALCOLOR;
3067     HL_TABLE()[highlight_ga.ga_len].sg_gui_fg = INVALCOLOR;
3068 # ifdef FEAT_GUI
3069     HL_TABLE()[highlight_ga.ga_len].sg_gui_sp = INVALCOLOR;
3070 # endif
3071 #endif
3072     ++highlight_ga.ga_len;
3073 
3074     return highlight_ga.ga_len;		    // ID is index plus one
3075 }
3076 
3077 /*
3078  * When, just after calling syn_add_group(), an error is discovered, this
3079  * function deletes the new name.
3080  */
3081     static void
3082 syn_unadd_group(void)
3083 {
3084     --highlight_ga.ga_len;
3085     vim_free(HL_TABLE()[highlight_ga.ga_len].sg_name);
3086     vim_free(HL_TABLE()[highlight_ga.ga_len].sg_name_u);
3087 }
3088 
3089 /*
3090  * Translate a group ID to highlight attributes.
3091  */
3092     int
3093 syn_id2attr(int hl_id)
3094 {
3095     int		attr;
3096     hl_group_T	*sgp;
3097 
3098     hl_id = syn_get_final_id(hl_id);
3099     sgp = &HL_TABLE()[hl_id - 1];	    // index is ID minus one
3100 
3101 #ifdef FEAT_GUI
3102     /*
3103      * Only use GUI attr when the GUI is being used.
3104      */
3105     if (gui.in_use)
3106 	attr = sgp->sg_gui_attr;
3107     else
3108 #endif
3109 	if (IS_CTERM)
3110 	    attr = sgp->sg_cterm_attr;
3111 	else
3112 	    attr = sgp->sg_term_attr;
3113 
3114     return attr;
3115 }
3116 
3117 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
3118 /*
3119  * Get the GUI colors and attributes for a group ID.
3120  * NOTE: the colors will be INVALCOLOR when not set, the color otherwise.
3121  */
3122     int
3123 syn_id2colors(int hl_id, guicolor_T *fgp, guicolor_T *bgp)
3124 {
3125     hl_group_T	*sgp;
3126 
3127     hl_id = syn_get_final_id(hl_id);
3128     sgp = &HL_TABLE()[hl_id - 1];	    // index is ID minus one
3129 
3130     *fgp = sgp->sg_gui_fg;
3131     *bgp = sgp->sg_gui_bg;
3132     return sgp->sg_gui;
3133 }
3134 #endif
3135 
3136 #if (defined(MSWIN) \
3137 	&& (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL)) \
3138 	&& defined(FEAT_TERMGUICOLORS)) || defined(PROTO)
3139     void
3140 syn_id2cterm_bg(int hl_id, int *fgp, int *bgp)
3141 {
3142     hl_group_T	*sgp;
3143 
3144     hl_id = syn_get_final_id(hl_id);
3145     sgp = &HL_TABLE()[hl_id - 1];	    // index is ID minus one
3146     *fgp = sgp->sg_cterm_fg - 1;
3147     *bgp = sgp->sg_cterm_bg - 1;
3148 }
3149 #endif
3150 
3151 /*
3152  * Translate a group ID to the final group ID (following links).
3153  */
3154     int
3155 syn_get_final_id(int hl_id)
3156 {
3157     int		count;
3158     hl_group_T	*sgp;
3159 
3160     if (hl_id > highlight_ga.ga_len || hl_id < 1)
3161 	return 0;			// Can be called from eval!!
3162 
3163     /*
3164      * Follow links until there is no more.
3165      * Look out for loops!  Break after 100 links.
3166      */
3167     for (count = 100; --count >= 0; )
3168     {
3169 	sgp = &HL_TABLE()[hl_id - 1];	    // index is ID minus one
3170 	if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len)
3171 	    break;
3172 	hl_id = sgp->sg_link;
3173     }
3174 
3175     return hl_id;
3176 }
3177 
3178 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
3179 /*
3180  * Call this function just after the GUI has started.
3181  * Also called when 'termguicolors' was set, gui.in_use will be FALSE then.
3182  * It finds the font and color handles for the highlighting groups.
3183  */
3184     void
3185 highlight_gui_started(void)
3186 {
3187     int	    idx;
3188 
3189     // First get the colors from the "Normal" and "Menu" group, if set
3190     if (USE_24BIT)
3191 	set_normal_colors();
3192 
3193     for (idx = 0; idx < highlight_ga.ga_len; ++idx)
3194 	gui_do_one_color(idx, FALSE, FALSE);
3195 
3196     highlight_changed();
3197 }
3198 
3199     static void
3200 gui_do_one_color(
3201     int		idx,
3202     int		do_menu UNUSED,	   // TRUE: might set the menu font
3203     int		do_tooltip UNUSED) // TRUE: might set the tooltip font
3204 {
3205     int		didit = FALSE;
3206 
3207 # ifdef FEAT_GUI
3208 #  ifdef FEAT_TERMGUICOLORS
3209     if (gui.in_use)
3210 #  endif
3211 	if (HL_TABLE()[idx].sg_font_name != NULL)
3212 	{
3213 	    hl_do_font(idx, HL_TABLE()[idx].sg_font_name, FALSE, do_menu,
3214 							    do_tooltip, TRUE);
3215 	    didit = TRUE;
3216 	}
3217 # endif
3218     if (HL_TABLE()[idx].sg_gui_fg_name != NULL)
3219     {
3220 	HL_TABLE()[idx].sg_gui_fg =
3221 			    color_name2handle(HL_TABLE()[idx].sg_gui_fg_name);
3222 	didit = TRUE;
3223     }
3224     if (HL_TABLE()[idx].sg_gui_bg_name != NULL)
3225     {
3226 	HL_TABLE()[idx].sg_gui_bg =
3227 			    color_name2handle(HL_TABLE()[idx].sg_gui_bg_name);
3228 	didit = TRUE;
3229     }
3230 # ifdef FEAT_GUI
3231     if (HL_TABLE()[idx].sg_gui_sp_name != NULL)
3232     {
3233 	HL_TABLE()[idx].sg_gui_sp =
3234 			    color_name2handle(HL_TABLE()[idx].sg_gui_sp_name);
3235 	didit = TRUE;
3236     }
3237 # endif
3238     if (didit)	// need to get a new attr number
3239 	set_hl_attr(idx);
3240 }
3241 #endif
3242 
3243 #if defined(USER_HIGHLIGHT) && defined(FEAT_STL_OPT)
3244 /*
3245  * Apply difference between User[1-9] and HLF_S to HLF_SNC, HLF_ST or HLF_STNC.
3246  */
3247     static void
3248 combine_stl_hlt(
3249 	int id,
3250 	int id_S,
3251 	int id_alt,
3252 	int hlcnt,
3253 	int i,
3254 	int hlf,
3255 	int *table)
3256 {
3257     hl_group_T *hlt = HL_TABLE();
3258 
3259     if (id_alt == 0)
3260     {
3261 	vim_memset(&hlt[hlcnt + i], 0, sizeof(hl_group_T));
3262 	hlt[hlcnt + i].sg_term = highlight_attr[hlf];
3263 	hlt[hlcnt + i].sg_cterm = highlight_attr[hlf];
3264 #  if defined(FEAT_GUI) || defined(FEAT_EVAL)
3265 	hlt[hlcnt + i].sg_gui = highlight_attr[hlf];
3266 #  endif
3267     }
3268     else
3269 	mch_memmove(&hlt[hlcnt + i],
3270 		    &hlt[id_alt - 1],
3271 		    sizeof(hl_group_T));
3272     hlt[hlcnt + i].sg_link = 0;
3273 
3274     hlt[hlcnt + i].sg_term ^=
3275 	hlt[id - 1].sg_term ^ hlt[id_S - 1].sg_term;
3276     if (hlt[id - 1].sg_start != hlt[id_S - 1].sg_start)
3277 	hlt[hlcnt + i].sg_start = hlt[id - 1].sg_start;
3278     if (hlt[id - 1].sg_stop != hlt[id_S - 1].sg_stop)
3279 	hlt[hlcnt + i].sg_stop = hlt[id - 1].sg_stop;
3280     hlt[hlcnt + i].sg_cterm ^=
3281 	hlt[id - 1].sg_cterm ^ hlt[id_S - 1].sg_cterm;
3282     if (hlt[id - 1].sg_cterm_fg != hlt[id_S - 1].sg_cterm_fg)
3283 	hlt[hlcnt + i].sg_cterm_fg = hlt[id - 1].sg_cterm_fg;
3284     if (hlt[id - 1].sg_cterm_bg != hlt[id_S - 1].sg_cterm_bg)
3285 	hlt[hlcnt + i].sg_cterm_bg = hlt[id - 1].sg_cterm_bg;
3286 #  if defined(FEAT_GUI) || defined(FEAT_EVAL)
3287     hlt[hlcnt + i].sg_gui ^=
3288 	hlt[id - 1].sg_gui ^ hlt[id_S - 1].sg_gui;
3289 #  endif
3290 #  ifdef FEAT_GUI
3291     if (hlt[id - 1].sg_gui_fg != hlt[id_S - 1].sg_gui_fg)
3292 	hlt[hlcnt + i].sg_gui_fg = hlt[id - 1].sg_gui_fg;
3293     if (hlt[id - 1].sg_gui_bg != hlt[id_S - 1].sg_gui_bg)
3294 	hlt[hlcnt + i].sg_gui_bg = hlt[id - 1].sg_gui_bg;
3295     if (hlt[id - 1].sg_gui_sp != hlt[id_S - 1].sg_gui_sp)
3296 	hlt[hlcnt + i].sg_gui_sp = hlt[id - 1].sg_gui_sp;
3297     if (hlt[id - 1].sg_font != hlt[id_S - 1].sg_font)
3298 	hlt[hlcnt + i].sg_font = hlt[id - 1].sg_font;
3299 #   ifdef FEAT_XFONTSET
3300     if (hlt[id - 1].sg_fontset != hlt[id_S - 1].sg_fontset)
3301 	hlt[hlcnt + i].sg_fontset = hlt[id - 1].sg_fontset;
3302 #   endif
3303 #  endif
3304     highlight_ga.ga_len = hlcnt + i + 1;
3305     set_hl_attr(hlcnt + i);	// At long last we can apply
3306     table[i] = syn_id2attr(hlcnt + i + 1);
3307 }
3308 #endif
3309 
3310 /*
3311  * Translate the 'highlight' option into attributes in highlight_attr[] and
3312  * set up the user highlights User1..9.  If FEAT_STL_OPT is in use, a set of
3313  * corresponding highlights to use on top of HLF_SNC is computed.
3314  * Called only when the 'highlight' option has been changed and upon first
3315  * screen redraw after any :highlight command.
3316  * Return FAIL when an invalid flag is found in 'highlight'.  OK otherwise.
3317  */
3318     int
3319 highlight_changed(void)
3320 {
3321     int		hlf;
3322     int		i;
3323     char_u	*p;
3324     int		attr;
3325     char_u	*end;
3326     int		id;
3327 #ifdef USER_HIGHLIGHT
3328     char_u      userhl[30];  // use 30 to avoid compiler warning
3329 # ifdef FEAT_STL_OPT
3330     int		id_S = -1;
3331     int		id_SNC = 0;
3332 #  ifdef FEAT_TERMINAL
3333     int		id_ST = 0;
3334     int		id_STNC = 0;
3335 #  endif
3336     int		hlcnt;
3337 # endif
3338 #endif
3339     static int	hl_flags[HLF_COUNT] = HL_FLAGS;
3340 
3341     need_highlight_changed = FALSE;
3342 
3343     /*
3344      * Clear all attributes.
3345      */
3346     for (hlf = 0; hlf < (int)HLF_COUNT; ++hlf)
3347 	highlight_attr[hlf] = 0;
3348 
3349     /*
3350      * First set all attributes to their default value.
3351      * Then use the attributes from the 'highlight' option.
3352      */
3353     for (i = 0; i < 2; ++i)
3354     {
3355 	if (i)
3356 	    p = p_hl;
3357 	else
3358 	    p = get_highlight_default();
3359 	if (p == NULL)	    // just in case
3360 	    continue;
3361 
3362 	while (*p)
3363 	{
3364 	    for (hlf = 0; hlf < (int)HLF_COUNT; ++hlf)
3365 		if (hl_flags[hlf] == *p)
3366 		    break;
3367 	    ++p;
3368 	    if (hlf == (int)HLF_COUNT || *p == NUL)
3369 		return FAIL;
3370 
3371 	    /*
3372 	     * Allow several hl_flags to be combined, like "bu" for
3373 	     * bold-underlined.
3374 	     */
3375 	    attr = 0;
3376 	    for ( ; *p && *p != ','; ++p)	    // parse up to comma
3377 	    {
3378 		if (VIM_ISWHITE(*p))		    // ignore white space
3379 		    continue;
3380 
3381 		if (attr > HL_ALL)  // Combination with ':' is not allowed.
3382 		    return FAIL;
3383 
3384 		switch (*p)
3385 		{
3386 		    case 'b':	attr |= HL_BOLD;
3387 				break;
3388 		    case 'i':	attr |= HL_ITALIC;
3389 				break;
3390 		    case '-':
3391 		    case 'n':			    // no highlighting
3392 				break;
3393 		    case 'r':	attr |= HL_INVERSE;
3394 				break;
3395 		    case 's':	attr |= HL_STANDOUT;
3396 				break;
3397 		    case 'u':	attr |= HL_UNDERLINE;
3398 				break;
3399 		    case 'c':	attr |= HL_UNDERCURL;
3400 				break;
3401 		    case 't':	attr |= HL_STRIKETHROUGH;
3402 				break;
3403 		    case ':':	++p;		    // highlight group name
3404 				if (attr || *p == NUL)	 // no combinations
3405 				    return FAIL;
3406 				end = vim_strchr(p, ',');
3407 				if (end == NULL)
3408 				    end = p + STRLEN(p);
3409 				id = syn_check_group(p, (int)(end - p));
3410 				if (id == 0)
3411 				    return FAIL;
3412 				attr = syn_id2attr(id);
3413 				p = end - 1;
3414 #if defined(FEAT_STL_OPT) && defined(USER_HIGHLIGHT)
3415 				if (hlf == (int)HLF_SNC)
3416 				    id_SNC = syn_get_final_id(id);
3417 # ifdef FEAT_TERMINAL
3418 				else if (hlf == (int)HLF_ST)
3419 				    id_ST = syn_get_final_id(id);
3420 				else if (hlf == (int)HLF_STNC)
3421 				    id_STNC = syn_get_final_id(id);
3422 # endif
3423 				else if (hlf == (int)HLF_S)
3424 				    id_S = syn_get_final_id(id);
3425 #endif
3426 				break;
3427 		    default:	return FAIL;
3428 		}
3429 	    }
3430 	    highlight_attr[hlf] = attr;
3431 
3432 	    p = skip_to_option_part(p);	    // skip comma and spaces
3433 	}
3434     }
3435 
3436 #ifdef USER_HIGHLIGHT
3437     /*
3438      * Setup the user highlights
3439      *
3440      * Temporarily utilize 28 more hl entries:
3441      * 9 for User1-User9 combined with StatusLineNC
3442      * 9 for User1-User9 combined with StatusLineTerm
3443      * 9 for User1-User9 combined with StatusLineTermNC
3444      * 1 for StatusLine default
3445      * Have to be in there simultaneously in case of table overflows in
3446      * get_attr_entry()
3447      */
3448 # ifdef FEAT_STL_OPT
3449     if (ga_grow(&highlight_ga, 28) == FAIL)
3450 	return FAIL;
3451     hlcnt = highlight_ga.ga_len;
3452     if (id_S == -1)
3453     {
3454 	// Make sure id_S is always valid to simplify code below. Use the last
3455 	// entry.
3456 	vim_memset(&HL_TABLE()[hlcnt + 27], 0, sizeof(hl_group_T));
3457 	HL_TABLE()[hlcnt + 18].sg_term = highlight_attr[HLF_S];
3458 	id_S = hlcnt + 19;
3459     }
3460 # endif
3461     for (i = 0; i < 9; i++)
3462     {
3463 	sprintf((char *)userhl, "User%d", i + 1);
3464 	id = syn_name2id(userhl);
3465 	if (id == 0)
3466 	{
3467 	    highlight_user[i] = 0;
3468 # ifdef FEAT_STL_OPT
3469 	    highlight_stlnc[i] = 0;
3470 #  ifdef FEAT_TERMINAL
3471 	    highlight_stlterm[i] = 0;
3472 	    highlight_stltermnc[i] = 0;
3473 #  endif
3474 # endif
3475 	}
3476 	else
3477 	{
3478 	    highlight_user[i] = syn_id2attr(id);
3479 # ifdef FEAT_STL_OPT
3480 	    combine_stl_hlt(id, id_S, id_SNC, hlcnt, i,
3481 						     HLF_SNC, highlight_stlnc);
3482 #  ifdef FEAT_TERMINAL
3483 	    combine_stl_hlt(id, id_S, id_ST, hlcnt + 9, i,
3484 						    HLF_ST, highlight_stlterm);
3485 	    combine_stl_hlt(id, id_S, id_STNC, hlcnt + 18, i,
3486 						HLF_STNC, highlight_stltermnc);
3487 #  endif
3488 # endif
3489 	}
3490     }
3491 # ifdef FEAT_STL_OPT
3492     highlight_ga.ga_len = hlcnt;
3493 # endif
3494 
3495 #endif // USER_HIGHLIGHT
3496 
3497     return OK;
3498 }
3499 
3500 static void highlight_list(void);
3501 static void highlight_list_two(int cnt, int attr);
3502 
3503 /*
3504  * Handle command line completion for :highlight command.
3505  */
3506     void
3507 set_context_in_highlight_cmd(expand_T *xp, char_u *arg)
3508 {
3509     char_u	*p;
3510 
3511     // Default: expand group names
3512     xp->xp_context = EXPAND_HIGHLIGHT;
3513     xp->xp_pattern = arg;
3514     include_link = 2;
3515     include_default = 1;
3516 
3517     // (part of) subcommand already typed
3518     if (*arg != NUL)
3519     {
3520 	p = skiptowhite(arg);
3521 	if (*p != NUL)			// past "default" or group name
3522 	{
3523 	    include_default = 0;
3524 	    if (STRNCMP("default", arg, p - arg) == 0)
3525 	    {
3526 		arg = skipwhite(p);
3527 		xp->xp_pattern = arg;
3528 		p = skiptowhite(arg);
3529 	    }
3530 	    if (*p != NUL)			// past group name
3531 	    {
3532 		include_link = 0;
3533 		if (arg[1] == 'i' && arg[0] == 'N')
3534 		    highlight_list();
3535 		if (STRNCMP("link", arg, p - arg) == 0
3536 			|| STRNCMP("clear", arg, p - arg) == 0)
3537 		{
3538 		    xp->xp_pattern = skipwhite(p);
3539 		    p = skiptowhite(xp->xp_pattern);
3540 		    if (*p != NUL)		// past first group name
3541 		    {
3542 			xp->xp_pattern = skipwhite(p);
3543 			p = skiptowhite(xp->xp_pattern);
3544 		    }
3545 		}
3546 		if (*p != NUL)			// past group name(s)
3547 		    xp->xp_context = EXPAND_NOTHING;
3548 	    }
3549 	}
3550     }
3551 }
3552 
3553 /*
3554  * List highlighting matches in a nice way.
3555  */
3556     static void
3557 highlight_list(void)
3558 {
3559     int		i;
3560 
3561     for (i = 10; --i >= 0; )
3562 	highlight_list_two(i, HL_ATTR(HLF_D));
3563     for (i = 40; --i >= 0; )
3564 	highlight_list_two(99, 0);
3565 }
3566 
3567     static void
3568 highlight_list_two(int cnt, int attr)
3569 {
3570     msg_puts_attr(&("N \bI \b!  \b"[cnt / 11]), attr);
3571     msg_clr_eos();
3572     out_flush();
3573     ui_delay(cnt == 99 ? 40L : (long)cnt * 50L, FALSE);
3574 }
3575 
3576 /*
3577  * Function given to ExpandGeneric() to obtain the list of group names.
3578  */
3579     char_u *
3580 get_highlight_name(expand_T *xp UNUSED, int idx)
3581 {
3582     return get_highlight_name_ext(xp, idx, TRUE);
3583 }
3584 
3585 /*
3586  * Obtain a highlight group name.
3587  * When "skip_cleared" is TRUE don't return a cleared entry.
3588  */
3589     char_u *
3590 get_highlight_name_ext(expand_T *xp UNUSED, int idx, int skip_cleared)
3591 {
3592     if (idx < 0)
3593 	return NULL;
3594 
3595     // Items are never removed from the table, skip the ones that were
3596     // cleared.
3597     if (skip_cleared && idx < highlight_ga.ga_len && HL_TABLE()[idx].sg_cleared)
3598 	return (char_u *)"";
3599 
3600     if (idx == highlight_ga.ga_len && include_none != 0)
3601 	return (char_u *)"none";
3602     if (idx == highlight_ga.ga_len + include_none && include_default != 0)
3603 	return (char_u *)"default";
3604     if (idx == highlight_ga.ga_len + include_none + include_default
3605 							 && include_link != 0)
3606 	return (char_u *)"link";
3607     if (idx == highlight_ga.ga_len + include_none + include_default + 1
3608 							 && include_link != 0)
3609 	return (char_u *)"clear";
3610     if (idx >= highlight_ga.ga_len)
3611 	return NULL;
3612     return HL_TABLE()[idx].sg_name;
3613 }
3614 
3615 #if defined(FEAT_GUI) || defined(PROTO)
3616 /*
3617  * Free all the highlight group fonts.
3618  * Used when quitting for systems which need it.
3619  */
3620     void
3621 free_highlight_fonts(void)
3622 {
3623     int	    idx;
3624 
3625     for (idx = 0; idx < highlight_ga.ga_len; ++idx)
3626     {
3627 	gui_mch_free_font(HL_TABLE()[idx].sg_font);
3628 	HL_TABLE()[idx].sg_font = NOFONT;
3629 # ifdef FEAT_XFONTSET
3630 	gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
3631 	HL_TABLE()[idx].sg_fontset = NOFONTSET;
3632 # endif
3633     }
3634 
3635     gui_mch_free_font(gui.norm_font);
3636 # ifdef FEAT_XFONTSET
3637     gui_mch_free_fontset(gui.fontset);
3638 # endif
3639 # ifndef FEAT_GUI_GTK
3640     gui_mch_free_font(gui.bold_font);
3641     gui_mch_free_font(gui.ital_font);
3642     gui_mch_free_font(gui.boldital_font);
3643 # endif
3644 }
3645 #endif
3646 
3647 
3648 #if defined(FEAT_SEARCH_EXTRA) || defined(PROTO)
3649 
3650 # define SEARCH_HL_PRIORITY 0
3651 
3652 /*
3653  * Add match to the match list of window 'wp'.  The pattern 'pat' will be
3654  * highlighted with the group 'grp' with priority 'prio'.
3655  * Optionally, a desired ID 'id' can be specified (greater than or equal to 1).
3656  * If no particular ID is desired, -1 must be specified for 'id'.
3657  * Return ID of added match, -1 on failure.
3658  */
3659     static int
3660 match_add(
3661     win_T	*wp,
3662     char_u	*grp,
3663     char_u	*pat,
3664     int		prio,
3665     int		id,
3666     list_T	*pos_list,
3667     char_u      *conceal_char UNUSED) // pointer to conceal replacement char
3668 {
3669     matchitem_T	*cur;
3670     matchitem_T	*prev;
3671     matchitem_T	*m;
3672     int		hlg_id;
3673     regprog_T	*regprog = NULL;
3674     int		rtype = SOME_VALID;
3675 
3676     if (*grp == NUL || (pat != NULL && *pat == NUL))
3677 	return -1;
3678     if (id < -1 || id == 0)
3679     {
3680 	semsg(_("E799: Invalid ID: %d (must be greater than or equal to 1)"),
3681 									   id);
3682 	return -1;
3683     }
3684     if (id != -1)
3685     {
3686 	cur = wp->w_match_head;
3687 	while (cur != NULL)
3688 	{
3689 	    if (cur->id == id)
3690 	    {
3691 		semsg(_("E801: ID already taken: %d"), id);
3692 		return -1;
3693 	    }
3694 	    cur = cur->next;
3695 	}
3696     }
3697     if ((hlg_id = syn_namen2id(grp, (int)STRLEN(grp))) == 0)
3698     {
3699 	semsg(_(e_nogroup), grp);
3700 	return -1;
3701     }
3702     if (pat != NULL && (regprog = vim_regcomp(pat, RE_MAGIC)) == NULL)
3703     {
3704 	semsg(_(e_invarg2), pat);
3705 	return -1;
3706     }
3707 
3708     // Find available match ID.
3709     while (id == -1)
3710     {
3711 	cur = wp->w_match_head;
3712 	while (cur != NULL && cur->id != wp->w_next_match_id)
3713 	    cur = cur->next;
3714 	if (cur == NULL)
3715 	    id = wp->w_next_match_id;
3716 	wp->w_next_match_id++;
3717     }
3718 
3719     // Build new match.
3720     m = ALLOC_CLEAR_ONE(matchitem_T);
3721     m->id = id;
3722     m->priority = prio;
3723     m->pattern = pat == NULL ? NULL : vim_strsave(pat);
3724     m->hlg_id = hlg_id;
3725     m->match.regprog = regprog;
3726     m->match.rmm_ic = FALSE;
3727     m->match.rmm_maxcol = 0;
3728 # if defined(FEAT_CONCEAL)
3729     m->conceal_char = 0;
3730     if (conceal_char != NULL)
3731 	m->conceal_char = (*mb_ptr2char)(conceal_char);
3732 # endif
3733 
3734     // Set up position matches
3735     if (pos_list != NULL)
3736     {
3737 	linenr_T	toplnum = 0;
3738 	linenr_T	botlnum = 0;
3739 	listitem_T	*li;
3740 	int		i;
3741 
3742 	for (i = 0, li = pos_list->lv_first; li != NULL && i < MAXPOSMATCH;
3743 							i++, li = li->li_next)
3744 	{
3745 	    linenr_T	lnum = 0;
3746 	    colnr_T	col = 0;
3747 	    int		len = 1;
3748 	    list_T	*subl;
3749 	    listitem_T	*subli;
3750 	    int		error = FALSE;
3751 
3752 	    if (li->li_tv.v_type == VAR_LIST)
3753 	    {
3754 		subl = li->li_tv.vval.v_list;
3755 		if (subl == NULL)
3756 		    goto fail;
3757 		subli = subl->lv_first;
3758 		if (subli == NULL)
3759 		    goto fail;
3760 		lnum = tv_get_number_chk(&subli->li_tv, &error);
3761 		if (error == TRUE)
3762 		    goto fail;
3763 		if (lnum == 0)
3764 		{
3765 		    --i;
3766 		    continue;
3767 		}
3768 		m->pos.pos[i].lnum = lnum;
3769 		subli = subli->li_next;
3770 		if (subli != NULL)
3771 		{
3772 		    col = tv_get_number_chk(&subli->li_tv, &error);
3773 		    if (error == TRUE)
3774 			goto fail;
3775 		    subli = subli->li_next;
3776 		    if (subli != NULL)
3777 		    {
3778 			len = tv_get_number_chk(&subli->li_tv, &error);
3779 			if (error == TRUE)
3780 			    goto fail;
3781 		    }
3782 		}
3783 		m->pos.pos[i].col = col;
3784 		m->pos.pos[i].len = len;
3785 	    }
3786 	    else if (li->li_tv.v_type == VAR_NUMBER)
3787 	    {
3788 		if (li->li_tv.vval.v_number == 0)
3789 		{
3790 		    --i;
3791 		    continue;
3792 		}
3793 		m->pos.pos[i].lnum = li->li_tv.vval.v_number;
3794 		m->pos.pos[i].col = 0;
3795 		m->pos.pos[i].len = 0;
3796 	    }
3797 	    else
3798 	    {
3799 		emsg(_("E290: List or number required"));
3800 		goto fail;
3801 	    }
3802 	    if (toplnum == 0 || lnum < toplnum)
3803 		toplnum = lnum;
3804 	    if (botlnum == 0 || lnum >= botlnum)
3805 		botlnum = lnum + 1;
3806 	}
3807 
3808 	// Calculate top and bottom lines for redrawing area
3809 	if (toplnum != 0)
3810 	{
3811 	    if (wp->w_buffer->b_mod_set)
3812 	    {
3813 		if (wp->w_buffer->b_mod_top > toplnum)
3814 		    wp->w_buffer->b_mod_top = toplnum;
3815 		if (wp->w_buffer->b_mod_bot < botlnum)
3816 		    wp->w_buffer->b_mod_bot = botlnum;
3817 	    }
3818 	    else
3819 	    {
3820 		wp->w_buffer->b_mod_set = TRUE;
3821 		wp->w_buffer->b_mod_top = toplnum;
3822 		wp->w_buffer->b_mod_bot = botlnum;
3823 		wp->w_buffer->b_mod_xlines = 0;
3824 	    }
3825 	    m->pos.toplnum = toplnum;
3826 	    m->pos.botlnum = botlnum;
3827 	    rtype = VALID;
3828 	}
3829     }
3830 
3831     // Insert new match.  The match list is in ascending order with regard to
3832     // the match priorities.
3833     cur = wp->w_match_head;
3834     prev = cur;
3835     while (cur != NULL && prio >= cur->priority)
3836     {
3837 	prev = cur;
3838 	cur = cur->next;
3839     }
3840     if (cur == prev)
3841 	wp->w_match_head = m;
3842     else
3843 	prev->next = m;
3844     m->next = cur;
3845 
3846     redraw_win_later(wp, rtype);
3847     return id;
3848 
3849 fail:
3850     vim_free(m);
3851     return -1;
3852 }
3853 
3854 /*
3855  * Delete match with ID 'id' in the match list of window 'wp'.
3856  * Print error messages if 'perr' is TRUE.
3857  */
3858     static int
3859 match_delete(win_T *wp, int id, int perr)
3860 {
3861     matchitem_T	*cur = wp->w_match_head;
3862     matchitem_T	*prev = cur;
3863     int		rtype = SOME_VALID;
3864 
3865     if (id < 1)
3866     {
3867 	if (perr == TRUE)
3868 	    semsg(_("E802: Invalid ID: %d (must be greater than or equal to 1)"),
3869 									  id);
3870 	return -1;
3871     }
3872     while (cur != NULL && cur->id != id)
3873     {
3874 	prev = cur;
3875 	cur = cur->next;
3876     }
3877     if (cur == NULL)
3878     {
3879 	if (perr == TRUE)
3880 	    semsg(_("E803: ID not found: %d"), id);
3881 	return -1;
3882     }
3883     if (cur == prev)
3884 	wp->w_match_head = cur->next;
3885     else
3886 	prev->next = cur->next;
3887     vim_regfree(cur->match.regprog);
3888     vim_free(cur->pattern);
3889     if (cur->pos.toplnum != 0)
3890     {
3891 	if (wp->w_buffer->b_mod_set)
3892 	{
3893 	    if (wp->w_buffer->b_mod_top > cur->pos.toplnum)
3894 		wp->w_buffer->b_mod_top = cur->pos.toplnum;
3895 	    if (wp->w_buffer->b_mod_bot < cur->pos.botlnum)
3896 		wp->w_buffer->b_mod_bot = cur->pos.botlnum;
3897 	}
3898 	else
3899 	{
3900 	    wp->w_buffer->b_mod_set = TRUE;
3901 	    wp->w_buffer->b_mod_top = cur->pos.toplnum;
3902 	    wp->w_buffer->b_mod_bot = cur->pos.botlnum;
3903 	    wp->w_buffer->b_mod_xlines = 0;
3904 	}
3905 	rtype = VALID;
3906     }
3907     vim_free(cur);
3908     redraw_win_later(wp, rtype);
3909     return 0;
3910 }
3911 
3912 /*
3913  * Delete all matches in the match list of window 'wp'.
3914  */
3915     void
3916 clear_matches(win_T *wp)
3917 {
3918     matchitem_T *m;
3919 
3920     while (wp->w_match_head != NULL)
3921     {
3922 	m = wp->w_match_head->next;
3923 	vim_regfree(wp->w_match_head->match.regprog);
3924 	vim_free(wp->w_match_head->pattern);
3925 	vim_free(wp->w_match_head);
3926 	wp->w_match_head = m;
3927     }
3928     redraw_win_later(wp, SOME_VALID);
3929 }
3930 
3931 /*
3932  * Get match from ID 'id' in window 'wp'.
3933  * Return NULL if match not found.
3934  */
3935     static matchitem_T *
3936 get_match(win_T *wp, int id)
3937 {
3938     matchitem_T *cur = wp->w_match_head;
3939 
3940     while (cur != NULL && cur->id != id)
3941 	cur = cur->next;
3942     return cur;
3943 }
3944 
3945 /*
3946  * Init for calling prepare_search_hl().
3947  */
3948     void
3949 init_search_hl(win_T *wp, match_T *search_hl)
3950 {
3951     matchitem_T *cur;
3952 
3953     // Setup for match and 'hlsearch' highlighting.  Disable any previous
3954     // match
3955     cur = wp->w_match_head;
3956     while (cur != NULL)
3957     {
3958 	cur->hl.rm = cur->match;
3959 	if (cur->hlg_id == 0)
3960 	    cur->hl.attr = 0;
3961 	else
3962 	    cur->hl.attr = syn_id2attr(cur->hlg_id);
3963 	cur->hl.buf = wp->w_buffer;
3964 	cur->hl.lnum = 0;
3965 	cur->hl.first_lnum = 0;
3966 # ifdef FEAT_RELTIME
3967 	// Set the time limit to 'redrawtime'.
3968 	profile_setlimit(p_rdt, &(cur->hl.tm));
3969 # endif
3970 	cur = cur->next;
3971     }
3972     search_hl->buf = wp->w_buffer;
3973     search_hl->lnum = 0;
3974     search_hl->first_lnum = 0;
3975     // time limit is set at the toplevel, for all windows
3976 }
3977 
3978 /*
3979  * If there is a match fill "shl" and return one.
3980  * Return zero otherwise.
3981  */
3982     static int
3983 next_search_hl_pos(
3984     match_T	    *shl,	// points to a match
3985     linenr_T	    lnum,
3986     posmatch_T	    *posmatch,	// match positions
3987     colnr_T	    mincol)	// minimal column for a match
3988 {
3989     int	    i;
3990     int	    found = -1;
3991 
3992     for (i = posmatch->cur; i < MAXPOSMATCH; i++)
3993     {
3994 	llpos_T	*pos = &posmatch->pos[i];
3995 
3996 	if (pos->lnum == 0)
3997 	    break;
3998 	if (pos->len == 0 && pos->col < mincol)
3999 	    continue;
4000 	if (pos->lnum == lnum)
4001 	{
4002 	    if (found >= 0)
4003 	    {
4004 		// if this match comes before the one at "found" then swap
4005 		// them
4006 		if (pos->col < posmatch->pos[found].col)
4007 		{
4008 		    llpos_T	tmp = *pos;
4009 
4010 		    *pos = posmatch->pos[found];
4011 		    posmatch->pos[found] = tmp;
4012 		}
4013 	    }
4014 	    else
4015 		found = i;
4016 	}
4017     }
4018     posmatch->cur = 0;
4019     if (found >= 0)
4020     {
4021 	colnr_T	start = posmatch->pos[found].col == 0
4022 					    ? 0 : posmatch->pos[found].col - 1;
4023 	colnr_T	end = posmatch->pos[found].col == 0
4024 				   ? MAXCOL : start + posmatch->pos[found].len;
4025 
4026 	shl->lnum = lnum;
4027 	shl->rm.startpos[0].lnum = 0;
4028 	shl->rm.startpos[0].col = start;
4029 	shl->rm.endpos[0].lnum = 0;
4030 	shl->rm.endpos[0].col = end;
4031 	shl->is_addpos = TRUE;
4032 	posmatch->cur = found + 1;
4033 	return 1;
4034     }
4035     return 0;
4036 }
4037 
4038 /*
4039  * Search for a next 'hlsearch' or match.
4040  * Uses shl->buf.
4041  * Sets shl->lnum and shl->rm contents.
4042  * Note: Assumes a previous match is always before "lnum", unless
4043  * shl->lnum is zero.
4044  * Careful: Any pointers for buffer lines will become invalid.
4045  */
4046     static void
4047 next_search_hl(
4048     win_T	    *win,
4049     match_T	    *search_hl,
4050     match_T	    *shl,	// points to search_hl or a match
4051     linenr_T	    lnum,
4052     colnr_T	    mincol,	// minimal column for a match
4053     matchitem_T	    *cur)	// to retrieve match positions if any
4054 {
4055     linenr_T	l;
4056     colnr_T	matchcol;
4057     long	nmatched;
4058     int		called_emsg_before = called_emsg;
4059 
4060     // for :{range}s/pat only highlight inside the range
4061     if (lnum < search_first_line || lnum > search_last_line)
4062     {
4063 	shl->lnum = 0;
4064 	return;
4065     }
4066 
4067     if (shl->lnum != 0)
4068     {
4069 	// Check for three situations:
4070 	// 1. If the "lnum" is below a previous match, start a new search.
4071 	// 2. If the previous match includes "mincol", use it.
4072 	// 3. Continue after the previous match.
4073 	l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum;
4074 	if (lnum > l)
4075 	    shl->lnum = 0;
4076 	else if (lnum < l || shl->rm.endpos[0].col > mincol)
4077 	    return;
4078     }
4079 
4080     /*
4081      * Repeat searching for a match until one is found that includes "mincol"
4082      * or none is found in this line.
4083      */
4084     for (;;)
4085     {
4086 # ifdef FEAT_RELTIME
4087 	// Stop searching after passing the time limit.
4088 	if (profile_passed_limit(&(shl->tm)))
4089 	{
4090 	    shl->lnum = 0;		// no match found in time
4091 	    break;
4092 	}
4093 # endif
4094 	// Three situations:
4095 	// 1. No useful previous match: search from start of line.
4096 	// 2. Not Vi compatible or empty match: continue at next character.
4097 	//    Break the loop if this is beyond the end of the line.
4098 	// 3. Vi compatible searching: continue at end of previous match.
4099 	if (shl->lnum == 0)
4100 	    matchcol = 0;
4101 	else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL
4102 		|| (shl->rm.endpos[0].lnum == 0
4103 		    && shl->rm.endpos[0].col <= shl->rm.startpos[0].col))
4104 	{
4105 	    char_u	*ml;
4106 
4107 	    matchcol = shl->rm.startpos[0].col;
4108 	    ml = ml_get_buf(shl->buf, lnum, FALSE) + matchcol;
4109 	    if (*ml == NUL)
4110 	    {
4111 		++matchcol;
4112 		shl->lnum = 0;
4113 		break;
4114 	    }
4115 	    if (has_mbyte)
4116 		matchcol += mb_ptr2len(ml);
4117 	    else
4118 		++matchcol;
4119 	}
4120 	else
4121 	    matchcol = shl->rm.endpos[0].col;
4122 
4123 	shl->lnum = lnum;
4124 	if (shl->rm.regprog != NULL)
4125 	{
4126 	    // Remember whether shl->rm is using a copy of the regprog in
4127 	    // cur->match.
4128 	    int regprog_is_copy = (shl != search_hl && cur != NULL
4129 				&& shl == &cur->hl
4130 				&& cur->match.regprog == cur->hl.rm.regprog);
4131 	    int timed_out = FALSE;
4132 
4133 	    nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum,
4134 		    matchcol,
4135 #ifdef FEAT_RELTIME
4136 		    &(shl->tm), &timed_out
4137 #else
4138 		    NULL, NULL
4139 #endif
4140 		    );
4141 	    // Copy the regprog, in case it got freed and recompiled.
4142 	    if (regprog_is_copy)
4143 		cur->match.regprog = cur->hl.rm.regprog;
4144 
4145 	    if (called_emsg > called_emsg_before || got_int || timed_out)
4146 	    {
4147 		// Error while handling regexp: stop using this regexp.
4148 		if (shl == search_hl)
4149 		{
4150 		    // don't free regprog in the match list, it's a copy
4151 		    vim_regfree(shl->rm.regprog);
4152 		    set_no_hlsearch(TRUE);
4153 		}
4154 		shl->rm.regprog = NULL;
4155 		shl->lnum = 0;
4156 		got_int = FALSE;  // avoid the "Type :quit to exit Vim" message
4157 		break;
4158 	    }
4159 	}
4160 	else if (cur != NULL)
4161 	    nmatched = next_search_hl_pos(shl, lnum, &(cur->pos), matchcol);
4162 	else
4163 	    nmatched = 0;
4164 	if (nmatched == 0)
4165 	{
4166 	    shl->lnum = 0;		// no match found
4167 	    break;
4168 	}
4169 	if (shl->rm.startpos[0].lnum > 0
4170 		|| shl->rm.startpos[0].col >= mincol
4171 		|| nmatched > 1
4172 		|| shl->rm.endpos[0].col > mincol)
4173 	{
4174 	    shl->lnum += shl->rm.startpos[0].lnum;
4175 	    break;			// useful match found
4176 	}
4177     }
4178 }
4179 
4180 /*
4181  * Advance to the match in window "wp" line "lnum" or past it.
4182  */
4183     void
4184 prepare_search_hl(win_T *wp, match_T *search_hl, linenr_T lnum)
4185 {
4186     matchitem_T *cur;		// points to the match list
4187     match_T	*shl;		// points to search_hl or a match
4188     int		shl_flag;	// flag to indicate whether search_hl
4189 				// has been processed or not
4190     int		pos_inprogress;	// marks that position match search is
4191 				// in progress
4192     int		n;
4193 
4194     // When using a multi-line pattern, start searching at the top
4195     // of the window or just after a closed fold.
4196     // Do this both for search_hl and the match list.
4197     cur = wp->w_match_head;
4198     shl_flag = WIN_IS_POPUP(wp);  // skip search_hl in a popup window
4199     while (cur != NULL || shl_flag == FALSE)
4200     {
4201 	if (shl_flag == FALSE)
4202 	{
4203 	    shl = search_hl;
4204 	    shl_flag = TRUE;
4205 	}
4206 	else
4207 	    shl = &cur->hl;
4208 	if (shl->rm.regprog != NULL
4209 		&& shl->lnum == 0
4210 		&& re_multiline(shl->rm.regprog))
4211 	{
4212 	    if (shl->first_lnum == 0)
4213 	    {
4214 # ifdef FEAT_FOLDING
4215 		for (shl->first_lnum = lnum;
4216 			   shl->first_lnum > wp->w_topline; --shl->first_lnum)
4217 		    if (hasFoldingWin(wp, shl->first_lnum - 1,
4218 						      NULL, NULL, TRUE, NULL))
4219 			break;
4220 # else
4221 		shl->first_lnum = wp->w_topline;
4222 # endif
4223 	    }
4224 	    if (cur != NULL)
4225 		cur->pos.cur = 0;
4226 	    pos_inprogress = TRUE;
4227 	    n = 0;
4228 	    while (shl->first_lnum < lnum && (shl->rm.regprog != NULL
4229 					  || (cur != NULL && pos_inprogress)))
4230 	    {
4231 		next_search_hl(wp, search_hl, shl, shl->first_lnum, (colnr_T)n,
4232 					       shl == search_hl ? NULL : cur);
4233 		pos_inprogress = cur == NULL || cur->pos.cur == 0
4234 							      ? FALSE : TRUE;
4235 		if (shl->lnum != 0)
4236 		{
4237 		    shl->first_lnum = shl->lnum
4238 				    + shl->rm.endpos[0].lnum
4239 				    - shl->rm.startpos[0].lnum;
4240 		    n = shl->rm.endpos[0].col;
4241 		}
4242 		else
4243 		{
4244 		    ++shl->first_lnum;
4245 		    n = 0;
4246 		}
4247 	    }
4248 	}
4249 	if (shl != search_hl && cur != NULL)
4250 	    cur = cur->next;
4251     }
4252 }
4253 
4254 /*
4255  * Prepare for 'hlsearch' and match highlighting in one window line.
4256  * Return TRUE if there is such highlighting and set "search_attr" to the
4257  * current highlight attribute.
4258  */
4259     int
4260 prepare_search_hl_line(
4261 	win_T	    *wp,
4262 	linenr_T    lnum,
4263 	colnr_T	    mincol,
4264 	char_u	    **line,
4265 	match_T	    *search_hl,
4266 	int	    *search_attr)
4267 {
4268     matchitem_T *cur;			// points to the match list
4269     match_T	*shl;			// points to search_hl or a match
4270     int		shl_flag;		// flag to indicate whether search_hl
4271 					// has been processed or not
4272     int		area_highlighting = FALSE;
4273 
4274     /*
4275      * Handle highlighting the last used search pattern and matches.
4276      * Do this for both search_hl and the match list.
4277      * Do not use search_hl in a popup window.
4278      */
4279     cur = wp->w_match_head;
4280     shl_flag = WIN_IS_POPUP(wp);
4281     while (cur != NULL || shl_flag == FALSE)
4282     {
4283 	if (shl_flag == FALSE)
4284 	{
4285 	    shl = search_hl;
4286 	    shl_flag = TRUE;
4287 	}
4288 	else
4289 	    shl = &cur->hl;
4290 	shl->startcol = MAXCOL;
4291 	shl->endcol = MAXCOL;
4292 	shl->attr_cur = 0;
4293 	shl->is_addpos = FALSE;
4294 	if (cur != NULL)
4295 	    cur->pos.cur = 0;
4296 	next_search_hl(wp, search_hl, shl, lnum, mincol,
4297 						shl == search_hl ? NULL : cur);
4298 
4299 	// Need to get the line again, a multi-line regexp may have made it
4300 	// invalid.
4301 	*line = ml_get_buf(wp->w_buffer, lnum, FALSE);
4302 
4303 	if (shl->lnum != 0 && shl->lnum <= lnum)
4304 	{
4305 	    if (shl->lnum == lnum)
4306 		shl->startcol = shl->rm.startpos[0].col;
4307 	    else
4308 		shl->startcol = 0;
4309 	    if (lnum == shl->lnum + shl->rm.endpos[0].lnum
4310 						- shl->rm.startpos[0].lnum)
4311 		shl->endcol = shl->rm.endpos[0].col;
4312 	    else
4313 		shl->endcol = MAXCOL;
4314 	    // Highlight one character for an empty match.
4315 	    if (shl->startcol == shl->endcol)
4316 	    {
4317 		if (has_mbyte && (*line)[shl->endcol] != NUL)
4318 		    shl->endcol += (*mb_ptr2len)((*line) + shl->endcol);
4319 		else
4320 		    ++shl->endcol;
4321 	    }
4322 	    if ((long)shl->startcol < mincol)  // match at leftcol
4323 	    {
4324 		shl->attr_cur = shl->attr;
4325 		*search_attr = shl->attr;
4326 	    }
4327 	    area_highlighting = TRUE;
4328 	}
4329 	if (shl != search_hl && cur != NULL)
4330 	    cur = cur->next;
4331     }
4332     return area_highlighting;
4333 }
4334 
4335 /*
4336  * For a position in a line: Check for start/end of 'hlsearch' and other
4337  * matches.
4338  * After end, check for start/end of next match.
4339  * When another match, have to check for start again.
4340  * Watch out for matching an empty string!
4341  * Return the updated search_attr.
4342  */
4343     int
4344 update_search_hl(
4345 	win_T	    *wp,
4346 	linenr_T    lnum,
4347 	colnr_T	    col,
4348 	char_u	    **line,
4349 	match_T	    *search_hl,
4350 	int	    *has_match_conc UNUSED,
4351 	int	    *match_conc UNUSED,
4352 	int	    did_line_attr,
4353 	int	    lcs_eol_one)
4354 {
4355     matchitem_T *cur;		    // points to the match list
4356     match_T	*shl;		    // points to search_hl or a match
4357     int		shl_flag;	    // flag to indicate whether search_hl
4358 				    // has been processed or not
4359     int		pos_inprogress;	    // marks that position match search is in
4360 				    // progress
4361     int		search_attr = 0;
4362 
4363 
4364     // Do this for 'search_hl' and the match list (ordered by priority).
4365     cur = wp->w_match_head;
4366     shl_flag = WIN_IS_POPUP(wp);
4367     while (cur != NULL || shl_flag == FALSE)
4368     {
4369 	if (shl_flag == FALSE
4370 		&& ((cur != NULL
4371 			&& cur->priority > SEARCH_HL_PRIORITY)
4372 		    || cur == NULL))
4373 	{
4374 	    shl = search_hl;
4375 	    shl_flag = TRUE;
4376 	}
4377 	else
4378 	    shl = &cur->hl;
4379 	if (cur != NULL)
4380 	    cur->pos.cur = 0;
4381 	pos_inprogress = TRUE;
4382 	while (shl->rm.regprog != NULL || (cur != NULL && pos_inprogress))
4383 	{
4384 	    if (shl->startcol != MAXCOL
4385 		    && col >= shl->startcol
4386 		    && col < shl->endcol)
4387 	    {
4388 		int next_col = col + mb_ptr2len(*line + col);
4389 
4390 		if (shl->endcol < next_col)
4391 		    shl->endcol = next_col;
4392 		shl->attr_cur = shl->attr;
4393 # ifdef FEAT_CONCEAL
4394 		// Match with the "Conceal" group results in hiding
4395 		// the match.
4396 		if (cur != NULL
4397 			&& shl != search_hl
4398 			&& syn_name2id((char_u *)"Conceal") == cur->hlg_id)
4399 		{
4400 		    *has_match_conc = col == shl->startcol ? 2 : 1;
4401 		    *match_conc = cur->conceal_char;
4402 		}
4403 		else
4404 		    *has_match_conc = *match_conc = 0;
4405 # endif
4406 	    }
4407 	    else if (col == shl->endcol)
4408 	    {
4409 		shl->attr_cur = 0;
4410 		next_search_hl(wp, search_hl, shl, lnum, col,
4411 					       shl == search_hl ? NULL : cur);
4412 		pos_inprogress = !(cur == NULL || cur->pos.cur == 0);
4413 
4414 		// Need to get the line again, a multi-line regexp may have
4415 		// made it invalid.
4416 		*line = ml_get_buf(wp->w_buffer, lnum, FALSE);
4417 
4418 		if (shl->lnum == lnum)
4419 		{
4420 		    shl->startcol = shl->rm.startpos[0].col;
4421 		    if (shl->rm.endpos[0].lnum == 0)
4422 			shl->endcol = shl->rm.endpos[0].col;
4423 		    else
4424 			shl->endcol = MAXCOL;
4425 
4426 		    if (shl->startcol == shl->endcol)
4427 		    {
4428 			// highlight empty match, try again after
4429 			// it
4430 			if (has_mbyte)
4431 			    shl->endcol += (*mb_ptr2len)(*line + shl->endcol);
4432 			else
4433 			    ++shl->endcol;
4434 		    }
4435 
4436 		    // Loop to check if the match starts at the
4437 		    // current position
4438 		    continue;
4439 		}
4440 	    }
4441 	    break;
4442 	}
4443 	if (shl != search_hl && cur != NULL)
4444 	    cur = cur->next;
4445     }
4446 
4447     // Use attributes from match with highest priority among 'search_hl' and
4448     // the match list.
4449     cur = wp->w_match_head;
4450     shl_flag = WIN_IS_POPUP(wp);
4451     while (cur != NULL || shl_flag == FALSE)
4452     {
4453 	if (shl_flag == FALSE
4454 		&& ((cur != NULL
4455 			&& cur->priority > SEARCH_HL_PRIORITY)
4456 		    || cur == NULL))
4457 	{
4458 	    shl = search_hl;
4459 	    shl_flag = TRUE;
4460 	}
4461 	else
4462 	    shl = &cur->hl;
4463 	if (shl->attr_cur != 0)
4464 	    search_attr = shl->attr_cur;
4465 	if (shl != search_hl && cur != NULL)
4466 	    cur = cur->next;
4467     }
4468     // Only highlight one character after the last column.
4469     if (*(*line + col) == NUL && (did_line_attr >= 1
4470 				       || (wp->w_p_list && lcs_eol_one == -1)))
4471 	search_attr = 0;
4472     return search_attr;
4473 }
4474 
4475     int
4476 get_prevcol_hl_flag(win_T *wp, match_T *search_hl, long curcol)
4477 {
4478     long	prevcol = curcol;
4479     int		prevcol_hl_flag = FALSE;
4480     matchitem_T *cur;			// points to the match list
4481 
4482     // we're not really at that column when skipping some text
4483     if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol)
4484 	++prevcol;
4485 
4486     if (!search_hl->is_addpos && prevcol == (long)search_hl->startcol)
4487 	prevcol_hl_flag = TRUE;
4488     else
4489     {
4490 	cur = wp->w_match_head;
4491 	while (cur != NULL)
4492 	{
4493 	    if (!cur->hl.is_addpos && prevcol == (long)cur->hl.startcol)
4494 	    {
4495 		prevcol_hl_flag = TRUE;
4496 		break;
4497 	    }
4498 	    cur = cur->next;
4499 	}
4500     }
4501     return prevcol_hl_flag;
4502 }
4503 
4504 /*
4505  * Get highlighting for the char after the text in "char_attr" from 'hlsearch'
4506  * or match highlighting.
4507  */
4508     void
4509 get_search_match_hl(win_T *wp, match_T *search_hl, long col, int *char_attr)
4510 {
4511     matchitem_T *cur;			// points to the match list
4512     match_T	*shl;			// points to search_hl or a match
4513     int		shl_flag;		// flag to indicate whether search_hl
4514 					// has been processed or not
4515 
4516     cur = wp->w_match_head;
4517     shl_flag = WIN_IS_POPUP(wp);
4518     while (cur != NULL || shl_flag == FALSE)
4519     {
4520 	if (shl_flag == FALSE
4521 		&& ((cur != NULL
4522 			&& cur->priority > SEARCH_HL_PRIORITY)
4523 		    || cur == NULL))
4524 	{
4525 	    shl = search_hl;
4526 	    shl_flag = TRUE;
4527 	}
4528 	else
4529 	    shl = &cur->hl;
4530 	if (col - 1 == (long)shl->startcol
4531 		&& (shl == search_hl || !shl->is_addpos))
4532 	    *char_attr = shl->attr;
4533 	if (shl != search_hl && cur != NULL)
4534 	    cur = cur->next;
4535     }
4536 }
4537 
4538 #endif // FEAT_SEARCH_EXTRA
4539 
4540 #if defined(FEAT_EVAL) || defined(PROTO)
4541 # ifdef FEAT_SEARCH_EXTRA
4542     static int
4543 matchadd_dict_arg(typval_T *tv, char_u **conceal_char, win_T **win)
4544 {
4545     dictitem_T *di;
4546 
4547     if (tv->v_type != VAR_DICT)
4548     {
4549 	emsg(_(e_dictreq));
4550 	return FAIL;
4551     }
4552 
4553     if (dict_find(tv->vval.v_dict, (char_u *)"conceal", -1) != NULL)
4554 	*conceal_char = dict_get_string(tv->vval.v_dict,
4555 						   (char_u *)"conceal", FALSE);
4556 
4557     if ((di = dict_find(tv->vval.v_dict, (char_u *)"window", -1)) != NULL)
4558     {
4559 	*win = find_win_by_nr_or_id(&di->di_tv);
4560 	if (*win == NULL)
4561 	{
4562 	    emsg(_(e_invalwindow));
4563 	    return FAIL;
4564 	}
4565     }
4566 
4567     return OK;
4568 }
4569 #endif
4570 
4571 /*
4572  * "clearmatches()" function
4573  */
4574     void
4575 f_clearmatches(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4576 {
4577 #ifdef FEAT_SEARCH_EXTRA
4578     win_T   *win = get_optional_window(argvars, 0);
4579 
4580     if (win != NULL)
4581 	clear_matches(win);
4582 #endif
4583 }
4584 
4585 /*
4586  * "getmatches()" function
4587  */
4588     void
4589 f_getmatches(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4590 {
4591 # ifdef FEAT_SEARCH_EXTRA
4592     dict_T	*dict;
4593     matchitem_T	*cur;
4594     int		i;
4595     win_T	*win = get_optional_window(argvars, 0);
4596 
4597     if (rettv_list_alloc(rettv) == FAIL || win == NULL)
4598 	return;
4599 
4600     cur = win->w_match_head;
4601     while (cur != NULL)
4602     {
4603 	dict = dict_alloc();
4604 	if (dict == NULL)
4605 	    return;
4606 	if (cur->match.regprog == NULL)
4607 	{
4608 	    // match added with matchaddpos()
4609 	    for (i = 0; i < MAXPOSMATCH; ++i)
4610 	    {
4611 		llpos_T	*llpos;
4612 		char	buf[30];  // use 30 to avoid compiler warning
4613 		list_T	*l;
4614 
4615 		llpos = &cur->pos.pos[i];
4616 		if (llpos->lnum == 0)
4617 		    break;
4618 		l = list_alloc();
4619 		if (l == NULL)
4620 		    break;
4621 		list_append_number(l, (varnumber_T)llpos->lnum);
4622 		if (llpos->col > 0)
4623 		{
4624 		    list_append_number(l, (varnumber_T)llpos->col);
4625 		    list_append_number(l, (varnumber_T)llpos->len);
4626 		}
4627 		sprintf(buf, "pos%d", i + 1);
4628 		dict_add_list(dict, buf, l);
4629 	    }
4630 	}
4631 	else
4632 	{
4633 	    dict_add_string(dict, "pattern", cur->pattern);
4634 	}
4635 	dict_add_string(dict, "group", syn_id2name(cur->hlg_id));
4636 	dict_add_number(dict, "priority", (long)cur->priority);
4637 	dict_add_number(dict, "id", (long)cur->id);
4638 #  if defined(FEAT_CONCEAL)
4639 	if (cur->conceal_char)
4640 	{
4641 	    char_u buf[MB_MAXBYTES + 1];
4642 
4643 	    buf[(*mb_char2bytes)((int)cur->conceal_char, buf)] = NUL;
4644 	    dict_add_string(dict, "conceal", (char_u *)&buf);
4645 	}
4646 #  endif
4647 	list_append_dict(rettv->vval.v_list, dict);
4648 	cur = cur->next;
4649     }
4650 # endif
4651 }
4652 
4653 /*
4654  * "setmatches()" function
4655  */
4656     void
4657 f_setmatches(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4658 {
4659 #ifdef FEAT_SEARCH_EXTRA
4660     list_T	*l;
4661     listitem_T	*li;
4662     dict_T	*d;
4663     list_T	*s = NULL;
4664     win_T	*win = get_optional_window(argvars, 1);
4665 
4666     rettv->vval.v_number = -1;
4667     if (argvars[0].v_type != VAR_LIST)
4668     {
4669 	emsg(_(e_listreq));
4670 	return;
4671     }
4672     if (win == NULL)
4673 	return;
4674 
4675     if ((l = argvars[0].vval.v_list) != NULL)
4676     {
4677 	// To some extent make sure that we are dealing with a list from
4678 	// "getmatches()".
4679 	li = l->lv_first;
4680 	while (li != NULL)
4681 	{
4682 	    if (li->li_tv.v_type != VAR_DICT
4683 		    || (d = li->li_tv.vval.v_dict) == NULL)
4684 	    {
4685 		emsg(_(e_invarg));
4686 		return;
4687 	    }
4688 	    if (!(dict_find(d, (char_u *)"group", -1) != NULL
4689 			&& (dict_find(d, (char_u *)"pattern", -1) != NULL
4690 			    || dict_find(d, (char_u *)"pos1", -1) != NULL)
4691 			&& dict_find(d, (char_u *)"priority", -1) != NULL
4692 			&& dict_find(d, (char_u *)"id", -1) != NULL))
4693 	    {
4694 		emsg(_(e_invarg));
4695 		return;
4696 	    }
4697 	    li = li->li_next;
4698 	}
4699 
4700 	clear_matches(win);
4701 	li = l->lv_first;
4702 	while (li != NULL)
4703 	{
4704 	    int		i = 0;
4705 	    char	buf[30];  // use 30 to avoid compiler warning
4706 	    dictitem_T  *di;
4707 	    char_u	*group;
4708 	    int		priority;
4709 	    int		id;
4710 	    char_u	*conceal;
4711 
4712 	    d = li->li_tv.vval.v_dict;
4713 	    if (dict_find(d, (char_u *)"pattern", -1) == NULL)
4714 	    {
4715 		if (s == NULL)
4716 		{
4717 		    s = list_alloc();
4718 		    if (s == NULL)
4719 			return;
4720 		}
4721 
4722 		// match from matchaddpos()
4723 		for (i = 1; i < 9; i++)
4724 		{
4725 		    sprintf((char *)buf, (char *)"pos%d", i);
4726 		    if ((di = dict_find(d, (char_u *)buf, -1)) != NULL)
4727 		    {
4728 			if (di->di_tv.v_type != VAR_LIST)
4729 			    return;
4730 
4731 			list_append_tv(s, &di->di_tv);
4732 			s->lv_refcount++;
4733 		    }
4734 		    else
4735 			break;
4736 		}
4737 	    }
4738 
4739 	    group = dict_get_string(d, (char_u *)"group", TRUE);
4740 	    priority = (int)dict_get_number(d, (char_u *)"priority");
4741 	    id = (int)dict_get_number(d, (char_u *)"id");
4742 	    conceal = dict_find(d, (char_u *)"conceal", -1) != NULL
4743 			      ? dict_get_string(d, (char_u *)"conceal", TRUE)
4744 			      : NULL;
4745 	    if (i == 0)
4746 	    {
4747 		match_add(win, group,
4748 		    dict_get_string(d, (char_u *)"pattern", FALSE),
4749 		    priority, id, NULL, conceal);
4750 	    }
4751 	    else
4752 	    {
4753 		match_add(win, group, NULL, priority, id, s, conceal);
4754 		list_unref(s);
4755 		s = NULL;
4756 	    }
4757 	    vim_free(group);
4758 	    vim_free(conceal);
4759 
4760 	    li = li->li_next;
4761 	}
4762 	rettv->vval.v_number = 0;
4763     }
4764 #endif
4765 }
4766 
4767 /*
4768  * "matchadd()" function
4769  */
4770     void
4771 f_matchadd(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4772 {
4773 # ifdef FEAT_SEARCH_EXTRA
4774     char_u	buf[NUMBUFLEN];
4775     char_u	*grp = tv_get_string_buf_chk(&argvars[0], buf);	// group
4776     char_u	*pat = tv_get_string_buf_chk(&argvars[1], buf);	// pattern
4777     int		prio = 10;	// default priority
4778     int		id = -1;
4779     int		error = FALSE;
4780     char_u	*conceal_char = NULL;
4781     win_T	*win = curwin;
4782 
4783     rettv->vval.v_number = -1;
4784 
4785     if (grp == NULL || pat == NULL)
4786 	return;
4787     if (argvars[2].v_type != VAR_UNKNOWN)
4788     {
4789 	prio = (int)tv_get_number_chk(&argvars[2], &error);
4790 	if (argvars[3].v_type != VAR_UNKNOWN)
4791 	{
4792 	    id = (int)tv_get_number_chk(&argvars[3], &error);
4793 	    if (argvars[4].v_type != VAR_UNKNOWN
4794 		&& matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL)
4795 		return;
4796 	}
4797     }
4798     if (error == TRUE)
4799 	return;
4800     if (id >= 1 && id <= 3)
4801     {
4802 	semsg(_("E798: ID is reserved for \":match\": %d"), id);
4803 	return;
4804     }
4805 
4806     rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL,
4807 								conceal_char);
4808 # endif
4809 }
4810 
4811 /*
4812  * "matchaddpos()" function
4813  */
4814     void
4815 f_matchaddpos(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4816 {
4817 # ifdef FEAT_SEARCH_EXTRA
4818     char_u	buf[NUMBUFLEN];
4819     char_u	*group;
4820     int		prio = 10;
4821     int		id = -1;
4822     int		error = FALSE;
4823     list_T	*l;
4824     char_u	*conceal_char = NULL;
4825     win_T	*win = curwin;
4826 
4827     rettv->vval.v_number = -1;
4828 
4829     group = tv_get_string_buf_chk(&argvars[0], buf);
4830     if (group == NULL)
4831 	return;
4832 
4833     if (argvars[1].v_type != VAR_LIST)
4834     {
4835 	semsg(_(e_listarg), "matchaddpos()");
4836 	return;
4837     }
4838     l = argvars[1].vval.v_list;
4839     if (l == NULL)
4840 	return;
4841 
4842     if (argvars[2].v_type != VAR_UNKNOWN)
4843     {
4844 	prio = (int)tv_get_number_chk(&argvars[2], &error);
4845 	if (argvars[3].v_type != VAR_UNKNOWN)
4846 	{
4847 	    id = (int)tv_get_number_chk(&argvars[3], &error);
4848 
4849 	    if (argvars[4].v_type != VAR_UNKNOWN
4850 		&& matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL)
4851 		return;
4852 	}
4853     }
4854     if (error == TRUE)
4855 	return;
4856 
4857     // id == 3 is ok because matchaddpos() is supposed to substitute :3match
4858     if (id == 1 || id == 2)
4859     {
4860 	semsg(_("E798: ID is reserved for \":match\": %d"), id);
4861 	return;
4862     }
4863 
4864     rettv->vval.v_number = match_add(win, group, NULL, prio, id, l,
4865 								conceal_char);
4866 # endif
4867 }
4868 
4869 /*
4870  * "matcharg()" function
4871  */
4872     void
4873 f_matcharg(typval_T *argvars UNUSED, typval_T *rettv)
4874 {
4875     if (rettv_list_alloc(rettv) == OK)
4876     {
4877 # ifdef FEAT_SEARCH_EXTRA
4878 	int	    id = (int)tv_get_number(&argvars[0]);
4879 	matchitem_T *m;
4880 
4881 	if (id >= 1 && id <= 3)
4882 	{
4883 	    if ((m = (matchitem_T *)get_match(curwin, id)) != NULL)
4884 	    {
4885 		list_append_string(rettv->vval.v_list,
4886 						syn_id2name(m->hlg_id), -1);
4887 		list_append_string(rettv->vval.v_list, m->pattern, -1);
4888 	    }
4889 	    else
4890 	    {
4891 		list_append_string(rettv->vval.v_list, NULL, -1);
4892 		list_append_string(rettv->vval.v_list, NULL, -1);
4893 	    }
4894 	}
4895 # endif
4896     }
4897 }
4898 
4899 /*
4900  * "matchdelete()" function
4901  */
4902     void
4903 f_matchdelete(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4904 {
4905 # ifdef FEAT_SEARCH_EXTRA
4906     win_T   *win = get_optional_window(argvars, 1);
4907 
4908     if (win == NULL)
4909 	rettv->vval.v_number = -1;
4910     else
4911 	rettv->vval.v_number = match_delete(win,
4912 				       (int)tv_get_number(&argvars[0]), TRUE);
4913 # endif
4914 }
4915 #endif
4916 
4917 #if defined(FEAT_SEARCH_EXTRA) || defined(PROTO)
4918 /*
4919  * ":[N]match {group} {pattern}"
4920  * Sets nextcmd to the start of the next command, if any.  Also called when
4921  * skipping commands to find the next command.
4922  */
4923     void
4924 ex_match(exarg_T *eap)
4925 {
4926     char_u	*p;
4927     char_u	*g = NULL;
4928     char_u	*end;
4929     int		c;
4930     int		id;
4931 
4932     if (eap->line2 <= 3)
4933 	id = eap->line2;
4934     else
4935     {
4936 	emsg(_(e_invcmd));
4937 	return;
4938     }
4939 
4940     // First clear any old pattern.
4941     if (!eap->skip)
4942 	match_delete(curwin, id, FALSE);
4943 
4944     if (ends_excmd(*eap->arg))
4945 	end = eap->arg;
4946     else if ((STRNICMP(eap->arg, "none", 4) == 0
4947 		&& (VIM_ISWHITE(eap->arg[4]) || ends_excmd(eap->arg[4]))))
4948 	end = eap->arg + 4;
4949     else
4950     {
4951 	p = skiptowhite(eap->arg);
4952 	if (!eap->skip)
4953 	    g = vim_strnsave(eap->arg, (int)(p - eap->arg));
4954 	p = skipwhite(p);
4955 	if (*p == NUL)
4956 	{
4957 	    // There must be two arguments.
4958 	    vim_free(g);
4959 	    semsg(_(e_invarg2), eap->arg);
4960 	    return;
4961 	}
4962 	end = skip_regexp(p + 1, *p, TRUE, NULL);
4963 	if (!eap->skip)
4964 	{
4965 	    if (*end != NUL && !ends_excmd(*skipwhite(end + 1)))
4966 	    {
4967 		vim_free(g);
4968 		eap->errmsg = e_trailing;
4969 		return;
4970 	    }
4971 	    if (*end != *p)
4972 	    {
4973 		vim_free(g);
4974 		semsg(_(e_invarg2), p);
4975 		return;
4976 	    }
4977 
4978 	    c = *end;
4979 	    *end = NUL;
4980 	    match_add(curwin, g, p + 1, 10, id, NULL, NULL);
4981 	    vim_free(g);
4982 	    *end = c;
4983 	}
4984     }
4985     eap->nextcmd = find_nextcmd(end);
4986 }
4987 #endif
4988