xref: /vim-8.2.3635/src/highlight.c (revision ed37d9b3)
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, 3,
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 (!init && ends_excmd2(line - 1, 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_excmd2(line, 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_excmd2(line, from_start) || ends_excmd2(line, to_start))
724 	{
725 	    semsg(_("E412: Not enough arguments: \":highlight link %s\""),
726 								  from_start);
727 	    return;
728 	}
729 
730 	if (!ends_excmd2(line, 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 	if (ends_excmd2(line, linep))
785 	{
786 #ifdef FEAT_GUI
787 	    // First, we do not destroy the old values, but allocate the new
788 	    // ones and update the display. THEN we destroy the old values.
789 	    // If we destroy the old values first, then the old values
790 	    // (such as GuiFont's or GuiFontset's) will still be displayed but
791 	    // invalid because they were free'd.
792 	    if (gui.in_use)
793 	    {
794 # ifdef FEAT_BEVAL_TIP
795 		gui_init_tooltip_font();
796 # endif
797 # if defined(FEAT_MENU) && (defined(FEAT_GUI_ATHENA) || defined(FEAT_GUI_MOTIF))
798 		gui_init_menu_font();
799 # endif
800 	    }
801 # if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_X11)
802 	    gui_mch_def_colors();
803 # endif
804 # ifdef FEAT_GUI_X11
805 #  ifdef FEAT_MENU
806 
807 	    // This only needs to be done when there is no Menu highlight
808 	    // group defined by default, which IS currently the case.
809 	    gui_mch_new_menu_colors();
810 #  endif
811 	    if (gui.in_use)
812 	    {
813 		gui_new_scrollbar_colors();
814 #  ifdef FEAT_BEVAL_GUI
815 		gui_mch_new_tooltip_colors();
816 #  endif
817 #  ifdef FEAT_MENU
818 		gui_mch_new_menu_font();
819 #  endif
820 	    }
821 # endif
822 
823 	    // Ok, we're done allocating the new default graphics items.
824 	    // The screen should already be refreshed at this point.
825 	    // It is now Ok to clear out the old data.
826 #endif
827 #ifdef FEAT_EVAL
828 	    do_unlet((char_u *)"g:colors_name", TRUE);
829 #endif
830 	    restore_cterm_colors();
831 
832 	    /*
833 	     * Clear all default highlight groups and load the defaults.
834 	     */
835 	    for (idx = 0; idx < highlight_ga.ga_len; ++idx)
836 		highlight_clear(idx);
837 	    init_highlight(TRUE, TRUE);
838 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
839 	    if (USE_24BIT)
840 		highlight_gui_started();
841 	    else
842 #endif
843 		highlight_changed();
844 	    redraw_later_clear();
845 	    return;
846 	}
847 	line = linep;
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_excmd2(line, 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 #ifdef FEAT_VTP
1489 	    control_console_color_rgb();
1490 #endif
1491 	}
1492 #ifdef FEAT_TERMINAL
1493 	else if (is_terminal_group)
1494 	    set_terminal_default_colors(
1495 		    HL_TABLE()[idx].sg_cterm_fg, HL_TABLE()[idx].sg_cterm_bg);
1496 #endif
1497 #ifdef FEAT_GUI_X11
1498 # ifdef FEAT_MENU
1499 	else if (is_menu_group)
1500 	{
1501 	    if (gui.in_use && do_colors)
1502 		gui_mch_new_menu_colors();
1503 	}
1504 # endif
1505 	else if (is_scrollbar_group)
1506 	{
1507 	    if (gui.in_use && do_colors)
1508 		gui_new_scrollbar_colors();
1509 	    else
1510 		set_hl_attr(idx);
1511 	}
1512 # ifdef FEAT_BEVAL_GUI
1513 	else if (is_tooltip_group)
1514 	{
1515 	    if (gui.in_use && do_colors)
1516 		gui_mch_new_tooltip_colors();
1517 	}
1518 # endif
1519 #endif
1520 	else
1521 	    set_hl_attr(idx);
1522 #ifdef FEAT_EVAL
1523 	HL_TABLE()[idx].sg_script_ctx = current_sctx;
1524 	HL_TABLE()[idx].sg_script_ctx.sc_lnum += SOURCING_LNUM;
1525 #endif
1526     }
1527 
1528     vim_free(key);
1529     vim_free(arg);
1530 
1531     // Only call highlight_changed() once, after a sequence of highlight
1532     // commands, and only if an attribute actually changed.
1533     if ((did_change
1534 	   || memcmp(&HL_TABLE()[idx], &item_before, sizeof(item_before)) != 0)
1535 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
1536 	    && !did_highlight_changed
1537 #endif
1538        )
1539     {
1540 	// Do not trigger a redraw when highlighting is changed while
1541 	// redrawing.  This may happen when evaluating 'statusline' changes the
1542 	// StatusLine group.
1543 	if (!updating_screen)
1544 	    redraw_all_later(NOT_VALID);
1545 	need_highlight_changed = TRUE;
1546     }
1547 }
1548 
1549 #if defined(EXITFREE) || defined(PROTO)
1550     void
1551 free_highlight(void)
1552 {
1553     int	    i;
1554 
1555     for (i = 0; i < highlight_ga.ga_len; ++i)
1556     {
1557 	highlight_clear(i);
1558 	vim_free(HL_TABLE()[i].sg_name);
1559 	vim_free(HL_TABLE()[i].sg_name_u);
1560     }
1561     ga_clear(&highlight_ga);
1562 }
1563 #endif
1564 
1565 /*
1566  * Reset the cterm colors to what they were before Vim was started, if
1567  * possible.  Otherwise reset them to zero.
1568  */
1569     void
1570 restore_cterm_colors(void)
1571 {
1572 #if defined(MSWIN) && !defined(FEAT_GUI_MSWIN)
1573     // Since t_me has been set, this probably means that the user
1574     // wants to use this as default colors.  Need to reset default
1575     // background/foreground colors.
1576     mch_set_normal_colors();
1577 #else
1578 # ifdef VIMDLL
1579     if (!gui.in_use)
1580     {
1581 	mch_set_normal_colors();
1582 	return;
1583     }
1584 # endif
1585     cterm_normal_fg_color = 0;
1586     cterm_normal_fg_bold = 0;
1587     cterm_normal_bg_color = 0;
1588 # ifdef FEAT_TERMGUICOLORS
1589     cterm_normal_fg_gui_color = INVALCOLOR;
1590     cterm_normal_bg_gui_color = INVALCOLOR;
1591 # endif
1592 #endif
1593 }
1594 
1595 /*
1596  * Return TRUE if highlight group "idx" has any settings.
1597  * When "check_link" is TRUE also check for an existing link.
1598  */
1599     static int
1600 hl_has_settings(int idx, int check_link)
1601 {
1602     return (   HL_TABLE()[idx].sg_term_attr != 0
1603 	    || HL_TABLE()[idx].sg_cterm_attr != 0
1604 	    || HL_TABLE()[idx].sg_cterm_fg != 0
1605 	    || HL_TABLE()[idx].sg_cterm_bg != 0
1606 #ifdef FEAT_GUI
1607 	    || HL_TABLE()[idx].sg_gui_attr != 0
1608 	    || HL_TABLE()[idx].sg_gui_fg_name != NULL
1609 	    || HL_TABLE()[idx].sg_gui_bg_name != NULL
1610 	    || HL_TABLE()[idx].sg_gui_sp_name != NULL
1611 	    || HL_TABLE()[idx].sg_font_name != NULL
1612 #endif
1613 	    || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK)));
1614 }
1615 
1616 /*
1617  * Clear highlighting for one group.
1618  */
1619     static void
1620 highlight_clear(int idx)
1621 {
1622     HL_TABLE()[idx].sg_cleared = TRUE;
1623 
1624     HL_TABLE()[idx].sg_term = 0;
1625     VIM_CLEAR(HL_TABLE()[idx].sg_start);
1626     VIM_CLEAR(HL_TABLE()[idx].sg_stop);
1627     HL_TABLE()[idx].sg_term_attr = 0;
1628     HL_TABLE()[idx].sg_cterm = 0;
1629     HL_TABLE()[idx].sg_cterm_bold = FALSE;
1630     HL_TABLE()[idx].sg_cterm_fg = 0;
1631     HL_TABLE()[idx].sg_cterm_bg = 0;
1632     HL_TABLE()[idx].sg_cterm_attr = 0;
1633 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
1634     HL_TABLE()[idx].sg_gui = 0;
1635     VIM_CLEAR(HL_TABLE()[idx].sg_gui_fg_name);
1636     VIM_CLEAR(HL_TABLE()[idx].sg_gui_bg_name);
1637     VIM_CLEAR(HL_TABLE()[idx].sg_gui_sp_name);
1638 #endif
1639 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
1640     HL_TABLE()[idx].sg_gui_fg = INVALCOLOR;
1641     HL_TABLE()[idx].sg_gui_bg = INVALCOLOR;
1642 #endif
1643 #ifdef FEAT_GUI
1644     HL_TABLE()[idx].sg_gui_sp = INVALCOLOR;
1645     gui_mch_free_font(HL_TABLE()[idx].sg_font);
1646     HL_TABLE()[idx].sg_font = NOFONT;
1647 # ifdef FEAT_XFONTSET
1648     gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
1649     HL_TABLE()[idx].sg_fontset = NOFONTSET;
1650 # endif
1651     VIM_CLEAR(HL_TABLE()[idx].sg_font_name);
1652     HL_TABLE()[idx].sg_gui_attr = 0;
1653 #endif
1654 #ifdef FEAT_EVAL
1655     // Clear the script ID only when there is no link, since that is not
1656     // cleared.
1657     if (HL_TABLE()[idx].sg_link == 0)
1658     {
1659 	HL_TABLE()[idx].sg_script_ctx.sc_sid = 0;
1660 	HL_TABLE()[idx].sg_script_ctx.sc_lnum = 0;
1661     }
1662 #endif
1663 }
1664 
1665 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
1666 /*
1667  * Set the normal foreground and background colors according to the "Normal"
1668  * highlighting group.  For X11 also set "Menu", "Scrollbar", and
1669  * "Tooltip" colors.
1670  */
1671     void
1672 set_normal_colors(void)
1673 {
1674 # ifdef FEAT_GUI
1675 #  ifdef FEAT_TERMGUICOLORS
1676     if (gui.in_use)
1677 #  endif
1678     {
1679 	if (set_group_colors((char_u *)"Normal",
1680 				 &gui.norm_pixel, &gui.back_pixel,
1681 				 FALSE, TRUE, FALSE))
1682 	{
1683 	    gui_mch_new_colors();
1684 	    must_redraw = CLEAR;
1685 	}
1686 #  ifdef FEAT_GUI_X11
1687 	if (set_group_colors((char_u *)"Menu",
1688 			     &gui.menu_fg_pixel, &gui.menu_bg_pixel,
1689 			     TRUE, FALSE, FALSE))
1690 	{
1691 #   ifdef FEAT_MENU
1692 	    gui_mch_new_menu_colors();
1693 #   endif
1694 	    must_redraw = CLEAR;
1695 	}
1696 #   ifdef FEAT_BEVAL_GUI
1697 	if (set_group_colors((char_u *)"Tooltip",
1698 			     &gui.tooltip_fg_pixel, &gui.tooltip_bg_pixel,
1699 			     FALSE, FALSE, TRUE))
1700 	{
1701 #    ifdef FEAT_TOOLBAR
1702 	    gui_mch_new_tooltip_colors();
1703 #    endif
1704 	    must_redraw = CLEAR;
1705 	}
1706 #   endif
1707 	if (set_group_colors((char_u *)"Scrollbar",
1708 			&gui.scroll_fg_pixel, &gui.scroll_bg_pixel,
1709 			FALSE, FALSE, FALSE))
1710 	{
1711 	    gui_new_scrollbar_colors();
1712 	    must_redraw = CLEAR;
1713 	}
1714 #  endif
1715     }
1716 # endif
1717 # ifdef FEAT_TERMGUICOLORS
1718 #  ifdef FEAT_GUI
1719     else
1720 #  endif
1721     {
1722 	int		idx;
1723 
1724 	idx = syn_name2id((char_u *)"Normal") - 1;
1725 	if (idx >= 0)
1726 	{
1727 	    gui_do_one_color(idx, FALSE, FALSE);
1728 
1729 	    // If the normal fg or bg color changed a complete redraw is
1730 	    // required.
1731 	    if (cterm_normal_fg_gui_color != HL_TABLE()[idx].sg_gui_fg
1732 		    || cterm_normal_bg_gui_color != HL_TABLE()[idx].sg_gui_bg)
1733 	    {
1734 		// if the GUI color is INVALCOLOR then we use the default cterm
1735 		// color
1736 		cterm_normal_fg_gui_color = HL_TABLE()[idx].sg_gui_fg;
1737 		cterm_normal_bg_gui_color = HL_TABLE()[idx].sg_gui_bg;
1738 		must_redraw = CLEAR;
1739 	    }
1740 	}
1741     }
1742 # endif
1743 }
1744 #endif
1745 
1746 #if defined(FEAT_GUI) || defined(PROTO)
1747 /*
1748  * Set the colors for "Normal", "Menu", "Tooltip" or "Scrollbar".
1749  */
1750     static int
1751 set_group_colors(
1752     char_u	*name,
1753     guicolor_T	*fgp,
1754     guicolor_T	*bgp,
1755     int		do_menu,
1756     int		use_norm,
1757     int		do_tooltip)
1758 {
1759     int		idx;
1760 
1761     idx = syn_name2id(name) - 1;
1762     if (idx >= 0)
1763     {
1764 	gui_do_one_color(idx, do_menu, do_tooltip);
1765 
1766 	if (HL_TABLE()[idx].sg_gui_fg != INVALCOLOR)
1767 	    *fgp = HL_TABLE()[idx].sg_gui_fg;
1768 	else if (use_norm)
1769 	    *fgp = gui.def_norm_pixel;
1770 	if (HL_TABLE()[idx].sg_gui_bg != INVALCOLOR)
1771 	    *bgp = HL_TABLE()[idx].sg_gui_bg;
1772 	else if (use_norm)
1773 	    *bgp = gui.def_back_pixel;
1774 	return TRUE;
1775     }
1776     return FALSE;
1777 }
1778 
1779 /*
1780  * Get the font of the "Normal" group.
1781  * Returns "" when it's not found or not set.
1782  */
1783     char_u *
1784 hl_get_font_name(void)
1785 {
1786     int		id;
1787     char_u	*s;
1788 
1789     id = syn_name2id((char_u *)"Normal");
1790     if (id > 0)
1791     {
1792 	s = HL_TABLE()[id - 1].sg_font_name;
1793 	if (s != NULL)
1794 	    return s;
1795     }
1796     return (char_u *)"";
1797 }
1798 
1799 /*
1800  * Set font for "Normal" group.  Called by gui_mch_init_font() when a font has
1801  * actually chosen to be used.
1802  */
1803     void
1804 hl_set_font_name(char_u *font_name)
1805 {
1806     int	    id;
1807 
1808     id = syn_name2id((char_u *)"Normal");
1809     if (id > 0)
1810     {
1811 	vim_free(HL_TABLE()[id - 1].sg_font_name);
1812 	HL_TABLE()[id - 1].sg_font_name = vim_strsave(font_name);
1813     }
1814 }
1815 
1816 /*
1817  * Set background color for "Normal" group.  Called by gui_set_bg_color()
1818  * when the color is known.
1819  */
1820     void
1821 hl_set_bg_color_name(
1822     char_u  *name)	    // must have been allocated
1823 {
1824     int	    id;
1825 
1826     if (name != NULL)
1827     {
1828 	id = syn_name2id((char_u *)"Normal");
1829 	if (id > 0)
1830 	{
1831 	    vim_free(HL_TABLE()[id - 1].sg_gui_bg_name);
1832 	    HL_TABLE()[id - 1].sg_gui_bg_name = name;
1833 	}
1834     }
1835 }
1836 
1837 /*
1838  * Set foreground color for "Normal" group.  Called by gui_set_fg_color()
1839  * when the color is known.
1840  */
1841     void
1842 hl_set_fg_color_name(
1843     char_u  *name)	    // must have been allocated
1844 {
1845     int	    id;
1846 
1847     if (name != NULL)
1848     {
1849 	id = syn_name2id((char_u *)"Normal");
1850 	if (id > 0)
1851 	{
1852 	    vim_free(HL_TABLE()[id - 1].sg_gui_fg_name);
1853 	    HL_TABLE()[id - 1].sg_gui_fg_name = name;
1854 	}
1855     }
1856 }
1857 
1858 /*
1859  * Return the handle for a font name.
1860  * Returns NOFONT when failed.
1861  */
1862     static GuiFont
1863 font_name2handle(char_u *name)
1864 {
1865     if (STRCMP(name, "NONE") == 0)
1866 	return NOFONT;
1867 
1868     return gui_mch_get_font(name, TRUE);
1869 }
1870 
1871 # ifdef FEAT_XFONTSET
1872 /*
1873  * Return the handle for a fontset name.
1874  * Returns NOFONTSET when failed.
1875  */
1876     static GuiFontset
1877 fontset_name2handle(char_u *name, int fixed_width)
1878 {
1879     if (STRCMP(name, "NONE") == 0)
1880 	return NOFONTSET;
1881 
1882     return gui_mch_get_fontset(name, TRUE, fixed_width);
1883 }
1884 # endif
1885 
1886 /*
1887  * Get the font or fontset for one highlight group.
1888  */
1889     static void
1890 hl_do_font(
1891     int		idx,
1892     char_u	*arg,
1893     int		do_normal,		// set normal font
1894     int		do_menu UNUSED,		// set menu font
1895     int		do_tooltip UNUSED,	// set tooltip font
1896     int		free_font)		// free current font/fontset
1897 {
1898 # ifdef FEAT_XFONTSET
1899     // If 'guifontset' is not empty, first try using the name as a
1900     // fontset.  If that doesn't work, use it as a font name.
1901     if (*p_guifontset != NUL
1902 #  ifdef FONTSET_ALWAYS
1903 	|| do_menu
1904 #  endif
1905 #  ifdef FEAT_BEVAL_TIP
1906 	// In Athena & Motif, the Tooltip highlight group is always a fontset
1907 	|| do_tooltip
1908 #  endif
1909 	    )
1910     {
1911 	if (free_font)
1912 	    gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
1913 	HL_TABLE()[idx].sg_fontset = fontset_name2handle(arg, 0
1914 #  ifdef FONTSET_ALWAYS
1915 		|| do_menu
1916 #  endif
1917 #  ifdef FEAT_BEVAL_TIP
1918 		|| do_tooltip
1919 #  endif
1920 		);
1921     }
1922     if (HL_TABLE()[idx].sg_fontset != NOFONTSET)
1923     {
1924 	// If it worked and it's the Normal group, use it as the normal
1925 	// fontset.  Same for the Menu group.
1926 	if (do_normal)
1927 	    gui_init_font(arg, TRUE);
1928 #   if (defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_ATHENA)) && defined(FEAT_MENU)
1929 	if (do_menu)
1930 	{
1931 #    ifdef FONTSET_ALWAYS
1932 	    gui.menu_fontset = HL_TABLE()[idx].sg_fontset;
1933 #    else
1934 	    // YIKES!  This is a bug waiting to crash the program
1935 	    gui.menu_font = HL_TABLE()[idx].sg_fontset;
1936 #    endif
1937 	    gui_mch_new_menu_font();
1938 	}
1939 #    ifdef FEAT_BEVAL_GUI
1940 	if (do_tooltip)
1941 	{
1942 	    // The Athena widget set cannot currently handle switching between
1943 	    // displaying a single font and a fontset.
1944 	    // If the XtNinternational resource is set to True at widget
1945 	    // creation, then a fontset is always used, otherwise an
1946 	    // XFontStruct is used.
1947 	    gui.tooltip_fontset = (XFontSet)HL_TABLE()[idx].sg_fontset;
1948 	    gui_mch_new_tooltip_font();
1949 	}
1950 #    endif
1951 #   endif
1952     }
1953     else
1954 # endif
1955     {
1956 	if (free_font)
1957 	    gui_mch_free_font(HL_TABLE()[idx].sg_font);
1958 	HL_TABLE()[idx].sg_font = font_name2handle(arg);
1959 	// If it worked and it's the Normal group, use it as the
1960 	// normal font.  Same for the Menu group.
1961 	if (HL_TABLE()[idx].sg_font != NOFONT)
1962 	{
1963 	    if (do_normal)
1964 		gui_init_font(arg, FALSE);
1965 #ifndef FONTSET_ALWAYS
1966 # if (defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_ATHENA)) && defined(FEAT_MENU)
1967 	    if (do_menu)
1968 	    {
1969 		gui.menu_font = HL_TABLE()[idx].sg_font;
1970 		gui_mch_new_menu_font();
1971 	    }
1972 # endif
1973 #endif
1974 	}
1975     }
1976 }
1977 
1978 #endif // FEAT_GUI
1979 
1980 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
1981 /*
1982  * Return the handle for a color name.
1983  * Returns INVALCOLOR when failed.
1984  */
1985     guicolor_T
1986 color_name2handle(char_u *name)
1987 {
1988     if (STRCMP(name, "NONE") == 0)
1989 	return INVALCOLOR;
1990 
1991     if (STRICMP(name, "fg") == 0 || STRICMP(name, "foreground") == 0)
1992     {
1993 #if defined(FEAT_TERMGUICOLORS) && defined(FEAT_GUI)
1994 	if (gui.in_use)
1995 #endif
1996 #ifdef FEAT_GUI
1997 	    return gui.norm_pixel;
1998 #endif
1999 #ifdef FEAT_TERMGUICOLORS
2000 	if (cterm_normal_fg_gui_color != INVALCOLOR)
2001 	    return cterm_normal_fg_gui_color;
2002 	// Guess that the foreground is black or white.
2003 	return GUI_GET_COLOR((char_u *)(*p_bg == 'l' ? "black" : "white"));
2004 #endif
2005     }
2006     if (STRICMP(name, "bg") == 0 || STRICMP(name, "background") == 0)
2007     {
2008 #if defined(FEAT_TERMGUICOLORS) && defined(FEAT_GUI)
2009 	if (gui.in_use)
2010 #endif
2011 #ifdef FEAT_GUI
2012 	    return gui.back_pixel;
2013 #endif
2014 #ifdef FEAT_TERMGUICOLORS
2015 	if (cterm_normal_bg_gui_color != INVALCOLOR)
2016 	    return cterm_normal_bg_gui_color;
2017 	// Guess that the background is white or black.
2018 	return GUI_GET_COLOR((char_u *)(*p_bg == 'l' ? "white" : "black"));
2019 #endif
2020     }
2021 
2022     return GUI_GET_COLOR(name);
2023 }
2024 #endif
2025 
2026 /*
2027  * Table with the specifications for an attribute number.
2028  * Note that this table is used by ALL buffers.  This is required because the
2029  * GUI can redraw at any time for any buffer.
2030  */
2031 static garray_T	term_attr_table = {0, 0, 0, 0, NULL};
2032 
2033 #define TERM_ATTR_ENTRY(idx) ((attrentry_T *)term_attr_table.ga_data)[idx]
2034 
2035 static garray_T	cterm_attr_table = {0, 0, 0, 0, NULL};
2036 
2037 #define CTERM_ATTR_ENTRY(idx) ((attrentry_T *)cterm_attr_table.ga_data)[idx]
2038 
2039 #ifdef FEAT_GUI
2040 static garray_T	gui_attr_table = {0, 0, 0, 0, NULL};
2041 
2042 #define GUI_ATTR_ENTRY(idx) ((attrentry_T *)gui_attr_table.ga_data)[idx]
2043 #endif
2044 
2045 /*
2046  * Return the attr number for a set of colors and font.
2047  * Add a new entry to the term_attr_table, cterm_attr_table or gui_attr_table
2048  * if the combination is new.
2049  * Return 0 for error (no more room).
2050  */
2051     static int
2052 get_attr_entry(garray_T *table, attrentry_T *aep)
2053 {
2054     int		i;
2055     attrentry_T	*taep;
2056     static int	recursive = FALSE;
2057 
2058     /*
2059      * Init the table, in case it wasn't done yet.
2060      */
2061     table->ga_itemsize = sizeof(attrentry_T);
2062     table->ga_growsize = 7;
2063 
2064     /*
2065      * Try to find an entry with the same specifications.
2066      */
2067     for (i = 0; i < table->ga_len; ++i)
2068     {
2069 	taep = &(((attrentry_T *)table->ga_data)[i]);
2070 	if (	   aep->ae_attr == taep->ae_attr
2071 		&& (
2072 #ifdef FEAT_GUI
2073 		       (table == &gui_attr_table
2074 			&& (aep->ae_u.gui.fg_color == taep->ae_u.gui.fg_color
2075 			    && aep->ae_u.gui.bg_color
2076 						    == taep->ae_u.gui.bg_color
2077 			    && aep->ae_u.gui.sp_color
2078 						    == taep->ae_u.gui.sp_color
2079 			    && aep->ae_u.gui.font == taep->ae_u.gui.font
2080 #  ifdef FEAT_XFONTSET
2081 			    && aep->ae_u.gui.fontset == taep->ae_u.gui.fontset
2082 #  endif
2083 			    ))
2084 		    ||
2085 #endif
2086 		       (table == &term_attr_table
2087 			&& (aep->ae_u.term.start == NULL)
2088 					    == (taep->ae_u.term.start == NULL)
2089 			&& (aep->ae_u.term.start == NULL
2090 			    || STRCMP(aep->ae_u.term.start,
2091 						  taep->ae_u.term.start) == 0)
2092 			&& (aep->ae_u.term.stop == NULL)
2093 					     == (taep->ae_u.term.stop == NULL)
2094 			&& (aep->ae_u.term.stop == NULL
2095 			    || STRCMP(aep->ae_u.term.stop,
2096 						  taep->ae_u.term.stop) == 0))
2097 		    || (table == &cterm_attr_table
2098 			    && aep->ae_u.cterm.fg_color
2099 						  == taep->ae_u.cterm.fg_color
2100 			    && aep->ae_u.cterm.bg_color
2101 						  == taep->ae_u.cterm.bg_color
2102 #ifdef FEAT_TERMGUICOLORS
2103 			    && aep->ae_u.cterm.fg_rgb
2104 						    == taep->ae_u.cterm.fg_rgb
2105 			    && aep->ae_u.cterm.bg_rgb
2106 						    == taep->ae_u.cterm.bg_rgb
2107 #endif
2108 		       )))
2109 
2110 	return i + ATTR_OFF;
2111     }
2112 
2113     if (table->ga_len + ATTR_OFF > MAX_TYPENR)
2114     {
2115 	/*
2116 	 * Running out of attribute entries!  remove all attributes, and
2117 	 * compute new ones for all groups.
2118 	 * When called recursively, we are really out of numbers.
2119 	 */
2120 	if (recursive)
2121 	{
2122 	    emsg(_("E424: Too many different highlighting attributes in use"));
2123 	    return 0;
2124 	}
2125 	recursive = TRUE;
2126 
2127 	clear_hl_tables();
2128 
2129 	must_redraw = CLEAR;
2130 
2131 	for (i = 0; i < highlight_ga.ga_len; ++i)
2132 	    set_hl_attr(i);
2133 
2134 	recursive = FALSE;
2135     }
2136 
2137     /*
2138      * This is a new combination of colors and font, add an entry.
2139      */
2140     if (ga_grow(table, 1) == FAIL)
2141 	return 0;
2142 
2143     taep = &(((attrentry_T *)table->ga_data)[table->ga_len]);
2144     CLEAR_POINTER(taep);
2145     taep->ae_attr = aep->ae_attr;
2146 #ifdef FEAT_GUI
2147     if (table == &gui_attr_table)
2148     {
2149 	taep->ae_u.gui.fg_color = aep->ae_u.gui.fg_color;
2150 	taep->ae_u.gui.bg_color = aep->ae_u.gui.bg_color;
2151 	taep->ae_u.gui.sp_color = aep->ae_u.gui.sp_color;
2152 	taep->ae_u.gui.font = aep->ae_u.gui.font;
2153 # ifdef FEAT_XFONTSET
2154 	taep->ae_u.gui.fontset = aep->ae_u.gui.fontset;
2155 # endif
2156     }
2157 #endif
2158     if (table == &term_attr_table)
2159     {
2160 	if (aep->ae_u.term.start == NULL)
2161 	    taep->ae_u.term.start = NULL;
2162 	else
2163 	    taep->ae_u.term.start = vim_strsave(aep->ae_u.term.start);
2164 	if (aep->ae_u.term.stop == NULL)
2165 	    taep->ae_u.term.stop = NULL;
2166 	else
2167 	    taep->ae_u.term.stop = vim_strsave(aep->ae_u.term.stop);
2168     }
2169     else if (table == &cterm_attr_table)
2170     {
2171 	taep->ae_u.cterm.fg_color = aep->ae_u.cterm.fg_color;
2172 	taep->ae_u.cterm.bg_color = aep->ae_u.cterm.bg_color;
2173 #ifdef FEAT_TERMGUICOLORS
2174 	taep->ae_u.cterm.fg_rgb = aep->ae_u.cterm.fg_rgb;
2175 	taep->ae_u.cterm.bg_rgb = aep->ae_u.cterm.bg_rgb;
2176 #endif
2177     }
2178     ++table->ga_len;
2179     return (table->ga_len - 1 + ATTR_OFF);
2180 }
2181 
2182 #if defined(FEAT_TERMINAL) || defined(PROTO)
2183 /*
2184  * Get an attribute index for a cterm entry.
2185  * Uses an existing entry when possible or adds one when needed.
2186  */
2187     int
2188 get_cterm_attr_idx(int attr, int fg, int bg)
2189 {
2190     attrentry_T		at_en;
2191 
2192     CLEAR_FIELD(at_en);
2193 #ifdef FEAT_TERMGUICOLORS
2194     at_en.ae_u.cterm.fg_rgb = INVALCOLOR;
2195     at_en.ae_u.cterm.bg_rgb = INVALCOLOR;
2196 #endif
2197     at_en.ae_attr = attr;
2198     at_en.ae_u.cterm.fg_color = fg;
2199     at_en.ae_u.cterm.bg_color = bg;
2200     return get_attr_entry(&cterm_attr_table, &at_en);
2201 }
2202 #endif
2203 
2204 #if (defined(FEAT_TERMINAL) && defined(FEAT_TERMGUICOLORS)) || defined(PROTO)
2205 /*
2206  * Get an attribute index for a 'termguicolors' entry.
2207  * Uses an existing entry when possible or adds one when needed.
2208  */
2209     int
2210 get_tgc_attr_idx(int attr, guicolor_T fg, guicolor_T bg)
2211 {
2212     attrentry_T		at_en;
2213 
2214     CLEAR_FIELD(at_en);
2215     at_en.ae_attr = attr;
2216     if (fg == INVALCOLOR && bg == INVALCOLOR)
2217     {
2218 	// If both GUI colors are not set fall back to the cterm colors.  Helps
2219 	// if the GUI only has an attribute, such as undercurl.
2220 	at_en.ae_u.cterm.fg_rgb = CTERMCOLOR;
2221 	at_en.ae_u.cterm.bg_rgb = CTERMCOLOR;
2222     }
2223     else
2224     {
2225 	at_en.ae_u.cterm.fg_rgb = fg;
2226 	at_en.ae_u.cterm.bg_rgb = bg;
2227     }
2228     return get_attr_entry(&cterm_attr_table, &at_en);
2229 }
2230 #endif
2231 
2232 #if (defined(FEAT_TERMINAL) && defined(FEAT_GUI)) || defined(PROTO)
2233 /*
2234  * Get an attribute index for a cterm entry.
2235  * Uses an existing entry when possible or adds one when needed.
2236  */
2237     int
2238 get_gui_attr_idx(int attr, guicolor_T fg, guicolor_T bg)
2239 {
2240     attrentry_T		at_en;
2241 
2242     CLEAR_FIELD(at_en);
2243     at_en.ae_attr = attr;
2244     at_en.ae_u.gui.fg_color = fg;
2245     at_en.ae_u.gui.bg_color = bg;
2246     return get_attr_entry(&gui_attr_table, &at_en);
2247 }
2248 #endif
2249 
2250 /*
2251  * Clear all highlight tables.
2252  */
2253     void
2254 clear_hl_tables(void)
2255 {
2256     int		i;
2257     attrentry_T	*taep;
2258 
2259 #ifdef FEAT_GUI
2260     ga_clear(&gui_attr_table);
2261 #endif
2262     for (i = 0; i < term_attr_table.ga_len; ++i)
2263     {
2264 	taep = &(((attrentry_T *)term_attr_table.ga_data)[i]);
2265 	vim_free(taep->ae_u.term.start);
2266 	vim_free(taep->ae_u.term.stop);
2267     }
2268     ga_clear(&term_attr_table);
2269     ga_clear(&cterm_attr_table);
2270 }
2271 
2272 /*
2273  * Combine special attributes (e.g., for spelling) with other attributes
2274  * (e.g., for syntax highlighting).
2275  * "prim_attr" overrules "char_attr".
2276  * This creates a new group when required.
2277  * Since we expect there to be few spelling mistakes we don't cache the
2278  * result.
2279  * Return the resulting attributes.
2280  */
2281     int
2282 hl_combine_attr(int char_attr, int prim_attr)
2283 {
2284     attrentry_T *char_aep = NULL;
2285     attrentry_T *spell_aep;
2286     attrentry_T new_en;
2287 
2288     if (char_attr == 0)
2289 	return prim_attr;
2290     if (char_attr <= HL_ALL && prim_attr <= HL_ALL)
2291 	return ATTR_COMBINE(char_attr, prim_attr);
2292 #ifdef FEAT_GUI
2293     if (gui.in_use)
2294     {
2295 	if (char_attr > HL_ALL)
2296 	    char_aep = syn_gui_attr2entry(char_attr);
2297 	if (char_aep != NULL)
2298 	    new_en = *char_aep;
2299 	else
2300 	{
2301 	    CLEAR_FIELD(new_en);
2302 	    new_en.ae_u.gui.fg_color = INVALCOLOR;
2303 	    new_en.ae_u.gui.bg_color = INVALCOLOR;
2304 	    new_en.ae_u.gui.sp_color = INVALCOLOR;
2305 	    if (char_attr <= HL_ALL)
2306 		new_en.ae_attr = char_attr;
2307 	}
2308 
2309 	if (prim_attr <= HL_ALL)
2310 	    new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, prim_attr);
2311 	else
2312 	{
2313 	    spell_aep = syn_gui_attr2entry(prim_attr);
2314 	    if (spell_aep != NULL)
2315 	    {
2316 		new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr,
2317 							   spell_aep->ae_attr);
2318 		if (spell_aep->ae_u.gui.fg_color != INVALCOLOR)
2319 		    new_en.ae_u.gui.fg_color = spell_aep->ae_u.gui.fg_color;
2320 		if (spell_aep->ae_u.gui.bg_color != INVALCOLOR)
2321 		    new_en.ae_u.gui.bg_color = spell_aep->ae_u.gui.bg_color;
2322 		if (spell_aep->ae_u.gui.sp_color != INVALCOLOR)
2323 		    new_en.ae_u.gui.sp_color = spell_aep->ae_u.gui.sp_color;
2324 		if (spell_aep->ae_u.gui.font != NOFONT)
2325 		    new_en.ae_u.gui.font = spell_aep->ae_u.gui.font;
2326 # ifdef FEAT_XFONTSET
2327 		if (spell_aep->ae_u.gui.fontset != NOFONTSET)
2328 		    new_en.ae_u.gui.fontset = spell_aep->ae_u.gui.fontset;
2329 # endif
2330 	    }
2331 	}
2332 	return get_attr_entry(&gui_attr_table, &new_en);
2333     }
2334 #endif
2335 
2336     if (IS_CTERM)
2337     {
2338 	if (char_attr > HL_ALL)
2339 	    char_aep = syn_cterm_attr2entry(char_attr);
2340 	if (char_aep != NULL)
2341 	    new_en = *char_aep;
2342 	else
2343 	{
2344 	    CLEAR_FIELD(new_en);
2345 #ifdef FEAT_TERMGUICOLORS
2346 	    new_en.ae_u.cterm.bg_rgb = INVALCOLOR;
2347 	    new_en.ae_u.cterm.fg_rgb = INVALCOLOR;
2348 #endif
2349 	    if (char_attr <= HL_ALL)
2350 		new_en.ae_attr = char_attr;
2351 	}
2352 
2353 	if (prim_attr <= HL_ALL)
2354 		new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, prim_attr);
2355 	else
2356 	{
2357 	    spell_aep = syn_cterm_attr2entry(prim_attr);
2358 	    if (spell_aep != NULL)
2359 	    {
2360 		new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr,
2361 							   spell_aep->ae_attr);
2362 		if (spell_aep->ae_u.cterm.fg_color > 0)
2363 		    new_en.ae_u.cterm.fg_color = spell_aep->ae_u.cterm.fg_color;
2364 		if (spell_aep->ae_u.cterm.bg_color > 0)
2365 		    new_en.ae_u.cterm.bg_color = spell_aep->ae_u.cterm.bg_color;
2366 #ifdef FEAT_TERMGUICOLORS
2367 		// If both fg and bg are not set fall back to cterm colors.
2368 		// Helps for SpellBad which uses undercurl in the GUI.
2369 		if (COLOR_INVALID(spell_aep->ae_u.cterm.fg_rgb)
2370 			&& COLOR_INVALID(spell_aep->ae_u.cterm.bg_rgb))
2371 		{
2372 		    if (spell_aep->ae_u.cterm.fg_color > 0)
2373 			new_en.ae_u.cterm.fg_rgb = CTERMCOLOR;
2374 		    if (spell_aep->ae_u.cterm.bg_color > 0)
2375 			new_en.ae_u.cterm.bg_rgb = CTERMCOLOR;
2376 		}
2377 		else
2378 		{
2379 		    if (spell_aep->ae_u.cterm.fg_rgb != INVALCOLOR)
2380 			new_en.ae_u.cterm.fg_rgb = spell_aep->ae_u.cterm.fg_rgb;
2381 		    if (spell_aep->ae_u.cterm.bg_rgb != INVALCOLOR)
2382 			new_en.ae_u.cterm.bg_rgb = spell_aep->ae_u.cterm.bg_rgb;
2383 		}
2384 #endif
2385 	    }
2386 	}
2387 	return get_attr_entry(&cterm_attr_table, &new_en);
2388     }
2389 
2390     if (char_attr > HL_ALL)
2391 	char_aep = syn_term_attr2entry(char_attr);
2392     if (char_aep != NULL)
2393 	new_en = *char_aep;
2394     else
2395     {
2396 	CLEAR_FIELD(new_en);
2397 	if (char_attr <= HL_ALL)
2398 	    new_en.ae_attr = char_attr;
2399     }
2400 
2401     if (prim_attr <= HL_ALL)
2402 	new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, prim_attr);
2403     else
2404     {
2405 	spell_aep = syn_term_attr2entry(prim_attr);
2406 	if (spell_aep != NULL)
2407 	{
2408 	    new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, spell_aep->ae_attr);
2409 	    if (spell_aep->ae_u.term.start != NULL)
2410 	    {
2411 		new_en.ae_u.term.start = spell_aep->ae_u.term.start;
2412 		new_en.ae_u.term.stop = spell_aep->ae_u.term.stop;
2413 	    }
2414 	}
2415     }
2416     return get_attr_entry(&term_attr_table, &new_en);
2417 }
2418 
2419 #ifdef FEAT_GUI
2420     attrentry_T *
2421 syn_gui_attr2entry(int attr)
2422 {
2423     attr -= ATTR_OFF;
2424     if (attr >= gui_attr_table.ga_len)	    // did ":syntax clear"
2425 	return NULL;
2426     return &(GUI_ATTR_ENTRY(attr));
2427 }
2428 #endif
2429 
2430 /*
2431  * Get the highlight attributes (HL_BOLD etc.) from an attribute nr.
2432  * Only to be used when "attr" > HL_ALL.
2433  */
2434     int
2435 syn_attr2attr(int attr)
2436 {
2437     attrentry_T	*aep;
2438 
2439 #ifdef FEAT_GUI
2440     if (gui.in_use)
2441 	aep = syn_gui_attr2entry(attr);
2442     else
2443 #endif
2444 	if (IS_CTERM)
2445 	    aep = syn_cterm_attr2entry(attr);
2446 	else
2447 	    aep = syn_term_attr2entry(attr);
2448 
2449     if (aep == NULL)	    // highlighting not set
2450 	return 0;
2451     return aep->ae_attr;
2452 }
2453 
2454 
2455     attrentry_T *
2456 syn_term_attr2entry(int attr)
2457 {
2458     attr -= ATTR_OFF;
2459     if (attr >= term_attr_table.ga_len)	    // did ":syntax clear"
2460 	return NULL;
2461     return &(TERM_ATTR_ENTRY(attr));
2462 }
2463 
2464     attrentry_T *
2465 syn_cterm_attr2entry(int attr)
2466 {
2467     attr -= ATTR_OFF;
2468     if (attr >= cterm_attr_table.ga_len)	// did ":syntax clear"
2469 	return NULL;
2470     return &(CTERM_ATTR_ENTRY(attr));
2471 }
2472 
2473 #define LIST_ATTR   1
2474 #define LIST_STRING 2
2475 #define LIST_INT    3
2476 
2477     static void
2478 highlight_list_one(int id)
2479 {
2480     hl_group_T	    *sgp;
2481     int		    didh = FALSE;
2482 
2483     sgp = &HL_TABLE()[id - 1];	    // index is ID minus one
2484 
2485     if (message_filtered(sgp->sg_name))
2486 	return;
2487 
2488     didh = highlight_list_arg(id, didh, LIST_ATTR,
2489 				    sgp->sg_term, NULL, "term");
2490     didh = highlight_list_arg(id, didh, LIST_STRING,
2491 				    0, sgp->sg_start, "start");
2492     didh = highlight_list_arg(id, didh, LIST_STRING,
2493 				    0, sgp->sg_stop, "stop");
2494 
2495     didh = highlight_list_arg(id, didh, LIST_ATTR,
2496 				    sgp->sg_cterm, NULL, "cterm");
2497     didh = highlight_list_arg(id, didh, LIST_INT,
2498 				    sgp->sg_cterm_fg, NULL, "ctermfg");
2499     didh = highlight_list_arg(id, didh, LIST_INT,
2500 				    sgp->sg_cterm_bg, NULL, "ctermbg");
2501 
2502 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
2503     didh = highlight_list_arg(id, didh, LIST_ATTR,
2504 				    sgp->sg_gui, NULL, "gui");
2505     didh = highlight_list_arg(id, didh, LIST_STRING,
2506 				    0, sgp->sg_gui_fg_name, "guifg");
2507     didh = highlight_list_arg(id, didh, LIST_STRING,
2508 				    0, sgp->sg_gui_bg_name, "guibg");
2509     didh = highlight_list_arg(id, didh, LIST_STRING,
2510 				    0, sgp->sg_gui_sp_name, "guisp");
2511 #endif
2512 #ifdef FEAT_GUI
2513     didh = highlight_list_arg(id, didh, LIST_STRING,
2514 				    0, sgp->sg_font_name, "font");
2515 #endif
2516 
2517     if (sgp->sg_link && !got_int)
2518     {
2519 	(void)syn_list_header(didh, 9999, id);
2520 	didh = TRUE;
2521 	msg_puts_attr("links to", HL_ATTR(HLF_D));
2522 	msg_putchar(' ');
2523 	msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name);
2524     }
2525 
2526     if (!didh)
2527 	highlight_list_arg(id, didh, LIST_STRING, 0, (char_u *)"cleared", "");
2528 #ifdef FEAT_EVAL
2529     if (p_verbose > 0)
2530 	last_set_msg(sgp->sg_script_ctx);
2531 #endif
2532 }
2533 
2534     static int
2535 highlight_list_arg(
2536     int		id,
2537     int		didh,
2538     int		type,
2539     int		iarg,
2540     char_u	*sarg,
2541     char	*name)
2542 {
2543     char_u	buf[100];
2544     char_u	*ts;
2545     int		i;
2546 
2547     if (got_int)
2548 	return FALSE;
2549     if (type == LIST_STRING ? (sarg != NULL) : (iarg != 0))
2550     {
2551 	ts = buf;
2552 	if (type == LIST_INT)
2553 	    sprintf((char *)buf, "%d", iarg - 1);
2554 	else if (type == LIST_STRING)
2555 	    ts = sarg;
2556 	else // type == LIST_ATTR
2557 	{
2558 	    buf[0] = NUL;
2559 	    for (i = 0; hl_attr_table[i] != 0; ++i)
2560 	    {
2561 		if (iarg & hl_attr_table[i])
2562 		{
2563 		    if (buf[0] != NUL)
2564 			vim_strcat(buf, (char_u *)",", 100);
2565 		    vim_strcat(buf, (char_u *)hl_name_table[i], 100);
2566 		    iarg &= ~hl_attr_table[i];	    // don't want "inverse"
2567 		}
2568 	    }
2569 	}
2570 
2571 	(void)syn_list_header(didh,
2572 			       (int)(vim_strsize(ts) + STRLEN(name) + 1), id);
2573 	didh = TRUE;
2574 	if (!got_int)
2575 	{
2576 	    if (*name != NUL)
2577 	    {
2578 		msg_puts_attr(name, HL_ATTR(HLF_D));
2579 		msg_puts_attr("=", HL_ATTR(HLF_D));
2580 	    }
2581 	    msg_outtrans(ts);
2582 	}
2583     }
2584     return didh;
2585 }
2586 
2587 #if (((defined(FEAT_EVAL) || defined(FEAT_PRINTER))) && defined(FEAT_SYN_HL)) || defined(PROTO)
2588 /*
2589  * Return "1" if highlight group "id" has attribute "flag".
2590  * Return NULL otherwise.
2591  */
2592     char_u *
2593 highlight_has_attr(
2594     int		id,
2595     int		flag,
2596     int		modec)	// 'g' for GUI, 'c' for cterm, 't' for term
2597 {
2598     int		attr;
2599 
2600     if (id <= 0 || id > highlight_ga.ga_len)
2601 	return NULL;
2602 
2603 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
2604     if (modec == 'g')
2605 	attr = HL_TABLE()[id - 1].sg_gui;
2606     else
2607 #endif
2608 	 if (modec == 'c')
2609 	attr = HL_TABLE()[id - 1].sg_cterm;
2610     else
2611 	attr = HL_TABLE()[id - 1].sg_term;
2612 
2613     if (attr & flag)
2614 	return (char_u *)"1";
2615     return NULL;
2616 }
2617 #endif
2618 
2619 #if (defined(FEAT_SYN_HL) && defined(FEAT_EVAL)) || defined(PROTO)
2620 /*
2621  * Return color name of highlight group "id".
2622  */
2623     char_u *
2624 highlight_color(
2625     int		id,
2626     char_u	*what,	// "font", "fg", "bg", "sp", "fg#", "bg#" or "sp#"
2627     int		modec)	// 'g' for GUI, 'c' for cterm, 't' for term
2628 {
2629     static char_u	name[20];
2630     int			n;
2631     int			fg = FALSE;
2632     int			sp = FALSE;
2633     int			font = FALSE;
2634 
2635     if (id <= 0 || id > highlight_ga.ga_len)
2636 	return NULL;
2637 
2638     if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'g')
2639 	fg = TRUE;
2640     else if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'o'
2641 	     && TOLOWER_ASC(what[2]) == 'n' && TOLOWER_ASC(what[3]) == 't')
2642 	font = TRUE;
2643     else if (TOLOWER_ASC(what[0]) == 's' && TOLOWER_ASC(what[1]) == 'p')
2644 	sp = TRUE;
2645     else if (!(TOLOWER_ASC(what[0]) == 'b' && TOLOWER_ASC(what[1]) == 'g'))
2646 	return NULL;
2647     if (modec == 'g')
2648     {
2649 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
2650 #  ifdef FEAT_GUI
2651 	// return font name
2652 	if (font)
2653 	    return HL_TABLE()[id - 1].sg_font_name;
2654 #  endif
2655 
2656 	// return #RRGGBB form (only possible when GUI is running)
2657 	if ((USE_24BIT) && what[2] == '#')
2658 	{
2659 	    guicolor_T		color;
2660 	    long_u		rgb;
2661 	    static char_u	buf[10];
2662 
2663 	    if (fg)
2664 		color = HL_TABLE()[id - 1].sg_gui_fg;
2665 	    else if (sp)
2666 #  ifdef FEAT_GUI
2667 		color = HL_TABLE()[id - 1].sg_gui_sp;
2668 #  else
2669 		color = INVALCOLOR;
2670 #  endif
2671 	    else
2672 		color = HL_TABLE()[id - 1].sg_gui_bg;
2673 	    if (color == INVALCOLOR)
2674 		return NULL;
2675 	    rgb = (long_u)GUI_MCH_GET_RGB(color);
2676 	    sprintf((char *)buf, "#%02x%02x%02x",
2677 				      (unsigned)(rgb >> 16),
2678 				      (unsigned)(rgb >> 8) & 255,
2679 				      (unsigned)rgb & 255);
2680 	    return buf;
2681 	}
2682 # endif
2683 	if (fg)
2684 	    return (HL_TABLE()[id - 1].sg_gui_fg_name);
2685 	if (sp)
2686 	    return (HL_TABLE()[id - 1].sg_gui_sp_name);
2687 	return (HL_TABLE()[id - 1].sg_gui_bg_name);
2688     }
2689     if (font || sp)
2690 	return NULL;
2691     if (modec == 'c')
2692     {
2693 	if (fg)
2694 	    n = HL_TABLE()[id - 1].sg_cterm_fg - 1;
2695 	else
2696 	    n = HL_TABLE()[id - 1].sg_cterm_bg - 1;
2697 	if (n < 0)
2698 	    return NULL;
2699 	sprintf((char *)name, "%d", n);
2700 	return name;
2701     }
2702     // term doesn't have color
2703     return NULL;
2704 }
2705 #endif
2706 
2707 #if (defined(FEAT_SYN_HL) \
2708 	    && (defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)) \
2709 	&& defined(FEAT_PRINTER)) || defined(PROTO)
2710 /*
2711  * Return color name of highlight group "id" as RGB value.
2712  */
2713     long_u
2714 highlight_gui_color_rgb(
2715     int		id,
2716     int		fg)	// TRUE = fg, FALSE = bg
2717 {
2718     guicolor_T	color;
2719 
2720     if (id <= 0 || id > highlight_ga.ga_len)
2721 	return 0L;
2722 
2723     if (fg)
2724 	color = HL_TABLE()[id - 1].sg_gui_fg;
2725     else
2726 	color = HL_TABLE()[id - 1].sg_gui_bg;
2727 
2728     if (color == INVALCOLOR)
2729 	return 0L;
2730 
2731     return GUI_MCH_GET_RGB(color);
2732 }
2733 #endif
2734 
2735 /*
2736  * Output the syntax list header.
2737  * Return TRUE when started a new line.
2738  */
2739     int
2740 syn_list_header(
2741     int	    did_header,		// did header already
2742     int	    outlen,		// length of string that comes
2743     int	    id)			// highlight group id
2744 {
2745     int	    endcol = 19;
2746     int	    newline = TRUE;
2747     int	    name_col = 0;
2748 
2749     if (!did_header)
2750     {
2751 	msg_putchar('\n');
2752 	if (got_int)
2753 	    return TRUE;
2754 	msg_outtrans(HL_TABLE()[id - 1].sg_name);
2755 	name_col = msg_col;
2756 	endcol = 15;
2757     }
2758     else if (msg_col + outlen + 1 >= Columns)
2759     {
2760 	msg_putchar('\n');
2761 	if (got_int)
2762 	    return TRUE;
2763     }
2764     else
2765     {
2766 	if (msg_col >= endcol)	// wrap around is like starting a new line
2767 	    newline = FALSE;
2768     }
2769 
2770     if (msg_col >= endcol)	// output at least one space
2771 	endcol = msg_col + 1;
2772     if (Columns <= endcol)	// avoid hang for tiny window
2773 	endcol = Columns - 1;
2774 
2775     msg_advance(endcol);
2776 
2777     // Show "xxx" with the attributes.
2778     if (!did_header)
2779     {
2780 	if (endcol == Columns - 1 && endcol <= name_col)
2781 	    msg_putchar(' ');
2782 	msg_puts_attr("xxx", syn_id2attr(id));
2783 	msg_putchar(' ');
2784     }
2785 
2786     return newline;
2787 }
2788 
2789 /*
2790  * Set the attribute numbers for a highlight group.
2791  * Called after one of the attributes has changed.
2792  */
2793     static void
2794 set_hl_attr(
2795     int		idx)	    // index in array
2796 {
2797     attrentry_T	    at_en;
2798     hl_group_T	    *sgp = HL_TABLE() + idx;
2799 
2800     // The "Normal" group doesn't need an attribute number
2801     if (sgp->sg_name_u != NULL && STRCMP(sgp->sg_name_u, "NORMAL") == 0)
2802 	return;
2803 
2804 #ifdef FEAT_GUI
2805     /*
2806      * For the GUI mode: If there are other than "normal" highlighting
2807      * attributes, need to allocate an attr number.
2808      */
2809     if (sgp->sg_gui_fg == INVALCOLOR
2810 	    && sgp->sg_gui_bg == INVALCOLOR
2811 	    && sgp->sg_gui_sp == INVALCOLOR
2812 	    && sgp->sg_font == NOFONT
2813 # ifdef FEAT_XFONTSET
2814 	    && sgp->sg_fontset == NOFONTSET
2815 # endif
2816 	    )
2817     {
2818 	sgp->sg_gui_attr = sgp->sg_gui;
2819     }
2820     else
2821     {
2822 	at_en.ae_attr = sgp->sg_gui;
2823 	at_en.ae_u.gui.fg_color = sgp->sg_gui_fg;
2824 	at_en.ae_u.gui.bg_color = sgp->sg_gui_bg;
2825 	at_en.ae_u.gui.sp_color = sgp->sg_gui_sp;
2826 	at_en.ae_u.gui.font = sgp->sg_font;
2827 # ifdef FEAT_XFONTSET
2828 	at_en.ae_u.gui.fontset = sgp->sg_fontset;
2829 # endif
2830 	sgp->sg_gui_attr = get_attr_entry(&gui_attr_table, &at_en);
2831     }
2832 #endif
2833     /*
2834      * For the term mode: If there are other than "normal" highlighting
2835      * attributes, need to allocate an attr number.
2836      */
2837     if (sgp->sg_start == NULL && sgp->sg_stop == NULL)
2838 	sgp->sg_term_attr = sgp->sg_term;
2839     else
2840     {
2841 	at_en.ae_attr = sgp->sg_term;
2842 	at_en.ae_u.term.start = sgp->sg_start;
2843 	at_en.ae_u.term.stop = sgp->sg_stop;
2844 	sgp->sg_term_attr = get_attr_entry(&term_attr_table, &at_en);
2845     }
2846 
2847     /*
2848      * For the color term mode: If there are other than "normal"
2849      * highlighting attributes, need to allocate an attr number.
2850      */
2851     if (sgp->sg_cterm_fg == 0 && sgp->sg_cterm_bg == 0
2852 # ifdef FEAT_TERMGUICOLORS
2853 	    && sgp->sg_gui_fg == INVALCOLOR
2854 	    && sgp->sg_gui_bg == INVALCOLOR
2855 # endif
2856 	    )
2857 	sgp->sg_cterm_attr = sgp->sg_cterm;
2858     else
2859     {
2860 	at_en.ae_attr = sgp->sg_cterm;
2861 	at_en.ae_u.cterm.fg_color = sgp->sg_cterm_fg;
2862 	at_en.ae_u.cterm.bg_color = sgp->sg_cterm_bg;
2863 # ifdef FEAT_TERMGUICOLORS
2864 #  ifdef MSWIN
2865 #   ifdef VIMDLL
2866 	// Only when not using the GUI.
2867 	if (!gui.in_use && !gui.starting)
2868 #   endif
2869 	{
2870 	    int id;
2871 	    guicolor_T fg, bg;
2872 
2873 	    id = syn_name2id((char_u *)"Normal");
2874 	    if (id > 0)
2875 	    {
2876 		syn_id2colors(id, &fg, &bg);
2877 		if (sgp->sg_gui_fg == INVALCOLOR)
2878 		    sgp->sg_gui_fg = fg;
2879 		if (sgp->sg_gui_bg == INVALCOLOR)
2880 		    sgp->sg_gui_bg = bg;
2881 	    }
2882 
2883 	}
2884 #  endif
2885 	at_en.ae_u.cterm.fg_rgb = GUI_MCH_GET_RGB2(sgp->sg_gui_fg);
2886 	at_en.ae_u.cterm.bg_rgb = GUI_MCH_GET_RGB2(sgp->sg_gui_bg);
2887 	if (at_en.ae_u.cterm.fg_rgb == INVALCOLOR
2888 		&& at_en.ae_u.cterm.bg_rgb == INVALCOLOR)
2889 	{
2890 	    // If both fg and bg are invalid fall back to the cterm colors.
2891 	    // Helps when the GUI only uses an attribute, e.g. undercurl.
2892 	    at_en.ae_u.cterm.fg_rgb = CTERMCOLOR;
2893 	    at_en.ae_u.cterm.bg_rgb = CTERMCOLOR;
2894 	}
2895 # endif
2896 	sgp->sg_cterm_attr = get_attr_entry(&cterm_attr_table, &at_en);
2897     }
2898 }
2899 
2900 /*
2901  * Lookup a highlight group name and return its ID.
2902  * If it is not found, 0 is returned.
2903  */
2904     int
2905 syn_name2id(char_u *name)
2906 {
2907     int		i;
2908     char_u	name_u[200];
2909 
2910     // Avoid using stricmp() too much, it's slow on some systems
2911     // Avoid alloc()/free(), these are slow too.  ID names over 200 chars
2912     // don't deserve to be found!
2913     vim_strncpy(name_u, name, 199);
2914     vim_strup(name_u);
2915     for (i = highlight_ga.ga_len; --i >= 0; )
2916 	if (HL_TABLE()[i].sg_name_u != NULL
2917 		&& STRCMP(name_u, HL_TABLE()[i].sg_name_u) == 0)
2918 	    break;
2919     return i + 1;
2920 }
2921 
2922 /*
2923  * Lookup a highlight group name and return its attributes.
2924  * Return zero if not found.
2925  */
2926     int
2927 syn_name2attr(char_u *name)
2928 {
2929     int id = syn_name2id(name);
2930 
2931     if (id != 0)
2932 	return syn_id2attr(id);
2933     return 0;
2934 }
2935 
2936 #if defined(FEAT_EVAL) || defined(PROTO)
2937 /*
2938  * Return TRUE if highlight group "name" exists.
2939  */
2940     int
2941 highlight_exists(char_u *name)
2942 {
2943     return (syn_name2id(name) > 0);
2944 }
2945 
2946 # if defined(FEAT_SEARCH_EXTRA) || defined(PROTO)
2947 /*
2948  * Return the name of highlight group "id".
2949  * When not a valid ID return an empty string.
2950  */
2951     char_u *
2952 syn_id2name(int id)
2953 {
2954     if (id <= 0 || id > highlight_ga.ga_len)
2955 	return (char_u *)"";
2956     return HL_TABLE()[id - 1].sg_name;
2957 }
2958 # endif
2959 #endif
2960 
2961 /*
2962  * Like syn_name2id(), but take a pointer + length argument.
2963  */
2964     int
2965 syn_namen2id(char_u *linep, int len)
2966 {
2967     char_u  *name;
2968     int	    id = 0;
2969 
2970     name = vim_strnsave(linep, len);
2971     if (name != NULL)
2972     {
2973 	id = syn_name2id(name);
2974 	vim_free(name);
2975     }
2976     return id;
2977 }
2978 
2979 /*
2980  * Find highlight group name in the table and return its ID.
2981  * The argument is a pointer to the name and the length of the name.
2982  * If it doesn't exist yet, a new entry is created.
2983  * Return 0 for failure.
2984  */
2985     int
2986 syn_check_group(char_u *pp, int len)
2987 {
2988     int	    id;
2989     char_u  *name;
2990 
2991     name = vim_strnsave(pp, len);
2992     if (name == NULL)
2993 	return 0;
2994 
2995     id = syn_name2id(name);
2996     if (id == 0)			// doesn't exist yet
2997 	id = syn_add_group(name);
2998     else
2999 	vim_free(name);
3000     return id;
3001 }
3002 
3003 /*
3004  * Add new highlight group and return its ID.
3005  * "name" must be an allocated string, it will be consumed.
3006  * Return 0 for failure.
3007  */
3008     static int
3009 syn_add_group(char_u *name)
3010 {
3011     char_u	*p;
3012     char_u	*name_up;
3013 
3014     // Check that the name is ASCII letters, digits and underscore.
3015     for (p = name; *p != NUL; ++p)
3016     {
3017 	if (!vim_isprintc(*p))
3018 	{
3019 	    emsg(_("E669: Unprintable character in group name"));
3020 	    vim_free(name);
3021 	    return 0;
3022 	}
3023 	else if (!ASCII_ISALNUM(*p) && *p != '_')
3024 	{
3025 	    // This is an error, but since there previously was no check only
3026 	    // give a warning.
3027 	    msg_source(HL_ATTR(HLF_W));
3028 	    msg(_("W18: Invalid character in group name"));
3029 	    break;
3030 	}
3031     }
3032 
3033     /*
3034      * First call for this growarray: init growing array.
3035      */
3036     if (highlight_ga.ga_data == NULL)
3037     {
3038 	highlight_ga.ga_itemsize = sizeof(hl_group_T);
3039 	highlight_ga.ga_growsize = 10;
3040     }
3041 
3042     if (highlight_ga.ga_len >= MAX_HL_ID)
3043     {
3044 	emsg(_("E849: Too many highlight and syntax groups"));
3045 	vim_free(name);
3046 	return 0;
3047     }
3048 
3049     /*
3050      * Make room for at least one other syntax_highlight entry.
3051      */
3052     if (ga_grow(&highlight_ga, 1) == FAIL)
3053     {
3054 	vim_free(name);
3055 	return 0;
3056     }
3057 
3058     name_up = vim_strsave_up(name);
3059     if (name_up == NULL)
3060     {
3061 	vim_free(name);
3062 	return 0;
3063     }
3064 
3065     CLEAR_POINTER(&(HL_TABLE()[highlight_ga.ga_len]));
3066     HL_TABLE()[highlight_ga.ga_len].sg_name = name;
3067     HL_TABLE()[highlight_ga.ga_len].sg_name_u = name_up;
3068 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3069     HL_TABLE()[highlight_ga.ga_len].sg_gui_bg = INVALCOLOR;
3070     HL_TABLE()[highlight_ga.ga_len].sg_gui_fg = INVALCOLOR;
3071 # ifdef FEAT_GUI
3072     HL_TABLE()[highlight_ga.ga_len].sg_gui_sp = INVALCOLOR;
3073 # endif
3074 #endif
3075     ++highlight_ga.ga_len;
3076 
3077     return highlight_ga.ga_len;		    // ID is index plus one
3078 }
3079 
3080 /*
3081  * When, just after calling syn_add_group(), an error is discovered, this
3082  * function deletes the new name.
3083  */
3084     static void
3085 syn_unadd_group(void)
3086 {
3087     --highlight_ga.ga_len;
3088     vim_free(HL_TABLE()[highlight_ga.ga_len].sg_name);
3089     vim_free(HL_TABLE()[highlight_ga.ga_len].sg_name_u);
3090 }
3091 
3092 /*
3093  * Translate a group ID to highlight attributes.
3094  */
3095     int
3096 syn_id2attr(int hl_id)
3097 {
3098     int		attr;
3099     hl_group_T	*sgp;
3100 
3101     hl_id = syn_get_final_id(hl_id);
3102     sgp = &HL_TABLE()[hl_id - 1];	    // index is ID minus one
3103 
3104 #ifdef FEAT_GUI
3105     /*
3106      * Only use GUI attr when the GUI is being used.
3107      */
3108     if (gui.in_use)
3109 	attr = sgp->sg_gui_attr;
3110     else
3111 #endif
3112 	if (IS_CTERM)
3113 	    attr = sgp->sg_cterm_attr;
3114 	else
3115 	    attr = sgp->sg_term_attr;
3116 
3117     return attr;
3118 }
3119 
3120 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
3121 /*
3122  * Get the GUI colors and attributes for a group ID.
3123  * NOTE: the colors will be INVALCOLOR when not set, the color otherwise.
3124  */
3125     int
3126 syn_id2colors(int hl_id, guicolor_T *fgp, guicolor_T *bgp)
3127 {
3128     hl_group_T	*sgp;
3129 
3130     hl_id = syn_get_final_id(hl_id);
3131     sgp = &HL_TABLE()[hl_id - 1];	    // index is ID minus one
3132 
3133     *fgp = sgp->sg_gui_fg;
3134     *bgp = sgp->sg_gui_bg;
3135     return sgp->sg_gui;
3136 }
3137 #endif
3138 
3139 #if (defined(MSWIN) \
3140 	    && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL)) \
3141 	    && defined(FEAT_TERMGUICOLORS)) \
3142 	|| defined(FEAT_TERMINAL) || defined(PROTO)
3143     void
3144 syn_id2cterm_bg(int hl_id, int *fgp, int *bgp)
3145 {
3146     hl_group_T	*sgp;
3147 
3148     hl_id = syn_get_final_id(hl_id);
3149     sgp = &HL_TABLE()[hl_id - 1];	    // index is ID minus one
3150     *fgp = sgp->sg_cterm_fg - 1;
3151     *bgp = sgp->sg_cterm_bg - 1;
3152 }
3153 #endif
3154 
3155 /*
3156  * Translate a group ID to the final group ID (following links).
3157  */
3158     int
3159 syn_get_final_id(int hl_id)
3160 {
3161     int		count;
3162     hl_group_T	*sgp;
3163 
3164     if (hl_id > highlight_ga.ga_len || hl_id < 1)
3165 	return 0;			// Can be called from eval!!
3166 
3167     /*
3168      * Follow links until there is no more.
3169      * Look out for loops!  Break after 100 links.
3170      */
3171     for (count = 100; --count >= 0; )
3172     {
3173 	sgp = &HL_TABLE()[hl_id - 1];	    // index is ID minus one
3174 	if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len)
3175 	    break;
3176 	hl_id = sgp->sg_link;
3177     }
3178 
3179     return hl_id;
3180 }
3181 
3182 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
3183 /*
3184  * Call this function just after the GUI has started.
3185  * Also called when 'termguicolors' was set, gui.in_use will be FALSE then.
3186  * It finds the font and color handles for the highlighting groups.
3187  */
3188     void
3189 highlight_gui_started(void)
3190 {
3191     int	    idx;
3192 
3193     // First get the colors from the "Normal" and "Menu" group, if set
3194     if (USE_24BIT)
3195 	set_normal_colors();
3196 
3197     for (idx = 0; idx < highlight_ga.ga_len; ++idx)
3198 	gui_do_one_color(idx, FALSE, FALSE);
3199 
3200     highlight_changed();
3201 }
3202 
3203     static void
3204 gui_do_one_color(
3205     int		idx,
3206     int		do_menu UNUSED,	   // TRUE: might set the menu font
3207     int		do_tooltip UNUSED) // TRUE: might set the tooltip font
3208 {
3209     int		didit = FALSE;
3210 
3211 # ifdef FEAT_GUI
3212 #  ifdef FEAT_TERMGUICOLORS
3213     if (gui.in_use)
3214 #  endif
3215 	if (HL_TABLE()[idx].sg_font_name != NULL)
3216 	{
3217 	    hl_do_font(idx, HL_TABLE()[idx].sg_font_name, FALSE, do_menu,
3218 							    do_tooltip, TRUE);
3219 	    didit = TRUE;
3220 	}
3221 # endif
3222     if (HL_TABLE()[idx].sg_gui_fg_name != NULL)
3223     {
3224 	HL_TABLE()[idx].sg_gui_fg =
3225 			    color_name2handle(HL_TABLE()[idx].sg_gui_fg_name);
3226 	didit = TRUE;
3227     }
3228     if (HL_TABLE()[idx].sg_gui_bg_name != NULL)
3229     {
3230 	HL_TABLE()[idx].sg_gui_bg =
3231 			    color_name2handle(HL_TABLE()[idx].sg_gui_bg_name);
3232 	didit = TRUE;
3233     }
3234 # ifdef FEAT_GUI
3235     if (HL_TABLE()[idx].sg_gui_sp_name != NULL)
3236     {
3237 	HL_TABLE()[idx].sg_gui_sp =
3238 			    color_name2handle(HL_TABLE()[idx].sg_gui_sp_name);
3239 	didit = TRUE;
3240     }
3241 # endif
3242     if (didit)	// need to get a new attr number
3243 	set_hl_attr(idx);
3244 }
3245 #endif
3246 
3247 #if defined(USER_HIGHLIGHT) && defined(FEAT_STL_OPT)
3248 /*
3249  * Apply difference between User[1-9] and HLF_S to HLF_SNC, HLF_ST or HLF_STNC.
3250  */
3251     static void
3252 combine_stl_hlt(
3253 	int id,
3254 	int id_S,
3255 	int id_alt,
3256 	int hlcnt,
3257 	int i,
3258 	int hlf,
3259 	int *table)
3260 {
3261     hl_group_T *hlt = HL_TABLE();
3262 
3263     if (id_alt == 0)
3264     {
3265 	CLEAR_POINTER(&hlt[hlcnt + i]);
3266 	hlt[hlcnt + i].sg_term = highlight_attr[hlf];
3267 	hlt[hlcnt + i].sg_cterm = highlight_attr[hlf];
3268 #  if defined(FEAT_GUI) || defined(FEAT_EVAL)
3269 	hlt[hlcnt + i].sg_gui = highlight_attr[hlf];
3270 #  endif
3271     }
3272     else
3273 	mch_memmove(&hlt[hlcnt + i],
3274 		    &hlt[id_alt - 1],
3275 		    sizeof(hl_group_T));
3276     hlt[hlcnt + i].sg_link = 0;
3277 
3278     hlt[hlcnt + i].sg_term ^=
3279 	hlt[id - 1].sg_term ^ hlt[id_S - 1].sg_term;
3280     if (hlt[id - 1].sg_start != hlt[id_S - 1].sg_start)
3281 	hlt[hlcnt + i].sg_start = hlt[id - 1].sg_start;
3282     if (hlt[id - 1].sg_stop != hlt[id_S - 1].sg_stop)
3283 	hlt[hlcnt + i].sg_stop = hlt[id - 1].sg_stop;
3284     hlt[hlcnt + i].sg_cterm ^=
3285 	hlt[id - 1].sg_cterm ^ hlt[id_S - 1].sg_cterm;
3286     if (hlt[id - 1].sg_cterm_fg != hlt[id_S - 1].sg_cterm_fg)
3287 	hlt[hlcnt + i].sg_cterm_fg = hlt[id - 1].sg_cterm_fg;
3288     if (hlt[id - 1].sg_cterm_bg != hlt[id_S - 1].sg_cterm_bg)
3289 	hlt[hlcnt + i].sg_cterm_bg = hlt[id - 1].sg_cterm_bg;
3290 #  if defined(FEAT_GUI) || defined(FEAT_EVAL)
3291     hlt[hlcnt + i].sg_gui ^=
3292 	hlt[id - 1].sg_gui ^ hlt[id_S - 1].sg_gui;
3293 #  endif
3294 #  ifdef FEAT_GUI
3295     if (hlt[id - 1].sg_gui_fg != hlt[id_S - 1].sg_gui_fg)
3296 	hlt[hlcnt + i].sg_gui_fg = hlt[id - 1].sg_gui_fg;
3297     if (hlt[id - 1].sg_gui_bg != hlt[id_S - 1].sg_gui_bg)
3298 	hlt[hlcnt + i].sg_gui_bg = hlt[id - 1].sg_gui_bg;
3299     if (hlt[id - 1].sg_gui_sp != hlt[id_S - 1].sg_gui_sp)
3300 	hlt[hlcnt + i].sg_gui_sp = hlt[id - 1].sg_gui_sp;
3301     if (hlt[id - 1].sg_font != hlt[id_S - 1].sg_font)
3302 	hlt[hlcnt + i].sg_font = hlt[id - 1].sg_font;
3303 #   ifdef FEAT_XFONTSET
3304     if (hlt[id - 1].sg_fontset != hlt[id_S - 1].sg_fontset)
3305 	hlt[hlcnt + i].sg_fontset = hlt[id - 1].sg_fontset;
3306 #   endif
3307 #  endif
3308     highlight_ga.ga_len = hlcnt + i + 1;
3309     set_hl_attr(hlcnt + i);	// At long last we can apply
3310     table[i] = syn_id2attr(hlcnt + i + 1);
3311 }
3312 #endif
3313 
3314 /*
3315  * Translate the 'highlight' option into attributes in highlight_attr[] and
3316  * set up the user highlights User1..9.  If FEAT_STL_OPT is in use, a set of
3317  * corresponding highlights to use on top of HLF_SNC is computed.
3318  * Called only when the 'highlight' option has been changed and upon first
3319  * screen redraw after any :highlight command.
3320  * Return FAIL when an invalid flag is found in 'highlight'.  OK otherwise.
3321  */
3322     int
3323 highlight_changed(void)
3324 {
3325     int		hlf;
3326     int		i;
3327     char_u	*p;
3328     int		attr;
3329     char_u	*end;
3330     int		id;
3331 #ifdef USER_HIGHLIGHT
3332     char_u      userhl[30];  // use 30 to avoid compiler warning
3333 # ifdef FEAT_STL_OPT
3334     int		id_S = -1;
3335     int		id_SNC = 0;
3336 #  ifdef FEAT_TERMINAL
3337     int		id_ST = 0;
3338     int		id_STNC = 0;
3339 #  endif
3340     int		hlcnt;
3341 # endif
3342 #endif
3343     static int	hl_flags[HLF_COUNT] = HL_FLAGS;
3344 
3345     need_highlight_changed = FALSE;
3346 
3347     /*
3348      * Clear all attributes.
3349      */
3350     for (hlf = 0; hlf < (int)HLF_COUNT; ++hlf)
3351 	highlight_attr[hlf] = 0;
3352 
3353     /*
3354      * First set all attributes to their default value.
3355      * Then use the attributes from the 'highlight' option.
3356      */
3357     for (i = 0; i < 2; ++i)
3358     {
3359 	if (i)
3360 	    p = p_hl;
3361 	else
3362 	    p = get_highlight_default();
3363 	if (p == NULL)	    // just in case
3364 	    continue;
3365 
3366 	while (*p)
3367 	{
3368 	    for (hlf = 0; hlf < (int)HLF_COUNT; ++hlf)
3369 		if (hl_flags[hlf] == *p)
3370 		    break;
3371 	    ++p;
3372 	    if (hlf == (int)HLF_COUNT || *p == NUL)
3373 		return FAIL;
3374 
3375 	    /*
3376 	     * Allow several hl_flags to be combined, like "bu" for
3377 	     * bold-underlined.
3378 	     */
3379 	    attr = 0;
3380 	    for ( ; *p && *p != ','; ++p)	    // parse up to comma
3381 	    {
3382 		if (VIM_ISWHITE(*p))		    // ignore white space
3383 		    continue;
3384 
3385 		if (attr > HL_ALL)  // Combination with ':' is not allowed.
3386 		    return FAIL;
3387 
3388 		switch (*p)
3389 		{
3390 		    case 'b':	attr |= HL_BOLD;
3391 				break;
3392 		    case 'i':	attr |= HL_ITALIC;
3393 				break;
3394 		    case '-':
3395 		    case 'n':			    // no highlighting
3396 				break;
3397 		    case 'r':	attr |= HL_INVERSE;
3398 				break;
3399 		    case 's':	attr |= HL_STANDOUT;
3400 				break;
3401 		    case 'u':	attr |= HL_UNDERLINE;
3402 				break;
3403 		    case 'c':	attr |= HL_UNDERCURL;
3404 				break;
3405 		    case 't':	attr |= HL_STRIKETHROUGH;
3406 				break;
3407 		    case ':':	++p;		    // highlight group name
3408 				if (attr || *p == NUL)	 // no combinations
3409 				    return FAIL;
3410 				end = vim_strchr(p, ',');
3411 				if (end == NULL)
3412 				    end = p + STRLEN(p);
3413 				id = syn_check_group(p, (int)(end - p));
3414 				if (id == 0)
3415 				    return FAIL;
3416 				attr = syn_id2attr(id);
3417 				p = end - 1;
3418 #if defined(FEAT_STL_OPT) && defined(USER_HIGHLIGHT)
3419 				if (hlf == (int)HLF_SNC)
3420 				    id_SNC = syn_get_final_id(id);
3421 # ifdef FEAT_TERMINAL
3422 				else if (hlf == (int)HLF_ST)
3423 				    id_ST = syn_get_final_id(id);
3424 				else if (hlf == (int)HLF_STNC)
3425 				    id_STNC = syn_get_final_id(id);
3426 # endif
3427 				else if (hlf == (int)HLF_S)
3428 				    id_S = syn_get_final_id(id);
3429 #endif
3430 				break;
3431 		    default:	return FAIL;
3432 		}
3433 	    }
3434 	    highlight_attr[hlf] = attr;
3435 
3436 	    p = skip_to_option_part(p);	    // skip comma and spaces
3437 	}
3438     }
3439 
3440 #ifdef USER_HIGHLIGHT
3441     /*
3442      * Setup the user highlights
3443      *
3444      * Temporarily utilize 28 more hl entries:
3445      * 9 for User1-User9 combined with StatusLineNC
3446      * 9 for User1-User9 combined with StatusLineTerm
3447      * 9 for User1-User9 combined with StatusLineTermNC
3448      * 1 for StatusLine default
3449      * Have to be in there simultaneously in case of table overflows in
3450      * get_attr_entry()
3451      */
3452 # ifdef FEAT_STL_OPT
3453     if (ga_grow(&highlight_ga, 28) == FAIL)
3454 	return FAIL;
3455     hlcnt = highlight_ga.ga_len;
3456     if (id_S == -1)
3457     {
3458 	// Make sure id_S is always valid to simplify code below. Use the last
3459 	// entry.
3460 	CLEAR_POINTER(&HL_TABLE()[hlcnt + 27]);
3461 	HL_TABLE()[hlcnt + 18].sg_term = highlight_attr[HLF_S];
3462 	id_S = hlcnt + 19;
3463     }
3464 # endif
3465     for (i = 0; i < 9; i++)
3466     {
3467 	sprintf((char *)userhl, "User%d", i + 1);
3468 	id = syn_name2id(userhl);
3469 	if (id == 0)
3470 	{
3471 	    highlight_user[i] = 0;
3472 # ifdef FEAT_STL_OPT
3473 	    highlight_stlnc[i] = 0;
3474 #  ifdef FEAT_TERMINAL
3475 	    highlight_stlterm[i] = 0;
3476 	    highlight_stltermnc[i] = 0;
3477 #  endif
3478 # endif
3479 	}
3480 	else
3481 	{
3482 	    highlight_user[i] = syn_id2attr(id);
3483 # ifdef FEAT_STL_OPT
3484 	    combine_stl_hlt(id, id_S, id_SNC, hlcnt, i,
3485 						     HLF_SNC, highlight_stlnc);
3486 #  ifdef FEAT_TERMINAL
3487 	    combine_stl_hlt(id, id_S, id_ST, hlcnt + 9, i,
3488 						    HLF_ST, highlight_stlterm);
3489 	    combine_stl_hlt(id, id_S, id_STNC, hlcnt + 18, i,
3490 						HLF_STNC, highlight_stltermnc);
3491 #  endif
3492 # endif
3493 	}
3494     }
3495 # ifdef FEAT_STL_OPT
3496     highlight_ga.ga_len = hlcnt;
3497 # endif
3498 
3499 #endif // USER_HIGHLIGHT
3500 
3501     return OK;
3502 }
3503 
3504 static void highlight_list(void);
3505 static void highlight_list_two(int cnt, int attr);
3506 
3507 /*
3508  * Handle command line completion for :highlight command.
3509  */
3510     void
3511 set_context_in_highlight_cmd(expand_T *xp, char_u *arg)
3512 {
3513     char_u	*p;
3514 
3515     // Default: expand group names
3516     xp->xp_context = EXPAND_HIGHLIGHT;
3517     xp->xp_pattern = arg;
3518     include_link = 2;
3519     include_default = 1;
3520 
3521     // (part of) subcommand already typed
3522     if (*arg != NUL)
3523     {
3524 	p = skiptowhite(arg);
3525 	if (*p != NUL)			// past "default" or group name
3526 	{
3527 	    include_default = 0;
3528 	    if (STRNCMP("default", arg, p - arg) == 0)
3529 	    {
3530 		arg = skipwhite(p);
3531 		xp->xp_pattern = arg;
3532 		p = skiptowhite(arg);
3533 	    }
3534 	    if (*p != NUL)			// past group name
3535 	    {
3536 		include_link = 0;
3537 		if (arg[1] == 'i' && arg[0] == 'N')
3538 		    highlight_list();
3539 		if (STRNCMP("link", arg, p - arg) == 0
3540 			|| STRNCMP("clear", arg, p - arg) == 0)
3541 		{
3542 		    xp->xp_pattern = skipwhite(p);
3543 		    p = skiptowhite(xp->xp_pattern);
3544 		    if (*p != NUL)		// past first group name
3545 		    {
3546 			xp->xp_pattern = skipwhite(p);
3547 			p = skiptowhite(xp->xp_pattern);
3548 		    }
3549 		}
3550 		if (*p != NUL)			// past group name(s)
3551 		    xp->xp_context = EXPAND_NOTHING;
3552 	    }
3553 	}
3554     }
3555 }
3556 
3557 /*
3558  * List highlighting matches in a nice way.
3559  */
3560     static void
3561 highlight_list(void)
3562 {
3563     int		i;
3564 
3565     for (i = 10; --i >= 0; )
3566 	highlight_list_two(i, HL_ATTR(HLF_D));
3567     for (i = 40; --i >= 0; )
3568 	highlight_list_two(99, 0);
3569 }
3570 
3571     static void
3572 highlight_list_two(int cnt, int attr)
3573 {
3574     msg_puts_attr(&("N \bI \b!  \b"[cnt / 11]), attr);
3575     msg_clr_eos();
3576     out_flush();
3577     ui_delay(cnt == 99 ? 40L : (long)cnt * 50L, FALSE);
3578 }
3579 
3580 /*
3581  * Function given to ExpandGeneric() to obtain the list of group names.
3582  */
3583     char_u *
3584 get_highlight_name(expand_T *xp UNUSED, int idx)
3585 {
3586     return get_highlight_name_ext(xp, idx, TRUE);
3587 }
3588 
3589 /*
3590  * Obtain a highlight group name.
3591  * When "skip_cleared" is TRUE don't return a cleared entry.
3592  */
3593     char_u *
3594 get_highlight_name_ext(expand_T *xp UNUSED, int idx, int skip_cleared)
3595 {
3596     if (idx < 0)
3597 	return NULL;
3598 
3599     // Items are never removed from the table, skip the ones that were
3600     // cleared.
3601     if (skip_cleared && idx < highlight_ga.ga_len && HL_TABLE()[idx].sg_cleared)
3602 	return (char_u *)"";
3603 
3604     if (idx == highlight_ga.ga_len && include_none != 0)
3605 	return (char_u *)"none";
3606     if (idx == highlight_ga.ga_len + include_none && include_default != 0)
3607 	return (char_u *)"default";
3608     if (idx == highlight_ga.ga_len + include_none + include_default
3609 							 && include_link != 0)
3610 	return (char_u *)"link";
3611     if (idx == highlight_ga.ga_len + include_none + include_default + 1
3612 							 && include_link != 0)
3613 	return (char_u *)"clear";
3614     if (idx >= highlight_ga.ga_len)
3615 	return NULL;
3616     return HL_TABLE()[idx].sg_name;
3617 }
3618 
3619 #if defined(FEAT_GUI) || defined(PROTO)
3620 /*
3621  * Free all the highlight group fonts.
3622  * Used when quitting for systems which need it.
3623  */
3624     void
3625 free_highlight_fonts(void)
3626 {
3627     int	    idx;
3628 
3629     for (idx = 0; idx < highlight_ga.ga_len; ++idx)
3630     {
3631 	gui_mch_free_font(HL_TABLE()[idx].sg_font);
3632 	HL_TABLE()[idx].sg_font = NOFONT;
3633 # ifdef FEAT_XFONTSET
3634 	gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
3635 	HL_TABLE()[idx].sg_fontset = NOFONTSET;
3636 # endif
3637     }
3638 
3639     gui_mch_free_font(gui.norm_font);
3640 # ifdef FEAT_XFONTSET
3641     gui_mch_free_fontset(gui.fontset);
3642 # endif
3643 # ifndef FEAT_GUI_GTK
3644     gui_mch_free_font(gui.bold_font);
3645     gui_mch_free_font(gui.ital_font);
3646     gui_mch_free_font(gui.boldital_font);
3647 # endif
3648 }
3649 #endif
3650 
3651 
3652 #if defined(FEAT_SEARCH_EXTRA) || defined(PROTO)
3653 
3654 # define SEARCH_HL_PRIORITY 0
3655 
3656 /*
3657  * Add match to the match list of window 'wp'.  The pattern 'pat' will be
3658  * highlighted with the group 'grp' with priority 'prio'.
3659  * Optionally, a desired ID 'id' can be specified (greater than or equal to 1).
3660  * If no particular ID is desired, -1 must be specified for 'id'.
3661  * Return ID of added match, -1 on failure.
3662  */
3663     static int
3664 match_add(
3665     win_T	*wp,
3666     char_u	*grp,
3667     char_u	*pat,
3668     int		prio,
3669     int		id,
3670     list_T	*pos_list,
3671     char_u      *conceal_char UNUSED) // pointer to conceal replacement char
3672 {
3673     matchitem_T	*cur;
3674     matchitem_T	*prev;
3675     matchitem_T	*m;
3676     int		hlg_id;
3677     regprog_T	*regprog = NULL;
3678     int		rtype = SOME_VALID;
3679 
3680     if (*grp == NUL || (pat != NULL && *pat == NUL))
3681 	return -1;
3682     if (id < -1 || id == 0)
3683     {
3684 	semsg(_("E799: Invalid ID: %d (must be greater than or equal to 1)"),
3685 									   id);
3686 	return -1;
3687     }
3688     if (id != -1)
3689     {
3690 	cur = wp->w_match_head;
3691 	while (cur != NULL)
3692 	{
3693 	    if (cur->id == id)
3694 	    {
3695 		semsg(_("E801: ID already taken: %d"), id);
3696 		return -1;
3697 	    }
3698 	    cur = cur->next;
3699 	}
3700     }
3701     if ((hlg_id = syn_namen2id(grp, (int)STRLEN(grp))) == 0)
3702     {
3703 	semsg(_(e_nogroup), grp);
3704 	return -1;
3705     }
3706     if (pat != NULL && (regprog = vim_regcomp(pat, RE_MAGIC)) == NULL)
3707     {
3708 	semsg(_(e_invarg2), pat);
3709 	return -1;
3710     }
3711 
3712     // Find available match ID.
3713     while (id == -1)
3714     {
3715 	cur = wp->w_match_head;
3716 	while (cur != NULL && cur->id != wp->w_next_match_id)
3717 	    cur = cur->next;
3718 	if (cur == NULL)
3719 	    id = wp->w_next_match_id;
3720 	wp->w_next_match_id++;
3721     }
3722 
3723     // Build new match.
3724     m = ALLOC_CLEAR_ONE(matchitem_T);
3725     m->id = id;
3726     m->priority = prio;
3727     m->pattern = pat == NULL ? NULL : vim_strsave(pat);
3728     m->hlg_id = hlg_id;
3729     m->match.regprog = regprog;
3730     m->match.rmm_ic = FALSE;
3731     m->match.rmm_maxcol = 0;
3732 # if defined(FEAT_CONCEAL)
3733     m->conceal_char = 0;
3734     if (conceal_char != NULL)
3735 	m->conceal_char = (*mb_ptr2char)(conceal_char);
3736 # endif
3737 
3738     // Set up position matches
3739     if (pos_list != NULL)
3740     {
3741 	linenr_T	toplnum = 0;
3742 	linenr_T	botlnum = 0;
3743 	listitem_T	*li;
3744 	int		i;
3745 
3746 	CHECK_LIST_MATERIALIZE(pos_list);
3747 	for (i = 0, li = pos_list->lv_first; li != NULL && i < MAXPOSMATCH;
3748 							i++, li = li->li_next)
3749 	{
3750 	    linenr_T	lnum = 0;
3751 	    colnr_T	col = 0;
3752 	    int		len = 1;
3753 	    list_T	*subl;
3754 	    listitem_T	*subli;
3755 	    int		error = FALSE;
3756 
3757 	    if (li->li_tv.v_type == VAR_LIST)
3758 	    {
3759 		subl = li->li_tv.vval.v_list;
3760 		if (subl == NULL)
3761 		    goto fail;
3762 		subli = subl->lv_first;
3763 		if (subli == NULL)
3764 		    goto fail;
3765 		lnum = tv_get_number_chk(&subli->li_tv, &error);
3766 		if (error == TRUE)
3767 		    goto fail;
3768 		if (lnum == 0)
3769 		{
3770 		    --i;
3771 		    continue;
3772 		}
3773 		m->pos.pos[i].lnum = lnum;
3774 		subli = subli->li_next;
3775 		if (subli != NULL)
3776 		{
3777 		    col = tv_get_number_chk(&subli->li_tv, &error);
3778 		    if (error == TRUE)
3779 			goto fail;
3780 		    subli = subli->li_next;
3781 		    if (subli != NULL)
3782 		    {
3783 			len = tv_get_number_chk(&subli->li_tv, &error);
3784 			if (error == TRUE)
3785 			    goto fail;
3786 		    }
3787 		}
3788 		m->pos.pos[i].col = col;
3789 		m->pos.pos[i].len = len;
3790 	    }
3791 	    else if (li->li_tv.v_type == VAR_NUMBER)
3792 	    {
3793 		if (li->li_tv.vval.v_number == 0)
3794 		{
3795 		    --i;
3796 		    continue;
3797 		}
3798 		m->pos.pos[i].lnum = li->li_tv.vval.v_number;
3799 		m->pos.pos[i].col = 0;
3800 		m->pos.pos[i].len = 0;
3801 	    }
3802 	    else
3803 	    {
3804 		emsg(_("E290: List or number required"));
3805 		goto fail;
3806 	    }
3807 	    if (toplnum == 0 || lnum < toplnum)
3808 		toplnum = lnum;
3809 	    if (botlnum == 0 || lnum >= botlnum)
3810 		botlnum = lnum + 1;
3811 	}
3812 
3813 	// Calculate top and bottom lines for redrawing area
3814 	if (toplnum != 0)
3815 	{
3816 	    if (wp->w_buffer->b_mod_set)
3817 	    {
3818 		if (wp->w_buffer->b_mod_top > toplnum)
3819 		    wp->w_buffer->b_mod_top = toplnum;
3820 		if (wp->w_buffer->b_mod_bot < botlnum)
3821 		    wp->w_buffer->b_mod_bot = botlnum;
3822 	    }
3823 	    else
3824 	    {
3825 		wp->w_buffer->b_mod_set = TRUE;
3826 		wp->w_buffer->b_mod_top = toplnum;
3827 		wp->w_buffer->b_mod_bot = botlnum;
3828 		wp->w_buffer->b_mod_xlines = 0;
3829 	    }
3830 	    m->pos.toplnum = toplnum;
3831 	    m->pos.botlnum = botlnum;
3832 	    rtype = VALID;
3833 	}
3834     }
3835 
3836     // Insert new match.  The match list is in ascending order with regard to
3837     // the match priorities.
3838     cur = wp->w_match_head;
3839     prev = cur;
3840     while (cur != NULL && prio >= cur->priority)
3841     {
3842 	prev = cur;
3843 	cur = cur->next;
3844     }
3845     if (cur == prev)
3846 	wp->w_match_head = m;
3847     else
3848 	prev->next = m;
3849     m->next = cur;
3850 
3851     redraw_win_later(wp, rtype);
3852     return id;
3853 
3854 fail:
3855     vim_free(m);
3856     return -1;
3857 }
3858 
3859 /*
3860  * Delete match with ID 'id' in the match list of window 'wp'.
3861  * Print error messages if 'perr' is TRUE.
3862  */
3863     static int
3864 match_delete(win_T *wp, int id, int perr)
3865 {
3866     matchitem_T	*cur = wp->w_match_head;
3867     matchitem_T	*prev = cur;
3868     int		rtype = SOME_VALID;
3869 
3870     if (id < 1)
3871     {
3872 	if (perr == TRUE)
3873 	    semsg(_("E802: Invalid ID: %d (must be greater than or equal to 1)"),
3874 									  id);
3875 	return -1;
3876     }
3877     while (cur != NULL && cur->id != id)
3878     {
3879 	prev = cur;
3880 	cur = cur->next;
3881     }
3882     if (cur == NULL)
3883     {
3884 	if (perr == TRUE)
3885 	    semsg(_("E803: ID not found: %d"), id);
3886 	return -1;
3887     }
3888     if (cur == prev)
3889 	wp->w_match_head = cur->next;
3890     else
3891 	prev->next = cur->next;
3892     vim_regfree(cur->match.regprog);
3893     vim_free(cur->pattern);
3894     if (cur->pos.toplnum != 0)
3895     {
3896 	if (wp->w_buffer->b_mod_set)
3897 	{
3898 	    if (wp->w_buffer->b_mod_top > cur->pos.toplnum)
3899 		wp->w_buffer->b_mod_top = cur->pos.toplnum;
3900 	    if (wp->w_buffer->b_mod_bot < cur->pos.botlnum)
3901 		wp->w_buffer->b_mod_bot = cur->pos.botlnum;
3902 	}
3903 	else
3904 	{
3905 	    wp->w_buffer->b_mod_set = TRUE;
3906 	    wp->w_buffer->b_mod_top = cur->pos.toplnum;
3907 	    wp->w_buffer->b_mod_bot = cur->pos.botlnum;
3908 	    wp->w_buffer->b_mod_xlines = 0;
3909 	}
3910 	rtype = VALID;
3911     }
3912     vim_free(cur);
3913     redraw_win_later(wp, rtype);
3914     return 0;
3915 }
3916 
3917 /*
3918  * Delete all matches in the match list of window 'wp'.
3919  */
3920     void
3921 clear_matches(win_T *wp)
3922 {
3923     matchitem_T *m;
3924 
3925     while (wp->w_match_head != NULL)
3926     {
3927 	m = wp->w_match_head->next;
3928 	vim_regfree(wp->w_match_head->match.regprog);
3929 	vim_free(wp->w_match_head->pattern);
3930 	vim_free(wp->w_match_head);
3931 	wp->w_match_head = m;
3932     }
3933     redraw_win_later(wp, SOME_VALID);
3934 }
3935 
3936 /*
3937  * Get match from ID 'id' in window 'wp'.
3938  * Return NULL if match not found.
3939  */
3940     static matchitem_T *
3941 get_match(win_T *wp, int id)
3942 {
3943     matchitem_T *cur = wp->w_match_head;
3944 
3945     while (cur != NULL && cur->id != id)
3946 	cur = cur->next;
3947     return cur;
3948 }
3949 
3950 /*
3951  * Init for calling prepare_search_hl().
3952  */
3953     void
3954 init_search_hl(win_T *wp, match_T *search_hl)
3955 {
3956     matchitem_T *cur;
3957 
3958     // Setup for match and 'hlsearch' highlighting.  Disable any previous
3959     // match
3960     cur = wp->w_match_head;
3961     while (cur != NULL)
3962     {
3963 	cur->hl.rm = cur->match;
3964 	if (cur->hlg_id == 0)
3965 	    cur->hl.attr = 0;
3966 	else
3967 	    cur->hl.attr = syn_id2attr(cur->hlg_id);
3968 	cur->hl.buf = wp->w_buffer;
3969 	cur->hl.lnum = 0;
3970 	cur->hl.first_lnum = 0;
3971 # ifdef FEAT_RELTIME
3972 	// Set the time limit to 'redrawtime'.
3973 	profile_setlimit(p_rdt, &(cur->hl.tm));
3974 # endif
3975 	cur = cur->next;
3976     }
3977     search_hl->buf = wp->w_buffer;
3978     search_hl->lnum = 0;
3979     search_hl->first_lnum = 0;
3980     // time limit is set at the toplevel, for all windows
3981 }
3982 
3983 /*
3984  * If there is a match fill "shl" and return one.
3985  * Return zero otherwise.
3986  */
3987     static int
3988 next_search_hl_pos(
3989     match_T	    *shl,	// points to a match
3990     linenr_T	    lnum,
3991     posmatch_T	    *posmatch,	// match positions
3992     colnr_T	    mincol)	// minimal column for a match
3993 {
3994     int	    i;
3995     int	    found = -1;
3996 
3997     for (i = posmatch->cur; i < MAXPOSMATCH; i++)
3998     {
3999 	llpos_T	*pos = &posmatch->pos[i];
4000 
4001 	if (pos->lnum == 0)
4002 	    break;
4003 	if (pos->len == 0 && pos->col < mincol)
4004 	    continue;
4005 	if (pos->lnum == lnum)
4006 	{
4007 	    if (found >= 0)
4008 	    {
4009 		// if this match comes before the one at "found" then swap
4010 		// them
4011 		if (pos->col < posmatch->pos[found].col)
4012 		{
4013 		    llpos_T	tmp = *pos;
4014 
4015 		    *pos = posmatch->pos[found];
4016 		    posmatch->pos[found] = tmp;
4017 		}
4018 	    }
4019 	    else
4020 		found = i;
4021 	}
4022     }
4023     posmatch->cur = 0;
4024     if (found >= 0)
4025     {
4026 	colnr_T	start = posmatch->pos[found].col == 0
4027 					    ? 0 : posmatch->pos[found].col - 1;
4028 	colnr_T	end = posmatch->pos[found].col == 0
4029 				   ? MAXCOL : start + posmatch->pos[found].len;
4030 
4031 	shl->lnum = lnum;
4032 	shl->rm.startpos[0].lnum = 0;
4033 	shl->rm.startpos[0].col = start;
4034 	shl->rm.endpos[0].lnum = 0;
4035 	shl->rm.endpos[0].col = end;
4036 	shl->is_addpos = TRUE;
4037 	posmatch->cur = found + 1;
4038 	return 1;
4039     }
4040     return 0;
4041 }
4042 
4043 /*
4044  * Search for a next 'hlsearch' or match.
4045  * Uses shl->buf.
4046  * Sets shl->lnum and shl->rm contents.
4047  * Note: Assumes a previous match is always before "lnum", unless
4048  * shl->lnum is zero.
4049  * Careful: Any pointers for buffer lines will become invalid.
4050  */
4051     static void
4052 next_search_hl(
4053     win_T	    *win,
4054     match_T	    *search_hl,
4055     match_T	    *shl,	// points to search_hl or a match
4056     linenr_T	    lnum,
4057     colnr_T	    mincol,	// minimal column for a match
4058     matchitem_T	    *cur)	// to retrieve match positions if any
4059 {
4060     linenr_T	l;
4061     colnr_T	matchcol;
4062     long	nmatched;
4063     int		called_emsg_before = called_emsg;
4064 
4065     // for :{range}s/pat only highlight inside the range
4066     if (lnum < search_first_line || lnum > search_last_line)
4067     {
4068 	shl->lnum = 0;
4069 	return;
4070     }
4071 
4072     if (shl->lnum != 0)
4073     {
4074 	// Check for three situations:
4075 	// 1. If the "lnum" is below a previous match, start a new search.
4076 	// 2. If the previous match includes "mincol", use it.
4077 	// 3. Continue after the previous match.
4078 	l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum;
4079 	if (lnum > l)
4080 	    shl->lnum = 0;
4081 	else if (lnum < l || shl->rm.endpos[0].col > mincol)
4082 	    return;
4083     }
4084 
4085     /*
4086      * Repeat searching for a match until one is found that includes "mincol"
4087      * or none is found in this line.
4088      */
4089     for (;;)
4090     {
4091 # ifdef FEAT_RELTIME
4092 	// Stop searching after passing the time limit.
4093 	if (profile_passed_limit(&(shl->tm)))
4094 	{
4095 	    shl->lnum = 0;		// no match found in time
4096 	    break;
4097 	}
4098 # endif
4099 	// Three situations:
4100 	// 1. No useful previous match: search from start of line.
4101 	// 2. Not Vi compatible or empty match: continue at next character.
4102 	//    Break the loop if this is beyond the end of the line.
4103 	// 3. Vi compatible searching: continue at end of previous match.
4104 	if (shl->lnum == 0)
4105 	    matchcol = 0;
4106 	else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL
4107 		|| (shl->rm.endpos[0].lnum == 0
4108 		    && shl->rm.endpos[0].col <= shl->rm.startpos[0].col))
4109 	{
4110 	    char_u	*ml;
4111 
4112 	    matchcol = shl->rm.startpos[0].col;
4113 	    ml = ml_get_buf(shl->buf, lnum, FALSE) + matchcol;
4114 	    if (*ml == NUL)
4115 	    {
4116 		++matchcol;
4117 		shl->lnum = 0;
4118 		break;
4119 	    }
4120 	    if (has_mbyte)
4121 		matchcol += mb_ptr2len(ml);
4122 	    else
4123 		++matchcol;
4124 	}
4125 	else
4126 	    matchcol = shl->rm.endpos[0].col;
4127 
4128 	shl->lnum = lnum;
4129 	if (shl->rm.regprog != NULL)
4130 	{
4131 	    // Remember whether shl->rm is using a copy of the regprog in
4132 	    // cur->match.
4133 	    int regprog_is_copy = (shl != search_hl && cur != NULL
4134 				&& shl == &cur->hl
4135 				&& cur->match.regprog == cur->hl.rm.regprog);
4136 	    int timed_out = FALSE;
4137 
4138 	    nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum,
4139 		    matchcol,
4140 #ifdef FEAT_RELTIME
4141 		    &(shl->tm), &timed_out
4142 #else
4143 		    NULL, NULL
4144 #endif
4145 		    );
4146 	    // Copy the regprog, in case it got freed and recompiled.
4147 	    if (regprog_is_copy)
4148 		cur->match.regprog = cur->hl.rm.regprog;
4149 
4150 	    if (called_emsg > called_emsg_before || got_int || timed_out)
4151 	    {
4152 		// Error while handling regexp: stop using this regexp.
4153 		if (shl == search_hl)
4154 		{
4155 		    // don't free regprog in the match list, it's a copy
4156 		    vim_regfree(shl->rm.regprog);
4157 		    set_no_hlsearch(TRUE);
4158 		}
4159 		shl->rm.regprog = NULL;
4160 		shl->lnum = 0;
4161 		got_int = FALSE;  // avoid the "Type :quit to exit Vim" message
4162 		break;
4163 	    }
4164 	}
4165 	else if (cur != NULL)
4166 	    nmatched = next_search_hl_pos(shl, lnum, &(cur->pos), matchcol);
4167 	else
4168 	    nmatched = 0;
4169 	if (nmatched == 0)
4170 	{
4171 	    shl->lnum = 0;		// no match found
4172 	    break;
4173 	}
4174 	if (shl->rm.startpos[0].lnum > 0
4175 		|| shl->rm.startpos[0].col >= mincol
4176 		|| nmatched > 1
4177 		|| shl->rm.endpos[0].col > mincol)
4178 	{
4179 	    shl->lnum += shl->rm.startpos[0].lnum;
4180 	    break;			// useful match found
4181 	}
4182     }
4183 }
4184 
4185 /*
4186  * Advance to the match in window "wp" line "lnum" or past it.
4187  */
4188     void
4189 prepare_search_hl(win_T *wp, match_T *search_hl, linenr_T lnum)
4190 {
4191     matchitem_T *cur;		// points to the match list
4192     match_T	*shl;		// points to search_hl or a match
4193     int		shl_flag;	// flag to indicate whether search_hl
4194 				// has been processed or not
4195     int		pos_inprogress;	// marks that position match search is
4196 				// in progress
4197     int		n;
4198 
4199     // When using a multi-line pattern, start searching at the top
4200     // of the window or just after a closed fold.
4201     // Do this both for search_hl and the match list.
4202     cur = wp->w_match_head;
4203     shl_flag = WIN_IS_POPUP(wp);  // skip search_hl in a popup window
4204     while (cur != NULL || shl_flag == FALSE)
4205     {
4206 	if (shl_flag == FALSE)
4207 	{
4208 	    shl = search_hl;
4209 	    shl_flag = TRUE;
4210 	}
4211 	else
4212 	    shl = &cur->hl;
4213 	if (shl->rm.regprog != NULL
4214 		&& shl->lnum == 0
4215 		&& re_multiline(shl->rm.regprog))
4216 	{
4217 	    if (shl->first_lnum == 0)
4218 	    {
4219 # ifdef FEAT_FOLDING
4220 		for (shl->first_lnum = lnum;
4221 			   shl->first_lnum > wp->w_topline; --shl->first_lnum)
4222 		    if (hasFoldingWin(wp, shl->first_lnum - 1,
4223 						      NULL, NULL, TRUE, NULL))
4224 			break;
4225 # else
4226 		shl->first_lnum = wp->w_topline;
4227 # endif
4228 	    }
4229 	    if (cur != NULL)
4230 		cur->pos.cur = 0;
4231 	    pos_inprogress = TRUE;
4232 	    n = 0;
4233 	    while (shl->first_lnum < lnum && (shl->rm.regprog != NULL
4234 					  || (cur != NULL && pos_inprogress)))
4235 	    {
4236 		next_search_hl(wp, search_hl, shl, shl->first_lnum, (colnr_T)n,
4237 					       shl == search_hl ? NULL : cur);
4238 		pos_inprogress = cur == NULL || cur->pos.cur == 0
4239 							      ? FALSE : TRUE;
4240 		if (shl->lnum != 0)
4241 		{
4242 		    shl->first_lnum = shl->lnum
4243 				    + shl->rm.endpos[0].lnum
4244 				    - shl->rm.startpos[0].lnum;
4245 		    n = shl->rm.endpos[0].col;
4246 		}
4247 		else
4248 		{
4249 		    ++shl->first_lnum;
4250 		    n = 0;
4251 		}
4252 	    }
4253 	}
4254 	if (shl != search_hl && cur != NULL)
4255 	    cur = cur->next;
4256     }
4257 }
4258 
4259 /*
4260  * Prepare for 'hlsearch' and match highlighting in one window line.
4261  * Return TRUE if there is such highlighting and set "search_attr" to the
4262  * current highlight attribute.
4263  */
4264     int
4265 prepare_search_hl_line(
4266 	win_T	    *wp,
4267 	linenr_T    lnum,
4268 	colnr_T	    mincol,
4269 	char_u	    **line,
4270 	match_T	    *search_hl,
4271 	int	    *search_attr)
4272 {
4273     matchitem_T *cur;			// points to the match list
4274     match_T	*shl;			// points to search_hl or a match
4275     int		shl_flag;		// flag to indicate whether search_hl
4276 					// has been processed or not
4277     int		area_highlighting = FALSE;
4278 
4279     /*
4280      * Handle highlighting the last used search pattern and matches.
4281      * Do this for both search_hl and the match list.
4282      * Do not use search_hl in a popup window.
4283      */
4284     cur = wp->w_match_head;
4285     shl_flag = WIN_IS_POPUP(wp);
4286     while (cur != NULL || shl_flag == FALSE)
4287     {
4288 	if (shl_flag == FALSE)
4289 	{
4290 	    shl = search_hl;
4291 	    shl_flag = TRUE;
4292 	}
4293 	else
4294 	    shl = &cur->hl;
4295 	shl->startcol = MAXCOL;
4296 	shl->endcol = MAXCOL;
4297 	shl->attr_cur = 0;
4298 	shl->is_addpos = FALSE;
4299 	if (cur != NULL)
4300 	    cur->pos.cur = 0;
4301 	next_search_hl(wp, search_hl, shl, lnum, mincol,
4302 						shl == search_hl ? NULL : cur);
4303 
4304 	// Need to get the line again, a multi-line regexp may have made it
4305 	// invalid.
4306 	*line = ml_get_buf(wp->w_buffer, lnum, FALSE);
4307 
4308 	if (shl->lnum != 0 && shl->lnum <= lnum)
4309 	{
4310 	    if (shl->lnum == lnum)
4311 		shl->startcol = shl->rm.startpos[0].col;
4312 	    else
4313 		shl->startcol = 0;
4314 	    if (lnum == shl->lnum + shl->rm.endpos[0].lnum
4315 						- shl->rm.startpos[0].lnum)
4316 		shl->endcol = shl->rm.endpos[0].col;
4317 	    else
4318 		shl->endcol = MAXCOL;
4319 	    // Highlight one character for an empty match.
4320 	    if (shl->startcol == shl->endcol)
4321 	    {
4322 		if (has_mbyte && (*line)[shl->endcol] != NUL)
4323 		    shl->endcol += (*mb_ptr2len)((*line) + shl->endcol);
4324 		else
4325 		    ++shl->endcol;
4326 	    }
4327 	    if ((long)shl->startcol < mincol)  // match at leftcol
4328 	    {
4329 		shl->attr_cur = shl->attr;
4330 		*search_attr = shl->attr;
4331 	    }
4332 	    area_highlighting = TRUE;
4333 	}
4334 	if (shl != search_hl && cur != NULL)
4335 	    cur = cur->next;
4336     }
4337     return area_highlighting;
4338 }
4339 
4340 /*
4341  * For a position in a line: Check for start/end of 'hlsearch' and other
4342  * matches.
4343  * After end, check for start/end of next match.
4344  * When another match, have to check for start again.
4345  * Watch out for matching an empty string!
4346  * Return the updated search_attr.
4347  */
4348     int
4349 update_search_hl(
4350 	win_T	    *wp,
4351 	linenr_T    lnum,
4352 	colnr_T	    col,
4353 	char_u	    **line,
4354 	match_T	    *search_hl,
4355 	int	    *has_match_conc UNUSED,
4356 	int	    *match_conc UNUSED,
4357 	int	    did_line_attr,
4358 	int	    lcs_eol_one)
4359 {
4360     matchitem_T *cur;		    // points to the match list
4361     match_T	*shl;		    // points to search_hl or a match
4362     int		shl_flag;	    // flag to indicate whether search_hl
4363 				    // has been processed or not
4364     int		pos_inprogress;	    // marks that position match search is in
4365 				    // progress
4366     int		search_attr = 0;
4367 
4368 
4369     // Do this for 'search_hl' and the match list (ordered by priority).
4370     cur = wp->w_match_head;
4371     shl_flag = WIN_IS_POPUP(wp);
4372     while (cur != NULL || shl_flag == FALSE)
4373     {
4374 	if (shl_flag == FALSE
4375 		&& ((cur != NULL
4376 			&& cur->priority > SEARCH_HL_PRIORITY)
4377 		    || cur == NULL))
4378 	{
4379 	    shl = search_hl;
4380 	    shl_flag = TRUE;
4381 	}
4382 	else
4383 	    shl = &cur->hl;
4384 	if (cur != NULL)
4385 	    cur->pos.cur = 0;
4386 	pos_inprogress = TRUE;
4387 	while (shl->rm.regprog != NULL || (cur != NULL && pos_inprogress))
4388 	{
4389 	    if (shl->startcol != MAXCOL
4390 		    && col >= shl->startcol
4391 		    && col < shl->endcol)
4392 	    {
4393 		int next_col = col + mb_ptr2len(*line + col);
4394 
4395 		if (shl->endcol < next_col)
4396 		    shl->endcol = next_col;
4397 		shl->attr_cur = shl->attr;
4398 # ifdef FEAT_CONCEAL
4399 		// Match with the "Conceal" group results in hiding
4400 		// the match.
4401 		if (cur != NULL
4402 			&& shl != search_hl
4403 			&& syn_name2id((char_u *)"Conceal") == cur->hlg_id)
4404 		{
4405 		    *has_match_conc = col == shl->startcol ? 2 : 1;
4406 		    *match_conc = cur->conceal_char;
4407 		}
4408 		else
4409 		    *has_match_conc = *match_conc = 0;
4410 # endif
4411 	    }
4412 	    else if (col == shl->endcol)
4413 	    {
4414 		shl->attr_cur = 0;
4415 		next_search_hl(wp, search_hl, shl, lnum, col,
4416 					       shl == search_hl ? NULL : cur);
4417 		pos_inprogress = !(cur == NULL || cur->pos.cur == 0);
4418 
4419 		// Need to get the line again, a multi-line regexp may have
4420 		// made it invalid.
4421 		*line = ml_get_buf(wp->w_buffer, lnum, FALSE);
4422 
4423 		if (shl->lnum == lnum)
4424 		{
4425 		    shl->startcol = shl->rm.startpos[0].col;
4426 		    if (shl->rm.endpos[0].lnum == 0)
4427 			shl->endcol = shl->rm.endpos[0].col;
4428 		    else
4429 			shl->endcol = MAXCOL;
4430 
4431 		    if (shl->startcol == shl->endcol)
4432 		    {
4433 			// highlight empty match, try again after
4434 			// it
4435 			if (has_mbyte)
4436 			    shl->endcol += (*mb_ptr2len)(*line + shl->endcol);
4437 			else
4438 			    ++shl->endcol;
4439 		    }
4440 
4441 		    // Loop to check if the match starts at the
4442 		    // current position
4443 		    continue;
4444 		}
4445 	    }
4446 	    break;
4447 	}
4448 	if (shl != search_hl && cur != NULL)
4449 	    cur = cur->next;
4450     }
4451 
4452     // Use attributes from match with highest priority among 'search_hl' and
4453     // the match list.
4454     cur = wp->w_match_head;
4455     shl_flag = WIN_IS_POPUP(wp);
4456     while (cur != NULL || shl_flag == FALSE)
4457     {
4458 	if (shl_flag == FALSE
4459 		&& ((cur != NULL
4460 			&& cur->priority > SEARCH_HL_PRIORITY)
4461 		    || cur == NULL))
4462 	{
4463 	    shl = search_hl;
4464 	    shl_flag = TRUE;
4465 	}
4466 	else
4467 	    shl = &cur->hl;
4468 	if (shl->attr_cur != 0)
4469 	    search_attr = shl->attr_cur;
4470 	if (shl != search_hl && cur != NULL)
4471 	    cur = cur->next;
4472     }
4473     // Only highlight one character after the last column.
4474     if (*(*line + col) == NUL && (did_line_attr >= 1
4475 				       || (wp->w_p_list && lcs_eol_one == -1)))
4476 	search_attr = 0;
4477     return search_attr;
4478 }
4479 
4480     int
4481 get_prevcol_hl_flag(win_T *wp, match_T *search_hl, long curcol)
4482 {
4483     long	prevcol = curcol;
4484     int		prevcol_hl_flag = FALSE;
4485     matchitem_T *cur;			// points to the match list
4486 
4487     // we're not really at that column when skipping some text
4488     if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol)
4489 	++prevcol;
4490 
4491     if (!search_hl->is_addpos && prevcol == (long)search_hl->startcol)
4492 	prevcol_hl_flag = TRUE;
4493     else
4494     {
4495 	cur = wp->w_match_head;
4496 	while (cur != NULL)
4497 	{
4498 	    if (!cur->hl.is_addpos && prevcol == (long)cur->hl.startcol)
4499 	    {
4500 		prevcol_hl_flag = TRUE;
4501 		break;
4502 	    }
4503 	    cur = cur->next;
4504 	}
4505     }
4506     return prevcol_hl_flag;
4507 }
4508 
4509 /*
4510  * Get highlighting for the char after the text in "char_attr" from 'hlsearch'
4511  * or match highlighting.
4512  */
4513     void
4514 get_search_match_hl(win_T *wp, match_T *search_hl, long col, int *char_attr)
4515 {
4516     matchitem_T *cur;			// points to the match list
4517     match_T	*shl;			// points to search_hl or a match
4518     int		shl_flag;		// flag to indicate whether search_hl
4519 					// has been processed or not
4520 
4521     cur = wp->w_match_head;
4522     shl_flag = WIN_IS_POPUP(wp);
4523     while (cur != NULL || shl_flag == FALSE)
4524     {
4525 	if (shl_flag == FALSE
4526 		&& ((cur != NULL
4527 			&& cur->priority > SEARCH_HL_PRIORITY)
4528 		    || cur == NULL))
4529 	{
4530 	    shl = search_hl;
4531 	    shl_flag = TRUE;
4532 	}
4533 	else
4534 	    shl = &cur->hl;
4535 	if (col - 1 == (long)shl->startcol
4536 		&& (shl == search_hl || !shl->is_addpos))
4537 	    *char_attr = shl->attr;
4538 	if (shl != search_hl && cur != NULL)
4539 	    cur = cur->next;
4540     }
4541 }
4542 
4543 #endif // FEAT_SEARCH_EXTRA
4544 
4545 #if defined(FEAT_EVAL) || defined(PROTO)
4546 # ifdef FEAT_SEARCH_EXTRA
4547     static int
4548 matchadd_dict_arg(typval_T *tv, char_u **conceal_char, win_T **win)
4549 {
4550     dictitem_T *di;
4551 
4552     if (tv->v_type != VAR_DICT)
4553     {
4554 	emsg(_(e_dictreq));
4555 	return FAIL;
4556     }
4557 
4558     if (dict_find(tv->vval.v_dict, (char_u *)"conceal", -1) != NULL)
4559 	*conceal_char = dict_get_string(tv->vval.v_dict,
4560 						   (char_u *)"conceal", FALSE);
4561 
4562     if ((di = dict_find(tv->vval.v_dict, (char_u *)"window", -1)) != NULL)
4563     {
4564 	*win = find_win_by_nr_or_id(&di->di_tv);
4565 	if (*win == NULL)
4566 	{
4567 	    emsg(_(e_invalwindow));
4568 	    return FAIL;
4569 	}
4570     }
4571 
4572     return OK;
4573 }
4574 #endif
4575 
4576 /*
4577  * "clearmatches()" function
4578  */
4579     void
4580 f_clearmatches(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4581 {
4582 #ifdef FEAT_SEARCH_EXTRA
4583     win_T   *win = get_optional_window(argvars, 0);
4584 
4585     if (win != NULL)
4586 	clear_matches(win);
4587 #endif
4588 }
4589 
4590 /*
4591  * "getmatches()" function
4592  */
4593     void
4594 f_getmatches(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4595 {
4596 # ifdef FEAT_SEARCH_EXTRA
4597     dict_T	*dict;
4598     matchitem_T	*cur;
4599     int		i;
4600     win_T	*win = get_optional_window(argvars, 0);
4601 
4602     if (rettv_list_alloc(rettv) == FAIL || win == NULL)
4603 	return;
4604 
4605     cur = win->w_match_head;
4606     while (cur != NULL)
4607     {
4608 	dict = dict_alloc();
4609 	if (dict == NULL)
4610 	    return;
4611 	if (cur->match.regprog == NULL)
4612 	{
4613 	    // match added with matchaddpos()
4614 	    for (i = 0; i < MAXPOSMATCH; ++i)
4615 	    {
4616 		llpos_T	*llpos;
4617 		char	buf[30];  // use 30 to avoid compiler warning
4618 		list_T	*l;
4619 
4620 		llpos = &cur->pos.pos[i];
4621 		if (llpos->lnum == 0)
4622 		    break;
4623 		l = list_alloc();
4624 		if (l == NULL)
4625 		    break;
4626 		list_append_number(l, (varnumber_T)llpos->lnum);
4627 		if (llpos->col > 0)
4628 		{
4629 		    list_append_number(l, (varnumber_T)llpos->col);
4630 		    list_append_number(l, (varnumber_T)llpos->len);
4631 		}
4632 		sprintf(buf, "pos%d", i + 1);
4633 		dict_add_list(dict, buf, l);
4634 	    }
4635 	}
4636 	else
4637 	{
4638 	    dict_add_string(dict, "pattern", cur->pattern);
4639 	}
4640 	dict_add_string(dict, "group", syn_id2name(cur->hlg_id));
4641 	dict_add_number(dict, "priority", (long)cur->priority);
4642 	dict_add_number(dict, "id", (long)cur->id);
4643 #  if defined(FEAT_CONCEAL)
4644 	if (cur->conceal_char)
4645 	{
4646 	    char_u buf[MB_MAXBYTES + 1];
4647 
4648 	    buf[(*mb_char2bytes)((int)cur->conceal_char, buf)] = NUL;
4649 	    dict_add_string(dict, "conceal", (char_u *)&buf);
4650 	}
4651 #  endif
4652 	list_append_dict(rettv->vval.v_list, dict);
4653 	cur = cur->next;
4654     }
4655 # endif
4656 }
4657 
4658 /*
4659  * "setmatches()" function
4660  */
4661     void
4662 f_setmatches(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4663 {
4664 #ifdef FEAT_SEARCH_EXTRA
4665     list_T	*l;
4666     listitem_T	*li;
4667     dict_T	*d;
4668     list_T	*s = NULL;
4669     win_T	*win = get_optional_window(argvars, 1);
4670 
4671     rettv->vval.v_number = -1;
4672     if (argvars[0].v_type != VAR_LIST)
4673     {
4674 	emsg(_(e_listreq));
4675 	return;
4676     }
4677     if (win == NULL)
4678 	return;
4679 
4680     if ((l = argvars[0].vval.v_list) != NULL)
4681     {
4682 	// To some extent make sure that we are dealing with a list from
4683 	// "getmatches()".
4684 	li = l->lv_first;
4685 	while (li != NULL)
4686 	{
4687 	    if (li->li_tv.v_type != VAR_DICT
4688 		    || (d = li->li_tv.vval.v_dict) == NULL)
4689 	    {
4690 		emsg(_(e_invarg));
4691 		return;
4692 	    }
4693 	    if (!(dict_find(d, (char_u *)"group", -1) != NULL
4694 			&& (dict_find(d, (char_u *)"pattern", -1) != NULL
4695 			    || dict_find(d, (char_u *)"pos1", -1) != NULL)
4696 			&& dict_find(d, (char_u *)"priority", -1) != NULL
4697 			&& dict_find(d, (char_u *)"id", -1) != NULL))
4698 	    {
4699 		emsg(_(e_invarg));
4700 		return;
4701 	    }
4702 	    li = li->li_next;
4703 	}
4704 
4705 	clear_matches(win);
4706 	li = l->lv_first;
4707 	while (li != NULL)
4708 	{
4709 	    int		i = 0;
4710 	    char	buf[30];  // use 30 to avoid compiler warning
4711 	    dictitem_T  *di;
4712 	    char_u	*group;
4713 	    int		priority;
4714 	    int		id;
4715 	    char_u	*conceal;
4716 
4717 	    d = li->li_tv.vval.v_dict;
4718 	    if (dict_find(d, (char_u *)"pattern", -1) == NULL)
4719 	    {
4720 		if (s == NULL)
4721 		{
4722 		    s = list_alloc();
4723 		    if (s == NULL)
4724 			return;
4725 		}
4726 
4727 		// match from matchaddpos()
4728 		for (i = 1; i < 9; i++)
4729 		{
4730 		    sprintf((char *)buf, (char *)"pos%d", i);
4731 		    if ((di = dict_find(d, (char_u *)buf, -1)) != NULL)
4732 		    {
4733 			if (di->di_tv.v_type != VAR_LIST)
4734 			    return;
4735 
4736 			list_append_tv(s, &di->di_tv);
4737 			s->lv_refcount++;
4738 		    }
4739 		    else
4740 			break;
4741 		}
4742 	    }
4743 
4744 	    group = dict_get_string(d, (char_u *)"group", TRUE);
4745 	    priority = (int)dict_get_number(d, (char_u *)"priority");
4746 	    id = (int)dict_get_number(d, (char_u *)"id");
4747 	    conceal = dict_find(d, (char_u *)"conceal", -1) != NULL
4748 			      ? dict_get_string(d, (char_u *)"conceal", TRUE)
4749 			      : NULL;
4750 	    if (i == 0)
4751 	    {
4752 		match_add(win, group,
4753 		    dict_get_string(d, (char_u *)"pattern", FALSE),
4754 		    priority, id, NULL, conceal);
4755 	    }
4756 	    else
4757 	    {
4758 		match_add(win, group, NULL, priority, id, s, conceal);
4759 		list_unref(s);
4760 		s = NULL;
4761 	    }
4762 	    vim_free(group);
4763 	    vim_free(conceal);
4764 
4765 	    li = li->li_next;
4766 	}
4767 	rettv->vval.v_number = 0;
4768     }
4769 #endif
4770 }
4771 
4772 /*
4773  * "matchadd()" function
4774  */
4775     void
4776 f_matchadd(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4777 {
4778 # ifdef FEAT_SEARCH_EXTRA
4779     char_u	buf[NUMBUFLEN];
4780     char_u	*grp = tv_get_string_buf_chk(&argvars[0], buf);	// group
4781     char_u	*pat = tv_get_string_buf_chk(&argvars[1], buf);	// pattern
4782     int		prio = 10;	// default priority
4783     int		id = -1;
4784     int		error = FALSE;
4785     char_u	*conceal_char = NULL;
4786     win_T	*win = curwin;
4787 
4788     rettv->vval.v_number = -1;
4789 
4790     if (grp == NULL || pat == NULL)
4791 	return;
4792     if (argvars[2].v_type != VAR_UNKNOWN)
4793     {
4794 	prio = (int)tv_get_number_chk(&argvars[2], &error);
4795 	if (argvars[3].v_type != VAR_UNKNOWN)
4796 	{
4797 	    id = (int)tv_get_number_chk(&argvars[3], &error);
4798 	    if (argvars[4].v_type != VAR_UNKNOWN
4799 		&& matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL)
4800 		return;
4801 	}
4802     }
4803     if (error == TRUE)
4804 	return;
4805     if (id >= 1 && id <= 3)
4806     {
4807 	semsg(_("E798: ID is reserved for \":match\": %d"), id);
4808 	return;
4809     }
4810 
4811     rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL,
4812 								conceal_char);
4813 # endif
4814 }
4815 
4816 /*
4817  * "matchaddpos()" function
4818  */
4819     void
4820 f_matchaddpos(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4821 {
4822 # ifdef FEAT_SEARCH_EXTRA
4823     char_u	buf[NUMBUFLEN];
4824     char_u	*group;
4825     int		prio = 10;
4826     int		id = -1;
4827     int		error = FALSE;
4828     list_T	*l;
4829     char_u	*conceal_char = NULL;
4830     win_T	*win = curwin;
4831 
4832     rettv->vval.v_number = -1;
4833 
4834     group = tv_get_string_buf_chk(&argvars[0], buf);
4835     if (group == NULL)
4836 	return;
4837 
4838     if (argvars[1].v_type != VAR_LIST)
4839     {
4840 	semsg(_(e_listarg), "matchaddpos()");
4841 	return;
4842     }
4843     l = argvars[1].vval.v_list;
4844     if (l == NULL)
4845 	return;
4846 
4847     if (argvars[2].v_type != VAR_UNKNOWN)
4848     {
4849 	prio = (int)tv_get_number_chk(&argvars[2], &error);
4850 	if (argvars[3].v_type != VAR_UNKNOWN)
4851 	{
4852 	    id = (int)tv_get_number_chk(&argvars[3], &error);
4853 
4854 	    if (argvars[4].v_type != VAR_UNKNOWN
4855 		&& matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL)
4856 		return;
4857 	}
4858     }
4859     if (error == TRUE)
4860 	return;
4861 
4862     // id == 3 is ok because matchaddpos() is supposed to substitute :3match
4863     if (id == 1 || id == 2)
4864     {
4865 	semsg(_("E798: ID is reserved for \":match\": %d"), id);
4866 	return;
4867     }
4868 
4869     rettv->vval.v_number = match_add(win, group, NULL, prio, id, l,
4870 								conceal_char);
4871 # endif
4872 }
4873 
4874 /*
4875  * "matcharg()" function
4876  */
4877     void
4878 f_matcharg(typval_T *argvars UNUSED, typval_T *rettv)
4879 {
4880     if (rettv_list_alloc(rettv) == OK)
4881     {
4882 # ifdef FEAT_SEARCH_EXTRA
4883 	int	    id = (int)tv_get_number(&argvars[0]);
4884 	matchitem_T *m;
4885 
4886 	if (id >= 1 && id <= 3)
4887 	{
4888 	    if ((m = (matchitem_T *)get_match(curwin, id)) != NULL)
4889 	    {
4890 		list_append_string(rettv->vval.v_list,
4891 						syn_id2name(m->hlg_id), -1);
4892 		list_append_string(rettv->vval.v_list, m->pattern, -1);
4893 	    }
4894 	    else
4895 	    {
4896 		list_append_string(rettv->vval.v_list, NULL, -1);
4897 		list_append_string(rettv->vval.v_list, NULL, -1);
4898 	    }
4899 	}
4900 # endif
4901     }
4902 }
4903 
4904 /*
4905  * "matchdelete()" function
4906  */
4907     void
4908 f_matchdelete(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4909 {
4910 # ifdef FEAT_SEARCH_EXTRA
4911     win_T   *win = get_optional_window(argvars, 1);
4912 
4913     if (win == NULL)
4914 	rettv->vval.v_number = -1;
4915     else
4916 	rettv->vval.v_number = match_delete(win,
4917 				       (int)tv_get_number(&argvars[0]), TRUE);
4918 # endif
4919 }
4920 #endif
4921 
4922 #if defined(FEAT_SEARCH_EXTRA) || defined(PROTO)
4923 /*
4924  * ":[N]match {group} {pattern}"
4925  * Sets nextcmd to the start of the next command, if any.  Also called when
4926  * skipping commands to find the next command.
4927  */
4928     void
4929 ex_match(exarg_T *eap)
4930 {
4931     char_u	*p;
4932     char_u	*g = NULL;
4933     char_u	*end;
4934     int		c;
4935     int		id;
4936 
4937     if (eap->line2 <= 3)
4938 	id = eap->line2;
4939     else
4940     {
4941 	emsg(_(e_invcmd));
4942 	return;
4943     }
4944 
4945     // First clear any old pattern.
4946     if (!eap->skip)
4947 	match_delete(curwin, id, FALSE);
4948 
4949     if (ends_excmd2(eap->cmd, eap->arg))
4950 	end = eap->arg;
4951     else if ((STRNICMP(eap->arg, "none", 4) == 0
4952 		&& (VIM_ISWHITE(eap->arg[4])
4953 				      || ends_excmd2(eap->arg, eap->arg + 4))))
4954 	end = eap->arg + 4;
4955     else
4956     {
4957 	p = skiptowhite(eap->arg);
4958 	if (!eap->skip)
4959 	    g = vim_strnsave(eap->arg, (int)(p - eap->arg));
4960 	p = skipwhite(p);
4961 	if (*p == NUL)
4962 	{
4963 	    // There must be two arguments.
4964 	    vim_free(g);
4965 	    semsg(_(e_invarg2), eap->arg);
4966 	    return;
4967 	}
4968 	end = skip_regexp(p + 1, *p, TRUE);
4969 	if (!eap->skip)
4970 	{
4971 	    if (*end != NUL && !ends_excmd2(end, skipwhite(end + 1)))
4972 	    {
4973 		vim_free(g);
4974 		eap->errmsg = e_trailing;
4975 		return;
4976 	    }
4977 	    if (*end != *p)
4978 	    {
4979 		vim_free(g);
4980 		semsg(_(e_invarg2), p);
4981 		return;
4982 	    }
4983 
4984 	    c = *end;
4985 	    *end = NUL;
4986 	    match_add(curwin, g, p + 1, 10, id, NULL, NULL);
4987 	    vim_free(g);
4988 	    *end = c;
4989 	}
4990     }
4991     eap->nextcmd = find_nextcmd(end);
4992 }
4993 #endif
4994