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