xref: /vim-8.2.3635/src/syntax.c (revision 3ad8772e)
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  * syntax.c: code for syntax highlighting
12  */
13 
14 #include "vim.h"
15 
16 /*
17  * Structure that stores information about a highlight group.
18  * The ID of a highlight group is also called group ID.  It is the index in
19  * the highlight_ga array PLUS ONE.
20  */
21 struct hl_group
22 {
23     char_u	*sg_name;	/* highlight group name */
24     char_u	*sg_name_u;	/* uppercase of sg_name */
25     int		sg_cleared;	/* "hi clear" was used */
26 /* for normal terminals */
27     int		sg_term;	/* "term=" highlighting attributes */
28     char_u	*sg_start;	/* terminal string for start highl */
29     char_u	*sg_stop;	/* terminal string for stop highl */
30     int		sg_term_attr;	/* Screen attr for term mode */
31 /* for color terminals */
32     int		sg_cterm;	/* "cterm=" highlighting attr */
33     int		sg_cterm_bold;	/* bold attr was set for light color */
34     int		sg_cterm_fg;	/* terminal fg color number + 1 */
35     int		sg_cterm_bg;	/* terminal bg color number + 1 */
36     int		sg_cterm_attr;	/* Screen attr for color term mode */
37 /* for when using the GUI */
38 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
39     guicolor_T	sg_gui_fg;	/* GUI foreground color handle */
40     guicolor_T	sg_gui_bg;	/* GUI background color handle */
41 #endif
42 #ifdef FEAT_GUI
43     guicolor_T	sg_gui_sp;	/* GUI special color handle */
44     GuiFont	sg_font;	/* GUI font handle */
45 #ifdef FEAT_XFONTSET
46     GuiFontset	sg_fontset;	/* GUI fontset handle */
47 #endif
48     char_u	*sg_font_name;  /* GUI font or fontset name */
49     int		sg_gui_attr;    /* Screen attr for GUI mode */
50 #endif
51 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
52 /* Store the sp color name for the GUI or synIDattr() */
53     int		sg_gui;		/* "gui=" highlighting attributes */
54     char_u	*sg_gui_fg_name;/* GUI foreground color name */
55     char_u	*sg_gui_bg_name;/* GUI background color name */
56     char_u	*sg_gui_sp_name;/* GUI special color name */
57 #endif
58     int		sg_link;	/* link to this highlight group ID */
59     int		sg_set;		/* combination of SG_* flags */
60 #ifdef FEAT_EVAL
61     scid_T	sg_scriptID;	/* script in which the group was last set */
62 #endif
63 };
64 
65 #define SG_TERM		1	/* term has been set */
66 #define SG_CTERM	2	/* cterm has been set */
67 #define SG_GUI		4	/* gui has been set */
68 #define SG_LINK		8	/* link has been set */
69 
70 static garray_T highlight_ga;	/* highlight groups for 'highlight' option */
71 
72 #define HL_TABLE() ((struct hl_group *)((highlight_ga.ga_data)))
73 
74 #define MAX_HL_ID       20000	/* maximum value for a highlight ID. */
75 
76 #ifdef FEAT_CMDL_COMPL
77 /* Flags to indicate an additional string for highlight name completion. */
78 static int include_none = 0;	/* when 1 include "None" */
79 static int include_default = 0;	/* when 1 include "default" */
80 static int include_link = 0;	/* when 2 include "link" and "clear" */
81 #endif
82 
83 /*
84  * The "term", "cterm" and "gui" arguments can be any combination of the
85  * following names, separated by commas (but no spaces!).
86  */
87 static char *(hl_name_table[]) =
88     {"bold", "standout", "underline", "undercurl",
89       "italic", "reverse", "inverse", "nocombine", "strikethrough", "NONE"};
90 static int hl_attr_table[] =
91     {HL_BOLD, HL_STANDOUT, HL_UNDERLINE, HL_UNDERCURL, HL_ITALIC, HL_INVERSE, HL_INVERSE, HL_NOCOMBINE, HL_STRIKETHROUGH, 0};
92 #define ATTR_COMBINE(attr_a, attr_b) ((((attr_b) & HL_NOCOMBINE) ? attr_b : (attr_a)) | (attr_b))
93 
94 static int get_attr_entry(garray_T *table, attrentry_T *aep);
95 static void syn_unadd_group(void);
96 static void set_hl_attr(int idx);
97 static void highlight_list_one(int id);
98 static int highlight_list_arg(int id, int didh, int type, int iarg, char_u *sarg, char *name);
99 static int syn_add_group(char_u *name);
100 static int syn_list_header(int did_header, int outlen, int id);
101 static int hl_has_settings(int idx, int check_link);
102 static void highlight_clear(int idx);
103 
104 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
105 static void gui_do_one_color(int idx, int do_menu, int do_tooltip);
106 #endif
107 #ifdef FEAT_GUI
108 static int  set_group_colors(char_u *name, guicolor_T *fgp, guicolor_T *bgp, int do_menu, int use_norm, int do_tooltip);
109 static GuiFont font_name2handle(char_u *name);
110 # ifdef FEAT_XFONTSET
111 static GuiFontset fontset_name2handle(char_u *name, int fixed_width);
112 # endif
113 static void hl_do_font(int idx, char_u *arg, int do_normal, int do_menu, int do_tooltip, int free_font);
114 #endif
115 
116 /*
117  * An attribute number is the index in attr_table plus ATTR_OFF.
118  */
119 #define ATTR_OFF (HL_ALL + 1)
120 
121 #if defined(FEAT_SYN_HL) || defined(PROTO)
122 
123 #define SYN_NAMELEN	50		/* maximum length of a syntax name */
124 
125 /* different types of offsets that are possible */
126 #define SPO_MS_OFF	0	/* match  start offset */
127 #define SPO_ME_OFF	1	/* match  end	offset */
128 #define SPO_HS_OFF	2	/* highl. start offset */
129 #define SPO_HE_OFF	3	/* highl. end	offset */
130 #define SPO_RS_OFF	4	/* region start offset */
131 #define SPO_RE_OFF	5	/* region end	offset */
132 #define SPO_LC_OFF	6	/* leading context offset */
133 #define SPO_COUNT	7
134 
135 static char *(spo_name_tab[SPO_COUNT]) =
136 	    {"ms=", "me=", "hs=", "he=", "rs=", "re=", "lc="};
137 
138 /*
139  * The patterns that are being searched for are stored in a syn_pattern.
140  * A match item consists of one pattern.
141  * A start/end item consists of n start patterns and m end patterns.
142  * A start/skip/end item consists of n start patterns, one skip pattern and m
143  * end patterns.
144  * For the latter two, the patterns are always consecutive: start-skip-end.
145  *
146  * A character offset can be given for the matched text (_m_start and _m_end)
147  * and for the actually highlighted text (_h_start and _h_end).
148  *
149  * Note that ordering of members is optimized to reduce padding.
150  */
151 typedef struct syn_pattern
152 {
153     char	 sp_type;		/* see SPTYPE_ defines below */
154     char	 sp_syncing;		/* this item used for syncing */
155     short	 sp_syn_match_id;	/* highlight group ID of pattern */
156     short	 sp_off_flags;		/* see below */
157     int		 sp_offsets[SPO_COUNT];	/* offsets */
158     int		 sp_flags;		/* see HL_ defines below */
159 #ifdef FEAT_CONCEAL
160     int		 sp_cchar;		/* conceal substitute character */
161 #endif
162     int		 sp_ic;			/* ignore-case flag for sp_prog */
163     int		 sp_sync_idx;		/* sync item index (syncing only) */
164     int		 sp_line_id;		/* ID of last line where tried */
165     int		 sp_startcol;		/* next match in sp_line_id line */
166     short	*sp_cont_list;		/* cont. group IDs, if non-zero */
167     short	*sp_next_list;		/* next group IDs, if non-zero */
168     struct sp_syn sp_syn;		/* struct passed to in_id_list() */
169     char_u	*sp_pattern;		/* regexp to match, pattern */
170     regprog_T	*sp_prog;		/* regexp to match, program */
171 #ifdef FEAT_PROFILE
172     syn_time_T	 sp_time;
173 #endif
174 } synpat_T;
175 
176 /* The sp_off_flags are computed like this:
177  * offset from the start of the matched text: (1 << SPO_XX_OFF)
178  * offset from the end	 of the matched text: (1 << (SPO_XX_OFF + SPO_COUNT))
179  * When both are present, only one is used.
180  */
181 
182 #define SPTYPE_MATCH	1	/* match keyword with this group ID */
183 #define SPTYPE_START	2	/* match a regexp, start of item */
184 #define SPTYPE_END	3	/* match a regexp, end of item */
185 #define SPTYPE_SKIP	4	/* match a regexp, skip within item */
186 
187 
188 #define SYN_ITEMS(buf)	((synpat_T *)((buf)->b_syn_patterns.ga_data))
189 
190 #define NONE_IDX	-2	/* value of sp_sync_idx for "NONE" */
191 
192 /*
193  * Flags for b_syn_sync_flags:
194  */
195 #define SF_CCOMMENT	0x01	/* sync on a C-style comment */
196 #define SF_MATCH	0x02	/* sync by matching a pattern */
197 
198 #define SYN_STATE_P(ssp)    ((bufstate_T *)((ssp)->ga_data))
199 
200 #define MAXKEYWLEN	80	    /* maximum length of a keyword */
201 
202 /*
203  * The attributes of the syntax item that has been recognized.
204  */
205 static int current_attr = 0;	    /* attr of current syntax word */
206 #ifdef FEAT_EVAL
207 static int current_id = 0;	    /* ID of current char for syn_get_id() */
208 static int current_trans_id = 0;    /* idem, transparency removed */
209 #endif
210 #ifdef FEAT_CONCEAL
211 static int current_flags = 0;
212 static int current_seqnr = 0;
213 static int current_sub_char = 0;
214 #endif
215 
216 typedef struct syn_cluster_S
217 {
218     char_u	    *scl_name;	    /* syntax cluster name */
219     char_u	    *scl_name_u;    /* uppercase of scl_name */
220     short	    *scl_list;	    /* IDs in this syntax cluster */
221 } syn_cluster_T;
222 
223 /*
224  * Methods of combining two clusters
225  */
226 #define CLUSTER_REPLACE	    1	/* replace first list with second */
227 #define CLUSTER_ADD	    2	/* add second list to first */
228 #define CLUSTER_SUBTRACT    3	/* subtract second list from first */
229 
230 #define SYN_CLSTR(buf)	((syn_cluster_T *)((buf)->b_syn_clusters.ga_data))
231 
232 /*
233  * Syntax group IDs have different types:
234  *     0 - 19999  normal syntax groups
235  * 20000 - 20999  ALLBUT indicator (current_syn_inc_tag added)
236  * 21000 - 21999  TOP indicator (current_syn_inc_tag added)
237  * 22000 - 22999  CONTAINED indicator (current_syn_inc_tag added)
238  * 23000 - 32767  cluster IDs (subtract SYNID_CLUSTER for the cluster ID)
239  */
240 #define SYNID_ALLBUT	MAX_HL_ID   /* syntax group ID for contains=ALLBUT */
241 #define SYNID_TOP	21000	    /* syntax group ID for contains=TOP */
242 #define SYNID_CONTAINED	22000	    /* syntax group ID for contains=CONTAINED */
243 #define SYNID_CLUSTER	23000	    /* first syntax group ID for clusters */
244 
245 #define MAX_SYN_INC_TAG	999	    /* maximum before the above overflow */
246 #define MAX_CLUSTER_ID  (32767 - SYNID_CLUSTER)
247 
248 /*
249  * Annoying Hack(TM):  ":syn include" needs this pointer to pass to
250  * expand_filename().  Most of the other syntax commands don't need it, so
251  * instead of passing it to them, we stow it here.
252  */
253 static char_u **syn_cmdlinep;
254 
255 /*
256  * Another Annoying Hack(TM):  To prevent rules from other ":syn include"'d
257  * files from leaking into ALLBUT lists, we assign a unique ID to the
258  * rules in each ":syn include"'d file.
259  */
260 static int current_syn_inc_tag = 0;
261 static int running_syn_inc_tag = 0;
262 
263 /*
264  * In a hashtable item "hi_key" points to "keyword" in a keyentry.
265  * This avoids adding a pointer to the hashtable item.
266  * KE2HIKEY() converts a var pointer to a hashitem key pointer.
267  * HIKEY2KE() converts a hashitem key pointer to a var pointer.
268  * HI2KE() converts a hashitem pointer to a var pointer.
269  */
270 static keyentry_T dumkey;
271 #define KE2HIKEY(kp)  ((kp)->keyword)
272 #define HIKEY2KE(p)   ((keyentry_T *)((p) - (dumkey.keyword - (char_u *)&dumkey)))
273 #define HI2KE(hi)      HIKEY2KE((hi)->hi_key)
274 
275 /*
276  * To reduce the time spent in keepend(), remember at which level in the state
277  * stack the first item with "keepend" is present.  When "-1", there is no
278  * "keepend" on the stack.
279  */
280 static int keepend_level = -1;
281 
282 static char msg_no_items[] = N_("No Syntax items defined for this buffer");
283 
284 /*
285  * For the current state we need to remember more than just the idx.
286  * When si_m_endpos.lnum is 0, the items other than si_idx are unknown.
287  * (The end positions have the column number of the next char)
288  */
289 typedef struct state_item
290 {
291     int		si_idx;			/* index of syntax pattern or
292 					   KEYWORD_IDX */
293     int		si_id;			/* highlight group ID for keywords */
294     int		si_trans_id;		/* idem, transparency removed */
295     int		si_m_lnum;		/* lnum of the match */
296     int		si_m_startcol;		/* starting column of the match */
297     lpos_T	si_m_endpos;		/* just after end posn of the match */
298     lpos_T	si_h_startpos;		/* start position of the highlighting */
299     lpos_T	si_h_endpos;		/* end position of the highlighting */
300     lpos_T	si_eoe_pos;		/* end position of end pattern */
301     int		si_end_idx;		/* group ID for end pattern or zero */
302     int		si_ends;		/* if match ends before si_m_endpos */
303     int		si_attr;		/* attributes in this state */
304     long	si_flags;		/* HL_HAS_EOL flag in this state, and
305 					 * HL_SKIP* for si_next_list */
306 #ifdef FEAT_CONCEAL
307     int		si_seqnr;		/* sequence number */
308     int		si_cchar;		/* substitution character for conceal */
309 #endif
310     short	*si_cont_list;		/* list of contained groups */
311     short	*si_next_list;		/* nextgroup IDs after this item ends */
312     reg_extmatch_T *si_extmatch;	/* \z(...\) matches from start
313 					 * pattern */
314 } stateitem_T;
315 
316 #define KEYWORD_IDX	-1	    /* value of si_idx for keywords */
317 #define ID_LIST_ALL	(short *)-1 /* valid of si_cont_list for containing all
318 				       but contained groups */
319 
320 #ifdef FEAT_CONCEAL
321 static int next_seqnr = 1;		/* value to use for si_seqnr */
322 #endif
323 
324 /*
325  * Struct to reduce the number of arguments to get_syn_options(), it's used
326  * very often.
327  */
328 typedef struct
329 {
330     int		flags;		/* flags for contained and transparent */
331     int		keyword;	/* TRUE for ":syn keyword" */
332     int		*sync_idx;	/* syntax item for "grouphere" argument, NULL
333 				   if not allowed */
334     char	has_cont_list;	/* TRUE if "cont_list" can be used */
335     short	*cont_list;	/* group IDs for "contains" argument */
336     short	*cont_in_list;	/* group IDs for "containedin" argument */
337     short	*next_list;	/* group IDs for "nextgroup" argument */
338 } syn_opt_arg_T;
339 
340 /*
341  * The next possible match in the current line for any pattern is remembered,
342  * to avoid having to try for a match in each column.
343  * If next_match_idx == -1, not tried (in this line) yet.
344  * If next_match_col == MAXCOL, no match found in this line.
345  * (All end positions have the column of the char after the end)
346  */
347 static int next_match_col;		/* column for start of next match */
348 static lpos_T next_match_m_endpos;	/* position for end of next match */
349 static lpos_T next_match_h_startpos;  /* pos. for highl. start of next match */
350 static lpos_T next_match_h_endpos;	/* pos. for highl. end of next match */
351 static int next_match_idx;		/* index of matched item */
352 static long next_match_flags;		/* flags for next match */
353 static lpos_T next_match_eos_pos;	/* end of start pattn (start region) */
354 static lpos_T next_match_eoe_pos;	/* pos. for end of end pattern */
355 static int next_match_end_idx;		/* ID of group for end pattn or zero */
356 static reg_extmatch_T *next_match_extmatch = NULL;
357 
358 /*
359  * A state stack is an array of integers or stateitem_T, stored in a
360  * garray_T.  A state stack is invalid if it's itemsize entry is zero.
361  */
362 #define INVALID_STATE(ssp)  ((ssp)->ga_itemsize == 0)
363 #define VALID_STATE(ssp)    ((ssp)->ga_itemsize != 0)
364 
365 /*
366  * The current state (within the line) of the recognition engine.
367  * When current_state.ga_itemsize is 0 the current state is invalid.
368  */
369 static win_T	*syn_win;		/* current window for highlighting */
370 static buf_T	*syn_buf;		/* current buffer for highlighting */
371 static synblock_T *syn_block;		/* current buffer for highlighting */
372 #ifdef FEAT_RELTIME
373 static proftime_T *syn_tm;		/* timeout limit */
374 #endif
375 static linenr_T current_lnum = 0;	/* lnum of current state */
376 static colnr_T	current_col = 0;	/* column of current state */
377 static int	current_state_stored = 0; /* TRUE if stored current state
378 					   * after setting current_finished */
379 static int	current_finished = 0;	/* current line has been finished */
380 static garray_T current_state		/* current stack of state_items */
381 		= {0, 0, 0, 0, NULL};
382 static short	*current_next_list = NULL; /* when non-zero, nextgroup list */
383 static int	current_next_flags = 0; /* flags for current_next_list */
384 static int	current_line_id = 0;	/* unique number for current line */
385 
386 #define CUR_STATE(idx)	((stateitem_T *)(current_state.ga_data))[idx]
387 
388 static void syn_sync(win_T *wp, linenr_T lnum, synstate_T *last_valid);
389 static void save_chartab(char_u *chartab);
390 static void restore_chartab(char_u *chartab);
391 static int syn_match_linecont(linenr_T lnum);
392 static void syn_start_line(void);
393 static void syn_update_ends(int startofline);
394 static void syn_stack_alloc(void);
395 static int syn_stack_cleanup(void);
396 static void syn_stack_free_entry(synblock_T *block, synstate_T *p);
397 static synstate_T *syn_stack_find_entry(linenr_T lnum);
398 static synstate_T *store_current_state(void);
399 static void load_current_state(synstate_T *from);
400 static void invalidate_current_state(void);
401 static int syn_stack_equal(synstate_T *sp);
402 static void validate_current_state(void);
403 static int syn_finish_line(int syncing);
404 static int syn_current_attr(int syncing, int displaying, int *can_spell, int keep_state);
405 static int did_match_already(int idx, garray_T *gap);
406 static stateitem_T *push_next_match(stateitem_T *cur_si);
407 static void check_state_ends(void);
408 static void update_si_attr(int idx);
409 static void check_keepend(void);
410 static void update_si_end(stateitem_T *sip, int startcol, int force);
411 static short *copy_id_list(short *list);
412 static int in_id_list(stateitem_T *item, short *cont_list, struct sp_syn *ssp, int contained);
413 static int push_current_state(int idx);
414 static void pop_current_state(void);
415 #ifdef FEAT_PROFILE
416 static void syn_clear_time(syn_time_T *tt);
417 static void syntime_clear(void);
418 #ifdef __BORLANDC__
419 static int _RTLENTRYF syn_compare_syntime(const void *v1, const void *v2);
420 #else
421 static int syn_compare_syntime(const void *v1, const void *v2);
422 #endif
423 static void syntime_report(void);
424 static int syn_time_on = FALSE;
425 # define IF_SYN_TIME(p) (p)
426 #else
427 # define IF_SYN_TIME(p) NULL
428 typedef int syn_time_T;
429 #endif
430 
431 static void syn_stack_apply_changes_block(synblock_T *block, buf_T *buf);
432 static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_endpos, long *flagsp, lpos_T *end_endpos, int *end_idx, reg_extmatch_T *start_ext);
433 static void clear_syn_state(synstate_T *p);
434 static void clear_current_state(void);
435 
436 static void limit_pos(lpos_T *pos, lpos_T *limit);
437 static void limit_pos_zero(lpos_T *pos, lpos_T *limit);
438 static void syn_add_end_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *spp, int idx, int extra);
439 static void syn_add_start_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *spp, int idx, int extra);
440 static char_u *syn_getcurline(void);
441 static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T *st);
442 static int check_keyword_id(char_u *line, int startcol, int *endcol, long *flags, short **next_list, stateitem_T *cur_si, int *ccharp);
443 static void syn_cmd_case(exarg_T *eap, int syncing);
444 static void syn_cmd_spell(exarg_T *eap, int syncing);
445 static void syntax_sync_clear(void);
446 static void syn_remove_pattern(synblock_T *block, int idx);
447 static void syn_clear_pattern(synblock_T *block, int i);
448 static void syn_clear_cluster(synblock_T *block, int i);
449 static void syn_cmd_clear(exarg_T *eap, int syncing);
450 static void syn_cmd_conceal(exarg_T *eap, int syncing);
451 static void syn_clear_one(int id, int syncing);
452 static void syn_cmd_on(exarg_T *eap, int syncing);
453 static void syn_cmd_enable(exarg_T *eap, int syncing);
454 static void syn_cmd_reset(exarg_T *eap, int syncing);
455 static void syn_cmd_manual(exarg_T *eap, int syncing);
456 static void syn_cmd_off(exarg_T *eap, int syncing);
457 static void syn_cmd_onoff(exarg_T *eap, char *name);
458 static void syn_cmd_list(exarg_T *eap, int syncing);
459 static void syn_lines_msg(void);
460 static void syn_match_msg(void);
461 static void syn_stack_free_block(synblock_T *block);
462 static void syn_list_one(int id, int syncing, int link_only);
463 static void syn_list_cluster(int id);
464 static void put_id_list(char_u *name, short *list, int attr);
465 static void put_pattern(char *s, int c, synpat_T *spp, int attr);
466 static int syn_list_keywords(int id, hashtab_T *ht, int did_header, int attr);
467 static void syn_clear_keyword(int id, hashtab_T *ht);
468 static void clear_keywtab(hashtab_T *ht);
469 static void add_keyword(char_u *name, int id, int flags, short *cont_in_list, short *next_list, int conceal_char);
470 static char_u *get_group_name(char_u *arg, char_u **name_end);
471 static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_char, int skip);
472 static void syn_cmd_include(exarg_T *eap, int syncing);
473 static void syn_cmd_iskeyword(exarg_T *eap, int syncing);
474 static void syn_cmd_keyword(exarg_T *eap, int syncing);
475 static void syn_cmd_match(exarg_T *eap, int syncing);
476 static void syn_cmd_region(exarg_T *eap, int syncing);
477 #ifdef __BORLANDC__
478 static int _RTLENTRYF syn_compare_stub(const void *v1, const void *v2);
479 #else
480 static int syn_compare_stub(const void *v1, const void *v2);
481 #endif
482 static void syn_cmd_cluster(exarg_T *eap, int syncing);
483 static int syn_scl_name2id(char_u *name);
484 static int syn_scl_namen2id(char_u *linep, int len);
485 static int syn_check_cluster(char_u *pp, int len);
486 static int syn_add_cluster(char_u *name);
487 static void init_syn_patterns(void);
488 static char_u *get_syn_pattern(char_u *arg, synpat_T *ci);
489 static void syn_cmd_sync(exarg_T *eap, int syncing);
490 static int get_id_list(char_u **arg, int keylen, short **list, int skip);
491 static void syn_combine_list(short **clstr1, short **clstr2, int list_op);
492 static void syn_incl_toplevel(int id, int *flagsp);
493 
494 #if defined(FEAT_RELTIME) || defined(PROTO)
495 /*
496  * Set the timeout used for syntax highlighting.
497  * Use NULL to reset, no timeout.
498  */
499     void
500 syn_set_timeout(proftime_T *tm)
501 {
502     syn_tm = tm;
503 }
504 #endif
505 
506 /*
507  * Start the syntax recognition for a line.  This function is normally called
508  * from the screen updating, once for each displayed line.
509  * The buffer is remembered in syn_buf, because get_syntax_attr() doesn't get
510  * it.	Careful: curbuf and curwin are likely to point to another buffer and
511  * window.
512  */
513     void
514 syntax_start(win_T *wp, linenr_T lnum)
515 {
516     synstate_T	*p;
517     synstate_T	*last_valid = NULL;
518     synstate_T	*last_min_valid = NULL;
519     synstate_T	*sp, *prev = NULL;
520     linenr_T	parsed_lnum;
521     linenr_T	first_stored;
522     int		dist;
523     static varnumber_T changedtick = 0;	/* remember the last change ID */
524 
525 #ifdef FEAT_CONCEAL
526     current_sub_char = NUL;
527 #endif
528 
529     /*
530      * After switching buffers, invalidate current_state.
531      * Also do this when a change was made, the current state may be invalid
532      * then.
533      */
534     if (syn_block != wp->w_s
535 	    || syn_buf != wp->w_buffer
536 	    || changedtick != CHANGEDTICK(syn_buf))
537     {
538 	invalidate_current_state();
539 	syn_buf = wp->w_buffer;
540 	syn_block = wp->w_s;
541     }
542     changedtick = CHANGEDTICK(syn_buf);
543     syn_win = wp;
544 
545     /*
546      * Allocate syntax stack when needed.
547      */
548     syn_stack_alloc();
549     if (syn_block->b_sst_array == NULL)
550 	return;		/* out of memory */
551     syn_block->b_sst_lasttick = display_tick;
552 
553     /*
554      * If the state of the end of the previous line is useful, store it.
555      */
556     if (VALID_STATE(&current_state)
557 	    && current_lnum < lnum
558 	    && current_lnum < syn_buf->b_ml.ml_line_count)
559     {
560 	(void)syn_finish_line(FALSE);
561 	if (!current_state_stored)
562 	{
563 	    ++current_lnum;
564 	    (void)store_current_state();
565 	}
566 
567 	/*
568 	 * If the current_lnum is now the same as "lnum", keep the current
569 	 * state (this happens very often!).  Otherwise invalidate
570 	 * current_state and figure it out below.
571 	 */
572 	if (current_lnum != lnum)
573 	    invalidate_current_state();
574     }
575     else
576 	invalidate_current_state();
577 
578     /*
579      * Try to synchronize from a saved state in b_sst_array[].
580      * Only do this if lnum is not before and not to far beyond a saved state.
581      */
582     if (INVALID_STATE(&current_state) && syn_block->b_sst_array != NULL)
583     {
584 	/* Find last valid saved state before start_lnum. */
585 	for (p = syn_block->b_sst_first; p != NULL; p = p->sst_next)
586 	{
587 	    if (p->sst_lnum > lnum)
588 		break;
589 	    if (p->sst_lnum <= lnum && p->sst_change_lnum == 0)
590 	    {
591 		last_valid = p;
592 		if (p->sst_lnum >= lnum - syn_block->b_syn_sync_minlines)
593 		    last_min_valid = p;
594 	    }
595 	}
596 	if (last_min_valid != NULL)
597 	    load_current_state(last_min_valid);
598     }
599 
600     /*
601      * If "lnum" is before or far beyond a line with a saved state, need to
602      * re-synchronize.
603      */
604     if (INVALID_STATE(&current_state))
605     {
606 	syn_sync(wp, lnum, last_valid);
607 	if (current_lnum == 1)
608 	    /* First line is always valid, no matter "minlines". */
609 	    first_stored = 1;
610 	else
611 	    /* Need to parse "minlines" lines before state can be considered
612 	     * valid to store. */
613 	    first_stored = current_lnum + syn_block->b_syn_sync_minlines;
614     }
615     else
616 	first_stored = current_lnum;
617 
618     /*
619      * Advance from the sync point or saved state until the current line.
620      * Save some entries for syncing with later on.
621      */
622     if (syn_block->b_sst_len <= Rows)
623 	dist = 999999;
624     else
625 	dist = syn_buf->b_ml.ml_line_count / (syn_block->b_sst_len - Rows) + 1;
626     while (current_lnum < lnum)
627     {
628 	syn_start_line();
629 	(void)syn_finish_line(FALSE);
630 	++current_lnum;
631 
632 	/* If we parsed at least "minlines" lines or started at a valid
633 	 * state, the current state is considered valid. */
634 	if (current_lnum >= first_stored)
635 	{
636 	    /* Check if the saved state entry is for the current line and is
637 	     * equal to the current state.  If so, then validate all saved
638 	     * states that depended on a change before the parsed line. */
639 	    if (prev == NULL)
640 		prev = syn_stack_find_entry(current_lnum - 1);
641 	    if (prev == NULL)
642 		sp = syn_block->b_sst_first;
643 	    else
644 		sp = prev;
645 	    while (sp != NULL && sp->sst_lnum < current_lnum)
646 		sp = sp->sst_next;
647 	    if (sp != NULL
648 		    && sp->sst_lnum == current_lnum
649 		    && syn_stack_equal(sp))
650 	    {
651 		parsed_lnum = current_lnum;
652 		prev = sp;
653 		while (sp != NULL && sp->sst_change_lnum <= parsed_lnum)
654 		{
655 		    if (sp->sst_lnum <= lnum)
656 			/* valid state before desired line, use this one */
657 			prev = sp;
658 		    else if (sp->sst_change_lnum == 0)
659 			/* past saved states depending on change, break here. */
660 			break;
661 		    sp->sst_change_lnum = 0;
662 		    sp = sp->sst_next;
663 		}
664 		load_current_state(prev);
665 	    }
666 	    /* Store the state at this line when it's the first one, the line
667 	     * where we start parsing, or some distance from the previously
668 	     * saved state.  But only when parsed at least 'minlines'. */
669 	    else if (prev == NULL
670 			|| current_lnum == lnum
671 			|| current_lnum >= prev->sst_lnum + dist)
672 		prev = store_current_state();
673 	}
674 
675 	/* This can take a long time: break when CTRL-C pressed.  The current
676 	 * state will be wrong then. */
677 	line_breakcheck();
678 	if (got_int)
679 	{
680 	    current_lnum = lnum;
681 	    break;
682 	}
683     }
684 
685     syn_start_line();
686 }
687 
688 /*
689  * We cannot simply discard growarrays full of state_items or buf_states; we
690  * have to manually release their extmatch pointers first.
691  */
692     static void
693 clear_syn_state(synstate_T *p)
694 {
695     int		i;
696     garray_T	*gap;
697 
698     if (p->sst_stacksize > SST_FIX_STATES)
699     {
700 	gap = &(p->sst_union.sst_ga);
701 	for (i = 0; i < gap->ga_len; i++)
702 	    unref_extmatch(SYN_STATE_P(gap)[i].bs_extmatch);
703 	ga_clear(gap);
704     }
705     else
706     {
707 	for (i = 0; i < p->sst_stacksize; i++)
708 	    unref_extmatch(p->sst_union.sst_stack[i].bs_extmatch);
709     }
710 }
711 
712 /*
713  * Cleanup the current_state stack.
714  */
715     static void
716 clear_current_state(void)
717 {
718     int		i;
719     stateitem_T	*sip;
720 
721     sip = (stateitem_T *)(current_state.ga_data);
722     for (i = 0; i < current_state.ga_len; i++)
723 	unref_extmatch(sip[i].si_extmatch);
724     ga_clear(&current_state);
725 }
726 
727 /*
728  * Try to find a synchronisation point for line "lnum".
729  *
730  * This sets current_lnum and the current state.  One of three methods is
731  * used:
732  * 1. Search backwards for the end of a C-comment.
733  * 2. Search backwards for given sync patterns.
734  * 3. Simply start on a given number of lines above "lnum".
735  */
736     static void
737 syn_sync(
738     win_T	*wp,
739     linenr_T	start_lnum,
740     synstate_T	*last_valid)
741 {
742     buf_T	*curbuf_save;
743     win_T	*curwin_save;
744     pos_T	cursor_save;
745     int		idx;
746     linenr_T	lnum;
747     linenr_T	end_lnum;
748     linenr_T	break_lnum;
749     int		had_sync_point;
750     stateitem_T	*cur_si;
751     synpat_T	*spp;
752     char_u	*line;
753     int		found_flags = 0;
754     int		found_match_idx = 0;
755     linenr_T	found_current_lnum = 0;
756     int		found_current_col= 0;
757     lpos_T	found_m_endpos;
758     colnr_T	prev_current_col;
759 
760     /*
761      * Clear any current state that might be hanging around.
762      */
763     invalidate_current_state();
764 
765     /*
766      * Start at least "minlines" back.  Default starting point for parsing is
767      * there.
768      * Start further back, to avoid that scrolling backwards will result in
769      * resyncing for every line.  Now it resyncs only one out of N lines,
770      * where N is minlines * 1.5, or minlines * 2 if minlines is small.
771      * Watch out for overflow when minlines is MAXLNUM.
772      */
773     if (syn_block->b_syn_sync_minlines > start_lnum)
774 	start_lnum = 1;
775     else
776     {
777 	if (syn_block->b_syn_sync_minlines == 1)
778 	    lnum = 1;
779 	else if (syn_block->b_syn_sync_minlines < 10)
780 	    lnum = syn_block->b_syn_sync_minlines * 2;
781 	else
782 	    lnum = syn_block->b_syn_sync_minlines * 3 / 2;
783 	if (syn_block->b_syn_sync_maxlines != 0
784 				     && lnum > syn_block->b_syn_sync_maxlines)
785 	    lnum = syn_block->b_syn_sync_maxlines;
786 	if (lnum >= start_lnum)
787 	    start_lnum = 1;
788 	else
789 	    start_lnum -= lnum;
790     }
791     current_lnum = start_lnum;
792 
793     /*
794      * 1. Search backwards for the end of a C-style comment.
795      */
796     if (syn_block->b_syn_sync_flags & SF_CCOMMENT)
797     {
798 	/* Need to make syn_buf the current buffer for a moment, to be able to
799 	 * use find_start_comment(). */
800 	curwin_save = curwin;
801 	curwin = wp;
802 	curbuf_save = curbuf;
803 	curbuf = syn_buf;
804 
805 	/*
806 	 * Skip lines that end in a backslash.
807 	 */
808 	for ( ; start_lnum > 1; --start_lnum)
809 	{
810 	    line = ml_get(start_lnum - 1);
811 	    if (*line == NUL || *(line + STRLEN(line) - 1) != '\\')
812 		break;
813 	}
814 	current_lnum = start_lnum;
815 
816 	/* set cursor to start of search */
817 	cursor_save = wp->w_cursor;
818 	wp->w_cursor.lnum = start_lnum;
819 	wp->w_cursor.col = 0;
820 
821 	/*
822 	 * If the line is inside a comment, need to find the syntax item that
823 	 * defines the comment.
824 	 * Restrict the search for the end of a comment to b_syn_sync_maxlines.
825 	 */
826 	if (find_start_comment((int)syn_block->b_syn_sync_maxlines) != NULL)
827 	{
828 	    for (idx = syn_block->b_syn_patterns.ga_len; --idx >= 0; )
829 		if (SYN_ITEMS(syn_block)[idx].sp_syn.id
830 						   == syn_block->b_syn_sync_id
831 			&& SYN_ITEMS(syn_block)[idx].sp_type == SPTYPE_START)
832 		{
833 		    validate_current_state();
834 		    if (push_current_state(idx) == OK)
835 			update_si_attr(current_state.ga_len - 1);
836 		    break;
837 		}
838 	}
839 
840 	/* restore cursor and buffer */
841 	wp->w_cursor = cursor_save;
842 	curwin = curwin_save;
843 	curbuf = curbuf_save;
844     }
845 
846     /*
847      * 2. Search backwards for given sync patterns.
848      */
849     else if (syn_block->b_syn_sync_flags & SF_MATCH)
850     {
851 	if (syn_block->b_syn_sync_maxlines != 0
852 			       && start_lnum > syn_block->b_syn_sync_maxlines)
853 	    break_lnum = start_lnum - syn_block->b_syn_sync_maxlines;
854 	else
855 	    break_lnum = 0;
856 
857 	found_m_endpos.lnum = 0;
858 	found_m_endpos.col = 0;
859 	end_lnum = start_lnum;
860 	lnum = start_lnum;
861 	while (--lnum > break_lnum)
862 	{
863 	    /* This can take a long time: break when CTRL-C pressed. */
864 	    line_breakcheck();
865 	    if (got_int)
866 	    {
867 		invalidate_current_state();
868 		current_lnum = start_lnum;
869 		break;
870 	    }
871 
872 	    /* Check if we have run into a valid saved state stack now. */
873 	    if (last_valid != NULL && lnum == last_valid->sst_lnum)
874 	    {
875 		load_current_state(last_valid);
876 		break;
877 	    }
878 
879 	    /*
880 	     * Check if the previous line has the line-continuation pattern.
881 	     */
882 	    if (lnum > 1 && syn_match_linecont(lnum - 1))
883 		continue;
884 
885 	    /*
886 	     * Start with nothing on the state stack
887 	     */
888 	    validate_current_state();
889 
890 	    for (current_lnum = lnum; current_lnum < end_lnum; ++current_lnum)
891 	    {
892 		syn_start_line();
893 		for (;;)
894 		{
895 		    had_sync_point = syn_finish_line(TRUE);
896 		    /*
897 		     * When a sync point has been found, remember where, and
898 		     * continue to look for another one, further on in the line.
899 		     */
900 		    if (had_sync_point && current_state.ga_len)
901 		    {
902 			cur_si = &CUR_STATE(current_state.ga_len - 1);
903 			if (cur_si->si_m_endpos.lnum > start_lnum)
904 			{
905 			    /* ignore match that goes to after where started */
906 			    current_lnum = end_lnum;
907 			    break;
908 			}
909 			if (cur_si->si_idx < 0)
910 			{
911 			    /* Cannot happen? */
912 			    found_flags = 0;
913 			    found_match_idx = KEYWORD_IDX;
914 			}
915 			else
916 			{
917 			    spp = &(SYN_ITEMS(syn_block)[cur_si->si_idx]);
918 			    found_flags = spp->sp_flags;
919 			    found_match_idx = spp->sp_sync_idx;
920 			}
921 			found_current_lnum = current_lnum;
922 			found_current_col = current_col;
923 			found_m_endpos = cur_si->si_m_endpos;
924 			/*
925 			 * Continue after the match (be aware of a zero-length
926 			 * match).
927 			 */
928 			if (found_m_endpos.lnum > current_lnum)
929 			{
930 			    current_lnum = found_m_endpos.lnum;
931 			    current_col = found_m_endpos.col;
932 			    if (current_lnum >= end_lnum)
933 				break;
934 			}
935 			else if (found_m_endpos.col > current_col)
936 			    current_col = found_m_endpos.col;
937 			else
938 			    ++current_col;
939 
940 			/* syn_current_attr() will have skipped the check for
941 			 * an item that ends here, need to do that now.  Be
942 			 * careful not to go past the NUL. */
943 			prev_current_col = current_col;
944 			if (syn_getcurline()[current_col] != NUL)
945 			    ++current_col;
946 			check_state_ends();
947 			current_col = prev_current_col;
948 		    }
949 		    else
950 			break;
951 		}
952 	    }
953 
954 	    /*
955 	     * If a sync point was encountered, break here.
956 	     */
957 	    if (found_flags)
958 	    {
959 		/*
960 		 * Put the item that was specified by the sync point on the
961 		 * state stack.  If there was no item specified, make the
962 		 * state stack empty.
963 		 */
964 		clear_current_state();
965 		if (found_match_idx >= 0
966 			&& push_current_state(found_match_idx) == OK)
967 		    update_si_attr(current_state.ga_len - 1);
968 
969 		/*
970 		 * When using "grouphere", continue from the sync point
971 		 * match, until the end of the line.  Parsing starts at
972 		 * the next line.
973 		 * For "groupthere" the parsing starts at start_lnum.
974 		 */
975 		if (found_flags & HL_SYNC_HERE)
976 		{
977 		    if (current_state.ga_len)
978 		    {
979 			cur_si = &CUR_STATE(current_state.ga_len - 1);
980 			cur_si->si_h_startpos.lnum = found_current_lnum;
981 			cur_si->si_h_startpos.col = found_current_col;
982 			update_si_end(cur_si, (int)current_col, TRUE);
983 			check_keepend();
984 		    }
985 		    current_col = found_m_endpos.col;
986 		    current_lnum = found_m_endpos.lnum;
987 		    (void)syn_finish_line(FALSE);
988 		    ++current_lnum;
989 		}
990 		else
991 		    current_lnum = start_lnum;
992 
993 		break;
994 	    }
995 
996 	    end_lnum = lnum;
997 	    invalidate_current_state();
998 	}
999 
1000 	/* Ran into start of the file or exceeded maximum number of lines */
1001 	if (lnum <= break_lnum)
1002 	{
1003 	    invalidate_current_state();
1004 	    current_lnum = break_lnum + 1;
1005 	}
1006     }
1007 
1008     validate_current_state();
1009 }
1010 
1011     static void
1012 save_chartab(char_u *chartab)
1013 {
1014     if (syn_block->b_syn_isk != empty_option)
1015     {
1016 	mch_memmove(chartab, syn_buf->b_chartab, (size_t)32);
1017 	mch_memmove(syn_buf->b_chartab, syn_win->w_s->b_syn_chartab,
1018 								  (size_t)32);
1019     }
1020 }
1021 
1022     static void
1023 restore_chartab(char_u *chartab)
1024 {
1025     if (syn_win->w_s->b_syn_isk != empty_option)
1026 	mch_memmove(syn_buf->b_chartab, chartab, (size_t)32);
1027 }
1028 
1029 /*
1030  * Return TRUE if the line-continuation pattern matches in line "lnum".
1031  */
1032     static int
1033 syn_match_linecont(linenr_T lnum)
1034 {
1035     regmmatch_T regmatch;
1036     int r;
1037     char_u	buf_chartab[32];  /* chartab array for syn iskyeyword */
1038 
1039     if (syn_block->b_syn_linecont_prog != NULL)
1040     {
1041 	/* use syntax iskeyword option */
1042 	save_chartab(buf_chartab);
1043 	regmatch.rmm_ic = syn_block->b_syn_linecont_ic;
1044 	regmatch.regprog = syn_block->b_syn_linecont_prog;
1045 	r = syn_regexec(&regmatch, lnum, (colnr_T)0,
1046 				IF_SYN_TIME(&syn_block->b_syn_linecont_time));
1047 	syn_block->b_syn_linecont_prog = regmatch.regprog;
1048 	restore_chartab(buf_chartab);
1049 	return r;
1050     }
1051     return FALSE;
1052 }
1053 
1054 /*
1055  * Prepare the current state for the start of a line.
1056  */
1057     static void
1058 syn_start_line(void)
1059 {
1060     current_finished = FALSE;
1061     current_col = 0;
1062 
1063     /*
1064      * Need to update the end of a start/skip/end that continues from the
1065      * previous line and regions that have "keepend".
1066      */
1067     if (current_state.ga_len > 0)
1068     {
1069 	syn_update_ends(TRUE);
1070 	check_state_ends();
1071     }
1072 
1073     next_match_idx = -1;
1074     ++current_line_id;
1075 #ifdef FEAT_CONCEAL
1076     next_seqnr = 1;
1077 #endif
1078 }
1079 
1080 /*
1081  * Check for items in the stack that need their end updated.
1082  * When "startofline" is TRUE the last item is always updated.
1083  * When "startofline" is FALSE the item with "keepend" is forcefully updated.
1084  */
1085     static void
1086 syn_update_ends(int startofline)
1087 {
1088     stateitem_T	*cur_si;
1089     int		i;
1090     int		seen_keepend;
1091 
1092     if (startofline)
1093     {
1094 	/* Check for a match carried over from a previous line with a
1095 	 * contained region.  The match ends as soon as the region ends. */
1096 	for (i = 0; i < current_state.ga_len; ++i)
1097 	{
1098 	    cur_si = &CUR_STATE(i);
1099 	    if (cur_si->si_idx >= 0
1100 		    && (SYN_ITEMS(syn_block)[cur_si->si_idx]).sp_type
1101 							       == SPTYPE_MATCH
1102 		    && cur_si->si_m_endpos.lnum < current_lnum)
1103 	    {
1104 		cur_si->si_flags |= HL_MATCHCONT;
1105 		cur_si->si_m_endpos.lnum = 0;
1106 		cur_si->si_m_endpos.col = 0;
1107 		cur_si->si_h_endpos = cur_si->si_m_endpos;
1108 		cur_si->si_ends = TRUE;
1109 	    }
1110 	}
1111     }
1112 
1113     /*
1114      * Need to update the end of a start/skip/end that continues from the
1115      * previous line.  And regions that have "keepend", because they may
1116      * influence contained items.  If we've just removed "extend"
1117      * (startofline == 0) then we should update ends of normal regions
1118      * contained inside "keepend" because "extend" could have extended
1119      * these "keepend" regions as well as contained normal regions.
1120      * Then check for items ending in column 0.
1121      */
1122     i = current_state.ga_len - 1;
1123     if (keepend_level >= 0)
1124 	for ( ; i > keepend_level; --i)
1125 	    if (CUR_STATE(i).si_flags & HL_EXTEND)
1126 		break;
1127 
1128     seen_keepend = FALSE;
1129     for ( ; i < current_state.ga_len; ++i)
1130     {
1131 	cur_si = &CUR_STATE(i);
1132 	if ((cur_si->si_flags & HL_KEEPEND)
1133 			    || (seen_keepend && !startofline)
1134 			    || (i == current_state.ga_len - 1 && startofline))
1135 	{
1136 	    cur_si->si_h_startpos.col = 0;	/* start highl. in col 0 */
1137 	    cur_si->si_h_startpos.lnum = current_lnum;
1138 
1139 	    if (!(cur_si->si_flags & HL_MATCHCONT))
1140 		update_si_end(cur_si, (int)current_col, !startofline);
1141 
1142 	    if (!startofline && (cur_si->si_flags & HL_KEEPEND))
1143 		seen_keepend = TRUE;
1144 	}
1145     }
1146     check_keepend();
1147 }
1148 
1149 /****************************************
1150  * Handling of the state stack cache.
1151  */
1152 
1153 /*
1154  * EXPLANATION OF THE SYNTAX STATE STACK CACHE
1155  *
1156  * To speed up syntax highlighting, the state stack for the start of some
1157  * lines is cached.  These entries can be used to start parsing at that point.
1158  *
1159  * The stack is kept in b_sst_array[] for each buffer.  There is a list of
1160  * valid entries.  b_sst_first points to the first one, then follow sst_next.
1161  * The entries are sorted on line number.  The first entry is often for line 2
1162  * (line 1 always starts with an empty stack).
1163  * There is also a list for free entries.  This construction is used to avoid
1164  * having to allocate and free memory blocks too often.
1165  *
1166  * When making changes to the buffer, this is logged in b_mod_*.  When calling
1167  * update_screen() to update the display, it will call
1168  * syn_stack_apply_changes() for each displayed buffer to adjust the cached
1169  * entries.  The entries which are inside the changed area are removed,
1170  * because they must be recomputed.  Entries below the changed have their line
1171  * number adjusted for deleted/inserted lines, and have their sst_change_lnum
1172  * set to indicate that a check must be made if the changed lines would change
1173  * the cached entry.
1174  *
1175  * When later displaying lines, an entry is stored for each line.  Displayed
1176  * lines are likely to be displayed again, in which case the state at the
1177  * start of the line is needed.
1178  * For not displayed lines, an entry is stored for every so many lines.  These
1179  * entries will be used e.g., when scrolling backwards.  The distance between
1180  * entries depends on the number of lines in the buffer.  For small buffers
1181  * the distance is fixed at SST_DIST, for large buffers there is a fixed
1182  * number of entries SST_MAX_ENTRIES, and the distance is computed.
1183  */
1184 
1185     static void
1186 syn_stack_free_block(synblock_T *block)
1187 {
1188     synstate_T	*p;
1189 
1190     if (block->b_sst_array != NULL)
1191     {
1192 	for (p = block->b_sst_first; p != NULL; p = p->sst_next)
1193 	    clear_syn_state(p);
1194 	VIM_CLEAR(block->b_sst_array);
1195 	block->b_sst_len = 0;
1196     }
1197 }
1198 /*
1199  * Free b_sst_array[] for buffer "buf".
1200  * Used when syntax items changed to force resyncing everywhere.
1201  */
1202     void
1203 syn_stack_free_all(synblock_T *block)
1204 {
1205 #ifdef FEAT_FOLDING
1206     win_T	*wp;
1207 #endif
1208 
1209     syn_stack_free_block(block);
1210 
1211 #ifdef FEAT_FOLDING
1212     /* When using "syntax" fold method, must update all folds. */
1213     FOR_ALL_WINDOWS(wp)
1214     {
1215 	if (wp->w_s == block && foldmethodIsSyntax(wp))
1216 	    foldUpdateAll(wp);
1217     }
1218 #endif
1219 }
1220 
1221 /*
1222  * Allocate the syntax state stack for syn_buf when needed.
1223  * If the number of entries in b_sst_array[] is much too big or a bit too
1224  * small, reallocate it.
1225  * Also used to allocate b_sst_array[] for the first time.
1226  */
1227     static void
1228 syn_stack_alloc(void)
1229 {
1230     long	len;
1231     synstate_T	*to, *from;
1232     synstate_T	*sstp;
1233 
1234     len = syn_buf->b_ml.ml_line_count / SST_DIST + Rows * 2;
1235     if (len < SST_MIN_ENTRIES)
1236 	len = SST_MIN_ENTRIES;
1237     else if (len > SST_MAX_ENTRIES)
1238 	len = SST_MAX_ENTRIES;
1239     if (syn_block->b_sst_len > len * 2 || syn_block->b_sst_len < len)
1240     {
1241 	/* Allocate 50% too much, to avoid reallocating too often. */
1242 	len = syn_buf->b_ml.ml_line_count;
1243 	len = (len + len / 2) / SST_DIST + Rows * 2;
1244 	if (len < SST_MIN_ENTRIES)
1245 	    len = SST_MIN_ENTRIES;
1246 	else if (len > SST_MAX_ENTRIES)
1247 	    len = SST_MAX_ENTRIES;
1248 
1249 	if (syn_block->b_sst_array != NULL)
1250 	{
1251 	    /* When shrinking the array, cleanup the existing stack.
1252 	     * Make sure that all valid entries fit in the new array. */
1253 	    while (syn_block->b_sst_len - syn_block->b_sst_freecount + 2 > len
1254 		    && syn_stack_cleanup())
1255 		;
1256 	    if (len < syn_block->b_sst_len - syn_block->b_sst_freecount + 2)
1257 		len = syn_block->b_sst_len - syn_block->b_sst_freecount + 2;
1258 	}
1259 
1260 	sstp = (synstate_T *)alloc_clear((unsigned)(len * sizeof(synstate_T)));
1261 	if (sstp == NULL)	/* out of memory! */
1262 	    return;
1263 
1264 	to = sstp - 1;
1265 	if (syn_block->b_sst_array != NULL)
1266 	{
1267 	    /* Move the states from the old array to the new one. */
1268 	    for (from = syn_block->b_sst_first; from != NULL;
1269 							from = from->sst_next)
1270 	    {
1271 		++to;
1272 		*to = *from;
1273 		to->sst_next = to + 1;
1274 	    }
1275 	}
1276 	if (to != sstp - 1)
1277 	{
1278 	    to->sst_next = NULL;
1279 	    syn_block->b_sst_first = sstp;
1280 	    syn_block->b_sst_freecount = len - (int)(to - sstp) - 1;
1281 	}
1282 	else
1283 	{
1284 	    syn_block->b_sst_first = NULL;
1285 	    syn_block->b_sst_freecount = len;
1286 	}
1287 
1288 	/* Create the list of free entries. */
1289 	syn_block->b_sst_firstfree = to + 1;
1290 	while (++to < sstp + len)
1291 	    to->sst_next = to + 1;
1292 	(sstp + len - 1)->sst_next = NULL;
1293 
1294 	vim_free(syn_block->b_sst_array);
1295 	syn_block->b_sst_array = sstp;
1296 	syn_block->b_sst_len = len;
1297     }
1298 }
1299 
1300 /*
1301  * Check for changes in a buffer to affect stored syntax states.  Uses the
1302  * b_mod_* fields.
1303  * Called from update_screen(), before screen is being updated, once for each
1304  * displayed buffer.
1305  */
1306     void
1307 syn_stack_apply_changes(buf_T *buf)
1308 {
1309     win_T	*wp;
1310 
1311     syn_stack_apply_changes_block(&buf->b_s, buf);
1312 
1313     FOR_ALL_WINDOWS(wp)
1314     {
1315 	if ((wp->w_buffer == buf) && (wp->w_s != &buf->b_s))
1316 	    syn_stack_apply_changes_block(wp->w_s, buf);
1317     }
1318 }
1319 
1320     static void
1321 syn_stack_apply_changes_block(synblock_T *block, buf_T *buf)
1322 {
1323     synstate_T	*p, *prev, *np;
1324     linenr_T	n;
1325 
1326     if (block->b_sst_array == NULL)	/* nothing to do */
1327 	return;
1328 
1329     prev = NULL;
1330     for (p = block->b_sst_first; p != NULL; )
1331     {
1332 	if (p->sst_lnum + block->b_syn_sync_linebreaks > buf->b_mod_top)
1333 	{
1334 	    n = p->sst_lnum + buf->b_mod_xlines;
1335 	    if (n <= buf->b_mod_bot)
1336 	    {
1337 		/* this state is inside the changed area, remove it */
1338 		np = p->sst_next;
1339 		if (prev == NULL)
1340 		    block->b_sst_first = np;
1341 		else
1342 		    prev->sst_next = np;
1343 		syn_stack_free_entry(block, p);
1344 		p = np;
1345 		continue;
1346 	    }
1347 	    /* This state is below the changed area.  Remember the line
1348 	     * that needs to be parsed before this entry can be made valid
1349 	     * again. */
1350 	    if (p->sst_change_lnum != 0 && p->sst_change_lnum > buf->b_mod_top)
1351 	    {
1352 		if (p->sst_change_lnum + buf->b_mod_xlines > buf->b_mod_top)
1353 		    p->sst_change_lnum += buf->b_mod_xlines;
1354 		else
1355 		    p->sst_change_lnum = buf->b_mod_top;
1356 	    }
1357 	    if (p->sst_change_lnum == 0
1358 		    || p->sst_change_lnum < buf->b_mod_bot)
1359 		p->sst_change_lnum = buf->b_mod_bot;
1360 
1361 	    p->sst_lnum = n;
1362 	}
1363 	prev = p;
1364 	p = p->sst_next;
1365     }
1366 }
1367 
1368 /*
1369  * Reduce the number of entries in the state stack for syn_buf.
1370  * Returns TRUE if at least one entry was freed.
1371  */
1372     static int
1373 syn_stack_cleanup(void)
1374 {
1375     synstate_T	*p, *prev;
1376     disptick_T	tick;
1377     int		above;
1378     int		dist;
1379     int		retval = FALSE;
1380 
1381     if (syn_block->b_sst_array == NULL || syn_block->b_sst_first == NULL)
1382 	return retval;
1383 
1384     /* Compute normal distance between non-displayed entries. */
1385     if (syn_block->b_sst_len <= Rows)
1386 	dist = 999999;
1387     else
1388 	dist = syn_buf->b_ml.ml_line_count / (syn_block->b_sst_len - Rows) + 1;
1389 
1390     /*
1391      * Go through the list to find the "tick" for the oldest entry that can
1392      * be removed.  Set "above" when the "tick" for the oldest entry is above
1393      * "b_sst_lasttick" (the display tick wraps around).
1394      */
1395     tick = syn_block->b_sst_lasttick;
1396     above = FALSE;
1397     prev = syn_block->b_sst_first;
1398     for (p = prev->sst_next; p != NULL; prev = p, p = p->sst_next)
1399     {
1400 	if (prev->sst_lnum + dist > p->sst_lnum)
1401 	{
1402 	    if (p->sst_tick > syn_block->b_sst_lasttick)
1403 	    {
1404 		if (!above || p->sst_tick < tick)
1405 		    tick = p->sst_tick;
1406 		above = TRUE;
1407 	    }
1408 	    else if (!above && p->sst_tick < tick)
1409 		tick = p->sst_tick;
1410 	}
1411     }
1412 
1413     /*
1414      * Go through the list to make the entries for the oldest tick at an
1415      * interval of several lines.
1416      */
1417     prev = syn_block->b_sst_first;
1418     for (p = prev->sst_next; p != NULL; prev = p, p = p->sst_next)
1419     {
1420 	if (p->sst_tick == tick && prev->sst_lnum + dist > p->sst_lnum)
1421 	{
1422 	    /* Move this entry from used list to free list */
1423 	    prev->sst_next = p->sst_next;
1424 	    syn_stack_free_entry(syn_block, p);
1425 	    p = prev;
1426 	    retval = TRUE;
1427 	}
1428     }
1429     return retval;
1430 }
1431 
1432 /*
1433  * Free the allocated memory for a syn_state item.
1434  * Move the entry into the free list.
1435  */
1436     static void
1437 syn_stack_free_entry(synblock_T *block, synstate_T *p)
1438 {
1439     clear_syn_state(p);
1440     p->sst_next = block->b_sst_firstfree;
1441     block->b_sst_firstfree = p;
1442     ++block->b_sst_freecount;
1443 }
1444 
1445 /*
1446  * Find an entry in the list of state stacks at or before "lnum".
1447  * Returns NULL when there is no entry or the first entry is after "lnum".
1448  */
1449     static synstate_T *
1450 syn_stack_find_entry(linenr_T lnum)
1451 {
1452     synstate_T	*p, *prev;
1453 
1454     prev = NULL;
1455     for (p = syn_block->b_sst_first; p != NULL; prev = p, p = p->sst_next)
1456     {
1457 	if (p->sst_lnum == lnum)
1458 	    return p;
1459 	if (p->sst_lnum > lnum)
1460 	    break;
1461     }
1462     return prev;
1463 }
1464 
1465 /*
1466  * Try saving the current state in b_sst_array[].
1467  * The current state must be valid for the start of the current_lnum line!
1468  */
1469     static synstate_T *
1470 store_current_state(void)
1471 {
1472     int		i;
1473     synstate_T	*p;
1474     bufstate_T	*bp;
1475     stateitem_T	*cur_si;
1476     synstate_T	*sp = syn_stack_find_entry(current_lnum);
1477 
1478     /*
1479      * If the current state contains a start or end pattern that continues
1480      * from the previous line, we can't use it.  Don't store it then.
1481      */
1482     for (i = current_state.ga_len - 1; i >= 0; --i)
1483     {
1484 	cur_si = &CUR_STATE(i);
1485 	if (cur_si->si_h_startpos.lnum >= current_lnum
1486 		|| cur_si->si_m_endpos.lnum >= current_lnum
1487 		|| cur_si->si_h_endpos.lnum >= current_lnum
1488 		|| (cur_si->si_end_idx
1489 		    && cur_si->si_eoe_pos.lnum >= current_lnum))
1490 	    break;
1491     }
1492     if (i >= 0)
1493     {
1494 	if (sp != NULL)
1495 	{
1496 	    /* find "sp" in the list and remove it */
1497 	    if (syn_block->b_sst_first == sp)
1498 		/* it's the first entry */
1499 		syn_block->b_sst_first = sp->sst_next;
1500 	    else
1501 	    {
1502 		/* find the entry just before this one to adjust sst_next */
1503 		for (p = syn_block->b_sst_first; p != NULL; p = p->sst_next)
1504 		    if (p->sst_next == sp)
1505 			break;
1506 		if (p != NULL)	/* just in case */
1507 		    p->sst_next = sp->sst_next;
1508 	    }
1509 	    syn_stack_free_entry(syn_block, sp);
1510 	    sp = NULL;
1511 	}
1512     }
1513     else if (sp == NULL || sp->sst_lnum != current_lnum)
1514     {
1515 	/*
1516 	 * Add a new entry
1517 	 */
1518 	/* If no free items, cleanup the array first. */
1519 	if (syn_block->b_sst_freecount == 0)
1520 	{
1521 	    (void)syn_stack_cleanup();
1522 	    /* "sp" may have been moved to the freelist now */
1523 	    sp = syn_stack_find_entry(current_lnum);
1524 	}
1525 	/* Still no free items?  Must be a strange problem... */
1526 	if (syn_block->b_sst_freecount == 0)
1527 	    sp = NULL;
1528 	else
1529 	{
1530 	    /* Take the first item from the free list and put it in the used
1531 	     * list, after *sp */
1532 	    p = syn_block->b_sst_firstfree;
1533 	    syn_block->b_sst_firstfree = p->sst_next;
1534 	    --syn_block->b_sst_freecount;
1535 	    if (sp == NULL)
1536 	    {
1537 		/* Insert in front of the list */
1538 		p->sst_next = syn_block->b_sst_first;
1539 		syn_block->b_sst_first = p;
1540 	    }
1541 	    else
1542 	    {
1543 		/* insert in list after *sp */
1544 		p->sst_next = sp->sst_next;
1545 		sp->sst_next = p;
1546 	    }
1547 	    sp = p;
1548 	    sp->sst_stacksize = 0;
1549 	    sp->sst_lnum = current_lnum;
1550 	}
1551     }
1552     if (sp != NULL)
1553     {
1554 	/* When overwriting an existing state stack, clear it first */
1555 	clear_syn_state(sp);
1556 	sp->sst_stacksize = current_state.ga_len;
1557 	if (current_state.ga_len > SST_FIX_STATES)
1558 	{
1559 	    /* Need to clear it, might be something remaining from when the
1560 	     * length was less than SST_FIX_STATES. */
1561 	    ga_init2(&sp->sst_union.sst_ga, (int)sizeof(bufstate_T), 1);
1562 	    if (ga_grow(&sp->sst_union.sst_ga, current_state.ga_len) == FAIL)
1563 		sp->sst_stacksize = 0;
1564 	    else
1565 		sp->sst_union.sst_ga.ga_len = current_state.ga_len;
1566 	    bp = SYN_STATE_P(&(sp->sst_union.sst_ga));
1567 	}
1568 	else
1569 	    bp = sp->sst_union.sst_stack;
1570 	for (i = 0; i < sp->sst_stacksize; ++i)
1571 	{
1572 	    bp[i].bs_idx = CUR_STATE(i).si_idx;
1573 	    bp[i].bs_flags = CUR_STATE(i).si_flags;
1574 #ifdef FEAT_CONCEAL
1575 	    bp[i].bs_seqnr = CUR_STATE(i).si_seqnr;
1576 	    bp[i].bs_cchar = CUR_STATE(i).si_cchar;
1577 #endif
1578 	    bp[i].bs_extmatch = ref_extmatch(CUR_STATE(i).si_extmatch);
1579 	}
1580 	sp->sst_next_flags = current_next_flags;
1581 	sp->sst_next_list = current_next_list;
1582 	sp->sst_tick = display_tick;
1583 	sp->sst_change_lnum = 0;
1584     }
1585     current_state_stored = TRUE;
1586     return sp;
1587 }
1588 
1589 /*
1590  * Copy a state stack from "from" in b_sst_array[] to current_state;
1591  */
1592     static void
1593 load_current_state(synstate_T *from)
1594 {
1595     int		i;
1596     bufstate_T	*bp;
1597 
1598     clear_current_state();
1599     validate_current_state();
1600     keepend_level = -1;
1601     if (from->sst_stacksize
1602 	    && ga_grow(&current_state, from->sst_stacksize) != FAIL)
1603     {
1604 	if (from->sst_stacksize > SST_FIX_STATES)
1605 	    bp = SYN_STATE_P(&(from->sst_union.sst_ga));
1606 	else
1607 	    bp = from->sst_union.sst_stack;
1608 	for (i = 0; i < from->sst_stacksize; ++i)
1609 	{
1610 	    CUR_STATE(i).si_idx = bp[i].bs_idx;
1611 	    CUR_STATE(i).si_flags = bp[i].bs_flags;
1612 #ifdef FEAT_CONCEAL
1613 	    CUR_STATE(i).si_seqnr = bp[i].bs_seqnr;
1614 	    CUR_STATE(i).si_cchar = bp[i].bs_cchar;
1615 #endif
1616 	    CUR_STATE(i).si_extmatch = ref_extmatch(bp[i].bs_extmatch);
1617 	    if (keepend_level < 0 && (CUR_STATE(i).si_flags & HL_KEEPEND))
1618 		keepend_level = i;
1619 	    CUR_STATE(i).si_ends = FALSE;
1620 	    CUR_STATE(i).si_m_lnum = 0;
1621 	    if (CUR_STATE(i).si_idx >= 0)
1622 		CUR_STATE(i).si_next_list =
1623 		     (SYN_ITEMS(syn_block)[CUR_STATE(i).si_idx]).sp_next_list;
1624 	    else
1625 		CUR_STATE(i).si_next_list = NULL;
1626 	    update_si_attr(i);
1627 	}
1628 	current_state.ga_len = from->sst_stacksize;
1629     }
1630     current_next_list = from->sst_next_list;
1631     current_next_flags = from->sst_next_flags;
1632     current_lnum = from->sst_lnum;
1633 }
1634 
1635 /*
1636  * Compare saved state stack "*sp" with the current state.
1637  * Return TRUE when they are equal.
1638  */
1639     static int
1640 syn_stack_equal(synstate_T *sp)
1641 {
1642     int		i, j;
1643     bufstate_T	*bp;
1644     reg_extmatch_T	*six, *bsx;
1645 
1646     /* First a quick check if the stacks have the same size end nextlist. */
1647     if (sp->sst_stacksize == current_state.ga_len
1648 	    && sp->sst_next_list == current_next_list)
1649     {
1650 	/* Need to compare all states on both stacks. */
1651 	if (sp->sst_stacksize > SST_FIX_STATES)
1652 	    bp = SYN_STATE_P(&(sp->sst_union.sst_ga));
1653 	else
1654 	    bp = sp->sst_union.sst_stack;
1655 
1656 	for (i = current_state.ga_len; --i >= 0; )
1657 	{
1658 	    /* If the item has another index the state is different. */
1659 	    if (bp[i].bs_idx != CUR_STATE(i).si_idx)
1660 		break;
1661 	    if (bp[i].bs_extmatch != CUR_STATE(i).si_extmatch)
1662 	    {
1663 		/* When the extmatch pointers are different, the strings in
1664 		 * them can still be the same.  Check if the extmatch
1665 		 * references are equal. */
1666 		bsx = bp[i].bs_extmatch;
1667 		six = CUR_STATE(i).si_extmatch;
1668 		/* If one of the extmatch pointers is NULL the states are
1669 		 * different. */
1670 		if (bsx == NULL || six == NULL)
1671 		    break;
1672 		for (j = 0; j < NSUBEXP; ++j)
1673 		{
1674 		    /* Check each referenced match string. They must all be
1675 		     * equal. */
1676 		    if (bsx->matches[j] != six->matches[j])
1677 		    {
1678 			/* If the pointer is different it can still be the
1679 			 * same text.  Compare the strings, ignore case when
1680 			 * the start item has the sp_ic flag set. */
1681 			if (bsx->matches[j] == NULL
1682 				|| six->matches[j] == NULL)
1683 			    break;
1684 			if ((SYN_ITEMS(syn_block)[CUR_STATE(i).si_idx]).sp_ic
1685 				? MB_STRICMP(bsx->matches[j],
1686 							 six->matches[j]) != 0
1687 				: STRCMP(bsx->matches[j], six->matches[j]) != 0)
1688 			    break;
1689 		    }
1690 		}
1691 		if (j != NSUBEXP)
1692 		    break;
1693 	    }
1694 	}
1695 	if (i < 0)
1696 	    return TRUE;
1697     }
1698     return FALSE;
1699 }
1700 
1701 /*
1702  * We stop parsing syntax above line "lnum".  If the stored state at or below
1703  * this line depended on a change before it, it now depends on the line below
1704  * the last parsed line.
1705  * The window looks like this:
1706  *	    line which changed
1707  *	    displayed line
1708  *	    displayed line
1709  * lnum ->  line below window
1710  */
1711     void
1712 syntax_end_parsing(linenr_T lnum)
1713 {
1714     synstate_T	*sp;
1715 
1716     sp = syn_stack_find_entry(lnum);
1717     if (sp != NULL && sp->sst_lnum < lnum)
1718 	sp = sp->sst_next;
1719 
1720     if (sp != NULL && sp->sst_change_lnum != 0)
1721 	sp->sst_change_lnum = lnum;
1722 }
1723 
1724 /*
1725  * End of handling of the state stack.
1726  ****************************************/
1727 
1728     static void
1729 invalidate_current_state(void)
1730 {
1731     clear_current_state();
1732     current_state.ga_itemsize = 0;	/* mark current_state invalid */
1733     current_next_list = NULL;
1734     keepend_level = -1;
1735 }
1736 
1737     static void
1738 validate_current_state(void)
1739 {
1740     current_state.ga_itemsize = sizeof(stateitem_T);
1741     current_state.ga_growsize = 3;
1742 }
1743 
1744 /*
1745  * Return TRUE if the syntax at start of lnum changed since last time.
1746  * This will only be called just after get_syntax_attr() for the previous
1747  * line, to check if the next line needs to be redrawn too.
1748  */
1749     int
1750 syntax_check_changed(linenr_T lnum)
1751 {
1752     int		retval = TRUE;
1753     synstate_T	*sp;
1754 
1755     /*
1756      * Check the state stack when:
1757      * - lnum is just below the previously syntaxed line.
1758      * - lnum is not before the lines with saved states.
1759      * - lnum is not past the lines with saved states.
1760      * - lnum is at or before the last changed line.
1761      */
1762     if (VALID_STATE(&current_state) && lnum == current_lnum + 1)
1763     {
1764 	sp = syn_stack_find_entry(lnum);
1765 	if (sp != NULL && sp->sst_lnum == lnum)
1766 	{
1767 	    /*
1768 	     * finish the previous line (needed when not all of the line was
1769 	     * drawn)
1770 	     */
1771 	    (void)syn_finish_line(FALSE);
1772 
1773 	    /*
1774 	     * Compare the current state with the previously saved state of
1775 	     * the line.
1776 	     */
1777 	    if (syn_stack_equal(sp))
1778 		retval = FALSE;
1779 
1780 	    /*
1781 	     * Store the current state in b_sst_array[] for later use.
1782 	     */
1783 	    ++current_lnum;
1784 	    (void)store_current_state();
1785 	}
1786     }
1787 
1788     return retval;
1789 }
1790 
1791 /*
1792  * Finish the current line.
1793  * This doesn't return any attributes, it only gets the state at the end of
1794  * the line.  It can start anywhere in the line, as long as the current state
1795  * is valid.
1796  */
1797     static int
1798 syn_finish_line(
1799     int	    syncing)		/* called for syncing */
1800 {
1801     stateitem_T	*cur_si;
1802     colnr_T	prev_current_col;
1803 
1804     while (!current_finished)
1805     {
1806 	(void)syn_current_attr(syncing, FALSE, NULL, FALSE);
1807 	/*
1808 	 * When syncing, and found some item, need to check the item.
1809 	 */
1810 	if (syncing && current_state.ga_len)
1811 	{
1812 	    /*
1813 	     * Check for match with sync item.
1814 	     */
1815 	    cur_si = &CUR_STATE(current_state.ga_len - 1);
1816 	    if (cur_si->si_idx >= 0
1817 		    && (SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags
1818 					  & (HL_SYNC_HERE|HL_SYNC_THERE)))
1819 		return TRUE;
1820 
1821 	    /* syn_current_attr() will have skipped the check for an item
1822 	     * that ends here, need to do that now.  Be careful not to go
1823 	     * past the NUL. */
1824 	    prev_current_col = current_col;
1825 	    if (syn_getcurline()[current_col] != NUL)
1826 		++current_col;
1827 	    check_state_ends();
1828 	    current_col = prev_current_col;
1829 	}
1830 	++current_col;
1831     }
1832     return FALSE;
1833 }
1834 
1835 /*
1836  * Return highlight attributes for next character.
1837  * Must first call syntax_start() once for the line.
1838  * "col" is normally 0 for the first use in a line, and increments by one each
1839  * time.  It's allowed to skip characters and to stop before the end of the
1840  * line.  But only a "col" after a previously used column is allowed.
1841  * When "can_spell" is not NULL set it to TRUE when spell-checking should be
1842  * done.
1843  */
1844     int
1845 get_syntax_attr(
1846     colnr_T	col,
1847     int		*can_spell,
1848     int		keep_state)	/* keep state of char at "col" */
1849 {
1850     int	    attr = 0;
1851 
1852     if (can_spell != NULL)
1853 	/* Default: Only do spelling when there is no @Spell cluster or when
1854 	 * ":syn spell toplevel" was used. */
1855 	*can_spell = syn_block->b_syn_spell == SYNSPL_DEFAULT
1856 		    ? (syn_block->b_spell_cluster_id == 0)
1857 		    : (syn_block->b_syn_spell == SYNSPL_TOP);
1858 
1859     /* check for out of memory situation */
1860     if (syn_block->b_sst_array == NULL)
1861 	return 0;
1862 
1863     /* After 'synmaxcol' the attribute is always zero. */
1864     if (syn_buf->b_p_smc > 0 && col >= (colnr_T)syn_buf->b_p_smc)
1865     {
1866 	clear_current_state();
1867 #ifdef FEAT_EVAL
1868 	current_id = 0;
1869 	current_trans_id = 0;
1870 #endif
1871 #ifdef FEAT_CONCEAL
1872 	current_flags = 0;
1873 	current_seqnr = 0;
1874 #endif
1875 	return 0;
1876     }
1877 
1878     /* Make sure current_state is valid */
1879     if (INVALID_STATE(&current_state))
1880 	validate_current_state();
1881 
1882     /*
1883      * Skip from the current column to "col", get the attributes for "col".
1884      */
1885     while (current_col <= col)
1886     {
1887 	attr = syn_current_attr(FALSE, TRUE, can_spell,
1888 				     current_col == col ? keep_state : FALSE);
1889 	++current_col;
1890     }
1891 
1892     return attr;
1893 }
1894 
1895 /*
1896  * Get syntax attributes for current_lnum, current_col.
1897  */
1898     static int
1899 syn_current_attr(
1900     int		syncing,		/* When 1: called for syncing */
1901     int		displaying,		/* result will be displayed */
1902     int		*can_spell,		/* return: do spell checking */
1903     int		keep_state)		/* keep syntax stack afterwards */
1904 {
1905     int		syn_id;
1906     lpos_T	endpos;		/* was: char_u *endp; */
1907     lpos_T	hl_startpos;	/* was: int hl_startcol; */
1908     lpos_T	hl_endpos;
1909     lpos_T	eos_pos;	/* end-of-start match (start region) */
1910     lpos_T	eoe_pos;	/* end-of-end pattern */
1911     int		end_idx;	/* group ID for end pattern */
1912     int		idx;
1913     synpat_T	*spp;
1914     stateitem_T	*cur_si, *sip = NULL;
1915     int		startcol;
1916     int		endcol;
1917     long	flags;
1918     int		cchar;
1919     short	*next_list;
1920     int		found_match;		    /* found usable match */
1921     static int	try_next_column = FALSE;    /* must try in next col */
1922     int		do_keywords;
1923     regmmatch_T	regmatch;
1924     lpos_T	pos;
1925     int		lc_col;
1926     reg_extmatch_T *cur_extmatch = NULL;
1927     char_u	buf_chartab[32];  /* chartab array for syn iskyeyword */
1928     char_u	*line;		/* current line.  NOTE: becomes invalid after
1929 				   looking for a pattern match! */
1930 
1931     /* variables for zero-width matches that have a "nextgroup" argument */
1932     int		keep_next_list;
1933     int		zero_width_next_list = FALSE;
1934     garray_T	zero_width_next_ga;
1935 
1936     /*
1937      * No character, no attributes!  Past end of line?
1938      * Do try matching with an empty line (could be the start of a region).
1939      */
1940     line = syn_getcurline();
1941     if (line[current_col] == NUL && current_col != 0)
1942     {
1943 	/*
1944 	 * If we found a match after the last column, use it.
1945 	 */
1946 	if (next_match_idx >= 0 && next_match_col >= (int)current_col
1947 						  && next_match_col != MAXCOL)
1948 	    (void)push_next_match(NULL);
1949 
1950 	current_finished = TRUE;
1951 	current_state_stored = FALSE;
1952 	return 0;
1953     }
1954 
1955     /* if the current or next character is NUL, we will finish the line now */
1956     if (line[current_col] == NUL || line[current_col + 1] == NUL)
1957     {
1958 	current_finished = TRUE;
1959 	current_state_stored = FALSE;
1960     }
1961 
1962     /*
1963      * When in the previous column there was a match but it could not be used
1964      * (empty match or already matched in this column) need to try again in
1965      * the next column.
1966      */
1967     if (try_next_column)
1968     {
1969 	next_match_idx = -1;
1970 	try_next_column = FALSE;
1971     }
1972 
1973     /* Only check for keywords when not syncing and there are some. */
1974     do_keywords = !syncing
1975 		    && (syn_block->b_keywtab.ht_used > 0
1976 			    || syn_block->b_keywtab_ic.ht_used > 0);
1977 
1978     /* Init the list of zero-width matches with a nextlist.  This is used to
1979      * avoid matching the same item in the same position twice. */
1980     ga_init2(&zero_width_next_ga, (int)sizeof(int), 10);
1981 
1982     /* use syntax iskeyword option */
1983     save_chartab(buf_chartab);
1984 
1985     /*
1986      * Repeat matching keywords and patterns, to find contained items at the
1987      * same column.  This stops when there are no extra matches at the current
1988      * column.
1989      */
1990     do
1991     {
1992 	found_match = FALSE;
1993 	keep_next_list = FALSE;
1994 	syn_id = 0;
1995 
1996 
1997 	/*
1998 	 * 1. Check for a current state.
1999 	 *    Only when there is no current state, or if the current state may
2000 	 *    contain other things, we need to check for keywords and patterns.
2001 	 *    Always need to check for contained items if some item has the
2002 	 *    "containedin" argument (takes extra time!).
2003 	 */
2004 	if (current_state.ga_len)
2005 	    cur_si = &CUR_STATE(current_state.ga_len - 1);
2006 	else
2007 	    cur_si = NULL;
2008 
2009 	if (syn_block->b_syn_containedin || cur_si == NULL
2010 					      || cur_si->si_cont_list != NULL)
2011 	{
2012 	    /*
2013 	     * 2. Check for keywords, if on a keyword char after a non-keyword
2014 	     *	  char.  Don't do this when syncing.
2015 	     */
2016 	    if (do_keywords)
2017 	    {
2018 	      line = syn_getcurline();
2019 	      if (vim_iswordp_buf(line + current_col, syn_buf)
2020 		      && (current_col == 0
2021 			  || !vim_iswordp_buf(line + current_col - 1
2022 #ifdef FEAT_MBYTE
2023 			      - (has_mbyte
2024 				  ? (*mb_head_off)(line, line + current_col - 1)
2025 				  : 0)
2026 #endif
2027 			       , syn_buf)))
2028 	      {
2029 		syn_id = check_keyword_id(line, (int)current_col,
2030 					 &endcol, &flags, &next_list, cur_si,
2031 					 &cchar);
2032 		if (syn_id != 0)
2033 		{
2034 		    if (push_current_state(KEYWORD_IDX) == OK)
2035 		    {
2036 			cur_si = &CUR_STATE(current_state.ga_len - 1);
2037 			cur_si->si_m_startcol = current_col;
2038 			cur_si->si_h_startpos.lnum = current_lnum;
2039 			cur_si->si_h_startpos.col = 0;	/* starts right away */
2040 			cur_si->si_m_endpos.lnum = current_lnum;
2041 			cur_si->si_m_endpos.col = endcol;
2042 			cur_si->si_h_endpos.lnum = current_lnum;
2043 			cur_si->si_h_endpos.col = endcol;
2044 			cur_si->si_ends = TRUE;
2045 			cur_si->si_end_idx = 0;
2046 			cur_si->si_flags = flags;
2047 #ifdef FEAT_CONCEAL
2048 			cur_si->si_seqnr = next_seqnr++;
2049 			cur_si->si_cchar = cchar;
2050 			if (current_state.ga_len > 1)
2051 			    cur_si->si_flags |=
2052 				  CUR_STATE(current_state.ga_len - 2).si_flags
2053 								 & HL_CONCEAL;
2054 #endif
2055 			cur_si->si_id = syn_id;
2056 			cur_si->si_trans_id = syn_id;
2057 			if (flags & HL_TRANSP)
2058 			{
2059 			    if (current_state.ga_len < 2)
2060 			    {
2061 				cur_si->si_attr = 0;
2062 				cur_si->si_trans_id = 0;
2063 			    }
2064 			    else
2065 			    {
2066 				cur_si->si_attr = CUR_STATE(
2067 					current_state.ga_len - 2).si_attr;
2068 				cur_si->si_trans_id = CUR_STATE(
2069 					current_state.ga_len - 2).si_trans_id;
2070 			    }
2071 			}
2072 			else
2073 			    cur_si->si_attr = syn_id2attr(syn_id);
2074 			cur_si->si_cont_list = NULL;
2075 			cur_si->si_next_list = next_list;
2076 			check_keepend();
2077 		    }
2078 		    else
2079 			vim_free(next_list);
2080 		}
2081 	      }
2082 	    }
2083 
2084 	    /*
2085 	     * 3. Check for patterns (only if no keyword found).
2086 	     */
2087 	    if (syn_id == 0 && syn_block->b_syn_patterns.ga_len)
2088 	    {
2089 		/*
2090 		 * If we didn't check for a match yet, or we are past it, check
2091 		 * for any match with a pattern.
2092 		 */
2093 		if (next_match_idx < 0 || next_match_col < (int)current_col)
2094 		{
2095 		    /*
2096 		     * Check all relevant patterns for a match at this
2097 		     * position.  This is complicated, because matching with a
2098 		     * pattern takes quite a bit of time, thus we want to
2099 		     * avoid doing it when it's not needed.
2100 		     */
2101 		    next_match_idx = 0;		/* no match in this line yet */
2102 		    next_match_col = MAXCOL;
2103 		    for (idx = syn_block->b_syn_patterns.ga_len; --idx >= 0; )
2104 		    {
2105 			spp = &(SYN_ITEMS(syn_block)[idx]);
2106 			if (	   spp->sp_syncing == syncing
2107 				&& (displaying || !(spp->sp_flags & HL_DISPLAY))
2108 				&& (spp->sp_type == SPTYPE_MATCH
2109 				    || spp->sp_type == SPTYPE_START)
2110 				&& (current_next_list != NULL
2111 				    ? in_id_list(NULL, current_next_list,
2112 							      &spp->sp_syn, 0)
2113 				    : (cur_si == NULL
2114 					? !(spp->sp_flags & HL_CONTAINED)
2115 					: in_id_list(cur_si,
2116 					    cur_si->si_cont_list, &spp->sp_syn,
2117 					    spp->sp_flags & HL_CONTAINED))))
2118 			{
2119 			    int r;
2120 
2121 			    /* If we already tried matching in this line, and
2122 			     * there isn't a match before next_match_col, skip
2123 			     * this item. */
2124 			    if (spp->sp_line_id == current_line_id
2125 				    && spp->sp_startcol >= next_match_col)
2126 				continue;
2127 			    spp->sp_line_id = current_line_id;
2128 
2129 			    lc_col = current_col - spp->sp_offsets[SPO_LC_OFF];
2130 			    if (lc_col < 0)
2131 				lc_col = 0;
2132 
2133 			    regmatch.rmm_ic = spp->sp_ic;
2134 			    regmatch.regprog = spp->sp_prog;
2135 			    r = syn_regexec(&regmatch,
2136 					     current_lnum,
2137 					     (colnr_T)lc_col,
2138 					     IF_SYN_TIME(&spp->sp_time));
2139 			    spp->sp_prog = regmatch.regprog;
2140 			    if (!r)
2141 			    {
2142 				/* no match in this line, try another one */
2143 				spp->sp_startcol = MAXCOL;
2144 				continue;
2145 			    }
2146 
2147 			    /*
2148 			     * Compute the first column of the match.
2149 			     */
2150 			    syn_add_start_off(&pos, &regmatch,
2151 							 spp, SPO_MS_OFF, -1);
2152 			    if (pos.lnum > current_lnum)
2153 			    {
2154 				/* must have used end of match in a next line,
2155 				 * we can't handle that */
2156 				spp->sp_startcol = MAXCOL;
2157 				continue;
2158 			    }
2159 			    startcol = pos.col;
2160 
2161 			    /* remember the next column where this pattern
2162 			     * matches in the current line */
2163 			    spp->sp_startcol = startcol;
2164 
2165 			    /*
2166 			     * If a previously found match starts at a lower
2167 			     * column number, don't use this one.
2168 			     */
2169 			    if (startcol >= next_match_col)
2170 				continue;
2171 
2172 			    /*
2173 			     * If we matched this pattern at this position
2174 			     * before, skip it.  Must retry in the next
2175 			     * column, because it may match from there.
2176 			     */
2177 			    if (did_match_already(idx, &zero_width_next_ga))
2178 			    {
2179 				try_next_column = TRUE;
2180 				continue;
2181 			    }
2182 
2183 			    endpos.lnum = regmatch.endpos[0].lnum;
2184 			    endpos.col = regmatch.endpos[0].col;
2185 
2186 			    /* Compute the highlight start. */
2187 			    syn_add_start_off(&hl_startpos, &regmatch,
2188 							 spp, SPO_HS_OFF, -1);
2189 
2190 			    /* Compute the region start. */
2191 			    /* Default is to use the end of the match. */
2192 			    syn_add_end_off(&eos_pos, &regmatch,
2193 							 spp, SPO_RS_OFF, 0);
2194 
2195 			    /*
2196 			     * Grab the external submatches before they get
2197 			     * overwritten.  Reference count doesn't change.
2198 			     */
2199 			    unref_extmatch(cur_extmatch);
2200 			    cur_extmatch = re_extmatch_out;
2201 			    re_extmatch_out = NULL;
2202 
2203 			    flags = 0;
2204 			    eoe_pos.lnum = 0;	/* avoid warning */
2205 			    eoe_pos.col = 0;
2206 			    end_idx = 0;
2207 			    hl_endpos.lnum = 0;
2208 
2209 			    /*
2210 			     * For a "oneline" the end must be found in the
2211 			     * same line too.  Search for it after the end of
2212 			     * the match with the start pattern.  Set the
2213 			     * resulting end positions at the same time.
2214 			     */
2215 			    if (spp->sp_type == SPTYPE_START
2216 					      && (spp->sp_flags & HL_ONELINE))
2217 			    {
2218 				lpos_T	startpos;
2219 
2220 				startpos = endpos;
2221 				find_endpos(idx, &startpos, &endpos, &hl_endpos,
2222 				    &flags, &eoe_pos, &end_idx, cur_extmatch);
2223 				if (endpos.lnum == 0)
2224 				    continue;	    /* not found */
2225 			    }
2226 
2227 			    /*
2228 			     * For a "match" the size must be > 0 after the
2229 			     * end offset needs has been added.  Except when
2230 			     * syncing.
2231 			     */
2232 			    else if (spp->sp_type == SPTYPE_MATCH)
2233 			    {
2234 				syn_add_end_off(&hl_endpos, &regmatch, spp,
2235 							       SPO_HE_OFF, 0);
2236 				syn_add_end_off(&endpos, &regmatch, spp,
2237 							       SPO_ME_OFF, 0);
2238 				if (endpos.lnum == current_lnum
2239 				      && (int)endpos.col + syncing < startcol)
2240 				{
2241 				    /*
2242 				     * If an empty string is matched, may need
2243 				     * to try matching again at next column.
2244 				     */
2245 				    if (regmatch.startpos[0].col
2246 						    == regmatch.endpos[0].col)
2247 					try_next_column = TRUE;
2248 				    continue;
2249 				}
2250 			    }
2251 
2252 			    /*
2253 			     * keep the best match so far in next_match_*
2254 			     */
2255 			    /* Highlighting must start after startpos and end
2256 			     * before endpos. */
2257 			    if (hl_startpos.lnum == current_lnum
2258 					   && (int)hl_startpos.col < startcol)
2259 				hl_startpos.col = startcol;
2260 			    limit_pos_zero(&hl_endpos, &endpos);
2261 
2262 			    next_match_idx = idx;
2263 			    next_match_col = startcol;
2264 			    next_match_m_endpos = endpos;
2265 			    next_match_h_endpos = hl_endpos;
2266 			    next_match_h_startpos = hl_startpos;
2267 			    next_match_flags = flags;
2268 			    next_match_eos_pos = eos_pos;
2269 			    next_match_eoe_pos = eoe_pos;
2270 			    next_match_end_idx = end_idx;
2271 			    unref_extmatch(next_match_extmatch);
2272 			    next_match_extmatch = cur_extmatch;
2273 			    cur_extmatch = NULL;
2274 			}
2275 		    }
2276 		}
2277 
2278 		/*
2279 		 * If we found a match at the current column, use it.
2280 		 */
2281 		if (next_match_idx >= 0 && next_match_col == (int)current_col)
2282 		{
2283 		    synpat_T	*lspp;
2284 
2285 		    /* When a zero-width item matched which has a nextgroup,
2286 		     * don't push the item but set nextgroup. */
2287 		    lspp = &(SYN_ITEMS(syn_block)[next_match_idx]);
2288 		    if (next_match_m_endpos.lnum == current_lnum
2289 			    && next_match_m_endpos.col == current_col
2290 			    && lspp->sp_next_list != NULL)
2291 		    {
2292 			current_next_list = lspp->sp_next_list;
2293 			current_next_flags = lspp->sp_flags;
2294 			keep_next_list = TRUE;
2295 			zero_width_next_list = TRUE;
2296 
2297 			/* Add the index to a list, so that we can check
2298 			 * later that we don't match it again (and cause an
2299 			 * endless loop). */
2300 			if (ga_grow(&zero_width_next_ga, 1) == OK)
2301 			{
2302 			    ((int *)(zero_width_next_ga.ga_data))
2303 				[zero_width_next_ga.ga_len++] = next_match_idx;
2304 			}
2305 			next_match_idx = -1;
2306 		    }
2307 		    else
2308 			cur_si = push_next_match(cur_si);
2309 		    found_match = TRUE;
2310 		}
2311 	    }
2312 	}
2313 
2314 	/*
2315 	 * Handle searching for nextgroup match.
2316 	 */
2317 	if (current_next_list != NULL && !keep_next_list)
2318 	{
2319 	    /*
2320 	     * If a nextgroup was not found, continue looking for one if:
2321 	     * - this is an empty line and the "skipempty" option was given
2322 	     * - we are on white space and the "skipwhite" option was given
2323 	     */
2324 	    if (!found_match)
2325 	    {
2326 		line = syn_getcurline();
2327 		if (((current_next_flags & HL_SKIPWHITE)
2328 			    && VIM_ISWHITE(line[current_col]))
2329 			|| ((current_next_flags & HL_SKIPEMPTY)
2330 			    && *line == NUL))
2331 		    break;
2332 	    }
2333 
2334 	    /*
2335 	     * If a nextgroup was found: Use it, and continue looking for
2336 	     * contained matches.
2337 	     * If a nextgroup was not found: Continue looking for a normal
2338 	     * match.
2339 	     * When did set current_next_list for a zero-width item and no
2340 	     * match was found don't loop (would get stuck).
2341 	     */
2342 	    current_next_list = NULL;
2343 	    next_match_idx = -1;
2344 	    if (!zero_width_next_list)
2345 		found_match = TRUE;
2346 	}
2347 
2348     } while (found_match);
2349 
2350     restore_chartab(buf_chartab);
2351 
2352     /*
2353      * Use attributes from the current state, if within its highlighting.
2354      * If not, use attributes from the current-but-one state, etc.
2355      */
2356     current_attr = 0;
2357 #ifdef FEAT_EVAL
2358     current_id = 0;
2359     current_trans_id = 0;
2360 #endif
2361 #ifdef FEAT_CONCEAL
2362     current_flags = 0;
2363     current_seqnr = 0;
2364 #endif
2365     if (cur_si != NULL)
2366     {
2367 #ifndef FEAT_EVAL
2368 	int	current_trans_id = 0;
2369 #endif
2370 	for (idx = current_state.ga_len - 1; idx >= 0; --idx)
2371 	{
2372 	    sip = &CUR_STATE(idx);
2373 	    if ((current_lnum > sip->si_h_startpos.lnum
2374 			|| (current_lnum == sip->si_h_startpos.lnum
2375 			    && current_col >= sip->si_h_startpos.col))
2376 		    && (sip->si_h_endpos.lnum == 0
2377 			|| current_lnum < sip->si_h_endpos.lnum
2378 			|| (current_lnum == sip->si_h_endpos.lnum
2379 			    && current_col < sip->si_h_endpos.col)))
2380 	    {
2381 		current_attr = sip->si_attr;
2382 #ifdef FEAT_EVAL
2383 		current_id = sip->si_id;
2384 #endif
2385 		current_trans_id = sip->si_trans_id;
2386 #ifdef FEAT_CONCEAL
2387 		current_flags = sip->si_flags;
2388 		current_seqnr = sip->si_seqnr;
2389 		current_sub_char = sip->si_cchar;
2390 #endif
2391 		break;
2392 	    }
2393 	}
2394 
2395 	if (can_spell != NULL)
2396 	{
2397 	    struct sp_syn   sps;
2398 
2399 	    /*
2400 	     * set "can_spell" to TRUE if spell checking is supposed to be
2401 	     * done in the current item.
2402 	     */
2403 	    if (syn_block->b_spell_cluster_id == 0)
2404 	    {
2405 		/* There is no @Spell cluster: Do spelling for items without
2406 		 * @NoSpell cluster. */
2407 		if (syn_block->b_nospell_cluster_id == 0
2408 						     || current_trans_id == 0)
2409 		    *can_spell = (syn_block->b_syn_spell != SYNSPL_NOTOP);
2410 		else
2411 		{
2412 		    sps.inc_tag = 0;
2413 		    sps.id = syn_block->b_nospell_cluster_id;
2414 		    sps.cont_in_list = NULL;
2415 		    *can_spell = !in_id_list(sip, sip->si_cont_list, &sps, 0);
2416 		}
2417 	    }
2418 	    else
2419 	    {
2420 		/* The @Spell cluster is defined: Do spelling in items with
2421 		 * the @Spell cluster.  But not when @NoSpell is also there.
2422 		 * At the toplevel only spell check when ":syn spell toplevel"
2423 		 * was used. */
2424 		if (current_trans_id == 0)
2425 		    *can_spell = (syn_block->b_syn_spell == SYNSPL_TOP);
2426 		else
2427 		{
2428 		    sps.inc_tag = 0;
2429 		    sps.id = syn_block->b_spell_cluster_id;
2430 		    sps.cont_in_list = NULL;
2431 		    *can_spell = in_id_list(sip, sip->si_cont_list, &sps, 0);
2432 
2433 		    if (syn_block->b_nospell_cluster_id != 0)
2434 		    {
2435 			sps.id = syn_block->b_nospell_cluster_id;
2436 			if (in_id_list(sip, sip->si_cont_list, &sps, 0))
2437 			    *can_spell = FALSE;
2438 		    }
2439 		}
2440 	    }
2441 	}
2442 
2443 
2444 	/*
2445 	 * Check for end of current state (and the states before it) at the
2446 	 * next column.  Don't do this for syncing, because we would miss a
2447 	 * single character match.
2448 	 * First check if the current state ends at the current column.  It
2449 	 * may be for an empty match and a containing item might end in the
2450 	 * current column.
2451 	 */
2452 	if (!syncing && !keep_state)
2453 	{
2454 	    check_state_ends();
2455 	    if (current_state.ga_len > 0
2456 				      && syn_getcurline()[current_col] != NUL)
2457 	    {
2458 		++current_col;
2459 		check_state_ends();
2460 		--current_col;
2461 	    }
2462 	}
2463     }
2464     else if (can_spell != NULL)
2465 	/* Default: Only do spelling when there is no @Spell cluster or when
2466 	 * ":syn spell toplevel" was used. */
2467 	*can_spell = syn_block->b_syn_spell == SYNSPL_DEFAULT
2468 		    ? (syn_block->b_spell_cluster_id == 0)
2469 		    : (syn_block->b_syn_spell == SYNSPL_TOP);
2470 
2471     /* nextgroup ends at end of line, unless "skipnl" or "skipempty" present */
2472     if (current_next_list != NULL
2473 	    && syn_getcurline()[current_col + 1] == NUL
2474 	    && !(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY)))
2475 	current_next_list = NULL;
2476 
2477     if (zero_width_next_ga.ga_len > 0)
2478 	ga_clear(&zero_width_next_ga);
2479 
2480     /* No longer need external matches.  But keep next_match_extmatch. */
2481     unref_extmatch(re_extmatch_out);
2482     re_extmatch_out = NULL;
2483     unref_extmatch(cur_extmatch);
2484 
2485     return current_attr;
2486 }
2487 
2488 
2489 /*
2490  * Check if we already matched pattern "idx" at the current column.
2491  */
2492     static int
2493 did_match_already(int idx, garray_T *gap)
2494 {
2495     int		i;
2496 
2497     for (i = current_state.ga_len; --i >= 0; )
2498 	if (CUR_STATE(i).si_m_startcol == (int)current_col
2499 		&& CUR_STATE(i).si_m_lnum == (int)current_lnum
2500 		&& CUR_STATE(i).si_idx == idx)
2501 	    return TRUE;
2502 
2503     /* Zero-width matches with a nextgroup argument are not put on the syntax
2504      * stack, and can only be matched once anyway. */
2505     for (i = gap->ga_len; --i >= 0; )
2506 	if (((int *)(gap->ga_data))[i] == idx)
2507 	    return TRUE;
2508 
2509     return FALSE;
2510 }
2511 
2512 /*
2513  * Push the next match onto the stack.
2514  */
2515     static stateitem_T *
2516 push_next_match(stateitem_T *cur_si)
2517 {
2518     synpat_T	*spp;
2519 #ifdef FEAT_CONCEAL
2520     int		 save_flags;
2521 #endif
2522 
2523     spp = &(SYN_ITEMS(syn_block)[next_match_idx]);
2524 
2525     /*
2526      * Push the item in current_state stack;
2527      */
2528     if (push_current_state(next_match_idx) == OK)
2529     {
2530 	/*
2531 	 * If it's a start-skip-end type that crosses lines, figure out how
2532 	 * much it continues in this line.  Otherwise just fill in the length.
2533 	 */
2534 	cur_si = &CUR_STATE(current_state.ga_len - 1);
2535 	cur_si->si_h_startpos = next_match_h_startpos;
2536 	cur_si->si_m_startcol = current_col;
2537 	cur_si->si_m_lnum = current_lnum;
2538 	cur_si->si_flags = spp->sp_flags;
2539 #ifdef FEAT_CONCEAL
2540 	cur_si->si_seqnr = next_seqnr++;
2541 	cur_si->si_cchar = spp->sp_cchar;
2542 	if (current_state.ga_len > 1)
2543 	    cur_si->si_flags |=
2544 		    CUR_STATE(current_state.ga_len - 2).si_flags & HL_CONCEAL;
2545 #endif
2546 	cur_si->si_next_list = spp->sp_next_list;
2547 	cur_si->si_extmatch = ref_extmatch(next_match_extmatch);
2548 	if (spp->sp_type == SPTYPE_START && !(spp->sp_flags & HL_ONELINE))
2549 	{
2550 	    /* Try to find the end pattern in the current line */
2551 	    update_si_end(cur_si, (int)(next_match_m_endpos.col), TRUE);
2552 	    check_keepend();
2553 	}
2554 	else
2555 	{
2556 	    cur_si->si_m_endpos = next_match_m_endpos;
2557 	    cur_si->si_h_endpos = next_match_h_endpos;
2558 	    cur_si->si_ends = TRUE;
2559 	    cur_si->si_flags |= next_match_flags;
2560 	    cur_si->si_eoe_pos = next_match_eoe_pos;
2561 	    cur_si->si_end_idx = next_match_end_idx;
2562 	}
2563 	if (keepend_level < 0 && (cur_si->si_flags & HL_KEEPEND))
2564 	    keepend_level = current_state.ga_len - 1;
2565 	check_keepend();
2566 	update_si_attr(current_state.ga_len - 1);
2567 
2568 #ifdef FEAT_CONCEAL
2569 	save_flags = cur_si->si_flags & (HL_CONCEAL | HL_CONCEALENDS);
2570 #endif
2571 	/*
2572 	 * If the start pattern has another highlight group, push another item
2573 	 * on the stack for the start pattern.
2574 	 */
2575 	if (	   spp->sp_type == SPTYPE_START
2576 		&& spp->sp_syn_match_id != 0
2577 		&& push_current_state(next_match_idx) == OK)
2578 	{
2579 	    cur_si = &CUR_STATE(current_state.ga_len - 1);
2580 	    cur_si->si_h_startpos = next_match_h_startpos;
2581 	    cur_si->si_m_startcol = current_col;
2582 	    cur_si->si_m_lnum = current_lnum;
2583 	    cur_si->si_m_endpos = next_match_eos_pos;
2584 	    cur_si->si_h_endpos = next_match_eos_pos;
2585 	    cur_si->si_ends = TRUE;
2586 	    cur_si->si_end_idx = 0;
2587 	    cur_si->si_flags = HL_MATCH;
2588 #ifdef FEAT_CONCEAL
2589 	    cur_si->si_seqnr = next_seqnr++;
2590 	    cur_si->si_flags |= save_flags;
2591 	    if (cur_si->si_flags & HL_CONCEALENDS)
2592 		cur_si->si_flags |= HL_CONCEAL;
2593 #endif
2594 	    cur_si->si_next_list = NULL;
2595 	    check_keepend();
2596 	    update_si_attr(current_state.ga_len - 1);
2597 	}
2598     }
2599 
2600     next_match_idx = -1;	/* try other match next time */
2601 
2602     return cur_si;
2603 }
2604 
2605 /*
2606  * Check for end of current state (and the states before it).
2607  */
2608     static void
2609 check_state_ends(void)
2610 {
2611     stateitem_T	*cur_si;
2612     int		had_extend;
2613 
2614     cur_si = &CUR_STATE(current_state.ga_len - 1);
2615     for (;;)
2616     {
2617 	if (cur_si->si_ends
2618 		&& (cur_si->si_m_endpos.lnum < current_lnum
2619 		    || (cur_si->si_m_endpos.lnum == current_lnum
2620 			&& cur_si->si_m_endpos.col <= current_col)))
2621 	{
2622 	    /*
2623 	     * If there is an end pattern group ID, highlight the end pattern
2624 	     * now.  No need to pop the current item from the stack.
2625 	     * Only do this if the end pattern continues beyond the current
2626 	     * position.
2627 	     */
2628 	    if (cur_si->si_end_idx
2629 		    && (cur_si->si_eoe_pos.lnum > current_lnum
2630 			|| (cur_si->si_eoe_pos.lnum == current_lnum
2631 			    && cur_si->si_eoe_pos.col > current_col)))
2632 	    {
2633 		cur_si->si_idx = cur_si->si_end_idx;
2634 		cur_si->si_end_idx = 0;
2635 		cur_si->si_m_endpos = cur_si->si_eoe_pos;
2636 		cur_si->si_h_endpos = cur_si->si_eoe_pos;
2637 		cur_si->si_flags |= HL_MATCH;
2638 #ifdef FEAT_CONCEAL
2639 		cur_si->si_seqnr = next_seqnr++;
2640 		if (cur_si->si_flags & HL_CONCEALENDS)
2641 		    cur_si->si_flags |= HL_CONCEAL;
2642 #endif
2643 		update_si_attr(current_state.ga_len - 1);
2644 
2645 		/* nextgroup= should not match in the end pattern */
2646 		current_next_list = NULL;
2647 
2648 		/* what matches next may be different now, clear it */
2649 		next_match_idx = 0;
2650 		next_match_col = MAXCOL;
2651 		break;
2652 	    }
2653 	    else
2654 	    {
2655 		/* handle next_list, unless at end of line and no "skipnl" or
2656 		 * "skipempty" */
2657 		current_next_list = cur_si->si_next_list;
2658 		current_next_flags = cur_si->si_flags;
2659 		if (!(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY))
2660 			&& syn_getcurline()[current_col] == NUL)
2661 		    current_next_list = NULL;
2662 
2663 		/* When the ended item has "extend", another item with
2664 		 * "keepend" now needs to check for its end. */
2665 		 had_extend = (cur_si->si_flags & HL_EXTEND);
2666 
2667 		pop_current_state();
2668 
2669 		if (current_state.ga_len == 0)
2670 		    break;
2671 
2672 		if (had_extend && keepend_level >= 0)
2673 		{
2674 		    syn_update_ends(FALSE);
2675 		    if (current_state.ga_len == 0)
2676 			break;
2677 		}
2678 
2679 		cur_si = &CUR_STATE(current_state.ga_len - 1);
2680 
2681 		/*
2682 		 * Only for a region the search for the end continues after
2683 		 * the end of the contained item.  If the contained match
2684 		 * included the end-of-line, break here, the region continues.
2685 		 * Don't do this when:
2686 		 * - "keepend" is used for the contained item
2687 		 * - not at the end of the line (could be end="x$"me=e-1).
2688 		 * - "excludenl" is used (HL_HAS_EOL won't be set)
2689 		 */
2690 		if (cur_si->si_idx >= 0
2691 			&& SYN_ITEMS(syn_block)[cur_si->si_idx].sp_type
2692 							       == SPTYPE_START
2693 			&& !(cur_si->si_flags & (HL_MATCH | HL_KEEPEND)))
2694 		{
2695 		    update_si_end(cur_si, (int)current_col, TRUE);
2696 		    check_keepend();
2697 		    if ((current_next_flags & HL_HAS_EOL)
2698 			    && keepend_level < 0
2699 			    && syn_getcurline()[current_col] == NUL)
2700 			break;
2701 		}
2702 	    }
2703 	}
2704 	else
2705 	    break;
2706     }
2707 }
2708 
2709 /*
2710  * Update an entry in the current_state stack for a match or region.  This
2711  * fills in si_attr, si_next_list and si_cont_list.
2712  */
2713     static void
2714 update_si_attr(int idx)
2715 {
2716     stateitem_T	*sip = &CUR_STATE(idx);
2717     synpat_T	*spp;
2718 
2719     /* This should not happen... */
2720     if (sip->si_idx < 0)
2721 	return;
2722 
2723     spp = &(SYN_ITEMS(syn_block)[sip->si_idx]);
2724     if (sip->si_flags & HL_MATCH)
2725 	sip->si_id = spp->sp_syn_match_id;
2726     else
2727 	sip->si_id = spp->sp_syn.id;
2728     sip->si_attr = syn_id2attr(sip->si_id);
2729     sip->si_trans_id = sip->si_id;
2730     if (sip->si_flags & HL_MATCH)
2731 	sip->si_cont_list = NULL;
2732     else
2733 	sip->si_cont_list = spp->sp_cont_list;
2734 
2735     /*
2736      * For transparent items, take attr from outer item.
2737      * Also take cont_list, if there is none.
2738      * Don't do this for the matchgroup of a start or end pattern.
2739      */
2740     if ((spp->sp_flags & HL_TRANSP) && !(sip->si_flags & HL_MATCH))
2741     {
2742 	if (idx == 0)
2743 	{
2744 	    sip->si_attr = 0;
2745 	    sip->si_trans_id = 0;
2746 	    if (sip->si_cont_list == NULL)
2747 		sip->si_cont_list = ID_LIST_ALL;
2748 	}
2749 	else
2750 	{
2751 	    sip->si_attr = CUR_STATE(idx - 1).si_attr;
2752 	    sip->si_trans_id = CUR_STATE(idx - 1).si_trans_id;
2753 	    sip->si_h_startpos = CUR_STATE(idx - 1).si_h_startpos;
2754 	    sip->si_h_endpos = CUR_STATE(idx - 1).si_h_endpos;
2755 	    if (sip->si_cont_list == NULL)
2756 	    {
2757 		sip->si_flags |= HL_TRANS_CONT;
2758 		sip->si_cont_list = CUR_STATE(idx - 1).si_cont_list;
2759 	    }
2760 	}
2761     }
2762 }
2763 
2764 /*
2765  * Check the current stack for patterns with "keepend" flag.
2766  * Propagate the match-end to contained items, until a "skipend" item is found.
2767  */
2768     static void
2769 check_keepend(void)
2770 {
2771     int		i;
2772     lpos_T	maxpos;
2773     lpos_T	maxpos_h;
2774     stateitem_T	*sip;
2775 
2776     /*
2777      * This check can consume a lot of time; only do it from the level where
2778      * there really is a keepend.
2779      */
2780     if (keepend_level < 0)
2781 	return;
2782 
2783     /*
2784      * Find the last index of an "extend" item.  "keepend" items before that
2785      * won't do anything.  If there is no "extend" item "i" will be
2786      * "keepend_level" and all "keepend" items will work normally.
2787      */
2788     for (i = current_state.ga_len - 1; i > keepend_level; --i)
2789 	if (CUR_STATE(i).si_flags & HL_EXTEND)
2790 	    break;
2791 
2792     maxpos.lnum = 0;
2793     maxpos.col = 0;
2794     maxpos_h.lnum = 0;
2795     maxpos_h.col = 0;
2796     for ( ; i < current_state.ga_len; ++i)
2797     {
2798 	sip = &CUR_STATE(i);
2799 	if (maxpos.lnum != 0)
2800 	{
2801 	    limit_pos_zero(&sip->si_m_endpos, &maxpos);
2802 	    limit_pos_zero(&sip->si_h_endpos, &maxpos_h);
2803 	    limit_pos_zero(&sip->si_eoe_pos, &maxpos);
2804 	    sip->si_ends = TRUE;
2805 	}
2806 	if (sip->si_ends && (sip->si_flags & HL_KEEPEND))
2807 	{
2808 	    if (maxpos.lnum == 0
2809 		    || maxpos.lnum > sip->si_m_endpos.lnum
2810 		    || (maxpos.lnum == sip->si_m_endpos.lnum
2811 			&& maxpos.col > sip->si_m_endpos.col))
2812 		maxpos = sip->si_m_endpos;
2813 	    if (maxpos_h.lnum == 0
2814 		    || maxpos_h.lnum > sip->si_h_endpos.lnum
2815 		    || (maxpos_h.lnum == sip->si_h_endpos.lnum
2816 			&& maxpos_h.col > sip->si_h_endpos.col))
2817 		maxpos_h = sip->si_h_endpos;
2818 	}
2819     }
2820 }
2821 
2822 /*
2823  * Update an entry in the current_state stack for a start-skip-end pattern.
2824  * This finds the end of the current item, if it's in the current line.
2825  *
2826  * Return the flags for the matched END.
2827  */
2828     static void
2829 update_si_end(
2830     stateitem_T	*sip,
2831     int		startcol,   /* where to start searching for the end */
2832     int		force)	    /* when TRUE overrule a previous end */
2833 {
2834     lpos_T	startpos;
2835     lpos_T	endpos;
2836     lpos_T	hl_endpos;
2837     lpos_T	end_endpos;
2838     int		end_idx;
2839 
2840     /* return quickly for a keyword */
2841     if (sip->si_idx < 0)
2842 	return;
2843 
2844     /* Don't update when it's already done.  Can be a match of an end pattern
2845      * that started in a previous line.  Watch out: can also be a "keepend"
2846      * from a containing item. */
2847     if (!force && sip->si_m_endpos.lnum >= current_lnum)
2848 	return;
2849 
2850     /*
2851      * We need to find the end of the region.  It may continue in the next
2852      * line.
2853      */
2854     end_idx = 0;
2855     startpos.lnum = current_lnum;
2856     startpos.col = startcol;
2857     find_endpos(sip->si_idx, &startpos, &endpos, &hl_endpos,
2858 		   &(sip->si_flags), &end_endpos, &end_idx, sip->si_extmatch);
2859 
2860     if (endpos.lnum == 0)
2861     {
2862 	/* No end pattern matched. */
2863 	if (SYN_ITEMS(syn_block)[sip->si_idx].sp_flags & HL_ONELINE)
2864 	{
2865 	    /* a "oneline" never continues in the next line */
2866 	    sip->si_ends = TRUE;
2867 	    sip->si_m_endpos.lnum = current_lnum;
2868 	    sip->si_m_endpos.col = (colnr_T)STRLEN(syn_getcurline());
2869 	}
2870 	else
2871 	{
2872 	    /* continues in the next line */
2873 	    sip->si_ends = FALSE;
2874 	    sip->si_m_endpos.lnum = 0;
2875 	}
2876 	sip->si_h_endpos = sip->si_m_endpos;
2877     }
2878     else
2879     {
2880 	/* match within this line */
2881 	sip->si_m_endpos = endpos;
2882 	sip->si_h_endpos = hl_endpos;
2883 	sip->si_eoe_pos = end_endpos;
2884 	sip->si_ends = TRUE;
2885 	sip->si_end_idx = end_idx;
2886     }
2887 }
2888 
2889 /*
2890  * Add a new state to the current state stack.
2891  * It is cleared and the index set to "idx".
2892  * Return FAIL if it's not possible (out of memory).
2893  */
2894     static int
2895 push_current_state(int idx)
2896 {
2897     if (ga_grow(&current_state, 1) == FAIL)
2898 	return FAIL;
2899     vim_memset(&CUR_STATE(current_state.ga_len), 0, sizeof(stateitem_T));
2900     CUR_STATE(current_state.ga_len).si_idx = idx;
2901     ++current_state.ga_len;
2902     return OK;
2903 }
2904 
2905 /*
2906  * Remove a state from the current_state stack.
2907  */
2908     static void
2909 pop_current_state(void)
2910 {
2911     if (current_state.ga_len)
2912     {
2913 	unref_extmatch(CUR_STATE(current_state.ga_len - 1).si_extmatch);
2914 	--current_state.ga_len;
2915     }
2916     /* after the end of a pattern, try matching a keyword or pattern */
2917     next_match_idx = -1;
2918 
2919     /* if first state with "keepend" is popped, reset keepend_level */
2920     if (keepend_level >= current_state.ga_len)
2921 	keepend_level = -1;
2922 }
2923 
2924 /*
2925  * Find the end of a start/skip/end syntax region after "startpos".
2926  * Only checks one line.
2927  * Also handles a match item that continued from a previous line.
2928  * If not found, the syntax item continues in the next line.  m_endpos->lnum
2929  * will be 0.
2930  * If found, the end of the region and the end of the highlighting is
2931  * computed.
2932  */
2933     static void
2934 find_endpos(
2935     int		idx,		/* index of the pattern */
2936     lpos_T	*startpos,	/* where to start looking for an END match */
2937     lpos_T	*m_endpos,	/* return: end of match */
2938     lpos_T	*hl_endpos,	/* return: end of highlighting */
2939     long	*flagsp,	/* return: flags of matching END */
2940     lpos_T	*end_endpos,	/* return: end of end pattern match */
2941     int		*end_idx,	/* return: group ID for end pat. match, or 0 */
2942     reg_extmatch_T *start_ext)	/* submatches from the start pattern */
2943 {
2944     colnr_T	matchcol;
2945     synpat_T	*spp, *spp_skip;
2946     int		start_idx;
2947     int		best_idx;
2948     regmmatch_T	regmatch;
2949     regmmatch_T	best_regmatch;	    /* startpos/endpos of best match */
2950     lpos_T	pos;
2951     char_u	*line;
2952     int		had_match = FALSE;
2953     char_u	buf_chartab[32];  /* chartab array for syn option iskyeyword */
2954 
2955     /* just in case we are invoked for a keyword */
2956     if (idx < 0)
2957 	return;
2958 
2959     /*
2960      * Check for being called with a START pattern.
2961      * Can happen with a match that continues to the next line, because it
2962      * contained a region.
2963      */
2964     spp = &(SYN_ITEMS(syn_block)[idx]);
2965     if (spp->sp_type != SPTYPE_START)
2966     {
2967 	*hl_endpos = *startpos;
2968 	return;
2969     }
2970 
2971     /*
2972      * Find the SKIP or first END pattern after the last START pattern.
2973      */
2974     for (;;)
2975     {
2976 	spp = &(SYN_ITEMS(syn_block)[idx]);
2977 	if (spp->sp_type != SPTYPE_START)
2978 	    break;
2979 	++idx;
2980     }
2981 
2982     /*
2983      *	Lookup the SKIP pattern (if present)
2984      */
2985     if (spp->sp_type == SPTYPE_SKIP)
2986     {
2987 	spp_skip = spp;
2988 	++idx;
2989     }
2990     else
2991 	spp_skip = NULL;
2992 
2993     /* Setup external matches for syn_regexec(). */
2994     unref_extmatch(re_extmatch_in);
2995     re_extmatch_in = ref_extmatch(start_ext);
2996 
2997     matchcol = startpos->col;	/* start looking for a match at sstart */
2998     start_idx = idx;		/* remember the first END pattern. */
2999     best_regmatch.startpos[0].col = 0;		/* avoid compiler warning */
3000 
3001     /* use syntax iskeyword option */
3002     save_chartab(buf_chartab);
3003 
3004     for (;;)
3005     {
3006 	/*
3007 	 * Find end pattern that matches first after "matchcol".
3008 	 */
3009 	best_idx = -1;
3010 	for (idx = start_idx; idx < syn_block->b_syn_patterns.ga_len; ++idx)
3011 	{
3012 	    int lc_col = matchcol;
3013 	    int r;
3014 
3015 	    spp = &(SYN_ITEMS(syn_block)[idx]);
3016 	    if (spp->sp_type != SPTYPE_END)	/* past last END pattern */
3017 		break;
3018 	    lc_col -= spp->sp_offsets[SPO_LC_OFF];
3019 	    if (lc_col < 0)
3020 		lc_col = 0;
3021 
3022 	    regmatch.rmm_ic = spp->sp_ic;
3023 	    regmatch.regprog = spp->sp_prog;
3024 	    r = syn_regexec(&regmatch, startpos->lnum, lc_col,
3025 						  IF_SYN_TIME(&spp->sp_time));
3026 	    spp->sp_prog = regmatch.regprog;
3027 	    if (r)
3028 	    {
3029 		if (best_idx == -1 || regmatch.startpos[0].col
3030 					      < best_regmatch.startpos[0].col)
3031 		{
3032 		    best_idx = idx;
3033 		    best_regmatch.startpos[0] = regmatch.startpos[0];
3034 		    best_regmatch.endpos[0] = regmatch.endpos[0];
3035 		}
3036 	    }
3037 	}
3038 
3039 	/*
3040 	 * If all end patterns have been tried, and there is no match, the
3041 	 * item continues until end-of-line.
3042 	 */
3043 	if (best_idx == -1)
3044 	    break;
3045 
3046 	/*
3047 	 * If the skip pattern matches before the end pattern,
3048 	 * continue searching after the skip pattern.
3049 	 */
3050 	if (spp_skip != NULL)
3051 	{
3052 	    int lc_col = matchcol - spp_skip->sp_offsets[SPO_LC_OFF];
3053 	    int r;
3054 
3055 	    if (lc_col < 0)
3056 		lc_col = 0;
3057 	    regmatch.rmm_ic = spp_skip->sp_ic;
3058 	    regmatch.regprog = spp_skip->sp_prog;
3059 	    r = syn_regexec(&regmatch, startpos->lnum, lc_col,
3060 					      IF_SYN_TIME(&spp_skip->sp_time));
3061 	    spp_skip->sp_prog = regmatch.regprog;
3062 	    if (r && regmatch.startpos[0].col
3063 					     <= best_regmatch.startpos[0].col)
3064 	    {
3065 		int line_len;
3066 
3067 		/* Add offset to skip pattern match */
3068 		syn_add_end_off(&pos, &regmatch, spp_skip, SPO_ME_OFF, 1);
3069 
3070 		/* If the skip pattern goes on to the next line, there is no
3071 		 * match with an end pattern in this line. */
3072 		if (pos.lnum > startpos->lnum)
3073 		    break;
3074 
3075 		line = ml_get_buf(syn_buf, startpos->lnum, FALSE);
3076 		line_len = (int)STRLEN(line);
3077 
3078 		/* take care of an empty match or negative offset */
3079 		if (pos.col <= matchcol)
3080 		    ++matchcol;
3081 		else if (pos.col <= regmatch.endpos[0].col)
3082 		    matchcol = pos.col;
3083 		else
3084 		    /* Be careful not to jump over the NUL at the end-of-line */
3085 		    for (matchcol = regmatch.endpos[0].col;
3086 			    matchcol < line_len && matchcol < pos.col;
3087 								   ++matchcol)
3088 			;
3089 
3090 		/* if the skip pattern includes end-of-line, break here */
3091 		if (matchcol >= line_len)
3092 		    break;
3093 
3094 		continue;	    /* start with first end pattern again */
3095 	    }
3096 	}
3097 
3098 	/*
3099 	 * Match from start pattern to end pattern.
3100 	 * Correct for match and highlight offset of end pattern.
3101 	 */
3102 	spp = &(SYN_ITEMS(syn_block)[best_idx]);
3103 	syn_add_end_off(m_endpos, &best_regmatch, spp, SPO_ME_OFF, 1);
3104 	/* can't end before the start */
3105 	if (m_endpos->lnum == startpos->lnum && m_endpos->col < startpos->col)
3106 	    m_endpos->col = startpos->col;
3107 
3108 	syn_add_end_off(end_endpos, &best_regmatch, spp, SPO_HE_OFF, 1);
3109 	/* can't end before the start */
3110 	if (end_endpos->lnum == startpos->lnum
3111 					   && end_endpos->col < startpos->col)
3112 	    end_endpos->col = startpos->col;
3113 	/* can't end after the match */
3114 	limit_pos(end_endpos, m_endpos);
3115 
3116 	/*
3117 	 * If the end group is highlighted differently, adjust the pointers.
3118 	 */
3119 	if (spp->sp_syn_match_id != spp->sp_syn.id && spp->sp_syn_match_id != 0)
3120 	{
3121 	    *end_idx = best_idx;
3122 	    if (spp->sp_off_flags & (1 << (SPO_RE_OFF + SPO_COUNT)))
3123 	    {
3124 		hl_endpos->lnum = best_regmatch.endpos[0].lnum;
3125 		hl_endpos->col = best_regmatch.endpos[0].col;
3126 	    }
3127 	    else
3128 	    {
3129 		hl_endpos->lnum = best_regmatch.startpos[0].lnum;
3130 		hl_endpos->col = best_regmatch.startpos[0].col;
3131 	    }
3132 	    hl_endpos->col += spp->sp_offsets[SPO_RE_OFF];
3133 
3134 	    /* can't end before the start */
3135 	    if (hl_endpos->lnum == startpos->lnum
3136 					    && hl_endpos->col < startpos->col)
3137 		hl_endpos->col = startpos->col;
3138 	    limit_pos(hl_endpos, m_endpos);
3139 
3140 	    /* now the match ends where the highlighting ends, it is turned
3141 	     * into the matchgroup for the end */
3142 	    *m_endpos = *hl_endpos;
3143 	}
3144 	else
3145 	{
3146 	    *end_idx = 0;
3147 	    *hl_endpos = *end_endpos;
3148 	}
3149 
3150 	*flagsp = spp->sp_flags;
3151 
3152 	had_match = TRUE;
3153 	break;
3154     }
3155 
3156     /* no match for an END pattern in this line */
3157     if (!had_match)
3158 	m_endpos->lnum = 0;
3159 
3160     restore_chartab(buf_chartab);
3161 
3162     /* Remove external matches. */
3163     unref_extmatch(re_extmatch_in);
3164     re_extmatch_in = NULL;
3165 }
3166 
3167 /*
3168  * Limit "pos" not to be after "limit".
3169  */
3170     static void
3171 limit_pos(lpos_T *pos, lpos_T *limit)
3172 {
3173     if (pos->lnum > limit->lnum)
3174 	*pos = *limit;
3175     else if (pos->lnum == limit->lnum && pos->col > limit->col)
3176 	pos->col = limit->col;
3177 }
3178 
3179 /*
3180  * Limit "pos" not to be after "limit", unless pos->lnum is zero.
3181  */
3182     static void
3183 limit_pos_zero(
3184     lpos_T	*pos,
3185     lpos_T	*limit)
3186 {
3187     if (pos->lnum == 0)
3188 	*pos = *limit;
3189     else
3190 	limit_pos(pos, limit);
3191 }
3192 
3193 /*
3194  * Add offset to matched text for end of match or highlight.
3195  */
3196     static void
3197 syn_add_end_off(
3198     lpos_T	*result,	/* returned position */
3199     regmmatch_T	*regmatch,	/* start/end of match */
3200     synpat_T	*spp,		/* matched pattern */
3201     int		idx,		/* index of offset */
3202     int		extra)		/* extra chars for offset to start */
3203 {
3204     int		col;
3205     int		off;
3206     char_u	*base;
3207     char_u	*p;
3208 
3209     if (spp->sp_off_flags & (1 << idx))
3210     {
3211 	result->lnum = regmatch->startpos[0].lnum;
3212 	col = regmatch->startpos[0].col;
3213 	off = spp->sp_offsets[idx] + extra;
3214     }
3215     else
3216     {
3217 	result->lnum = regmatch->endpos[0].lnum;
3218 	col = regmatch->endpos[0].col;
3219 	off = spp->sp_offsets[idx];
3220     }
3221     /* Don't go past the end of the line.  Matters for "rs=e+2" when there
3222      * is a matchgroup. Watch out for match with last NL in the buffer. */
3223     if (result->lnum > syn_buf->b_ml.ml_line_count)
3224 	col = 0;
3225     else if (off != 0)
3226     {
3227 	base = ml_get_buf(syn_buf, result->lnum, FALSE);
3228 	p = base + col;
3229 	if (off > 0)
3230 	{
3231 	    while (off-- > 0 && *p != NUL)
3232 		MB_PTR_ADV(p);
3233 	}
3234 	else if (off < 0)
3235 	{
3236 	    while (off++ < 0 && base < p)
3237 		MB_PTR_BACK(base, p);
3238 	}
3239 	col = (int)(p - base);
3240     }
3241     result->col = col;
3242 }
3243 
3244 /*
3245  * Add offset to matched text for start of match or highlight.
3246  * Avoid resulting column to become negative.
3247  */
3248     static void
3249 syn_add_start_off(
3250     lpos_T	*result,	/* returned position */
3251     regmmatch_T	*regmatch,	/* start/end of match */
3252     synpat_T	*spp,
3253     int		idx,
3254     int		extra)	    /* extra chars for offset to end */
3255 {
3256     int		col;
3257     int		off;
3258     char_u	*base;
3259     char_u	*p;
3260 
3261     if (spp->sp_off_flags & (1 << (idx + SPO_COUNT)))
3262     {
3263 	result->lnum = regmatch->endpos[0].lnum;
3264 	col = regmatch->endpos[0].col;
3265 	off = spp->sp_offsets[idx] + extra;
3266     }
3267     else
3268     {
3269 	result->lnum = regmatch->startpos[0].lnum;
3270 	col = regmatch->startpos[0].col;
3271 	off = spp->sp_offsets[idx];
3272     }
3273     if (result->lnum > syn_buf->b_ml.ml_line_count)
3274     {
3275 	/* a "\n" at the end of the pattern may take us below the last line */
3276 	result->lnum = syn_buf->b_ml.ml_line_count;
3277 	col = (int)STRLEN(ml_get_buf(syn_buf, result->lnum, FALSE));
3278     }
3279     if (off != 0)
3280     {
3281 	base = ml_get_buf(syn_buf, result->lnum, FALSE);
3282 	p = base + col;
3283 	if (off > 0)
3284 	{
3285 	    while (off-- && *p != NUL)
3286 		MB_PTR_ADV(p);
3287 	}
3288 	else if (off < 0)
3289 	{
3290 	    while (off++ && base < p)
3291 		MB_PTR_BACK(base, p);
3292 	}
3293 	col = (int)(p - base);
3294     }
3295     result->col = col;
3296 }
3297 
3298 /*
3299  * Get current line in syntax buffer.
3300  */
3301     static char_u *
3302 syn_getcurline(void)
3303 {
3304     return ml_get_buf(syn_buf, current_lnum, FALSE);
3305 }
3306 
3307 /*
3308  * Call vim_regexec() to find a match with "rmp" in "syn_buf".
3309  * Returns TRUE when there is a match.
3310  */
3311     static int
3312 syn_regexec(
3313     regmmatch_T	*rmp,
3314     linenr_T	lnum,
3315     colnr_T	col,
3316     syn_time_T  *st UNUSED)
3317 {
3318     int r;
3319 #ifdef FEAT_RELTIME
3320     int timed_out = FALSE;
3321 #endif
3322 #ifdef FEAT_PROFILE
3323     proftime_T	pt;
3324 
3325     if (syn_time_on)
3326 	profile_start(&pt);
3327 #endif
3328 
3329     rmp->rmm_maxcol = syn_buf->b_p_smc;
3330     r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col,
3331 #ifdef FEAT_RELTIME
3332 	    syn_tm, &timed_out
3333 #else
3334 	    NULL, NULL
3335 #endif
3336 	    );
3337 
3338 #ifdef FEAT_PROFILE
3339     if (syn_time_on)
3340     {
3341 	profile_end(&pt);
3342 	profile_add(&st->total, &pt);
3343 	if (profile_cmp(&pt, &st->slowest) < 0)
3344 	    st->slowest = pt;
3345 	++st->count;
3346 	if (r > 0)
3347 	    ++st->match;
3348     }
3349 #endif
3350 #ifdef FEAT_RELTIME
3351     if (timed_out)
3352 	syn_win->w_s->b_syn_slow = TRUE;
3353 #endif
3354 
3355     if (r > 0)
3356     {
3357 	rmp->startpos[0].lnum += lnum;
3358 	rmp->endpos[0].lnum += lnum;
3359 	return TRUE;
3360     }
3361     return FALSE;
3362 }
3363 
3364 /*
3365  * Check one position in a line for a matching keyword.
3366  * The caller must check if a keyword can start at startcol.
3367  * Return its ID if found, 0 otherwise.
3368  */
3369     static int
3370 check_keyword_id(
3371     char_u	*line,
3372     int		startcol,	/* position in line to check for keyword */
3373     int		*endcolp,	/* return: character after found keyword */
3374     long	*flagsp,	/* return: flags of matching keyword */
3375     short	**next_listp,	/* return: next_list of matching keyword */
3376     stateitem_T	*cur_si,	/* item at the top of the stack */
3377     int		*ccharp UNUSED)	/* conceal substitution char */
3378 {
3379     keyentry_T	*kp;
3380     char_u	*kwp;
3381     int		round;
3382     int		kwlen;
3383     char_u	keyword[MAXKEYWLEN + 1]; /* assume max. keyword len is 80 */
3384     hashtab_T	*ht;
3385     hashitem_T	*hi;
3386 
3387     /* Find first character after the keyword.  First character was already
3388      * checked. */
3389     kwp = line + startcol;
3390     kwlen = 0;
3391     do
3392     {
3393 #ifdef FEAT_MBYTE
3394 	if (has_mbyte)
3395 	    kwlen += (*mb_ptr2len)(kwp + kwlen);
3396 	else
3397 #endif
3398 	    ++kwlen;
3399     }
3400     while (vim_iswordp_buf(kwp + kwlen, syn_buf));
3401 
3402     if (kwlen > MAXKEYWLEN)
3403 	return 0;
3404 
3405     /*
3406      * Must make a copy of the keyword, so we can add a NUL and make it
3407      * lowercase.
3408      */
3409     vim_strncpy(keyword, kwp, kwlen);
3410 
3411     /*
3412      * Try twice:
3413      * 1. matching case
3414      * 2. ignoring case
3415      */
3416     for (round = 1; round <= 2; ++round)
3417     {
3418 	ht = round == 1 ? &syn_block->b_keywtab : &syn_block->b_keywtab_ic;
3419 	if (ht->ht_used == 0)
3420 	    continue;
3421 	if (round == 2)	/* ignore case */
3422 	    (void)str_foldcase(kwp, kwlen, keyword, MAXKEYWLEN + 1);
3423 
3424 	/*
3425 	 * Find keywords that match.  There can be several with different
3426 	 * attributes.
3427 	 * When current_next_list is non-zero accept only that group, otherwise:
3428 	 *  Accept a not-contained keyword at toplevel.
3429 	 *  Accept a keyword at other levels only if it is in the contains list.
3430 	 */
3431 	hi = hash_find(ht, keyword);
3432 	if (!HASHITEM_EMPTY(hi))
3433 	    for (kp = HI2KE(hi); kp != NULL; kp = kp->ke_next)
3434 	    {
3435 		if (current_next_list != 0
3436 			? in_id_list(NULL, current_next_list, &kp->k_syn, 0)
3437 			: (cur_si == NULL
3438 			    ? !(kp->flags & HL_CONTAINED)
3439 			    : in_id_list(cur_si, cur_si->si_cont_list,
3440 				      &kp->k_syn, kp->flags & HL_CONTAINED)))
3441 		{
3442 		    *endcolp = startcol + kwlen;
3443 		    *flagsp = kp->flags;
3444 		    *next_listp = kp->next_list;
3445 #ifdef FEAT_CONCEAL
3446 		    *ccharp = kp->k_char;
3447 #endif
3448 		    return kp->k_syn.id;
3449 		}
3450 	    }
3451     }
3452     return 0;
3453 }
3454 
3455 /*
3456  * Handle ":syntax conceal" command.
3457  */
3458     static void
3459 syn_cmd_conceal(exarg_T *eap UNUSED, int syncing UNUSED)
3460 {
3461 #ifdef FEAT_CONCEAL
3462     char_u	*arg = eap->arg;
3463     char_u	*next;
3464 
3465     eap->nextcmd = find_nextcmd(arg);
3466     if (eap->skip)
3467 	return;
3468 
3469     next = skiptowhite(arg);
3470     if (*arg == NUL)
3471     {
3472 	if (curwin->w_s->b_syn_conceal)
3473 	    MSG(_("syntax conceal on"));
3474 	else
3475 	    MSG(_("syntax conceal off"));
3476     }
3477     else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2)
3478 	curwin->w_s->b_syn_conceal = TRUE;
3479     else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3)
3480 	curwin->w_s->b_syn_conceal = FALSE;
3481     else
3482 	EMSG2(_("E390: Illegal argument: %s"), arg);
3483 #endif
3484 }
3485 
3486 /*
3487  * Handle ":syntax case" command.
3488  */
3489     static void
3490 syn_cmd_case(exarg_T *eap, int syncing UNUSED)
3491 {
3492     char_u	*arg = eap->arg;
3493     char_u	*next;
3494 
3495     eap->nextcmd = find_nextcmd(arg);
3496     if (eap->skip)
3497 	return;
3498 
3499     next = skiptowhite(arg);
3500     if (*arg == NUL)
3501     {
3502 	if (curwin->w_s->b_syn_ic)
3503 	    MSG(_("syntax case ignore"));
3504 	else
3505 	    MSG(_("syntax case match"));
3506     }
3507     else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5)
3508 	curwin->w_s->b_syn_ic = FALSE;
3509     else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6)
3510 	curwin->w_s->b_syn_ic = TRUE;
3511     else
3512 	EMSG2(_("E390: Illegal argument: %s"), arg);
3513 }
3514 
3515 /*
3516  * Handle ":syntax spell" command.
3517  */
3518     static void
3519 syn_cmd_spell(exarg_T *eap, int syncing UNUSED)
3520 {
3521     char_u	*arg = eap->arg;
3522     char_u	*next;
3523 
3524     eap->nextcmd = find_nextcmd(arg);
3525     if (eap->skip)
3526 	return;
3527 
3528     next = skiptowhite(arg);
3529     if (*arg == NUL)
3530     {
3531 	if (curwin->w_s->b_syn_spell == SYNSPL_TOP)
3532 	    MSG(_("syntax spell toplevel"));
3533 	else if (curwin->w_s->b_syn_spell == SYNSPL_NOTOP)
3534 	    MSG(_("syntax spell notoplevel"));
3535 	else
3536 	    MSG(_("syntax spell default"));
3537     }
3538     else if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8)
3539 	curwin->w_s->b_syn_spell = SYNSPL_TOP;
3540     else if (STRNICMP(arg, "notoplevel", 10) == 0 && next - arg == 10)
3541 	curwin->w_s->b_syn_spell = SYNSPL_NOTOP;
3542     else if (STRNICMP(arg, "default", 7) == 0 && next - arg == 7)
3543 	curwin->w_s->b_syn_spell = SYNSPL_DEFAULT;
3544     else
3545     {
3546 	EMSG2(_("E390: Illegal argument: %s"), arg);
3547 	return;
3548     }
3549 
3550     /* assume spell checking changed, force a redraw */
3551     redraw_win_later(curwin, NOT_VALID);
3552 }
3553 
3554 /*
3555  * Handle ":syntax iskeyword" command.
3556  */
3557     static void
3558 syn_cmd_iskeyword(exarg_T *eap, int syncing UNUSED)
3559 {
3560     char_u	*arg = eap->arg;
3561     char_u	save_chartab[32];
3562     char_u	*save_isk;
3563 
3564     if (eap->skip)
3565 	return;
3566 
3567     arg = skipwhite(arg);
3568     if (*arg == NUL)
3569     {
3570 	MSG_PUTS("\n");
3571 	MSG_PUTS(_("syntax iskeyword "));
3572 	if (curwin->w_s->b_syn_isk != empty_option)
3573 	    msg_outtrans(curwin->w_s->b_syn_isk);
3574 	else
3575 	    msg_outtrans((char_u *)"not set");
3576     }
3577     else
3578     {
3579 	if (STRNICMP(arg, "clear", 5) == 0)
3580 	{
3581 	    mch_memmove(curwin->w_s->b_syn_chartab, curbuf->b_chartab,
3582 								  (size_t)32);
3583 	    clear_string_option(&curwin->w_s->b_syn_isk);
3584 	}
3585 	else
3586 	{
3587 	    mch_memmove(save_chartab, curbuf->b_chartab, (size_t)32);
3588 	    save_isk = curbuf->b_p_isk;
3589 	    curbuf->b_p_isk = vim_strsave(arg);
3590 
3591 	    buf_init_chartab(curbuf, FALSE);
3592 	    mch_memmove(curwin->w_s->b_syn_chartab, curbuf->b_chartab,
3593 								  (size_t)32);
3594 	    mch_memmove(curbuf->b_chartab, save_chartab, (size_t)32);
3595 	    clear_string_option(&curwin->w_s->b_syn_isk);
3596 	    curwin->w_s->b_syn_isk = curbuf->b_p_isk;
3597 	    curbuf->b_p_isk = save_isk;
3598 	}
3599     }
3600     redraw_win_later(curwin, NOT_VALID);
3601 }
3602 
3603 /*
3604  * Clear all syntax info for one buffer.
3605  */
3606     void
3607 syntax_clear(synblock_T *block)
3608 {
3609     int i;
3610 
3611     block->b_syn_error = FALSE;	    /* clear previous error */
3612 #ifdef FEAT_RELTIME
3613     block->b_syn_slow = FALSE;	    /* clear previous timeout */
3614 #endif
3615     block->b_syn_ic = FALSE;	    /* Use case, by default */
3616     block->b_syn_spell = SYNSPL_DEFAULT; /* default spell checking */
3617     block->b_syn_containedin = FALSE;
3618 #ifdef FEAT_CONCEAL
3619     block->b_syn_conceal = FALSE;
3620 #endif
3621 
3622     /* free the keywords */
3623     clear_keywtab(&block->b_keywtab);
3624     clear_keywtab(&block->b_keywtab_ic);
3625 
3626     /* free the syntax patterns */
3627     for (i = block->b_syn_patterns.ga_len; --i >= 0; )
3628 	syn_clear_pattern(block, i);
3629     ga_clear(&block->b_syn_patterns);
3630 
3631     /* free the syntax clusters */
3632     for (i = block->b_syn_clusters.ga_len; --i >= 0; )
3633 	syn_clear_cluster(block, i);
3634     ga_clear(&block->b_syn_clusters);
3635     block->b_spell_cluster_id = 0;
3636     block->b_nospell_cluster_id = 0;
3637 
3638     block->b_syn_sync_flags = 0;
3639     block->b_syn_sync_minlines = 0;
3640     block->b_syn_sync_maxlines = 0;
3641     block->b_syn_sync_linebreaks = 0;
3642 
3643     vim_regfree(block->b_syn_linecont_prog);
3644     block->b_syn_linecont_prog = NULL;
3645     VIM_CLEAR(block->b_syn_linecont_pat);
3646 #ifdef FEAT_FOLDING
3647     block->b_syn_folditems = 0;
3648 #endif
3649     clear_string_option(&block->b_syn_isk);
3650 
3651     /* free the stored states */
3652     syn_stack_free_all(block);
3653     invalidate_current_state();
3654 
3655     /* Reset the counter for ":syn include" */
3656     running_syn_inc_tag = 0;
3657 }
3658 
3659 /*
3660  * Get rid of ownsyntax for window "wp".
3661  */
3662     void
3663 reset_synblock(win_T *wp)
3664 {
3665     if (wp->w_s != &wp->w_buffer->b_s)
3666     {
3667 	syntax_clear(wp->w_s);
3668 	vim_free(wp->w_s);
3669 	wp->w_s = &wp->w_buffer->b_s;
3670     }
3671 }
3672 
3673 /*
3674  * Clear syncing info for one buffer.
3675  */
3676     static void
3677 syntax_sync_clear(void)
3678 {
3679     int i;
3680 
3681     /* free the syntax patterns */
3682     for (i = curwin->w_s->b_syn_patterns.ga_len; --i >= 0; )
3683 	if (SYN_ITEMS(curwin->w_s)[i].sp_syncing)
3684 	    syn_remove_pattern(curwin->w_s, i);
3685 
3686     curwin->w_s->b_syn_sync_flags = 0;
3687     curwin->w_s->b_syn_sync_minlines = 0;
3688     curwin->w_s->b_syn_sync_maxlines = 0;
3689     curwin->w_s->b_syn_sync_linebreaks = 0;
3690 
3691     vim_regfree(curwin->w_s->b_syn_linecont_prog);
3692     curwin->w_s->b_syn_linecont_prog = NULL;
3693     VIM_CLEAR(curwin->w_s->b_syn_linecont_pat);
3694     clear_string_option(&curwin->w_s->b_syn_isk);
3695 
3696     syn_stack_free_all(curwin->w_s);	/* Need to recompute all syntax. */
3697 }
3698 
3699 /*
3700  * Remove one pattern from the buffer's pattern list.
3701  */
3702     static void
3703 syn_remove_pattern(
3704     synblock_T	*block,
3705     int		idx)
3706 {
3707     synpat_T	*spp;
3708 
3709     spp = &(SYN_ITEMS(block)[idx]);
3710 #ifdef FEAT_FOLDING
3711     if (spp->sp_flags & HL_FOLD)
3712 	--block->b_syn_folditems;
3713 #endif
3714     syn_clear_pattern(block, idx);
3715     mch_memmove(spp, spp + 1,
3716 		   sizeof(synpat_T) * (block->b_syn_patterns.ga_len - idx - 1));
3717     --block->b_syn_patterns.ga_len;
3718 }
3719 
3720 /*
3721  * Clear and free one syntax pattern.  When clearing all, must be called from
3722  * last to first!
3723  */
3724     static void
3725 syn_clear_pattern(synblock_T *block, int i)
3726 {
3727     vim_free(SYN_ITEMS(block)[i].sp_pattern);
3728     vim_regfree(SYN_ITEMS(block)[i].sp_prog);
3729     /* Only free sp_cont_list and sp_next_list of first start pattern */
3730     if (i == 0 || SYN_ITEMS(block)[i - 1].sp_type != SPTYPE_START)
3731     {
3732 	vim_free(SYN_ITEMS(block)[i].sp_cont_list);
3733 	vim_free(SYN_ITEMS(block)[i].sp_next_list);
3734 	vim_free(SYN_ITEMS(block)[i].sp_syn.cont_in_list);
3735     }
3736 }
3737 
3738 /*
3739  * Clear and free one syntax cluster.
3740  */
3741     static void
3742 syn_clear_cluster(synblock_T *block, int i)
3743 {
3744     vim_free(SYN_CLSTR(block)[i].scl_name);
3745     vim_free(SYN_CLSTR(block)[i].scl_name_u);
3746     vim_free(SYN_CLSTR(block)[i].scl_list);
3747 }
3748 
3749 /*
3750  * Handle ":syntax clear" command.
3751  */
3752     static void
3753 syn_cmd_clear(exarg_T *eap, int syncing)
3754 {
3755     char_u	*arg = eap->arg;
3756     char_u	*arg_end;
3757     int		id;
3758 
3759     eap->nextcmd = find_nextcmd(arg);
3760     if (eap->skip)
3761 	return;
3762 
3763     /*
3764      * We have to disable this within ":syn include @group filename",
3765      * because otherwise @group would get deleted.
3766      * Only required for Vim 5.x syntax files, 6.0 ones don't contain ":syn
3767      * clear".
3768      */
3769     if (curwin->w_s->b_syn_topgrp != 0)
3770 	return;
3771 
3772     if (ends_excmd(*arg))
3773     {
3774 	/*
3775 	 * No argument: Clear all syntax items.
3776 	 */
3777 	if (syncing)
3778 	    syntax_sync_clear();
3779 	else
3780 	{
3781 	    syntax_clear(curwin->w_s);
3782 	    if (curwin->w_s == &curwin->w_buffer->b_s)
3783 		do_unlet((char_u *)"b:current_syntax", TRUE);
3784 	    do_unlet((char_u *)"w:current_syntax", TRUE);
3785 	}
3786     }
3787     else
3788     {
3789 	/*
3790 	 * Clear the group IDs that are in the argument.
3791 	 */
3792 	while (!ends_excmd(*arg))
3793 	{
3794 	    arg_end = skiptowhite(arg);
3795 	    if (*arg == '@')
3796 	    {
3797 		id = syn_scl_namen2id(arg + 1, (int)(arg_end - arg - 1));
3798 		if (id == 0)
3799 		{
3800 		    EMSG2(_("E391: No such syntax cluster: %s"), arg);
3801 		    break;
3802 		}
3803 		else
3804 		{
3805 		    /*
3806 		     * We can't physically delete a cluster without changing
3807 		     * the IDs of other clusters, so we do the next best thing
3808 		     * and make it empty.
3809 		     */
3810 		    short scl_id = id - SYNID_CLUSTER;
3811 
3812 		    VIM_CLEAR(SYN_CLSTR(curwin->w_s)[scl_id].scl_list);
3813 		}
3814 	    }
3815 	    else
3816 	    {
3817 		id = syn_namen2id(arg, (int)(arg_end - arg));
3818 		if (id == 0)
3819 		{
3820 		    EMSG2(_(e_nogroup), arg);
3821 		    break;
3822 		}
3823 		else
3824 		    syn_clear_one(id, syncing);
3825 	    }
3826 	    arg = skipwhite(arg_end);
3827 	}
3828     }
3829     redraw_curbuf_later(SOME_VALID);
3830     syn_stack_free_all(curwin->w_s);		/* Need to recompute all syntax. */
3831 }
3832 
3833 /*
3834  * Clear one syntax group for the current buffer.
3835  */
3836     static void
3837 syn_clear_one(int id, int syncing)
3838 {
3839     synpat_T	*spp;
3840     int		idx;
3841 
3842     /* Clear keywords only when not ":syn sync clear group-name" */
3843     if (!syncing)
3844     {
3845 	(void)syn_clear_keyword(id, &curwin->w_s->b_keywtab);
3846 	(void)syn_clear_keyword(id, &curwin->w_s->b_keywtab_ic);
3847     }
3848 
3849     /* clear the patterns for "id" */
3850     for (idx = curwin->w_s->b_syn_patterns.ga_len; --idx >= 0; )
3851     {
3852 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
3853 	if (spp->sp_syn.id != id || spp->sp_syncing != syncing)
3854 	    continue;
3855 	syn_remove_pattern(curwin->w_s, idx);
3856     }
3857 }
3858 
3859 /*
3860  * Handle ":syntax on" command.
3861  */
3862     static void
3863 syn_cmd_on(exarg_T *eap, int syncing UNUSED)
3864 {
3865     syn_cmd_onoff(eap, "syntax");
3866 }
3867 
3868 /*
3869  * Handle ":syntax enable" command.
3870  */
3871     static void
3872 syn_cmd_enable(exarg_T *eap, int syncing UNUSED)
3873 {
3874     set_internal_string_var((char_u *)"syntax_cmd", (char_u *)"enable");
3875     syn_cmd_onoff(eap, "syntax");
3876     do_unlet((char_u *)"g:syntax_cmd", TRUE);
3877 }
3878 
3879 /*
3880  * Handle ":syntax reset" command.
3881  * It actually resets highlighting, not syntax.
3882  */
3883     static void
3884 syn_cmd_reset(exarg_T *eap, int syncing UNUSED)
3885 {
3886     eap->nextcmd = check_nextcmd(eap->arg);
3887     if (!eap->skip)
3888     {
3889 	set_internal_string_var((char_u *)"syntax_cmd", (char_u *)"reset");
3890 	do_cmdline_cmd((char_u *)"runtime! syntax/syncolor.vim");
3891 	do_unlet((char_u *)"g:syntax_cmd", TRUE);
3892     }
3893 }
3894 
3895 /*
3896  * Handle ":syntax manual" command.
3897  */
3898     static void
3899 syn_cmd_manual(exarg_T *eap, int syncing UNUSED)
3900 {
3901     syn_cmd_onoff(eap, "manual");
3902 }
3903 
3904 /*
3905  * Handle ":syntax off" command.
3906  */
3907     static void
3908 syn_cmd_off(exarg_T *eap, int syncing UNUSED)
3909 {
3910     syn_cmd_onoff(eap, "nosyntax");
3911 }
3912 
3913     static void
3914 syn_cmd_onoff(exarg_T *eap, char *name)
3915 {
3916     char_u	buf[100];
3917 
3918     eap->nextcmd = check_nextcmd(eap->arg);
3919     if (!eap->skip)
3920     {
3921 	STRCPY(buf, "so ");
3922 	vim_snprintf((char *)buf + 3, sizeof(buf) - 3, SYNTAX_FNAME, name);
3923 	do_cmdline_cmd(buf);
3924     }
3925 }
3926 
3927 /*
3928  * Handle ":syntax [list]" command: list current syntax words.
3929  */
3930     static void
3931 syn_cmd_list(
3932     exarg_T	*eap,
3933     int		syncing)	    /* when TRUE: list syncing items */
3934 {
3935     char_u	*arg = eap->arg;
3936     int		id;
3937     char_u	*arg_end;
3938 
3939     eap->nextcmd = find_nextcmd(arg);
3940     if (eap->skip)
3941 	return;
3942 
3943     if (!syntax_present(curwin))
3944     {
3945 	MSG(_(msg_no_items));
3946 	return;
3947     }
3948 
3949     if (syncing)
3950     {
3951 	if (curwin->w_s->b_syn_sync_flags & SF_CCOMMENT)
3952 	{
3953 	    MSG_PUTS(_("syncing on C-style comments"));
3954 	    syn_lines_msg();
3955 	    syn_match_msg();
3956 	    return;
3957 	}
3958 	else if (!(curwin->w_s->b_syn_sync_flags & SF_MATCH))
3959 	{
3960 	    if (curwin->w_s->b_syn_sync_minlines == 0)
3961 		MSG_PUTS(_("no syncing"));
3962 	    else
3963 	    {
3964 		MSG_PUTS(_("syncing starts "));
3965 		msg_outnum(curwin->w_s->b_syn_sync_minlines);
3966 		MSG_PUTS(_(" lines before top line"));
3967 		syn_match_msg();
3968 	    }
3969 	    return;
3970 	}
3971 	MSG_PUTS_TITLE(_("\n--- Syntax sync items ---"));
3972 	if (curwin->w_s->b_syn_sync_minlines > 0
3973 		|| curwin->w_s->b_syn_sync_maxlines > 0
3974 		|| curwin->w_s->b_syn_sync_linebreaks > 0)
3975 	{
3976 	    MSG_PUTS(_("\nsyncing on items"));
3977 	    syn_lines_msg();
3978 	    syn_match_msg();
3979 	}
3980     }
3981     else
3982 	MSG_PUTS_TITLE(_("\n--- Syntax items ---"));
3983     if (ends_excmd(*arg))
3984     {
3985 	/*
3986 	 * No argument: List all group IDs and all syntax clusters.
3987 	 */
3988 	for (id = 1; id <= highlight_ga.ga_len && !got_int; ++id)
3989 	    syn_list_one(id, syncing, FALSE);
3990 	for (id = 0; id < curwin->w_s->b_syn_clusters.ga_len && !got_int; ++id)
3991 	    syn_list_cluster(id);
3992     }
3993     else
3994     {
3995 	/*
3996 	 * List the group IDs and syntax clusters that are in the argument.
3997 	 */
3998 	while (!ends_excmd(*arg) && !got_int)
3999 	{
4000 	    arg_end = skiptowhite(arg);
4001 	    if (*arg == '@')
4002 	    {
4003 		id = syn_scl_namen2id(arg + 1, (int)(arg_end - arg - 1));
4004 		if (id == 0)
4005 		    EMSG2(_("E392: No such syntax cluster: %s"), arg);
4006 		else
4007 		    syn_list_cluster(id - SYNID_CLUSTER);
4008 	    }
4009 	    else
4010 	    {
4011 		id = syn_namen2id(arg, (int)(arg_end - arg));
4012 		if (id == 0)
4013 		    EMSG2(_(e_nogroup), arg);
4014 		else
4015 		    syn_list_one(id, syncing, TRUE);
4016 	    }
4017 	    arg = skipwhite(arg_end);
4018 	}
4019     }
4020     eap->nextcmd = check_nextcmd(arg);
4021 }
4022 
4023     static void
4024 syn_lines_msg(void)
4025 {
4026     if (curwin->w_s->b_syn_sync_maxlines > 0
4027 				      || curwin->w_s->b_syn_sync_minlines > 0)
4028     {
4029 	MSG_PUTS("; ");
4030 	if (curwin->w_s->b_syn_sync_minlines > 0)
4031 	{
4032 	    MSG_PUTS(_("minimal "));
4033 	    msg_outnum(curwin->w_s->b_syn_sync_minlines);
4034 	    if (curwin->w_s->b_syn_sync_maxlines)
4035 		MSG_PUTS(", ");
4036 	}
4037 	if (curwin->w_s->b_syn_sync_maxlines > 0)
4038 	{
4039 	    MSG_PUTS(_("maximal "));
4040 	    msg_outnum(curwin->w_s->b_syn_sync_maxlines);
4041 	}
4042 	MSG_PUTS(_(" lines before top line"));
4043     }
4044 }
4045 
4046     static void
4047 syn_match_msg(void)
4048 {
4049     if (curwin->w_s->b_syn_sync_linebreaks > 0)
4050     {
4051 	MSG_PUTS(_("; match "));
4052 	msg_outnum(curwin->w_s->b_syn_sync_linebreaks);
4053 	MSG_PUTS(_(" line breaks"));
4054     }
4055 }
4056 
4057 static int  last_matchgroup;
4058 
4059 struct name_list
4060 {
4061     int		flag;
4062     char	*name;
4063 };
4064 
4065 static void syn_list_flags(struct name_list *nl, int flags, int attr);
4066 
4067 /*
4068  * List one syntax item, for ":syntax" or "syntax list syntax_name".
4069  */
4070     static void
4071 syn_list_one(
4072     int		id,
4073     int		syncing,	    /* when TRUE: list syncing items */
4074     int		link_only)	    /* when TRUE; list link-only too */
4075 {
4076     int		attr;
4077     int		idx;
4078     int		did_header = FALSE;
4079     synpat_T	*spp;
4080     static struct name_list namelist1[] =
4081 		{
4082 		    {HL_DISPLAY, "display"},
4083 		    {HL_CONTAINED, "contained"},
4084 		    {HL_ONELINE, "oneline"},
4085 		    {HL_KEEPEND, "keepend"},
4086 		    {HL_EXTEND, "extend"},
4087 		    {HL_EXCLUDENL, "excludenl"},
4088 		    {HL_TRANSP, "transparent"},
4089 		    {HL_FOLD, "fold"},
4090 #ifdef FEAT_CONCEAL
4091 		    {HL_CONCEAL, "conceal"},
4092 		    {HL_CONCEALENDS, "concealends"},
4093 #endif
4094 		    {0, NULL}
4095 		};
4096     static struct name_list namelist2[] =
4097 		{
4098 		    {HL_SKIPWHITE, "skipwhite"},
4099 		    {HL_SKIPNL, "skipnl"},
4100 		    {HL_SKIPEMPTY, "skipempty"},
4101 		    {0, NULL}
4102 		};
4103 
4104     attr = HL_ATTR(HLF_D);		/* highlight like directories */
4105 
4106     /* list the keywords for "id" */
4107     if (!syncing)
4108     {
4109 	did_header = syn_list_keywords(id, &curwin->w_s->b_keywtab, FALSE, attr);
4110 	did_header = syn_list_keywords(id, &curwin->w_s->b_keywtab_ic,
4111 							    did_header, attr);
4112     }
4113 
4114     /* list the patterns for "id" */
4115     for (idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len && !got_int; ++idx)
4116     {
4117 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
4118 	if (spp->sp_syn.id != id || spp->sp_syncing != syncing)
4119 	    continue;
4120 
4121 	(void)syn_list_header(did_header, 999, id);
4122 	did_header = TRUE;
4123 	last_matchgroup = 0;
4124 	if (spp->sp_type == SPTYPE_MATCH)
4125 	{
4126 	    put_pattern("match", ' ', spp, attr);
4127 	    msg_putchar(' ');
4128 	}
4129 	else if (spp->sp_type == SPTYPE_START)
4130 	{
4131 	    while (SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_START)
4132 		put_pattern("start", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr);
4133 	    if (SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_SKIP)
4134 		put_pattern("skip", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr);
4135 	    while (idx < curwin->w_s->b_syn_patterns.ga_len
4136 			      && SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_END)
4137 		put_pattern("end", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr);
4138 	    --idx;
4139 	    msg_putchar(' ');
4140 	}
4141 	syn_list_flags(namelist1, spp->sp_flags, attr);
4142 
4143 	if (spp->sp_cont_list != NULL)
4144 	    put_id_list((char_u *)"contains", spp->sp_cont_list, attr);
4145 
4146 	if (spp->sp_syn.cont_in_list != NULL)
4147 	    put_id_list((char_u *)"containedin",
4148 					      spp->sp_syn.cont_in_list, attr);
4149 
4150 	if (spp->sp_next_list != NULL)
4151 	{
4152 	    put_id_list((char_u *)"nextgroup", spp->sp_next_list, attr);
4153 	    syn_list_flags(namelist2, spp->sp_flags, attr);
4154 	}
4155 	if (spp->sp_flags & (HL_SYNC_HERE|HL_SYNC_THERE))
4156 	{
4157 	    if (spp->sp_flags & HL_SYNC_HERE)
4158 		msg_puts_attr((char_u *)"grouphere", attr);
4159 	    else
4160 		msg_puts_attr((char_u *)"groupthere", attr);
4161 	    msg_putchar(' ');
4162 	    if (spp->sp_sync_idx >= 0)
4163 		msg_outtrans(HL_TABLE()[SYN_ITEMS(curwin->w_s)
4164 				   [spp->sp_sync_idx].sp_syn.id - 1].sg_name);
4165 	    else
4166 		MSG_PUTS("NONE");
4167 	    msg_putchar(' ');
4168 	}
4169     }
4170 
4171     /* list the link, if there is one */
4172     if (HL_TABLE()[id - 1].sg_link && (did_header || link_only) && !got_int)
4173     {
4174 	(void)syn_list_header(did_header, 999, id);
4175 	msg_puts_attr((char_u *)"links to", attr);
4176 	msg_putchar(' ');
4177 	msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name);
4178     }
4179 }
4180 
4181     static void
4182 syn_list_flags(struct name_list *nlist, int flags, int attr)
4183 {
4184     int		i;
4185 
4186     for (i = 0; nlist[i].flag != 0; ++i)
4187 	if (flags & nlist[i].flag)
4188 	{
4189 	    msg_puts_attr((char_u *)nlist[i].name, attr);
4190 	    msg_putchar(' ');
4191 	}
4192 }
4193 
4194 /*
4195  * List one syntax cluster, for ":syntax" or "syntax list syntax_name".
4196  */
4197     static void
4198 syn_list_cluster(int id)
4199 {
4200     int	    endcol = 15;
4201 
4202     /* slight hack:  roughly duplicate the guts of syn_list_header() */
4203     msg_putchar('\n');
4204     msg_outtrans(SYN_CLSTR(curwin->w_s)[id].scl_name);
4205 
4206     if (msg_col >= endcol)	/* output at least one space */
4207 	endcol = msg_col + 1;
4208     if (Columns <= endcol)	/* avoid hang for tiny window */
4209 	endcol = Columns - 1;
4210 
4211     msg_advance(endcol);
4212     if (SYN_CLSTR(curwin->w_s)[id].scl_list != NULL)
4213     {
4214 	put_id_list((char_u *)"cluster", SYN_CLSTR(curwin->w_s)[id].scl_list,
4215 		    HL_ATTR(HLF_D));
4216     }
4217     else
4218     {
4219 	msg_puts_attr((char_u *)"cluster", HL_ATTR(HLF_D));
4220 	msg_puts((char_u *)"=NONE");
4221     }
4222 }
4223 
4224     static void
4225 put_id_list(char_u *name, short *list, int attr)
4226 {
4227     short		*p;
4228 
4229     msg_puts_attr(name, attr);
4230     msg_putchar('=');
4231     for (p = list; *p; ++p)
4232     {
4233 	if (*p >= SYNID_ALLBUT && *p < SYNID_TOP)
4234 	{
4235 	    if (p[1])
4236 		MSG_PUTS("ALLBUT");
4237 	    else
4238 		MSG_PUTS("ALL");
4239 	}
4240 	else if (*p >= SYNID_TOP && *p < SYNID_CONTAINED)
4241 	{
4242 	    MSG_PUTS("TOP");
4243 	}
4244 	else if (*p >= SYNID_CONTAINED && *p < SYNID_CLUSTER)
4245 	{
4246 	    MSG_PUTS("CONTAINED");
4247 	}
4248 	else if (*p >= SYNID_CLUSTER)
4249 	{
4250 	    short scl_id = *p - SYNID_CLUSTER;
4251 
4252 	    msg_putchar('@');
4253 	    msg_outtrans(SYN_CLSTR(curwin->w_s)[scl_id].scl_name);
4254 	}
4255 	else
4256 	    msg_outtrans(HL_TABLE()[*p - 1].sg_name);
4257 	if (p[1])
4258 	    msg_putchar(',');
4259     }
4260     msg_putchar(' ');
4261 }
4262 
4263     static void
4264 put_pattern(
4265     char	*s,
4266     int		c,
4267     synpat_T	*spp,
4268     int		attr)
4269 {
4270     long	n;
4271     int		mask;
4272     int		first;
4273     static char	*sepchars = "/+=-#@\"|'^&";
4274     int		i;
4275 
4276     /* May have to write "matchgroup=group" */
4277     if (last_matchgroup != spp->sp_syn_match_id)
4278     {
4279 	last_matchgroup = spp->sp_syn_match_id;
4280 	msg_puts_attr((char_u *)"matchgroup", attr);
4281 	msg_putchar('=');
4282 	if (last_matchgroup == 0)
4283 	    msg_outtrans((char_u *)"NONE");
4284 	else
4285 	    msg_outtrans(HL_TABLE()[last_matchgroup - 1].sg_name);
4286 	msg_putchar(' ');
4287     }
4288 
4289     /* output the name of the pattern and an '=' or ' ' */
4290     msg_puts_attr((char_u *)s, attr);
4291     msg_putchar(c);
4292 
4293     /* output the pattern, in between a char that is not in the pattern */
4294     for (i = 0; vim_strchr(spp->sp_pattern, sepchars[i]) != NULL; )
4295 	if (sepchars[++i] == NUL)
4296 	{
4297 	    i = 0;	/* no good char found, just use the first one */
4298 	    break;
4299 	}
4300     msg_putchar(sepchars[i]);
4301     msg_outtrans(spp->sp_pattern);
4302     msg_putchar(sepchars[i]);
4303 
4304     /* output any pattern options */
4305     first = TRUE;
4306     for (i = 0; i < SPO_COUNT; ++i)
4307     {
4308 	mask = (1 << i);
4309 	if (spp->sp_off_flags & (mask + (mask << SPO_COUNT)))
4310 	{
4311 	    if (!first)
4312 		msg_putchar(',');	/* separate with commas */
4313 	    msg_puts((char_u *)spo_name_tab[i]);
4314 	    n = spp->sp_offsets[i];
4315 	    if (i != SPO_LC_OFF)
4316 	    {
4317 		if (spp->sp_off_flags & mask)
4318 		    msg_putchar('s');
4319 		else
4320 		    msg_putchar('e');
4321 		if (n > 0)
4322 		    msg_putchar('+');
4323 	    }
4324 	    if (n || i == SPO_LC_OFF)
4325 		msg_outnum(n);
4326 	    first = FALSE;
4327 	}
4328     }
4329     msg_putchar(' ');
4330 }
4331 
4332 /*
4333  * List or clear the keywords for one syntax group.
4334  * Return TRUE if the header has been printed.
4335  */
4336     static int
4337 syn_list_keywords(
4338     int		id,
4339     hashtab_T	*ht,
4340     int		did_header,		/* header has already been printed */
4341     int		attr)
4342 {
4343     int		outlen;
4344     hashitem_T	*hi;
4345     keyentry_T	*kp;
4346     int		todo;
4347     int		prev_contained = 0;
4348     short	*prev_next_list = NULL;
4349     short	*prev_cont_in_list = NULL;
4350     int		prev_skipnl = 0;
4351     int		prev_skipwhite = 0;
4352     int		prev_skipempty = 0;
4353 
4354     /*
4355      * Unfortunately, this list of keywords is not sorted on alphabet but on
4356      * hash value...
4357      */
4358     todo = (int)ht->ht_used;
4359     for (hi = ht->ht_array; todo > 0 && !got_int; ++hi)
4360     {
4361 	if (!HASHITEM_EMPTY(hi))
4362 	{
4363 	    --todo;
4364 	    for (kp = HI2KE(hi); kp != NULL && !got_int; kp = kp->ke_next)
4365 	    {
4366 		if (kp->k_syn.id == id)
4367 		{
4368 		    if (prev_contained != (kp->flags & HL_CONTAINED)
4369 			    || prev_skipnl != (kp->flags & HL_SKIPNL)
4370 			    || prev_skipwhite != (kp->flags & HL_SKIPWHITE)
4371 			    || prev_skipempty != (kp->flags & HL_SKIPEMPTY)
4372 			    || prev_cont_in_list != kp->k_syn.cont_in_list
4373 			    || prev_next_list != kp->next_list)
4374 			outlen = 9999;
4375 		    else
4376 			outlen = (int)STRLEN(kp->keyword);
4377 		    /* output "contained" and "nextgroup" on each line */
4378 		    if (syn_list_header(did_header, outlen, id))
4379 		    {
4380 			prev_contained = 0;
4381 			prev_next_list = NULL;
4382 			prev_cont_in_list = NULL;
4383 			prev_skipnl = 0;
4384 			prev_skipwhite = 0;
4385 			prev_skipempty = 0;
4386 		    }
4387 		    did_header = TRUE;
4388 		    if (prev_contained != (kp->flags & HL_CONTAINED))
4389 		    {
4390 			msg_puts_attr((char_u *)"contained", attr);
4391 			msg_putchar(' ');
4392 			prev_contained = (kp->flags & HL_CONTAINED);
4393 		    }
4394 		    if (kp->k_syn.cont_in_list != prev_cont_in_list)
4395 		    {
4396 			put_id_list((char_u *)"containedin",
4397 						kp->k_syn.cont_in_list, attr);
4398 			msg_putchar(' ');
4399 			prev_cont_in_list = kp->k_syn.cont_in_list;
4400 		    }
4401 		    if (kp->next_list != prev_next_list)
4402 		    {
4403 			put_id_list((char_u *)"nextgroup", kp->next_list, attr);
4404 			msg_putchar(' ');
4405 			prev_next_list = kp->next_list;
4406 			if (kp->flags & HL_SKIPNL)
4407 			{
4408 			    msg_puts_attr((char_u *)"skipnl", attr);
4409 			    msg_putchar(' ');
4410 			    prev_skipnl = (kp->flags & HL_SKIPNL);
4411 			}
4412 			if (kp->flags & HL_SKIPWHITE)
4413 			{
4414 			    msg_puts_attr((char_u *)"skipwhite", attr);
4415 			    msg_putchar(' ');
4416 			    prev_skipwhite = (kp->flags & HL_SKIPWHITE);
4417 			}
4418 			if (kp->flags & HL_SKIPEMPTY)
4419 			{
4420 			    msg_puts_attr((char_u *)"skipempty", attr);
4421 			    msg_putchar(' ');
4422 			    prev_skipempty = (kp->flags & HL_SKIPEMPTY);
4423 			}
4424 		    }
4425 		    msg_outtrans(kp->keyword);
4426 		}
4427 	    }
4428 	}
4429     }
4430 
4431     return did_header;
4432 }
4433 
4434     static void
4435 syn_clear_keyword(int id, hashtab_T *ht)
4436 {
4437     hashitem_T	*hi;
4438     keyentry_T	*kp;
4439     keyentry_T	*kp_prev;
4440     keyentry_T	*kp_next;
4441     int		todo;
4442 
4443     hash_lock(ht);
4444     todo = (int)ht->ht_used;
4445     for (hi = ht->ht_array; todo > 0; ++hi)
4446     {
4447 	if (!HASHITEM_EMPTY(hi))
4448 	{
4449 	    --todo;
4450 	    kp_prev = NULL;
4451 	    for (kp = HI2KE(hi); kp != NULL; )
4452 	    {
4453 		if (kp->k_syn.id == id)
4454 		{
4455 		    kp_next = kp->ke_next;
4456 		    if (kp_prev == NULL)
4457 		    {
4458 			if (kp_next == NULL)
4459 			    hash_remove(ht, hi);
4460 			else
4461 			    hi->hi_key = KE2HIKEY(kp_next);
4462 		    }
4463 		    else
4464 			kp_prev->ke_next = kp_next;
4465 		    vim_free(kp->next_list);
4466 		    vim_free(kp->k_syn.cont_in_list);
4467 		    vim_free(kp);
4468 		    kp = kp_next;
4469 		}
4470 		else
4471 		{
4472 		    kp_prev = kp;
4473 		    kp = kp->ke_next;
4474 		}
4475 	    }
4476 	}
4477     }
4478     hash_unlock(ht);
4479 }
4480 
4481 /*
4482  * Clear a whole keyword table.
4483  */
4484     static void
4485 clear_keywtab(hashtab_T *ht)
4486 {
4487     hashitem_T	*hi;
4488     int		todo;
4489     keyentry_T	*kp;
4490     keyentry_T	*kp_next;
4491 
4492     todo = (int)ht->ht_used;
4493     for (hi = ht->ht_array; todo > 0; ++hi)
4494     {
4495 	if (!HASHITEM_EMPTY(hi))
4496 	{
4497 	    --todo;
4498 	    for (kp = HI2KE(hi); kp != NULL; kp = kp_next)
4499 	    {
4500 		kp_next = kp->ke_next;
4501 		vim_free(kp->next_list);
4502 		vim_free(kp->k_syn.cont_in_list);
4503 		vim_free(kp);
4504 	    }
4505 	}
4506     }
4507     hash_clear(ht);
4508     hash_init(ht);
4509 }
4510 
4511 /*
4512  * Add a keyword to the list of keywords.
4513  */
4514     static void
4515 add_keyword(
4516     char_u	*name,	    /* name of keyword */
4517     int		id,	    /* group ID for this keyword */
4518     int		flags,	    /* flags for this keyword */
4519     short	*cont_in_list, /* containedin for this keyword */
4520     short	*next_list, /* nextgroup for this keyword */
4521     int		conceal_char)
4522 {
4523     keyentry_T	*kp;
4524     hashtab_T	*ht;
4525     hashitem_T	*hi;
4526     char_u	*name_ic;
4527     long_u	hash;
4528     char_u	name_folded[MAXKEYWLEN + 1];
4529 
4530     if (curwin->w_s->b_syn_ic)
4531 	name_ic = str_foldcase(name, (int)STRLEN(name),
4532 						 name_folded, MAXKEYWLEN + 1);
4533     else
4534 	name_ic = name;
4535     kp = (keyentry_T *)alloc((int)(sizeof(keyentry_T) + STRLEN(name_ic)));
4536     if (kp == NULL)
4537 	return;
4538     STRCPY(kp->keyword, name_ic);
4539     kp->k_syn.id = id;
4540     kp->k_syn.inc_tag = current_syn_inc_tag;
4541     kp->flags = flags;
4542     kp->k_char = conceal_char;
4543     kp->k_syn.cont_in_list = copy_id_list(cont_in_list);
4544     if (cont_in_list != NULL)
4545 	curwin->w_s->b_syn_containedin = TRUE;
4546     kp->next_list = copy_id_list(next_list);
4547 
4548     if (curwin->w_s->b_syn_ic)
4549 	ht = &curwin->w_s->b_keywtab_ic;
4550     else
4551 	ht = &curwin->w_s->b_keywtab;
4552 
4553     hash = hash_hash(kp->keyword);
4554     hi = hash_lookup(ht, kp->keyword, hash);
4555     if (HASHITEM_EMPTY(hi))
4556     {
4557 	/* new keyword, add to hashtable */
4558 	kp->ke_next = NULL;
4559 	hash_add_item(ht, hi, kp->keyword, hash);
4560     }
4561     else
4562     {
4563 	/* keyword already exists, prepend to list */
4564 	kp->ke_next = HI2KE(hi);
4565 	hi->hi_key = KE2HIKEY(kp);
4566     }
4567 }
4568 
4569 /*
4570  * Get the start and end of the group name argument.
4571  * Return a pointer to the first argument.
4572  * Return NULL if the end of the command was found instead of further args.
4573  */
4574     static char_u *
4575 get_group_name(
4576     char_u	*arg,		/* start of the argument */
4577     char_u	**name_end)	/* pointer to end of the name */
4578 {
4579     char_u	*rest;
4580 
4581     *name_end = skiptowhite(arg);
4582     rest = skipwhite(*name_end);
4583 
4584     /*
4585      * Check if there are enough arguments.  The first argument may be a
4586      * pattern, where '|' is allowed, so only check for NUL.
4587      */
4588     if (ends_excmd(*arg) || *rest == NUL)
4589 	return NULL;
4590     return rest;
4591 }
4592 
4593 /*
4594  * Check for syntax command option arguments.
4595  * This can be called at any place in the list of arguments, and just picks
4596  * out the arguments that are known.  Can be called several times in a row to
4597  * collect all options in between other arguments.
4598  * Return a pointer to the next argument (which isn't an option).
4599  * Return NULL for any error;
4600  */
4601     static char_u *
4602 get_syn_options(
4603     char_u	    *arg,		/* next argument to be checked */
4604     syn_opt_arg_T   *opt,		/* various things */
4605     int		    *conceal_char UNUSED,
4606     int		    skip)		/* TRUE if skipping over command */
4607 {
4608     char_u	*gname_start, *gname;
4609     int		syn_id;
4610     int		len;
4611     char	*p;
4612     int		i;
4613     int		fidx;
4614     static struct flag
4615     {
4616 	char	*name;
4617 	int	argtype;
4618 	int	flags;
4619     } flagtab[] = { {"cCoOnNtTaAiInNeEdD",	0,	HL_CONTAINED},
4620 		    {"oOnNeElLiInNeE",		0,	HL_ONELINE},
4621 		    {"kKeEeEpPeEnNdD",		0,	HL_KEEPEND},
4622 		    {"eExXtTeEnNdD",		0,	HL_EXTEND},
4623 		    {"eExXcClLuUdDeEnNlL",	0,	HL_EXCLUDENL},
4624 		    {"tTrRaAnNsSpPaArReEnNtT",	0,	HL_TRANSP},
4625 		    {"sSkKiIpPnNlL",		0,	HL_SKIPNL},
4626 		    {"sSkKiIpPwWhHiItTeE",	0,	HL_SKIPWHITE},
4627 		    {"sSkKiIpPeEmMpPtTyY",	0,	HL_SKIPEMPTY},
4628 		    {"gGrRoOuUpPhHeErReE",	0,	HL_SYNC_HERE},
4629 		    {"gGrRoOuUpPtThHeErReE",	0,	HL_SYNC_THERE},
4630 		    {"dDiIsSpPlLaAyY",		0,	HL_DISPLAY},
4631 		    {"fFoOlLdD",		0,	HL_FOLD},
4632 		    {"cCoOnNcCeEaAlL",		0,	HL_CONCEAL},
4633 		    {"cCoOnNcCeEaAlLeEnNdDsS",	0,	HL_CONCEALENDS},
4634 		    {"cCcChHaArR",		11,	0},
4635 		    {"cCoOnNtTaAiInNsS",	1,	0},
4636 		    {"cCoOnNtTaAiInNeEdDiInN",	2,	0},
4637 		    {"nNeExXtTgGrRoOuUpP",	3,	0},
4638 		};
4639     static char *first_letters = "cCoOkKeEtTsSgGdDfFnN";
4640 
4641     if (arg == NULL)		/* already detected error */
4642 	return NULL;
4643 
4644 #ifdef FEAT_CONCEAL
4645     if (curwin->w_s->b_syn_conceal)
4646 	opt->flags |= HL_CONCEAL;
4647 #endif
4648 
4649     for (;;)
4650     {
4651 	/*
4652 	 * This is used very often when a large number of keywords is defined.
4653 	 * Need to skip quickly when no option name is found.
4654 	 * Also avoid tolower(), it's slow.
4655 	 */
4656 	if (strchr(first_letters, *arg) == NULL)
4657 	    break;
4658 
4659 	for (fidx = sizeof(flagtab) / sizeof(struct flag); --fidx >= 0; )
4660 	{
4661 	    p = flagtab[fidx].name;
4662 	    for (i = 0, len = 0; p[i] != NUL; i += 2, ++len)
4663 		if (arg[len] != p[i] && arg[len] != p[i + 1])
4664 		    break;
4665 	    if (p[i] == NUL && (VIM_ISWHITE(arg[len])
4666 				    || (flagtab[fidx].argtype > 0
4667 					 ? arg[len] == '='
4668 					 : ends_excmd(arg[len]))))
4669 	    {
4670 		if (opt->keyword
4671 			&& (flagtab[fidx].flags == HL_DISPLAY
4672 			    || flagtab[fidx].flags == HL_FOLD
4673 			    || flagtab[fidx].flags == HL_EXTEND))
4674 		    /* treat "display", "fold" and "extend" as a keyword */
4675 		    fidx = -1;
4676 		break;
4677 	    }
4678 	}
4679 	if (fidx < 0)	    /* no match found */
4680 	    break;
4681 
4682 	if (flagtab[fidx].argtype == 1)
4683 	{
4684 	    if (!opt->has_cont_list)
4685 	    {
4686 		EMSG(_("E395: contains argument not accepted here"));
4687 		return NULL;
4688 	    }
4689 	    if (get_id_list(&arg, 8, &opt->cont_list, skip) == FAIL)
4690 		return NULL;
4691 	}
4692 	else if (flagtab[fidx].argtype == 2)
4693 	{
4694 	    if (get_id_list(&arg, 11, &opt->cont_in_list, skip) == FAIL)
4695 		return NULL;
4696 	}
4697 	else if (flagtab[fidx].argtype == 3)
4698 	{
4699 	    if (get_id_list(&arg, 9, &opt->next_list, skip) == FAIL)
4700 		return NULL;
4701 	}
4702 	else if (flagtab[fidx].argtype == 11 && arg[5] == '=')
4703 	{
4704 #ifdef FEAT_MBYTE
4705 	    /* cchar=? */
4706 	    if (has_mbyte)
4707 	    {
4708 # ifdef FEAT_CONCEAL
4709 		*conceal_char = mb_ptr2char(arg + 6);
4710 # endif
4711 		arg += mb_ptr2len(arg + 6) - 1;
4712 	    }
4713 	    else
4714 #endif
4715 	    {
4716 #ifdef FEAT_CONCEAL
4717 		*conceal_char = arg[6];
4718 #else
4719 		;
4720 #endif
4721 	    }
4722 #ifdef FEAT_CONCEAL
4723 	    if (!vim_isprintc_strict(*conceal_char))
4724 	    {
4725 		EMSG(_("E844: invalid cchar value"));
4726 		return NULL;
4727 	    }
4728 #endif
4729 	    arg = skipwhite(arg + 7);
4730 	}
4731 	else
4732 	{
4733 	    opt->flags |= flagtab[fidx].flags;
4734 	    arg = skipwhite(arg + len);
4735 
4736 	    if (flagtab[fidx].flags == HL_SYNC_HERE
4737 		    || flagtab[fidx].flags == HL_SYNC_THERE)
4738 	    {
4739 		if (opt->sync_idx == NULL)
4740 		{
4741 		    EMSG(_("E393: group[t]here not accepted here"));
4742 		    return NULL;
4743 		}
4744 		gname_start = arg;
4745 		arg = skiptowhite(arg);
4746 		if (gname_start == arg)
4747 		    return NULL;
4748 		gname = vim_strnsave(gname_start, (int)(arg - gname_start));
4749 		if (gname == NULL)
4750 		    return NULL;
4751 		if (STRCMP(gname, "NONE") == 0)
4752 		    *opt->sync_idx = NONE_IDX;
4753 		else
4754 		{
4755 		    syn_id = syn_name2id(gname);
4756 		    for (i = curwin->w_s->b_syn_patterns.ga_len; --i >= 0; )
4757 			if (SYN_ITEMS(curwin->w_s)[i].sp_syn.id == syn_id
4758 			      && SYN_ITEMS(curwin->w_s)[i].sp_type == SPTYPE_START)
4759 			{
4760 			    *opt->sync_idx = i;
4761 			    break;
4762 			}
4763 		    if (i < 0)
4764 		    {
4765 			EMSG2(_("E394: Didn't find region item for %s"), gname);
4766 			vim_free(gname);
4767 			return NULL;
4768 		    }
4769 		}
4770 
4771 		vim_free(gname);
4772 		arg = skipwhite(arg);
4773 	    }
4774 #ifdef FEAT_FOLDING
4775 	    else if (flagtab[fidx].flags == HL_FOLD
4776 						&& foldmethodIsSyntax(curwin))
4777 		/* Need to update folds later. */
4778 		foldUpdateAll(curwin);
4779 #endif
4780 	}
4781     }
4782 
4783     return arg;
4784 }
4785 
4786 /*
4787  * Adjustments to syntax item when declared in a ":syn include"'d file.
4788  * Set the contained flag, and if the item is not already contained, add it
4789  * to the specified top-level group, if any.
4790  */
4791     static void
4792 syn_incl_toplevel(int id, int *flagsp)
4793 {
4794     if ((*flagsp & HL_CONTAINED) || curwin->w_s->b_syn_topgrp == 0)
4795 	return;
4796     *flagsp |= HL_CONTAINED;
4797     if (curwin->w_s->b_syn_topgrp >= SYNID_CLUSTER)
4798     {
4799 	/* We have to alloc this, because syn_combine_list() will free it. */
4800 	short	    *grp_list = (short *)alloc((unsigned)(2 * sizeof(short)));
4801 	int	    tlg_id = curwin->w_s->b_syn_topgrp - SYNID_CLUSTER;
4802 
4803 	if (grp_list != NULL)
4804 	{
4805 	    grp_list[0] = id;
4806 	    grp_list[1] = 0;
4807 	    syn_combine_list(&SYN_CLSTR(curwin->w_s)[tlg_id].scl_list, &grp_list,
4808 			 CLUSTER_ADD);
4809 	}
4810     }
4811 }
4812 
4813 /*
4814  * Handle ":syntax include [@{group-name}] filename" command.
4815  */
4816     static void
4817 syn_cmd_include(exarg_T *eap, int syncing UNUSED)
4818 {
4819     char_u	*arg = eap->arg;
4820     int		sgl_id = 1;
4821     char_u	*group_name_end;
4822     char_u	*rest;
4823     char_u	*errormsg = NULL;
4824     int		prev_toplvl_grp;
4825     int		prev_syn_inc_tag;
4826     int		source = FALSE;
4827 
4828     eap->nextcmd = find_nextcmd(arg);
4829     if (eap->skip)
4830 	return;
4831 
4832     if (arg[0] == '@')
4833     {
4834 	++arg;
4835 	rest = get_group_name(arg, &group_name_end);
4836 	if (rest == NULL)
4837 	{
4838 	    EMSG((char_u *)_("E397: Filename required"));
4839 	    return;
4840 	}
4841 	sgl_id = syn_check_cluster(arg, (int)(group_name_end - arg));
4842 	if (sgl_id == 0)
4843 	    return;
4844 	/* separate_nextcmd() and expand_filename() depend on this */
4845 	eap->arg = rest;
4846     }
4847 
4848     /*
4849      * Everything that's left, up to the next command, should be the
4850      * filename to include.
4851      */
4852     eap->argt |= (XFILE | NOSPC);
4853     separate_nextcmd(eap);
4854     if (*eap->arg == '<' || *eap->arg == '$' || mch_isFullName(eap->arg))
4855     {
4856 	/* For an absolute path, "$VIM/..." or "<sfile>.." we ":source" the
4857 	 * file.  Need to expand the file name first.  In other cases
4858 	 * ":runtime!" is used. */
4859 	source = TRUE;
4860 	if (expand_filename(eap, syn_cmdlinep, &errormsg) == FAIL)
4861 	{
4862 	    if (errormsg != NULL)
4863 		EMSG(errormsg);
4864 	    return;
4865 	}
4866     }
4867 
4868     /*
4869      * Save and restore the existing top-level grouplist id and ":syn
4870      * include" tag around the actual inclusion.
4871      */
4872     if (running_syn_inc_tag >= MAX_SYN_INC_TAG)
4873     {
4874 	EMSG((char_u *)_("E847: Too many syntax includes"));
4875 	return;
4876     }
4877     prev_syn_inc_tag = current_syn_inc_tag;
4878     current_syn_inc_tag = ++running_syn_inc_tag;
4879     prev_toplvl_grp = curwin->w_s->b_syn_topgrp;
4880     curwin->w_s->b_syn_topgrp = sgl_id;
4881     if (source ? do_source(eap->arg, FALSE, DOSO_NONE) == FAIL
4882 				: source_runtime(eap->arg, DIP_ALL) == FAIL)
4883 	EMSG2(_(e_notopen), eap->arg);
4884     curwin->w_s->b_syn_topgrp = prev_toplvl_grp;
4885     current_syn_inc_tag = prev_syn_inc_tag;
4886 }
4887 
4888 /*
4889  * Handle ":syntax keyword {group-name} [{option}] keyword .." command.
4890  */
4891     static void
4892 syn_cmd_keyword(exarg_T *eap, int syncing UNUSED)
4893 {
4894     char_u	*arg = eap->arg;
4895     char_u	*group_name_end;
4896     int		syn_id;
4897     char_u	*rest;
4898     char_u	*keyword_copy = NULL;
4899     char_u	*p;
4900     char_u	*kw;
4901     syn_opt_arg_T syn_opt_arg;
4902     int		cnt;
4903     int		conceal_char = NUL;
4904 
4905     rest = get_group_name(arg, &group_name_end);
4906 
4907     if (rest != NULL)
4908     {
4909 	if (eap->skip)
4910 	    syn_id = -1;
4911 	else
4912 	    syn_id = syn_check_group(arg, (int)(group_name_end - arg));
4913 	if (syn_id != 0)
4914 	    /* allocate a buffer, for removing backslashes in the keyword */
4915 	    keyword_copy = alloc((unsigned)STRLEN(rest) + 1);
4916 	if (keyword_copy != NULL)
4917 	{
4918 	    syn_opt_arg.flags = 0;
4919 	    syn_opt_arg.keyword = TRUE;
4920 	    syn_opt_arg.sync_idx = NULL;
4921 	    syn_opt_arg.has_cont_list = FALSE;
4922 	    syn_opt_arg.cont_in_list = NULL;
4923 	    syn_opt_arg.next_list = NULL;
4924 
4925 	    /*
4926 	     * The options given apply to ALL keywords, so all options must be
4927 	     * found before keywords can be created.
4928 	     * 1: collect the options and copy the keywords to keyword_copy.
4929 	     */
4930 	    cnt = 0;
4931 	    p = keyword_copy;
4932 	    for ( ; rest != NULL && !ends_excmd(*rest); rest = skipwhite(rest))
4933 	    {
4934 		rest = get_syn_options(rest, &syn_opt_arg, &conceal_char,
4935 								    eap->skip);
4936 		if (rest == NULL || ends_excmd(*rest))
4937 		    break;
4938 		/* Copy the keyword, removing backslashes, and add a NUL. */
4939 		while (*rest != NUL && !VIM_ISWHITE(*rest))
4940 		{
4941 		    if (*rest == '\\' && rest[1] != NUL)
4942 			++rest;
4943 		    *p++ = *rest++;
4944 		}
4945 		*p++ = NUL;
4946 		++cnt;
4947 	    }
4948 
4949 	    if (!eap->skip)
4950 	    {
4951 		/* Adjust flags for use of ":syn include". */
4952 		syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
4953 
4954 		/*
4955 		 * 2: Add an entry for each keyword.
4956 		 */
4957 		for (kw = keyword_copy; --cnt >= 0; kw += STRLEN(kw) + 1)
4958 		{
4959 		    for (p = vim_strchr(kw, '['); ; )
4960 		    {
4961 			if (p != NULL)
4962 			    *p = NUL;
4963 			add_keyword(kw, syn_id, syn_opt_arg.flags,
4964 				syn_opt_arg.cont_in_list,
4965 					 syn_opt_arg.next_list, conceal_char);
4966 			if (p == NULL)
4967 			    break;
4968 			if (p[1] == NUL)
4969 			{
4970 			    EMSG2(_("E789: Missing ']': %s"), kw);
4971 			    goto error;
4972 			}
4973 			if (p[1] == ']')
4974 			{
4975 			    if (p[2] != NUL)
4976 			    {
4977 				EMSG3(_("E890: trailing char after ']': %s]%s"),
4978 								kw, &p[2]);
4979 				goto error;
4980 			    }
4981 			    kw = p + 1;		/* skip over the "]" */
4982 			    break;
4983 			}
4984 #ifdef FEAT_MBYTE
4985 			if (has_mbyte)
4986 			{
4987 			    int l = (*mb_ptr2len)(p + 1);
4988 
4989 			    mch_memmove(p, p + 1, l);
4990 			    p += l;
4991 			}
4992 			else
4993 #endif
4994 			{
4995 			    p[0] = p[1];
4996 			    ++p;
4997 			}
4998 		    }
4999 		}
5000 	    }
5001 error:
5002 	    vim_free(keyword_copy);
5003 	    vim_free(syn_opt_arg.cont_in_list);
5004 	    vim_free(syn_opt_arg.next_list);
5005 	}
5006     }
5007 
5008     if (rest != NULL)
5009 	eap->nextcmd = check_nextcmd(rest);
5010     else
5011 	EMSG2(_(e_invarg2), arg);
5012 
5013     redraw_curbuf_later(SOME_VALID);
5014     syn_stack_free_all(curwin->w_s);		/* Need to recompute all syntax. */
5015 }
5016 
5017 /*
5018  * Handle ":syntax match {name} [{options}] {pattern} [{options}]".
5019  *
5020  * Also ":syntax sync match {name} [[grouphere | groupthere] {group-name}] .."
5021  */
5022     static void
5023 syn_cmd_match(
5024     exarg_T	*eap,
5025     int		syncing)	    /* TRUE for ":syntax sync match .. " */
5026 {
5027     char_u	*arg = eap->arg;
5028     char_u	*group_name_end;
5029     char_u	*rest;
5030     synpat_T	item;		/* the item found in the line */
5031     int		syn_id;
5032     int		idx;
5033     syn_opt_arg_T syn_opt_arg;
5034     int		sync_idx = 0;
5035     int		conceal_char = NUL;
5036 
5037     /* Isolate the group name, check for validity */
5038     rest = get_group_name(arg, &group_name_end);
5039 
5040     /* Get options before the pattern */
5041     syn_opt_arg.flags = 0;
5042     syn_opt_arg.keyword = FALSE;
5043     syn_opt_arg.sync_idx = syncing ? &sync_idx : NULL;
5044     syn_opt_arg.has_cont_list = TRUE;
5045     syn_opt_arg.cont_list = NULL;
5046     syn_opt_arg.cont_in_list = NULL;
5047     syn_opt_arg.next_list = NULL;
5048     rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
5049 
5050     /* get the pattern. */
5051     init_syn_patterns();
5052     vim_memset(&item, 0, sizeof(item));
5053     rest = get_syn_pattern(rest, &item);
5054     if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL))
5055 	syn_opt_arg.flags |= HL_HAS_EOL;
5056 
5057     /* Get options after the pattern */
5058     rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
5059 
5060     if (rest != NULL)		/* all arguments are valid */
5061     {
5062 	/*
5063 	 * Check for trailing command and illegal trailing arguments.
5064 	 */
5065 	eap->nextcmd = check_nextcmd(rest);
5066 	if (!ends_excmd(*rest) || eap->skip)
5067 	    rest = NULL;
5068 	else if (ga_grow(&curwin->w_s->b_syn_patterns, 1) != FAIL
5069 		&& (syn_id = syn_check_group(arg,
5070 					   (int)(group_name_end - arg))) != 0)
5071 	{
5072 	    syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
5073 	    /*
5074 	     * Store the pattern in the syn_items list
5075 	     */
5076 	    idx = curwin->w_s->b_syn_patterns.ga_len;
5077 	    SYN_ITEMS(curwin->w_s)[idx] = item;
5078 	    SYN_ITEMS(curwin->w_s)[idx].sp_syncing = syncing;
5079 	    SYN_ITEMS(curwin->w_s)[idx].sp_type = SPTYPE_MATCH;
5080 	    SYN_ITEMS(curwin->w_s)[idx].sp_syn.id = syn_id;
5081 	    SYN_ITEMS(curwin->w_s)[idx].sp_syn.inc_tag = current_syn_inc_tag;
5082 	    SYN_ITEMS(curwin->w_s)[idx].sp_flags = syn_opt_arg.flags;
5083 	    SYN_ITEMS(curwin->w_s)[idx].sp_sync_idx = sync_idx;
5084 	    SYN_ITEMS(curwin->w_s)[idx].sp_cont_list = syn_opt_arg.cont_list;
5085 	    SYN_ITEMS(curwin->w_s)[idx].sp_syn.cont_in_list =
5086 						     syn_opt_arg.cont_in_list;
5087 #ifdef FEAT_CONCEAL
5088 	    SYN_ITEMS(curwin->w_s)[idx].sp_cchar = conceal_char;
5089 #endif
5090 	    if (syn_opt_arg.cont_in_list != NULL)
5091 		curwin->w_s->b_syn_containedin = TRUE;
5092 	    SYN_ITEMS(curwin->w_s)[idx].sp_next_list = syn_opt_arg.next_list;
5093 	    ++curwin->w_s->b_syn_patterns.ga_len;
5094 
5095 	    /* remember that we found a match for syncing on */
5096 	    if (syn_opt_arg.flags & (HL_SYNC_HERE|HL_SYNC_THERE))
5097 		curwin->w_s->b_syn_sync_flags |= SF_MATCH;
5098 #ifdef FEAT_FOLDING
5099 	    if (syn_opt_arg.flags & HL_FOLD)
5100 		++curwin->w_s->b_syn_folditems;
5101 #endif
5102 
5103 	    redraw_curbuf_later(SOME_VALID);
5104 	    syn_stack_free_all(curwin->w_s);	/* Need to recompute all syntax. */
5105 	    return;	/* don't free the progs and patterns now */
5106 	}
5107     }
5108 
5109     /*
5110      * Something failed, free the allocated memory.
5111      */
5112     vim_regfree(item.sp_prog);
5113     vim_free(item.sp_pattern);
5114     vim_free(syn_opt_arg.cont_list);
5115     vim_free(syn_opt_arg.cont_in_list);
5116     vim_free(syn_opt_arg.next_list);
5117 
5118     if (rest == NULL)
5119 	EMSG2(_(e_invarg2), arg);
5120 }
5121 
5122 /*
5123  * Handle ":syntax region {group-name} [matchgroup={group-name}]
5124  *		start {start} .. [skip {skip}] end {end} .. [{options}]".
5125  */
5126     static void
5127 syn_cmd_region(
5128     exarg_T	*eap,
5129     int		syncing)	    /* TRUE for ":syntax sync region .." */
5130 {
5131     char_u		*arg = eap->arg;
5132     char_u		*group_name_end;
5133     char_u		*rest;			/* next arg, NULL on error */
5134     char_u		*key_end;
5135     char_u		*key = NULL;
5136     char_u		*p;
5137     int			item;
5138 #define ITEM_START	    0
5139 #define ITEM_SKIP	    1
5140 #define ITEM_END	    2
5141 #define ITEM_MATCHGROUP	    3
5142     struct pat_ptr
5143     {
5144 	synpat_T	*pp_synp;		/* pointer to syn_pattern */
5145 	int		pp_matchgroup_id;	/* matchgroup ID */
5146 	struct pat_ptr	*pp_next;		/* pointer to next pat_ptr */
5147     }			*(pat_ptrs[3]);
5148 					/* patterns found in the line */
5149     struct pat_ptr	*ppp;
5150     struct pat_ptr	*ppp_next;
5151     int			pat_count = 0;		/* nr of syn_patterns found */
5152     int			syn_id;
5153     int			matchgroup_id = 0;
5154     int			not_enough = FALSE;	/* not enough arguments */
5155     int			illegal = FALSE;	/* illegal arguments */
5156     int			success = FALSE;
5157     int			idx;
5158     syn_opt_arg_T	syn_opt_arg;
5159     int			conceal_char = NUL;
5160 
5161     /* Isolate the group name, check for validity */
5162     rest = get_group_name(arg, &group_name_end);
5163 
5164     pat_ptrs[0] = NULL;
5165     pat_ptrs[1] = NULL;
5166     pat_ptrs[2] = NULL;
5167 
5168     init_syn_patterns();
5169 
5170     syn_opt_arg.flags = 0;
5171     syn_opt_arg.keyword = FALSE;
5172     syn_opt_arg.sync_idx = NULL;
5173     syn_opt_arg.has_cont_list = TRUE;
5174     syn_opt_arg.cont_list = NULL;
5175     syn_opt_arg.cont_in_list = NULL;
5176     syn_opt_arg.next_list = NULL;
5177 
5178     /*
5179      * get the options, patterns and matchgroup.
5180      */
5181     while (rest != NULL && !ends_excmd(*rest))
5182     {
5183 	/* Check for option arguments */
5184 	rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
5185 	if (rest == NULL || ends_excmd(*rest))
5186 	    break;
5187 
5188 	/* must be a pattern or matchgroup then */
5189 	key_end = rest;
5190 	while (*key_end && !VIM_ISWHITE(*key_end) && *key_end != '=')
5191 	    ++key_end;
5192 	vim_free(key);
5193 	key = vim_strnsave_up(rest, (int)(key_end - rest));
5194 	if (key == NULL)			/* out of memory */
5195 	{
5196 	    rest = NULL;
5197 	    break;
5198 	}
5199 	if (STRCMP(key, "MATCHGROUP") == 0)
5200 	    item = ITEM_MATCHGROUP;
5201 	else if (STRCMP(key, "START") == 0)
5202 	    item = ITEM_START;
5203 	else if (STRCMP(key, "END") == 0)
5204 	    item = ITEM_END;
5205 	else if (STRCMP(key, "SKIP") == 0)
5206 	{
5207 	    if (pat_ptrs[ITEM_SKIP] != NULL)	/* one skip pattern allowed */
5208 	    {
5209 		illegal = TRUE;
5210 		break;
5211 	    }
5212 	    item = ITEM_SKIP;
5213 	}
5214 	else
5215 	    break;
5216 	rest = skipwhite(key_end);
5217 	if (*rest != '=')
5218 	{
5219 	    rest = NULL;
5220 	    EMSG2(_("E398: Missing '=': %s"), arg);
5221 	    break;
5222 	}
5223 	rest = skipwhite(rest + 1);
5224 	if (*rest == NUL)
5225 	{
5226 	    not_enough = TRUE;
5227 	    break;
5228 	}
5229 
5230 	if (item == ITEM_MATCHGROUP)
5231 	{
5232 	    p = skiptowhite(rest);
5233 	    if ((p - rest == 4 && STRNCMP(rest, "NONE", 4) == 0) || eap->skip)
5234 		matchgroup_id = 0;
5235 	    else
5236 	    {
5237 		matchgroup_id = syn_check_group(rest, (int)(p - rest));
5238 		if (matchgroup_id == 0)
5239 		{
5240 		    illegal = TRUE;
5241 		    break;
5242 		}
5243 	    }
5244 	    rest = skipwhite(p);
5245 	}
5246 	else
5247 	{
5248 	    /*
5249 	     * Allocate room for a syn_pattern, and link it in the list of
5250 	     * syn_patterns for this item, at the start (because the list is
5251 	     * used from end to start).
5252 	     */
5253 	    ppp = (struct pat_ptr *)alloc((unsigned)sizeof(struct pat_ptr));
5254 	    if (ppp == NULL)
5255 	    {
5256 		rest = NULL;
5257 		break;
5258 	    }
5259 	    ppp->pp_next = pat_ptrs[item];
5260 	    pat_ptrs[item] = ppp;
5261 	    ppp->pp_synp = (synpat_T *)alloc_clear((unsigned)sizeof(synpat_T));
5262 	    if (ppp->pp_synp == NULL)
5263 	    {
5264 		rest = NULL;
5265 		break;
5266 	    }
5267 
5268 	    /*
5269 	     * Get the syntax pattern and the following offset(s).
5270 	     */
5271 	    /* Enable the appropriate \z specials. */
5272 	    if (item == ITEM_START)
5273 		reg_do_extmatch = REX_SET;
5274 	    else if (item == ITEM_SKIP || item == ITEM_END)
5275 		reg_do_extmatch = REX_USE;
5276 	    rest = get_syn_pattern(rest, ppp->pp_synp);
5277 	    reg_do_extmatch = 0;
5278 	    if (item == ITEM_END && vim_regcomp_had_eol()
5279 				       && !(syn_opt_arg.flags & HL_EXCLUDENL))
5280 		ppp->pp_synp->sp_flags |= HL_HAS_EOL;
5281 	    ppp->pp_matchgroup_id = matchgroup_id;
5282 	    ++pat_count;
5283 	}
5284     }
5285     vim_free(key);
5286     if (illegal || not_enough)
5287 	rest = NULL;
5288 
5289     /*
5290      * Must have a "start" and "end" pattern.
5291      */
5292     if (rest != NULL && (pat_ptrs[ITEM_START] == NULL ||
5293 						  pat_ptrs[ITEM_END] == NULL))
5294     {
5295 	not_enough = TRUE;
5296 	rest = NULL;
5297     }
5298 
5299     if (rest != NULL)
5300     {
5301 	/*
5302 	 * Check for trailing garbage or command.
5303 	 * If OK, add the item.
5304 	 */
5305 	eap->nextcmd = check_nextcmd(rest);
5306 	if (!ends_excmd(*rest) || eap->skip)
5307 	    rest = NULL;
5308 	else if (ga_grow(&(curwin->w_s->b_syn_patterns), pat_count) != FAIL
5309 		&& (syn_id = syn_check_group(arg,
5310 					   (int)(group_name_end - arg))) != 0)
5311 	{
5312 	    syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
5313 	    /*
5314 	     * Store the start/skip/end in the syn_items list
5315 	     */
5316 	    idx = curwin->w_s->b_syn_patterns.ga_len;
5317 	    for (item = ITEM_START; item <= ITEM_END; ++item)
5318 	    {
5319 		for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp->pp_next)
5320 		{
5321 		    SYN_ITEMS(curwin->w_s)[idx] = *(ppp->pp_synp);
5322 		    SYN_ITEMS(curwin->w_s)[idx].sp_syncing = syncing;
5323 		    SYN_ITEMS(curwin->w_s)[idx].sp_type =
5324 			    (item == ITEM_START) ? SPTYPE_START :
5325 			    (item == ITEM_SKIP) ? SPTYPE_SKIP : SPTYPE_END;
5326 		    SYN_ITEMS(curwin->w_s)[idx].sp_flags |= syn_opt_arg.flags;
5327 		    SYN_ITEMS(curwin->w_s)[idx].sp_syn.id = syn_id;
5328 		    SYN_ITEMS(curwin->w_s)[idx].sp_syn.inc_tag =
5329 							  current_syn_inc_tag;
5330 		    SYN_ITEMS(curwin->w_s)[idx].sp_syn_match_id =
5331 							ppp->pp_matchgroup_id;
5332 #ifdef FEAT_CONCEAL
5333 		    SYN_ITEMS(curwin->w_s)[idx].sp_cchar = conceal_char;
5334 #endif
5335 		    if (item == ITEM_START)
5336 		    {
5337 			SYN_ITEMS(curwin->w_s)[idx].sp_cont_list =
5338 							syn_opt_arg.cont_list;
5339 			SYN_ITEMS(curwin->w_s)[idx].sp_syn.cont_in_list =
5340 						     syn_opt_arg.cont_in_list;
5341 			if (syn_opt_arg.cont_in_list != NULL)
5342 			    curwin->w_s->b_syn_containedin = TRUE;
5343 			SYN_ITEMS(curwin->w_s)[idx].sp_next_list =
5344 							syn_opt_arg.next_list;
5345 		    }
5346 		    ++curwin->w_s->b_syn_patterns.ga_len;
5347 		    ++idx;
5348 #ifdef FEAT_FOLDING
5349 		    if (syn_opt_arg.flags & HL_FOLD)
5350 			++curwin->w_s->b_syn_folditems;
5351 #endif
5352 		}
5353 	    }
5354 
5355 	    redraw_curbuf_later(SOME_VALID);
5356 	    syn_stack_free_all(curwin->w_s);	/* Need to recompute all syntax. */
5357 	    success = TRUE;	    /* don't free the progs and patterns now */
5358 	}
5359     }
5360 
5361     /*
5362      * Free the allocated memory.
5363      */
5364     for (item = ITEM_START; item <= ITEM_END; ++item)
5365 	for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp_next)
5366 	{
5367 	    if (!success)
5368 	    {
5369 		vim_regfree(ppp->pp_synp->sp_prog);
5370 		vim_free(ppp->pp_synp->sp_pattern);
5371 	    }
5372 	    vim_free(ppp->pp_synp);
5373 	    ppp_next = ppp->pp_next;
5374 	    vim_free(ppp);
5375 	}
5376 
5377     if (!success)
5378     {
5379 	vim_free(syn_opt_arg.cont_list);
5380 	vim_free(syn_opt_arg.cont_in_list);
5381 	vim_free(syn_opt_arg.next_list);
5382 	if (not_enough)
5383 	    EMSG2(_("E399: Not enough arguments: syntax region %s"), arg);
5384 	else if (illegal || rest == NULL)
5385 	    EMSG2(_(e_invarg2), arg);
5386     }
5387 }
5388 
5389 /*
5390  * A simple syntax group ID comparison function suitable for use in qsort()
5391  */
5392     static int
5393 #ifdef __BORLANDC__
5394 _RTLENTRYF
5395 #endif
5396 syn_compare_stub(const void *v1, const void *v2)
5397 {
5398     const short	*s1 = v1;
5399     const short	*s2 = v2;
5400 
5401     return (*s1 > *s2 ? 1 : *s1 < *s2 ? -1 : 0);
5402 }
5403 
5404 /*
5405  * Combines lists of syntax clusters.
5406  * *clstr1 and *clstr2 must both be allocated memory; they will be consumed.
5407  */
5408     static void
5409 syn_combine_list(short **clstr1, short **clstr2, int list_op)
5410 {
5411     int		count1 = 0;
5412     int		count2 = 0;
5413     short	*g1;
5414     short	*g2;
5415     short	*clstr = NULL;
5416     int		count;
5417     int		round;
5418 
5419     /*
5420      * Handle degenerate cases.
5421      */
5422     if (*clstr2 == NULL)
5423 	return;
5424     if (*clstr1 == NULL || list_op == CLUSTER_REPLACE)
5425     {
5426 	if (list_op == CLUSTER_REPLACE)
5427 	    vim_free(*clstr1);
5428 	if (list_op == CLUSTER_REPLACE || list_op == CLUSTER_ADD)
5429 	    *clstr1 = *clstr2;
5430 	else
5431 	    vim_free(*clstr2);
5432 	return;
5433     }
5434 
5435     for (g1 = *clstr1; *g1; g1++)
5436 	++count1;
5437     for (g2 = *clstr2; *g2; g2++)
5438 	++count2;
5439 
5440     /*
5441      * For speed purposes, sort both lists.
5442      */
5443     qsort(*clstr1, (size_t)count1, sizeof(short), syn_compare_stub);
5444     qsort(*clstr2, (size_t)count2, sizeof(short), syn_compare_stub);
5445 
5446     /*
5447      * We proceed in two passes; in round 1, we count the elements to place
5448      * in the new list, and in round 2, we allocate and populate the new
5449      * list.  For speed, we use a mergesort-like method, adding the smaller
5450      * of the current elements in each list to the new list.
5451      */
5452     for (round = 1; round <= 2; round++)
5453     {
5454 	g1 = *clstr1;
5455 	g2 = *clstr2;
5456 	count = 0;
5457 
5458 	/*
5459 	 * First, loop through the lists until one of them is empty.
5460 	 */
5461 	while (*g1 && *g2)
5462 	{
5463 	    /*
5464 	     * We always want to add from the first list.
5465 	     */
5466 	    if (*g1 < *g2)
5467 	    {
5468 		if (round == 2)
5469 		    clstr[count] = *g1;
5470 		count++;
5471 		g1++;
5472 		continue;
5473 	    }
5474 	    /*
5475 	     * We only want to add from the second list if we're adding the
5476 	     * lists.
5477 	     */
5478 	    if (list_op == CLUSTER_ADD)
5479 	    {
5480 		if (round == 2)
5481 		    clstr[count] = *g2;
5482 		count++;
5483 	    }
5484 	    if (*g1 == *g2)
5485 		g1++;
5486 	    g2++;
5487 	}
5488 
5489 	/*
5490 	 * Now add the leftovers from whichever list didn't get finished
5491 	 * first.  As before, we only want to add from the second list if
5492 	 * we're adding the lists.
5493 	 */
5494 	for (; *g1; g1++, count++)
5495 	    if (round == 2)
5496 		clstr[count] = *g1;
5497 	if (list_op == CLUSTER_ADD)
5498 	    for (; *g2; g2++, count++)
5499 		if (round == 2)
5500 		    clstr[count] = *g2;
5501 
5502 	if (round == 1)
5503 	{
5504 	    /*
5505 	     * If the group ended up empty, we don't need to allocate any
5506 	     * space for it.
5507 	     */
5508 	    if (count == 0)
5509 	    {
5510 		clstr = NULL;
5511 		break;
5512 	    }
5513 	    clstr = (short *)alloc((unsigned)((count + 1) * sizeof(short)));
5514 	    if (clstr == NULL)
5515 		break;
5516 	    clstr[count] = 0;
5517 	}
5518     }
5519 
5520     /*
5521      * Finally, put the new list in place.
5522      */
5523     vim_free(*clstr1);
5524     vim_free(*clstr2);
5525     *clstr1 = clstr;
5526 }
5527 
5528 /*
5529  * Lookup a syntax cluster name and return its ID.
5530  * If it is not found, 0 is returned.
5531  */
5532     static int
5533 syn_scl_name2id(char_u *name)
5534 {
5535     int		i;
5536     char_u	*name_u;
5537 
5538     /* Avoid using stricmp() too much, it's slow on some systems */
5539     name_u = vim_strsave_up(name);
5540     if (name_u == NULL)
5541 	return 0;
5542     for (i = curwin->w_s->b_syn_clusters.ga_len; --i >= 0; )
5543 	if (SYN_CLSTR(curwin->w_s)[i].scl_name_u != NULL
5544 		&& STRCMP(name_u, SYN_CLSTR(curwin->w_s)[i].scl_name_u) == 0)
5545 	    break;
5546     vim_free(name_u);
5547     return (i < 0 ? 0 : i + SYNID_CLUSTER);
5548 }
5549 
5550 /*
5551  * Like syn_scl_name2id(), but take a pointer + length argument.
5552  */
5553     static int
5554 syn_scl_namen2id(char_u *linep, int len)
5555 {
5556     char_u  *name;
5557     int	    id = 0;
5558 
5559     name = vim_strnsave(linep, len);
5560     if (name != NULL)
5561     {
5562 	id = syn_scl_name2id(name);
5563 	vim_free(name);
5564     }
5565     return id;
5566 }
5567 
5568 /*
5569  * Find syntax cluster name in the table and return its ID.
5570  * The argument is a pointer to the name and the length of the name.
5571  * If it doesn't exist yet, a new entry is created.
5572  * Return 0 for failure.
5573  */
5574     static int
5575 syn_check_cluster(char_u *pp, int len)
5576 {
5577     int		id;
5578     char_u	*name;
5579 
5580     name = vim_strnsave(pp, len);
5581     if (name == NULL)
5582 	return 0;
5583 
5584     id = syn_scl_name2id(name);
5585     if (id == 0)			/* doesn't exist yet */
5586 	id = syn_add_cluster(name);
5587     else
5588 	vim_free(name);
5589     return id;
5590 }
5591 
5592 /*
5593  * Add new syntax cluster and return its ID.
5594  * "name" must be an allocated string, it will be consumed.
5595  * Return 0 for failure.
5596  */
5597     static int
5598 syn_add_cluster(char_u *name)
5599 {
5600     int		len;
5601 
5602     /*
5603      * First call for this growarray: init growing array.
5604      */
5605     if (curwin->w_s->b_syn_clusters.ga_data == NULL)
5606     {
5607 	curwin->w_s->b_syn_clusters.ga_itemsize = sizeof(syn_cluster_T);
5608 	curwin->w_s->b_syn_clusters.ga_growsize = 10;
5609     }
5610 
5611     len = curwin->w_s->b_syn_clusters.ga_len;
5612     if (len >= MAX_CLUSTER_ID)
5613     {
5614 	EMSG((char_u *)_("E848: Too many syntax clusters"));
5615 	vim_free(name);
5616 	return 0;
5617     }
5618 
5619     /*
5620      * Make room for at least one other cluster entry.
5621      */
5622     if (ga_grow(&curwin->w_s->b_syn_clusters, 1) == FAIL)
5623     {
5624 	vim_free(name);
5625 	return 0;
5626     }
5627 
5628     vim_memset(&(SYN_CLSTR(curwin->w_s)[len]), 0, sizeof(syn_cluster_T));
5629     SYN_CLSTR(curwin->w_s)[len].scl_name = name;
5630     SYN_CLSTR(curwin->w_s)[len].scl_name_u = vim_strsave_up(name);
5631     SYN_CLSTR(curwin->w_s)[len].scl_list = NULL;
5632     ++curwin->w_s->b_syn_clusters.ga_len;
5633 
5634     if (STRICMP(name, "Spell") == 0)
5635 	curwin->w_s->b_spell_cluster_id = len + SYNID_CLUSTER;
5636     if (STRICMP(name, "NoSpell") == 0)
5637 	curwin->w_s->b_nospell_cluster_id = len + SYNID_CLUSTER;
5638 
5639     return len + SYNID_CLUSTER;
5640 }
5641 
5642 /*
5643  * Handle ":syntax cluster {cluster-name} [contains={groupname},..]
5644  *		[add={groupname},..] [remove={groupname},..]".
5645  */
5646     static void
5647 syn_cmd_cluster(exarg_T *eap, int syncing UNUSED)
5648 {
5649     char_u	*arg = eap->arg;
5650     char_u	*group_name_end;
5651     char_u	*rest;
5652     int		scl_id;
5653     short	*clstr_list;
5654     int		got_clstr = FALSE;
5655     int		opt_len;
5656     int		list_op;
5657 
5658     eap->nextcmd = find_nextcmd(arg);
5659     if (eap->skip)
5660 	return;
5661 
5662     rest = get_group_name(arg, &group_name_end);
5663 
5664     if (rest != NULL)
5665     {
5666 	scl_id = syn_check_cluster(arg, (int)(group_name_end - arg));
5667 	if (scl_id == 0)
5668 	    return;
5669 	scl_id -= SYNID_CLUSTER;
5670 
5671 	for (;;)
5672 	{
5673 	    if (STRNICMP(rest, "add", 3) == 0
5674 		    && (VIM_ISWHITE(rest[3]) || rest[3] == '='))
5675 	    {
5676 		opt_len = 3;
5677 		list_op = CLUSTER_ADD;
5678 	    }
5679 	    else if (STRNICMP(rest, "remove", 6) == 0
5680 		    && (VIM_ISWHITE(rest[6]) || rest[6] == '='))
5681 	    {
5682 		opt_len = 6;
5683 		list_op = CLUSTER_SUBTRACT;
5684 	    }
5685 	    else if (STRNICMP(rest, "contains", 8) == 0
5686 			&& (VIM_ISWHITE(rest[8]) || rest[8] == '='))
5687 	    {
5688 		opt_len = 8;
5689 		list_op = CLUSTER_REPLACE;
5690 	    }
5691 	    else
5692 		break;
5693 
5694 	    clstr_list = NULL;
5695 	    if (get_id_list(&rest, opt_len, &clstr_list, eap->skip) == FAIL)
5696 	    {
5697 		EMSG2(_(e_invarg2), rest);
5698 		break;
5699 	    }
5700 	    if (scl_id >= 0)
5701 		syn_combine_list(&SYN_CLSTR(curwin->w_s)[scl_id].scl_list,
5702 			     &clstr_list, list_op);
5703 	    else
5704 		vim_free(clstr_list);
5705 	    got_clstr = TRUE;
5706 	}
5707 
5708 	if (got_clstr)
5709 	{
5710 	    redraw_curbuf_later(SOME_VALID);
5711 	    syn_stack_free_all(curwin->w_s);	/* Need to recompute all. */
5712 	}
5713     }
5714 
5715     if (!got_clstr)
5716 	EMSG(_("E400: No cluster specified"));
5717     if (rest == NULL || !ends_excmd(*rest))
5718 	EMSG2(_(e_invarg2), arg);
5719 }
5720 
5721 /*
5722  * On first call for current buffer: Init growing array.
5723  */
5724     static void
5725 init_syn_patterns(void)
5726 {
5727     curwin->w_s->b_syn_patterns.ga_itemsize = sizeof(synpat_T);
5728     curwin->w_s->b_syn_patterns.ga_growsize = 10;
5729 }
5730 
5731 /*
5732  * Get one pattern for a ":syntax match" or ":syntax region" command.
5733  * Stores the pattern and program in a synpat_T.
5734  * Returns a pointer to the next argument, or NULL in case of an error.
5735  */
5736     static char_u *
5737 get_syn_pattern(char_u *arg, synpat_T *ci)
5738 {
5739     char_u	*end;
5740     int		*p;
5741     int		idx;
5742     char_u	*cpo_save;
5743 
5744     /* need at least three chars */
5745     if (arg == NULL || arg[0] == NUL || arg[1] == NUL || arg[2] == NUL)
5746 	return NULL;
5747 
5748     end = skip_regexp(arg + 1, *arg, TRUE, NULL);
5749     if (*end != *arg)			    /* end delimiter not found */
5750     {
5751 	EMSG2(_("E401: Pattern delimiter not found: %s"), arg);
5752 	return NULL;
5753     }
5754     /* store the pattern and compiled regexp program */
5755     if ((ci->sp_pattern = vim_strnsave(arg + 1, (int)(end - arg - 1))) == NULL)
5756 	return NULL;
5757 
5758     /* Make 'cpoptions' empty, to avoid the 'l' flag */
5759     cpo_save = p_cpo;
5760     p_cpo = (char_u *)"";
5761     ci->sp_prog = vim_regcomp(ci->sp_pattern, RE_MAGIC);
5762     p_cpo = cpo_save;
5763 
5764     if (ci->sp_prog == NULL)
5765 	return NULL;
5766     ci->sp_ic = curwin->w_s->b_syn_ic;
5767 #ifdef FEAT_PROFILE
5768     syn_clear_time(&ci->sp_time);
5769 #endif
5770 
5771     /*
5772      * Check for a match, highlight or region offset.
5773      */
5774     ++end;
5775     do
5776     {
5777 	for (idx = SPO_COUNT; --idx >= 0; )
5778 	    if (STRNCMP(end, spo_name_tab[idx], 3) == 0)
5779 		break;
5780 	if (idx >= 0)
5781 	{
5782 	    p = &(ci->sp_offsets[idx]);
5783 	    if (idx != SPO_LC_OFF)
5784 		switch (end[3])
5785 		{
5786 		    case 's':   break;
5787 		    case 'b':   break;
5788 		    case 'e':   idx += SPO_COUNT; break;
5789 		    default:    idx = -1; break;
5790 		}
5791 	    if (idx >= 0)
5792 	    {
5793 		ci->sp_off_flags |= (1 << idx);
5794 		if (idx == SPO_LC_OFF)	    /* lc=99 */
5795 		{
5796 		    end += 3;
5797 		    *p = getdigits(&end);
5798 
5799 		    /* "lc=" offset automatically sets "ms=" offset */
5800 		    if (!(ci->sp_off_flags & (1 << SPO_MS_OFF)))
5801 		    {
5802 			ci->sp_off_flags |= (1 << SPO_MS_OFF);
5803 			ci->sp_offsets[SPO_MS_OFF] = *p;
5804 		    }
5805 		}
5806 		else			    /* yy=x+99 */
5807 		{
5808 		    end += 4;
5809 		    if (*end == '+')
5810 		    {
5811 			++end;
5812 			*p = getdigits(&end);		/* positive offset */
5813 		    }
5814 		    else if (*end == '-')
5815 		    {
5816 			++end;
5817 			*p = -getdigits(&end);		/* negative offset */
5818 		    }
5819 		}
5820 		if (*end != ',')
5821 		    break;
5822 		++end;
5823 	    }
5824 	}
5825     } while (idx >= 0);
5826 
5827     if (!ends_excmd(*end) && !VIM_ISWHITE(*end))
5828     {
5829 	EMSG2(_("E402: Garbage after pattern: %s"), arg);
5830 	return NULL;
5831     }
5832     return skipwhite(end);
5833 }
5834 
5835 /*
5836  * Handle ":syntax sync .." command.
5837  */
5838     static void
5839 syn_cmd_sync(exarg_T *eap, int syncing UNUSED)
5840 {
5841     char_u	*arg_start = eap->arg;
5842     char_u	*arg_end;
5843     char_u	*key = NULL;
5844     char_u	*next_arg;
5845     int		illegal = FALSE;
5846     int		finished = FALSE;
5847     long	n;
5848     char_u	*cpo_save;
5849 
5850     if (ends_excmd(*arg_start))
5851     {
5852 	syn_cmd_list(eap, TRUE);
5853 	return;
5854     }
5855 
5856     while (!ends_excmd(*arg_start))
5857     {
5858 	arg_end = skiptowhite(arg_start);
5859 	next_arg = skipwhite(arg_end);
5860 	vim_free(key);
5861 	key = vim_strnsave_up(arg_start, (int)(arg_end - arg_start));
5862 	if (STRCMP(key, "CCOMMENT") == 0)
5863 	{
5864 	    if (!eap->skip)
5865 		curwin->w_s->b_syn_sync_flags |= SF_CCOMMENT;
5866 	    if (!ends_excmd(*next_arg))
5867 	    {
5868 		arg_end = skiptowhite(next_arg);
5869 		if (!eap->skip)
5870 		    curwin->w_s->b_syn_sync_id = syn_check_group(next_arg,
5871 						   (int)(arg_end - next_arg));
5872 		next_arg = skipwhite(arg_end);
5873 	    }
5874 	    else if (!eap->skip)
5875 		curwin->w_s->b_syn_sync_id = syn_name2id((char_u *)"Comment");
5876 	}
5877 	else if (  STRNCMP(key, "LINES", 5) == 0
5878 		|| STRNCMP(key, "MINLINES", 8) == 0
5879 		|| STRNCMP(key, "MAXLINES", 8) == 0
5880 		|| STRNCMP(key, "LINEBREAKS", 10) == 0)
5881 	{
5882 	    if (key[4] == 'S')
5883 		arg_end = key + 6;
5884 	    else if (key[0] == 'L')
5885 		arg_end = key + 11;
5886 	    else
5887 		arg_end = key + 9;
5888 	    if (arg_end[-1] != '=' || !VIM_ISDIGIT(*arg_end))
5889 	    {
5890 		illegal = TRUE;
5891 		break;
5892 	    }
5893 	    n = getdigits(&arg_end);
5894 	    if (!eap->skip)
5895 	    {
5896 		if (key[4] == 'B')
5897 		    curwin->w_s->b_syn_sync_linebreaks = n;
5898 		else if (key[1] == 'A')
5899 		    curwin->w_s->b_syn_sync_maxlines = n;
5900 		else
5901 		    curwin->w_s->b_syn_sync_minlines = n;
5902 	    }
5903 	}
5904 	else if (STRCMP(key, "FROMSTART") == 0)
5905 	{
5906 	    if (!eap->skip)
5907 	    {
5908 		curwin->w_s->b_syn_sync_minlines = MAXLNUM;
5909 		curwin->w_s->b_syn_sync_maxlines = 0;
5910 	    }
5911 	}
5912 	else if (STRCMP(key, "LINECONT") == 0)
5913 	{
5914 	    if (*next_arg == NUL)	   /* missing pattern */
5915 	    {
5916 		illegal = TRUE;
5917 		break;
5918 	    }
5919 	    if (curwin->w_s->b_syn_linecont_pat != NULL)
5920 	    {
5921 		EMSG(_("E403: syntax sync: line continuations pattern specified twice"));
5922 		finished = TRUE;
5923 		break;
5924 	    }
5925 	    arg_end = skip_regexp(next_arg + 1, *next_arg, TRUE, NULL);
5926 	    if (*arg_end != *next_arg)	    /* end delimiter not found */
5927 	    {
5928 		illegal = TRUE;
5929 		break;
5930 	    }
5931 
5932 	    if (!eap->skip)
5933 	    {
5934 		/* store the pattern and compiled regexp program */
5935 		if ((curwin->w_s->b_syn_linecont_pat = vim_strnsave(next_arg + 1,
5936 				      (int)(arg_end - next_arg - 1))) == NULL)
5937 		{
5938 		    finished = TRUE;
5939 		    break;
5940 		}
5941 		curwin->w_s->b_syn_linecont_ic = curwin->w_s->b_syn_ic;
5942 
5943 		/* Make 'cpoptions' empty, to avoid the 'l' flag */
5944 		cpo_save = p_cpo;
5945 		p_cpo = (char_u *)"";
5946 		curwin->w_s->b_syn_linecont_prog =
5947 		       vim_regcomp(curwin->w_s->b_syn_linecont_pat, RE_MAGIC);
5948 		p_cpo = cpo_save;
5949 #ifdef FEAT_PROFILE
5950 		syn_clear_time(&curwin->w_s->b_syn_linecont_time);
5951 #endif
5952 
5953 		if (curwin->w_s->b_syn_linecont_prog == NULL)
5954 		{
5955 		    VIM_CLEAR(curwin->w_s->b_syn_linecont_pat);
5956 		    finished = TRUE;
5957 		    break;
5958 		}
5959 	    }
5960 	    next_arg = skipwhite(arg_end + 1);
5961 	}
5962 	else
5963 	{
5964 	    eap->arg = next_arg;
5965 	    if (STRCMP(key, "MATCH") == 0)
5966 		syn_cmd_match(eap, TRUE);
5967 	    else if (STRCMP(key, "REGION") == 0)
5968 		syn_cmd_region(eap, TRUE);
5969 	    else if (STRCMP(key, "CLEAR") == 0)
5970 		syn_cmd_clear(eap, TRUE);
5971 	    else
5972 		illegal = TRUE;
5973 	    finished = TRUE;
5974 	    break;
5975 	}
5976 	arg_start = next_arg;
5977     }
5978     vim_free(key);
5979     if (illegal)
5980 	EMSG2(_("E404: Illegal arguments: %s"), arg_start);
5981     else if (!finished)
5982     {
5983 	eap->nextcmd = check_nextcmd(arg_start);
5984 	redraw_curbuf_later(SOME_VALID);
5985 	syn_stack_free_all(curwin->w_s);	/* Need to recompute all syntax. */
5986     }
5987 }
5988 
5989 /*
5990  * Convert a line of highlight group names into a list of group ID numbers.
5991  * "arg" should point to the "contains" or "nextgroup" keyword.
5992  * "arg" is advanced to after the last group name.
5993  * Careful: the argument is modified (NULs added).
5994  * returns FAIL for some error, OK for success.
5995  */
5996     static int
5997 get_id_list(
5998     char_u	**arg,
5999     int		keylen,		/* length of keyword */
6000     short	**list,		/* where to store the resulting list, if not
6001 				   NULL, the list is silently skipped! */
6002     int		skip)
6003 {
6004     char_u	*p = NULL;
6005     char_u	*end;
6006     int		round;
6007     int		count;
6008     int		total_count = 0;
6009     short	*retval = NULL;
6010     char_u	*name;
6011     regmatch_T	regmatch;
6012     int		id;
6013     int		i;
6014     int		failed = FALSE;
6015 
6016     /*
6017      * We parse the list twice:
6018      * round == 1: count the number of items, allocate the array.
6019      * round == 2: fill the array with the items.
6020      * In round 1 new groups may be added, causing the number of items to
6021      * grow when a regexp is used.  In that case round 1 is done once again.
6022      */
6023     for (round = 1; round <= 2; ++round)
6024     {
6025 	/*
6026 	 * skip "contains"
6027 	 */
6028 	p = skipwhite(*arg + keylen);
6029 	if (*p != '=')
6030 	{
6031 	    EMSG2(_("E405: Missing equal sign: %s"), *arg);
6032 	    break;
6033 	}
6034 	p = skipwhite(p + 1);
6035 	if (ends_excmd(*p))
6036 	{
6037 	    EMSG2(_("E406: Empty argument: %s"), *arg);
6038 	    break;
6039 	}
6040 
6041 	/*
6042 	 * parse the arguments after "contains"
6043 	 */
6044 	count = 0;
6045 	while (!ends_excmd(*p))
6046 	{
6047 	    for (end = p; *end && !VIM_ISWHITE(*end) && *end != ','; ++end)
6048 		;
6049 	    name = alloc((int)(end - p + 3));	    /* leave room for "^$" */
6050 	    if (name == NULL)
6051 	    {
6052 		failed = TRUE;
6053 		break;
6054 	    }
6055 	    vim_strncpy(name + 1, p, end - p);
6056 	    if (       STRCMP(name + 1, "ALLBUT") == 0
6057 		    || STRCMP(name + 1, "ALL") == 0
6058 		    || STRCMP(name + 1, "TOP") == 0
6059 		    || STRCMP(name + 1, "CONTAINED") == 0)
6060 	    {
6061 		if (TOUPPER_ASC(**arg) != 'C')
6062 		{
6063 		    EMSG2(_("E407: %s not allowed here"), name + 1);
6064 		    failed = TRUE;
6065 		    vim_free(name);
6066 		    break;
6067 		}
6068 		if (count != 0)
6069 		{
6070 		    EMSG2(_("E408: %s must be first in contains list"),
6071 								     name + 1);
6072 		    failed = TRUE;
6073 		    vim_free(name);
6074 		    break;
6075 		}
6076 		if (name[1] == 'A')
6077 		    id = SYNID_ALLBUT;
6078 		else if (name[1] == 'T')
6079 		    id = SYNID_TOP;
6080 		else
6081 		    id = SYNID_CONTAINED;
6082 		id += current_syn_inc_tag;
6083 	    }
6084 	    else if (name[1] == '@')
6085 	    {
6086 		if (skip)
6087 		    id = -1;
6088 		else
6089 		    id = syn_check_cluster(name + 2, (int)(end - p - 1));
6090 	    }
6091 	    else
6092 	    {
6093 		/*
6094 		 * Handle full group name.
6095 		 */
6096 		if (vim_strpbrk(name + 1, (char_u *)"\\.*^$~[") == NULL)
6097 		    id = syn_check_group(name + 1, (int)(end - p));
6098 		else
6099 		{
6100 		    /*
6101 		     * Handle match of regexp with group names.
6102 		     */
6103 		    *name = '^';
6104 		    STRCAT(name, "$");
6105 		    regmatch.regprog = vim_regcomp(name, RE_MAGIC);
6106 		    if (regmatch.regprog == NULL)
6107 		    {
6108 			failed = TRUE;
6109 			vim_free(name);
6110 			break;
6111 		    }
6112 
6113 		    regmatch.rm_ic = TRUE;
6114 		    id = 0;
6115 		    for (i = highlight_ga.ga_len; --i >= 0; )
6116 		    {
6117 			if (vim_regexec(&regmatch, HL_TABLE()[i].sg_name,
6118 								  (colnr_T)0))
6119 			{
6120 			    if (round == 2)
6121 			    {
6122 				/* Got more items than expected; can happen
6123 				 * when adding items that match:
6124 				 * "contains=a.*b,axb".
6125 				 * Go back to first round */
6126 				if (count >= total_count)
6127 				{
6128 				    vim_free(retval);
6129 				    round = 1;
6130 				}
6131 				else
6132 				    retval[count] = i + 1;
6133 			    }
6134 			    ++count;
6135 			    id = -1;	    /* remember that we found one */
6136 			}
6137 		    }
6138 		    vim_regfree(regmatch.regprog);
6139 		}
6140 	    }
6141 	    vim_free(name);
6142 	    if (id == 0)
6143 	    {
6144 		EMSG2(_("E409: Unknown group name: %s"), p);
6145 		failed = TRUE;
6146 		break;
6147 	    }
6148 	    if (id > 0)
6149 	    {
6150 		if (round == 2)
6151 		{
6152 		    /* Got more items than expected, go back to first round */
6153 		    if (count >= total_count)
6154 		    {
6155 			vim_free(retval);
6156 			round = 1;
6157 		    }
6158 		    else
6159 			retval[count] = id;
6160 		}
6161 		++count;
6162 	    }
6163 	    p = skipwhite(end);
6164 	    if (*p != ',')
6165 		break;
6166 	    p = skipwhite(p + 1);	/* skip comma in between arguments */
6167 	}
6168 	if (failed)
6169 	    break;
6170 	if (round == 1)
6171 	{
6172 	    retval = (short *)alloc((unsigned)((count + 1) * sizeof(short)));
6173 	    if (retval == NULL)
6174 		break;
6175 	    retval[count] = 0;	    /* zero means end of the list */
6176 	    total_count = count;
6177 	}
6178     }
6179 
6180     *arg = p;
6181     if (failed || retval == NULL)
6182     {
6183 	vim_free(retval);
6184 	return FAIL;
6185     }
6186 
6187     if (*list == NULL)
6188 	*list = retval;
6189     else
6190 	vim_free(retval);	/* list already found, don't overwrite it */
6191 
6192     return OK;
6193 }
6194 
6195 /*
6196  * Make a copy of an ID list.
6197  */
6198     static short *
6199 copy_id_list(short *list)
6200 {
6201     int	    len;
6202     int	    count;
6203     short   *retval;
6204 
6205     if (list == NULL)
6206 	return NULL;
6207 
6208     for (count = 0; list[count]; ++count)
6209 	;
6210     len = (count + 1) * sizeof(short);
6211     retval = (short *)alloc((unsigned)len);
6212     if (retval != NULL)
6213 	mch_memmove(retval, list, (size_t)len);
6214 
6215     return retval;
6216 }
6217 
6218 /*
6219  * Check if syntax group "ssp" is in the ID list "list" of "cur_si".
6220  * "cur_si" can be NULL if not checking the "containedin" list.
6221  * Used to check if a syntax item is in the "contains" or "nextgroup" list of
6222  * the current item.
6223  * This function is called very often, keep it fast!!
6224  */
6225     static int
6226 in_id_list(
6227     stateitem_T	*cur_si,	/* current item or NULL */
6228     short	*list,		/* id list */
6229     struct sp_syn *ssp,		/* group id and ":syn include" tag of group */
6230     int		contained)	/* group id is contained */
6231 {
6232     int		retval;
6233     short	*scl_list;
6234     short	item;
6235     short	id = ssp->id;
6236     static int	depth = 0;
6237     int		r;
6238 
6239     /* If ssp has a "containedin" list and "cur_si" is in it, return TRUE. */
6240     if (cur_si != NULL && ssp->cont_in_list != NULL
6241 					    && !(cur_si->si_flags & HL_MATCH))
6242     {
6243 	/* Ignore transparent items without a contains argument.  Double check
6244 	 * that we don't go back past the first one. */
6245 	while ((cur_si->si_flags & HL_TRANS_CONT)
6246 		&& cur_si > (stateitem_T *)(current_state.ga_data))
6247 	    --cur_si;
6248 	/* cur_si->si_idx is -1 for keywords, these never contain anything. */
6249 	if (cur_si->si_idx >= 0 && in_id_list(NULL, ssp->cont_in_list,
6250 		&(SYN_ITEMS(syn_block)[cur_si->si_idx].sp_syn),
6251 		  SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags & HL_CONTAINED))
6252 	    return TRUE;
6253     }
6254 
6255     if (list == NULL)
6256 	return FALSE;
6257 
6258     /*
6259      * If list is ID_LIST_ALL, we are in a transparent item that isn't
6260      * inside anything.  Only allow not-contained groups.
6261      */
6262     if (list == ID_LIST_ALL)
6263 	return !contained;
6264 
6265     /*
6266      * If the first item is "ALLBUT", return TRUE if "id" is NOT in the
6267      * contains list.  We also require that "id" is at the same ":syn include"
6268      * level as the list.
6269      */
6270     item = *list;
6271     if (item >= SYNID_ALLBUT && item < SYNID_CLUSTER)
6272     {
6273 	if (item < SYNID_TOP)
6274 	{
6275 	    /* ALL or ALLBUT: accept all groups in the same file */
6276 	    if (item - SYNID_ALLBUT != ssp->inc_tag)
6277 		return FALSE;
6278 	}
6279 	else if (item < SYNID_CONTAINED)
6280 	{
6281 	    /* TOP: accept all not-contained groups in the same file */
6282 	    if (item - SYNID_TOP != ssp->inc_tag || contained)
6283 		return FALSE;
6284 	}
6285 	else
6286 	{
6287 	    /* CONTAINED: accept all contained groups in the same file */
6288 	    if (item - SYNID_CONTAINED != ssp->inc_tag || !contained)
6289 		return FALSE;
6290 	}
6291 	item = *++list;
6292 	retval = FALSE;
6293     }
6294     else
6295 	retval = TRUE;
6296 
6297     /*
6298      * Return "retval" if id is in the contains list.
6299      */
6300     while (item != 0)
6301     {
6302 	if (item == id)
6303 	    return retval;
6304 	if (item >= SYNID_CLUSTER)
6305 	{
6306 	    scl_list = SYN_CLSTR(syn_block)[item - SYNID_CLUSTER].scl_list;
6307 	    /* restrict recursiveness to 30 to avoid an endless loop for a
6308 	     * cluster that includes itself (indirectly) */
6309 	    if (scl_list != NULL && depth < 30)
6310 	    {
6311 		++depth;
6312 		r = in_id_list(NULL, scl_list, ssp, contained);
6313 		--depth;
6314 		if (r)
6315 		    return retval;
6316 	    }
6317 	}
6318 	item = *++list;
6319     }
6320     return !retval;
6321 }
6322 
6323 struct subcommand
6324 {
6325     char    *name;			/* subcommand name */
6326     void    (*func)(exarg_T *, int);	/* function to call */
6327 };
6328 
6329 static struct subcommand subcommands[] =
6330 {
6331     {"case",		syn_cmd_case},
6332     {"clear",		syn_cmd_clear},
6333     {"cluster",		syn_cmd_cluster},
6334     {"conceal",		syn_cmd_conceal},
6335     {"enable",		syn_cmd_enable},
6336     {"include",		syn_cmd_include},
6337     {"iskeyword",	syn_cmd_iskeyword},
6338     {"keyword",		syn_cmd_keyword},
6339     {"list",		syn_cmd_list},
6340     {"manual",		syn_cmd_manual},
6341     {"match",		syn_cmd_match},
6342     {"on",		syn_cmd_on},
6343     {"off",		syn_cmd_off},
6344     {"region",		syn_cmd_region},
6345     {"reset",		syn_cmd_reset},
6346     {"spell",		syn_cmd_spell},
6347     {"sync",		syn_cmd_sync},
6348     {"",		syn_cmd_list},
6349     {NULL, NULL}
6350 };
6351 
6352 /*
6353  * ":syntax".
6354  * This searches the subcommands[] table for the subcommand name, and calls a
6355  * syntax_subcommand() function to do the rest.
6356  */
6357     void
6358 ex_syntax(exarg_T *eap)
6359 {
6360     char_u	*arg = eap->arg;
6361     char_u	*subcmd_end;
6362     char_u	*subcmd_name;
6363     int		i;
6364 
6365     syn_cmdlinep = eap->cmdlinep;
6366 
6367     /* isolate subcommand name */
6368     for (subcmd_end = arg; ASCII_ISALPHA(*subcmd_end); ++subcmd_end)
6369 	;
6370     subcmd_name = vim_strnsave(arg, (int)(subcmd_end - arg));
6371     if (subcmd_name != NULL)
6372     {
6373 	if (eap->skip)		/* skip error messages for all subcommands */
6374 	    ++emsg_skip;
6375 	for (i = 0; ; ++i)
6376 	{
6377 	    if (subcommands[i].name == NULL)
6378 	    {
6379 		EMSG2(_("E410: Invalid :syntax subcommand: %s"), subcmd_name);
6380 		break;
6381 	    }
6382 	    if (STRCMP(subcmd_name, (char_u *)subcommands[i].name) == 0)
6383 	    {
6384 		eap->arg = skipwhite(subcmd_end);
6385 		(subcommands[i].func)(eap, FALSE);
6386 		break;
6387 	    }
6388 	}
6389 	vim_free(subcmd_name);
6390 	if (eap->skip)
6391 	    --emsg_skip;
6392     }
6393 }
6394 
6395     void
6396 ex_ownsyntax(exarg_T *eap)
6397 {
6398     char_u	*old_value;
6399     char_u	*new_value;
6400 
6401     if (curwin->w_s == &curwin->w_buffer->b_s)
6402     {
6403 	curwin->w_s = (synblock_T *)alloc(sizeof(synblock_T));
6404 	memset(curwin->w_s, 0, sizeof(synblock_T));
6405 	hash_init(&curwin->w_s->b_keywtab);
6406 	hash_init(&curwin->w_s->b_keywtab_ic);
6407 #ifdef FEAT_SPELL
6408 	/* TODO: keep the spell checking as it was. */
6409 	curwin->w_p_spell = FALSE;	/* No spell checking */
6410 	clear_string_option(&curwin->w_s->b_p_spc);
6411 	clear_string_option(&curwin->w_s->b_p_spf);
6412 	clear_string_option(&curwin->w_s->b_p_spl);
6413 #endif
6414 	clear_string_option(&curwin->w_s->b_syn_isk);
6415     }
6416 
6417     /* save value of b:current_syntax */
6418     old_value = get_var_value((char_u *)"b:current_syntax");
6419     if (old_value != NULL)
6420 	old_value = vim_strsave(old_value);
6421 
6422 #ifdef FEAT_AUTOCMD
6423     /* Apply the "syntax" autocommand event, this finds and loads the syntax
6424      * file. */
6425     apply_autocmds(EVENT_SYNTAX, eap->arg, curbuf->b_fname, TRUE, curbuf);
6426 #endif
6427 
6428     /* move value of b:current_syntax to w:current_syntax */
6429     new_value = get_var_value((char_u *)"b:current_syntax");
6430     if (new_value != NULL)
6431 	set_internal_string_var((char_u *)"w:current_syntax", new_value);
6432 
6433     /* restore value of b:current_syntax */
6434     if (old_value == NULL)
6435 	do_unlet((char_u *)"b:current_syntax", TRUE);
6436     else
6437     {
6438 	set_internal_string_var((char_u *)"b:current_syntax", old_value);
6439 	vim_free(old_value);
6440     }
6441 }
6442 
6443     int
6444 syntax_present(win_T *win)
6445 {
6446     return (win->w_s->b_syn_patterns.ga_len != 0
6447 	    || win->w_s->b_syn_clusters.ga_len != 0
6448 	    || win->w_s->b_keywtab.ht_used > 0
6449 	    || win->w_s->b_keywtab_ic.ht_used > 0);
6450 }
6451 
6452 #if defined(FEAT_CMDL_COMPL) || defined(PROTO)
6453 
6454 static enum
6455 {
6456     EXP_SUBCMD,	    /* expand ":syn" sub-commands */
6457     EXP_CASE,	    /* expand ":syn case" arguments */
6458     EXP_SPELL,	    /* expand ":syn spell" arguments */
6459     EXP_SYNC	    /* expand ":syn sync" arguments */
6460 } expand_what;
6461 
6462 /*
6463  * Reset include_link, include_default, include_none to 0.
6464  * Called when we are done expanding.
6465  */
6466     void
6467 reset_expand_highlight(void)
6468 {
6469     include_link = include_default = include_none = 0;
6470 }
6471 
6472 /*
6473  * Handle command line completion for :match and :echohl command: Add "None"
6474  * as highlight group.
6475  */
6476     void
6477 set_context_in_echohl_cmd(expand_T *xp, char_u *arg)
6478 {
6479     xp->xp_context = EXPAND_HIGHLIGHT;
6480     xp->xp_pattern = arg;
6481     include_none = 1;
6482 }
6483 
6484 /*
6485  * Handle command line completion for :syntax command.
6486  */
6487     void
6488 set_context_in_syntax_cmd(expand_T *xp, char_u *arg)
6489 {
6490     char_u	*p;
6491 
6492     /* Default: expand subcommands */
6493     xp->xp_context = EXPAND_SYNTAX;
6494     expand_what = EXP_SUBCMD;
6495     xp->xp_pattern = arg;
6496     include_link = 0;
6497     include_default = 0;
6498 
6499     /* (part of) subcommand already typed */
6500     if (*arg != NUL)
6501     {
6502 	p = skiptowhite(arg);
6503 	if (*p != NUL)		    /* past first word */
6504 	{
6505 	    xp->xp_pattern = skipwhite(p);
6506 	    if (*skiptowhite(xp->xp_pattern) != NUL)
6507 		xp->xp_context = EXPAND_NOTHING;
6508 	    else if (STRNICMP(arg, "case", p - arg) == 0)
6509 		expand_what = EXP_CASE;
6510 	    else if (STRNICMP(arg, "spell", p - arg) == 0)
6511 		expand_what = EXP_SPELL;
6512 	    else if (STRNICMP(arg, "sync", p - arg) == 0)
6513 		expand_what = EXP_SYNC;
6514 	    else if (  STRNICMP(arg, "keyword", p - arg) == 0
6515 		    || STRNICMP(arg, "region", p - arg) == 0
6516 		    || STRNICMP(arg, "match", p - arg) == 0
6517 		    || STRNICMP(arg, "list", p - arg) == 0)
6518 		xp->xp_context = EXPAND_HIGHLIGHT;
6519 	    else
6520 		xp->xp_context = EXPAND_NOTHING;
6521 	}
6522     }
6523 }
6524 
6525 /*
6526  * Function given to ExpandGeneric() to obtain the list syntax names for
6527  * expansion.
6528  */
6529     char_u *
6530 get_syntax_name(expand_T *xp UNUSED, int idx)
6531 {
6532     switch (expand_what)
6533     {
6534 	case EXP_SUBCMD:
6535 	    return (char_u *)subcommands[idx].name;
6536 	case EXP_CASE:
6537 	{
6538 	    static char *case_args[] = {"match", "ignore", NULL};
6539 	    return (char_u *)case_args[idx];
6540 	}
6541 	case EXP_SPELL:
6542 	{
6543 	    static char *spell_args[] =
6544 		{"toplevel", "notoplevel", "default", NULL};
6545 	    return (char_u *)spell_args[idx];
6546 	}
6547 	case EXP_SYNC:
6548 	{
6549 	    static char *sync_args[] =
6550 		{"ccomment", "clear", "fromstart",
6551 		 "linebreaks=", "linecont", "lines=", "match",
6552 		 "maxlines=", "minlines=", "region", NULL};
6553 	    return (char_u *)sync_args[idx];
6554 	}
6555     }
6556     return NULL;
6557 }
6558 
6559 #endif /* FEAT_CMDL_COMPL */
6560 
6561 /*
6562  * Function called for expression evaluation: get syntax ID at file position.
6563  */
6564     int
6565 syn_get_id(
6566     win_T	*wp,
6567     long	lnum,
6568     colnr_T	col,
6569     int		trans,	     /* remove transparency */
6570     int		*spellp,     /* return: can do spell checking */
6571     int		keep_state)  /* keep state of char at "col" */
6572 {
6573     /* When the position is not after the current position and in the same
6574      * line of the same buffer, need to restart parsing. */
6575     if (wp->w_buffer != syn_buf
6576 	    || lnum != current_lnum
6577 	    || col < current_col)
6578 	syntax_start(wp, lnum);
6579     else if (wp->w_buffer == syn_buf
6580 	    && lnum == current_lnum
6581 	    && col > current_col)
6582 	/* next_match may not be correct when moving around, e.g. with the
6583 	 * "skip" expression in searchpair() */
6584 	next_match_idx = -1;
6585 
6586     (void)get_syntax_attr(col, spellp, keep_state);
6587 
6588     return (trans ? current_trans_id : current_id);
6589 }
6590 
6591 #if defined(FEAT_CONCEAL) || defined(PROTO)
6592 /*
6593  * Get extra information about the syntax item.  Must be called right after
6594  * get_syntax_attr().
6595  * Stores the current item sequence nr in "*seqnrp".
6596  * Returns the current flags.
6597  */
6598     int
6599 get_syntax_info(int *seqnrp)
6600 {
6601     *seqnrp = current_seqnr;
6602     return current_flags;
6603 }
6604 
6605 /*
6606  * Return conceal substitution character
6607  */
6608     int
6609 syn_get_sub_char(void)
6610 {
6611     return current_sub_char;
6612 }
6613 #endif
6614 
6615 #if defined(FEAT_EVAL) || defined(PROTO)
6616 /*
6617  * Return the syntax ID at position "i" in the current stack.
6618  * The caller must have called syn_get_id() before to fill the stack.
6619  * Returns -1 when "i" is out of range.
6620  */
6621     int
6622 syn_get_stack_item(int i)
6623 {
6624     if (i >= current_state.ga_len)
6625     {
6626 	/* Need to invalidate the state, because we didn't properly finish it
6627 	 * for the last character, "keep_state" was TRUE. */
6628 	invalidate_current_state();
6629 	current_col = MAXCOL;
6630 	return -1;
6631     }
6632     return CUR_STATE(i).si_id;
6633 }
6634 #endif
6635 
6636 #if defined(FEAT_FOLDING) || defined(PROTO)
6637 /*
6638  * Function called to get folding level for line "lnum" in window "wp".
6639  */
6640     int
6641 syn_get_foldlevel(win_T *wp, long lnum)
6642 {
6643     int		level = 0;
6644     int		i;
6645 
6646     /* Return quickly when there are no fold items at all. */
6647     if (wp->w_s->b_syn_folditems != 0
6648 	    && !wp->w_s->b_syn_error
6649 # ifdef SYN_TIME_LIMIT
6650 	    && !wp->w_s->b_syn_slow
6651 # endif
6652 	    )
6653     {
6654 	syntax_start(wp, lnum);
6655 
6656 	for (i = 0; i < current_state.ga_len; ++i)
6657 	    if (CUR_STATE(i).si_flags & HL_FOLD)
6658 		++level;
6659     }
6660     if (level > wp->w_p_fdn)
6661     {
6662 	level = wp->w_p_fdn;
6663 	if (level < 0)
6664 	    level = 0;
6665     }
6666     return level;
6667 }
6668 #endif
6669 
6670 #if defined(FEAT_PROFILE) || defined(PROTO)
6671 /*
6672  * ":syntime".
6673  */
6674     void
6675 ex_syntime(exarg_T *eap)
6676 {
6677     if (STRCMP(eap->arg, "on") == 0)
6678 	syn_time_on = TRUE;
6679     else if (STRCMP(eap->arg, "off") == 0)
6680 	syn_time_on = FALSE;
6681     else if (STRCMP(eap->arg, "clear") == 0)
6682 	syntime_clear();
6683     else if (STRCMP(eap->arg, "report") == 0)
6684 	syntime_report();
6685     else
6686 	EMSG2(_(e_invarg2), eap->arg);
6687 }
6688 
6689     static void
6690 syn_clear_time(syn_time_T *st)
6691 {
6692     profile_zero(&st->total);
6693     profile_zero(&st->slowest);
6694     st->count = 0;
6695     st->match = 0;
6696 }
6697 
6698 /*
6699  * Clear the syntax timing for the current buffer.
6700  */
6701     static void
6702 syntime_clear(void)
6703 {
6704     int		idx;
6705     synpat_T	*spp;
6706 
6707     if (!syntax_present(curwin))
6708     {
6709 	MSG(_(msg_no_items));
6710 	return;
6711     }
6712     for (idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx)
6713     {
6714 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
6715 	syn_clear_time(&spp->sp_time);
6716     }
6717 }
6718 
6719 #if defined(FEAT_CMDL_COMPL) || defined(PROTO)
6720 /*
6721  * Function given to ExpandGeneric() to obtain the possible arguments of the
6722  * ":syntime {on,off,clear,report}" command.
6723  */
6724     char_u *
6725 get_syntime_arg(expand_T *xp UNUSED, int idx)
6726 {
6727     switch (idx)
6728     {
6729 	case 0: return (char_u *)"on";
6730 	case 1: return (char_u *)"off";
6731 	case 2: return (char_u *)"clear";
6732 	case 3: return (char_u *)"report";
6733     }
6734     return NULL;
6735 }
6736 #endif
6737 
6738 typedef struct
6739 {
6740     proftime_T	total;
6741     int		count;
6742     int		match;
6743     proftime_T	slowest;
6744     proftime_T	average;
6745     int		id;
6746     char_u	*pattern;
6747 } time_entry_T;
6748 
6749     static int
6750 #ifdef __BORLANDC__
6751 _RTLENTRYF
6752 #endif
6753 syn_compare_syntime(const void *v1, const void *v2)
6754 {
6755     const time_entry_T	*s1 = v1;
6756     const time_entry_T	*s2 = v2;
6757 
6758     return profile_cmp(&s1->total, &s2->total);
6759 }
6760 
6761 /*
6762  * Clear the syntax timing for the current buffer.
6763  */
6764     static void
6765 syntime_report(void)
6766 {
6767     int		idx;
6768     synpat_T	*spp;
6769 # ifdef FEAT_FLOAT
6770     proftime_T	tm;
6771 # endif
6772     int		len;
6773     proftime_T	total_total;
6774     int		total_count = 0;
6775     garray_T    ga;
6776     time_entry_T *p;
6777 
6778     if (!syntax_present(curwin))
6779     {
6780 	MSG(_(msg_no_items));
6781 	return;
6782     }
6783 
6784     ga_init2(&ga, sizeof(time_entry_T), 50);
6785     profile_zero(&total_total);
6786     for (idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx)
6787     {
6788 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
6789 	if (spp->sp_time.count > 0)
6790 	{
6791 	    (void)ga_grow(&ga, 1);
6792 	    p = ((time_entry_T *)ga.ga_data) + ga.ga_len;
6793 	    p->total = spp->sp_time.total;
6794 	    profile_add(&total_total, &spp->sp_time.total);
6795 	    p->count = spp->sp_time.count;
6796 	    p->match = spp->sp_time.match;
6797 	    total_count += spp->sp_time.count;
6798 	    p->slowest = spp->sp_time.slowest;
6799 # ifdef FEAT_FLOAT
6800 	    profile_divide(&spp->sp_time.total, spp->sp_time.count, &tm);
6801 	    p->average = tm;
6802 # endif
6803 	    p->id = spp->sp_syn.id;
6804 	    p->pattern = spp->sp_pattern;
6805 	    ++ga.ga_len;
6806 	}
6807     }
6808 
6809     /* Sort on total time. Skip if there are no items to avoid passing NULL
6810      * pointer to qsort(). */
6811     if (ga.ga_len > 1)
6812 	qsort(ga.ga_data, (size_t)ga.ga_len, sizeof(time_entry_T),
6813 							 syn_compare_syntime);
6814 
6815     MSG_PUTS_TITLE(_("  TOTAL      COUNT  MATCH   SLOWEST     AVERAGE   NAME               PATTERN"));
6816     MSG_PUTS("\n");
6817     for (idx = 0; idx < ga.ga_len && !got_int; ++idx)
6818     {
6819 	p = ((time_entry_T *)ga.ga_data) + idx;
6820 
6821 	MSG_PUTS(profile_msg(&p->total));
6822 	MSG_PUTS(" "); /* make sure there is always a separating space */
6823 	msg_advance(13);
6824 	msg_outnum(p->count);
6825 	MSG_PUTS(" ");
6826 	msg_advance(20);
6827 	msg_outnum(p->match);
6828 	MSG_PUTS(" ");
6829 	msg_advance(26);
6830 	MSG_PUTS(profile_msg(&p->slowest));
6831 	MSG_PUTS(" ");
6832 	msg_advance(38);
6833 # ifdef FEAT_FLOAT
6834 	MSG_PUTS(profile_msg(&p->average));
6835 	MSG_PUTS(" ");
6836 # endif
6837 	msg_advance(50);
6838 	msg_outtrans(HL_TABLE()[p->id - 1].sg_name);
6839 	MSG_PUTS(" ");
6840 
6841 	msg_advance(69);
6842 	if (Columns < 80)
6843 	    len = 20; /* will wrap anyway */
6844 	else
6845 	    len = Columns - 70;
6846 	if (len > (int)STRLEN(p->pattern))
6847 	    len = (int)STRLEN(p->pattern);
6848 	msg_outtrans_len(p->pattern, len);
6849 	MSG_PUTS("\n");
6850     }
6851     ga_clear(&ga);
6852     if (!got_int)
6853     {
6854 	MSG_PUTS("\n");
6855 	MSG_PUTS(profile_msg(&total_total));
6856 	msg_advance(13);
6857 	msg_outnum(total_count);
6858 	MSG_PUTS("\n");
6859     }
6860 }
6861 #endif
6862 
6863 #endif /* FEAT_SYN_HL */
6864 
6865 /**************************************
6866  *  Highlighting stuff		      *
6867  **************************************/
6868 
6869 /*
6870  * The default highlight groups.  These are compiled-in for fast startup and
6871  * they still work when the runtime files can't be found.
6872  * When making changes here, also change runtime/colors/default.vim!
6873  * The #ifdefs are needed to reduce the amount of static data.  Helps to make
6874  * the 16 bit DOS (museum) version compile.
6875  */
6876 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
6877 # define CENT(a, b) b
6878 #else
6879 # define CENT(a, b) a
6880 #endif
6881 static char *(highlight_init_both[]) = {
6882     CENT("ErrorMsg term=standout ctermbg=DarkRed ctermfg=White",
6883 	 "ErrorMsg term=standout ctermbg=DarkRed ctermfg=White guibg=Red guifg=White"),
6884     CENT("IncSearch term=reverse cterm=reverse",
6885 	 "IncSearch term=reverse cterm=reverse gui=reverse"),
6886     CENT("ModeMsg term=bold cterm=bold",
6887 	 "ModeMsg term=bold cterm=bold gui=bold"),
6888     CENT("NonText term=bold ctermfg=Blue",
6889 	 "NonText term=bold ctermfg=Blue gui=bold guifg=Blue"),
6890     CENT("StatusLine term=reverse,bold cterm=reverse,bold",
6891 	 "StatusLine term=reverse,bold cterm=reverse,bold gui=reverse,bold"),
6892     CENT("StatusLineNC term=reverse cterm=reverse",
6893 	 "StatusLineNC term=reverse cterm=reverse gui=reverse"),
6894     "default link EndOfBuffer NonText",
6895     CENT("VertSplit term=reverse cterm=reverse",
6896 	 "VertSplit term=reverse cterm=reverse gui=reverse"),
6897 #ifdef FEAT_CLIPBOARD
6898     CENT("VisualNOS term=underline,bold cterm=underline,bold",
6899 	 "VisualNOS term=underline,bold cterm=underline,bold gui=underline,bold"),
6900 #endif
6901 #ifdef FEAT_DIFF
6902     CENT("DiffText term=reverse cterm=bold ctermbg=Red",
6903 	 "DiffText term=reverse cterm=bold ctermbg=Red gui=bold guibg=Red"),
6904 #endif
6905 #ifdef FEAT_INS_EXPAND
6906     CENT("PmenuSbar ctermbg=Grey",
6907 	 "PmenuSbar ctermbg=Grey guibg=Grey"),
6908 #endif
6909     CENT("TabLineSel term=bold cterm=bold",
6910 	 "TabLineSel term=bold cterm=bold gui=bold"),
6911     CENT("TabLineFill term=reverse cterm=reverse",
6912 	 "TabLineFill term=reverse cterm=reverse gui=reverse"),
6913 #ifdef FEAT_GUI
6914     "Cursor guibg=fg guifg=bg",
6915     "lCursor guibg=fg guifg=bg", /* should be different, but what? */
6916 #endif
6917     "default link QuickFixLine Search",
6918     NULL
6919 };
6920 
6921 /* Default colors only used with a light background. */
6922 static char *(highlight_init_light[]) = {
6923     CENT("Directory term=bold ctermfg=DarkBlue",
6924 	 "Directory term=bold ctermfg=DarkBlue guifg=Blue"),
6925     CENT("LineNr term=underline ctermfg=Brown",
6926 	 "LineNr term=underline ctermfg=Brown guifg=Brown"),
6927     CENT("CursorLineNr term=bold ctermfg=Brown",
6928 	 "CursorLineNr term=bold ctermfg=Brown gui=bold guifg=Brown"),
6929     CENT("MoreMsg term=bold ctermfg=DarkGreen",
6930 	 "MoreMsg term=bold ctermfg=DarkGreen gui=bold guifg=SeaGreen"),
6931     CENT("Question term=standout ctermfg=DarkGreen",
6932 	 "Question term=standout ctermfg=DarkGreen gui=bold guifg=SeaGreen"),
6933     CENT("Search term=reverse ctermbg=Yellow ctermfg=NONE",
6934 	 "Search term=reverse ctermbg=Yellow ctermfg=NONE guibg=Yellow guifg=NONE"),
6935 #ifdef FEAT_SPELL
6936     CENT("SpellBad term=reverse ctermbg=LightRed",
6937 	 "SpellBad term=reverse ctermbg=LightRed guisp=Red gui=undercurl"),
6938     CENT("SpellCap term=reverse ctermbg=LightBlue",
6939 	 "SpellCap term=reverse ctermbg=LightBlue guisp=Blue gui=undercurl"),
6940     CENT("SpellRare term=reverse ctermbg=LightMagenta",
6941 	 "SpellRare term=reverse ctermbg=LightMagenta guisp=Magenta gui=undercurl"),
6942     CENT("SpellLocal term=underline ctermbg=Cyan",
6943 	 "SpellLocal term=underline ctermbg=Cyan guisp=DarkCyan gui=undercurl"),
6944 #endif
6945 #ifdef FEAT_INS_EXPAND
6946     CENT("PmenuThumb ctermbg=Black",
6947 	 "PmenuThumb ctermbg=Black guibg=Black"),
6948     CENT("Pmenu ctermbg=LightMagenta ctermfg=Black",
6949 	 "Pmenu ctermbg=LightMagenta ctermfg=Black guibg=LightMagenta"),
6950     CENT("PmenuSel ctermbg=LightGrey ctermfg=Black",
6951 	 "PmenuSel ctermbg=LightGrey ctermfg=Black guibg=Grey"),
6952 #endif
6953     CENT("SpecialKey term=bold ctermfg=DarkBlue",
6954 	 "SpecialKey term=bold ctermfg=DarkBlue guifg=Blue"),
6955     CENT("Title term=bold ctermfg=DarkMagenta",
6956 	 "Title term=bold ctermfg=DarkMagenta gui=bold guifg=Magenta"),
6957     CENT("WarningMsg term=standout ctermfg=DarkRed",
6958 	 "WarningMsg term=standout ctermfg=DarkRed guifg=Red"),
6959 #ifdef FEAT_WILDMENU
6960     CENT("WildMenu term=standout ctermbg=Yellow ctermfg=Black",
6961 	 "WildMenu term=standout ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black"),
6962 #endif
6963 #ifdef FEAT_FOLDING
6964     CENT("Folded term=standout ctermbg=Grey ctermfg=DarkBlue",
6965 	 "Folded term=standout ctermbg=Grey ctermfg=DarkBlue guibg=LightGrey guifg=DarkBlue"),
6966     CENT("FoldColumn term=standout ctermbg=Grey ctermfg=DarkBlue",
6967 	 "FoldColumn term=standout ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue"),
6968 #endif
6969 #ifdef FEAT_SIGNS
6970     CENT("SignColumn term=standout ctermbg=Grey ctermfg=DarkBlue",
6971 	 "SignColumn term=standout ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue"),
6972 #endif
6973     CENT("Visual term=reverse",
6974 	 "Visual term=reverse guibg=LightGrey"),
6975 #ifdef FEAT_DIFF
6976     CENT("DiffAdd term=bold ctermbg=LightBlue",
6977 	 "DiffAdd term=bold ctermbg=LightBlue guibg=LightBlue"),
6978     CENT("DiffChange term=bold ctermbg=LightMagenta",
6979 	 "DiffChange term=bold ctermbg=LightMagenta guibg=LightMagenta"),
6980     CENT("DiffDelete term=bold ctermfg=Blue ctermbg=LightCyan",
6981 	 "DiffDelete term=bold ctermfg=Blue ctermbg=LightCyan gui=bold guifg=Blue guibg=LightCyan"),
6982 #endif
6983     CENT("TabLine term=underline cterm=underline ctermfg=black ctermbg=LightGrey",
6984 	 "TabLine term=underline cterm=underline ctermfg=black ctermbg=LightGrey gui=underline guibg=LightGrey"),
6985 #ifdef FEAT_SYN_HL
6986     CENT("CursorColumn term=reverse ctermbg=LightGrey",
6987 	 "CursorColumn term=reverse ctermbg=LightGrey guibg=Grey90"),
6988     CENT("CursorLine term=underline cterm=underline",
6989 	 "CursorLine term=underline cterm=underline guibg=Grey90"),
6990     CENT("ColorColumn term=reverse ctermbg=LightRed",
6991 	 "ColorColumn term=reverse ctermbg=LightRed guibg=LightRed"),
6992 #endif
6993 #ifdef FEAT_CONCEAL
6994     CENT("Conceal ctermbg=DarkGrey ctermfg=LightGrey",
6995 	 "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey"),
6996 #endif
6997 #ifdef FEAT_AUTOCMD
6998     CENT("MatchParen term=reverse ctermbg=Cyan",
6999 	 "MatchParen term=reverse ctermbg=Cyan guibg=Cyan"),
7000 #endif
7001 #ifdef FEAT_GUI
7002     "Normal gui=NONE",
7003 #endif
7004 #ifdef FEAT_TERMINAL
7005     CENT("StatusLineTerm term=reverse,bold cterm=bold ctermfg=White ctermbg=DarkGreen",
7006 	 "StatusLineTerm term=reverse,bold cterm=bold ctermfg=White ctermbg=DarkGreen gui=bold guifg=bg guibg=DarkGreen"),
7007     CENT("StatusLineTermNC term=reverse ctermfg=White ctermbg=DarkGreen",
7008 	 "StatusLineTermNC term=reverse ctermfg=White ctermbg=DarkGreen guifg=bg guibg=DarkGreen"),
7009 #endif
7010 #ifdef FEAT_MENU
7011     CENT("ToolbarLine term=underline ctermbg=LightGrey",
7012 	 "ToolbarLine term=underline ctermbg=LightGrey guibg=LightGrey"),
7013     CENT("ToolbarButton cterm=bold ctermfg=White ctermbg=DarkGrey",
7014 	 "ToolbarButton cterm=bold ctermfg=White ctermbg=DarkGrey gui=bold guifg=White guibg=Grey40"),
7015 #endif
7016     NULL
7017 };
7018 
7019 /* Default colors only used with a dark background. */
7020 static char *(highlight_init_dark[]) = {
7021     CENT("Directory term=bold ctermfg=LightCyan",
7022 	 "Directory term=bold ctermfg=LightCyan guifg=Cyan"),
7023     CENT("LineNr term=underline ctermfg=Yellow",
7024 	 "LineNr term=underline ctermfg=Yellow guifg=Yellow"),
7025     CENT("CursorLineNr term=bold ctermfg=Yellow",
7026 	 "CursorLineNr term=bold ctermfg=Yellow gui=bold guifg=Yellow"),
7027     CENT("MoreMsg term=bold ctermfg=LightGreen",
7028 	 "MoreMsg term=bold ctermfg=LightGreen gui=bold guifg=SeaGreen"),
7029     CENT("Question term=standout ctermfg=LightGreen",
7030 	 "Question term=standout ctermfg=LightGreen gui=bold guifg=Green"),
7031     CENT("Search term=reverse ctermbg=Yellow ctermfg=Black",
7032 	 "Search term=reverse ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black"),
7033     CENT("SpecialKey term=bold ctermfg=LightBlue",
7034 	 "SpecialKey term=bold ctermfg=LightBlue guifg=Cyan"),
7035 #ifdef FEAT_SPELL
7036     CENT("SpellBad term=reverse ctermbg=Red",
7037 	 "SpellBad term=reverse ctermbg=Red guisp=Red gui=undercurl"),
7038     CENT("SpellCap term=reverse ctermbg=Blue",
7039 	 "SpellCap term=reverse ctermbg=Blue guisp=Blue gui=undercurl"),
7040     CENT("SpellRare term=reverse ctermbg=Magenta",
7041 	 "SpellRare term=reverse ctermbg=Magenta guisp=Magenta gui=undercurl"),
7042     CENT("SpellLocal term=underline ctermbg=Cyan",
7043 	 "SpellLocal term=underline ctermbg=Cyan guisp=Cyan gui=undercurl"),
7044 #endif
7045 #ifdef FEAT_INS_EXPAND
7046     CENT("PmenuThumb ctermbg=White",
7047 	 "PmenuThumb ctermbg=White guibg=White"),
7048     CENT("Pmenu ctermbg=Magenta ctermfg=Black",
7049 	 "Pmenu ctermbg=Magenta ctermfg=Black guibg=Magenta"),
7050     CENT("PmenuSel ctermbg=Black ctermfg=DarkGrey",
7051 	 "PmenuSel ctermbg=Black ctermfg=DarkGrey guibg=DarkGrey"),
7052 #endif
7053     CENT("Title term=bold ctermfg=LightMagenta",
7054 	 "Title term=bold ctermfg=LightMagenta gui=bold guifg=Magenta"),
7055     CENT("WarningMsg term=standout ctermfg=LightRed",
7056 	 "WarningMsg term=standout ctermfg=LightRed guifg=Red"),
7057 #ifdef FEAT_WILDMENU
7058     CENT("WildMenu term=standout ctermbg=Yellow ctermfg=Black",
7059 	 "WildMenu term=standout ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black"),
7060 #endif
7061 #ifdef FEAT_FOLDING
7062     CENT("Folded term=standout ctermbg=DarkGrey ctermfg=Cyan",
7063 	 "Folded term=standout ctermbg=DarkGrey ctermfg=Cyan guibg=DarkGrey guifg=Cyan"),
7064     CENT("FoldColumn term=standout ctermbg=DarkGrey ctermfg=Cyan",
7065 	 "FoldColumn term=standout ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan"),
7066 #endif
7067 #ifdef FEAT_SIGNS
7068     CENT("SignColumn term=standout ctermbg=DarkGrey ctermfg=Cyan",
7069 	 "SignColumn term=standout ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan"),
7070 #endif
7071     CENT("Visual term=reverse",
7072 	 "Visual term=reverse guibg=DarkGrey"),
7073 #ifdef FEAT_DIFF
7074     CENT("DiffAdd term=bold ctermbg=DarkBlue",
7075 	 "DiffAdd term=bold ctermbg=DarkBlue guibg=DarkBlue"),
7076     CENT("DiffChange term=bold ctermbg=DarkMagenta",
7077 	 "DiffChange term=bold ctermbg=DarkMagenta guibg=DarkMagenta"),
7078     CENT("DiffDelete term=bold ctermfg=Blue ctermbg=DarkCyan",
7079 	 "DiffDelete term=bold ctermfg=Blue ctermbg=DarkCyan gui=bold guifg=Blue guibg=DarkCyan"),
7080 #endif
7081     CENT("TabLine term=underline cterm=underline ctermfg=white ctermbg=DarkGrey",
7082 	 "TabLine term=underline cterm=underline ctermfg=white ctermbg=DarkGrey gui=underline guibg=DarkGrey"),
7083 #ifdef FEAT_SYN_HL
7084     CENT("CursorColumn term=reverse ctermbg=DarkGrey",
7085 	 "CursorColumn term=reverse ctermbg=DarkGrey guibg=Grey40"),
7086     CENT("CursorLine term=underline cterm=underline",
7087 	 "CursorLine term=underline cterm=underline guibg=Grey40"),
7088     CENT("ColorColumn term=reverse ctermbg=DarkRed",
7089 	 "ColorColumn term=reverse ctermbg=DarkRed guibg=DarkRed"),
7090 #endif
7091 #ifdef FEAT_AUTOCMD
7092     CENT("MatchParen term=reverse ctermbg=DarkCyan",
7093 	 "MatchParen term=reverse ctermbg=DarkCyan guibg=DarkCyan"),
7094 #endif
7095 #ifdef FEAT_CONCEAL
7096     CENT("Conceal ctermbg=DarkGrey ctermfg=LightGrey",
7097 	 "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey"),
7098 #endif
7099 #ifdef FEAT_GUI
7100     "Normal gui=NONE",
7101 #endif
7102 #ifdef FEAT_TERMINAL
7103     CENT("StatusLineTerm term=reverse,bold cterm=bold ctermfg=Black ctermbg=LightGreen",
7104 	 "StatusLineTerm term=reverse,bold cterm=bold ctermfg=Black ctermbg=LightGreen gui=bold guifg=bg guibg=LightGreen"),
7105     CENT("StatusLineTermNC term=reverse ctermfg=Black ctermbg=LightGreen",
7106 	 "StatusLineTermNC term=reverse ctermfg=Black ctermbg=LightGreen guifg=bg guibg=LightGreen"),
7107 #endif
7108 #ifdef FEAT_MENU
7109     CENT("ToolbarLine term=underline ctermbg=DarkGrey",
7110 	 "ToolbarLine term=underline ctermbg=DarkGrey guibg=Grey50"),
7111     CENT("ToolbarButton cterm=bold ctermfg=Black ctermbg=LightGrey",
7112 	 "ToolbarButton cterm=bold ctermfg=Black ctermbg=LightGrey gui=bold guifg=Black guibg=LightGrey"),
7113 #endif
7114     NULL
7115 };
7116 
7117     void
7118 init_highlight(
7119     int		both,	    /* include groups where 'bg' doesn't matter */
7120     int		reset)	    /* clear group first */
7121 {
7122     int		i;
7123     char	**pp;
7124     static int	had_both = FALSE;
7125 #ifdef FEAT_EVAL
7126     char_u	*p;
7127 
7128     /*
7129      * Try finding the color scheme file.  Used when a color file was loaded
7130      * and 'background' or 't_Co' is changed.
7131      */
7132     p = get_var_value((char_u *)"g:colors_name");
7133     if (p != NULL)
7134     {
7135        /* The value of g:colors_name could be freed when sourcing the script,
7136 	* making "p" invalid, so copy it. */
7137        char_u *copy_p = vim_strsave(p);
7138        int    r;
7139 
7140        if (copy_p != NULL)
7141        {
7142 	   r = load_colors(copy_p);
7143 	   vim_free(copy_p);
7144 	   if (r == OK)
7145 	       return;
7146        }
7147     }
7148 
7149 #endif
7150 
7151     /*
7152      * Didn't use a color file, use the compiled-in colors.
7153      */
7154     if (both)
7155     {
7156 	had_both = TRUE;
7157 	pp = highlight_init_both;
7158 	for (i = 0; pp[i] != NULL; ++i)
7159 	    do_highlight((char_u *)pp[i], reset, TRUE);
7160     }
7161     else if (!had_both)
7162 	/* Don't do anything before the call with both == TRUE from main().
7163 	 * Not everything has been setup then, and that call will overrule
7164 	 * everything anyway. */
7165 	return;
7166 
7167     if (*p_bg == 'l')
7168 	pp = highlight_init_light;
7169     else
7170 	pp = highlight_init_dark;
7171     for (i = 0; pp[i] != NULL; ++i)
7172 	do_highlight((char_u *)pp[i], reset, TRUE);
7173 
7174     /* Reverse looks ugly, but grey may not work for 8 colors.  Thus let it
7175      * depend on the number of colors available.
7176      * With 8 colors brown is equal to yellow, need to use black for Search fg
7177      * to avoid Statement highlighted text disappears.
7178      * Clear the attributes, needed when changing the t_Co value. */
7179     if (t_colors > 8)
7180 	do_highlight((char_u *)(*p_bg == 'l'
7181 		    ? "Visual cterm=NONE ctermbg=LightGrey"
7182 		    : "Visual cterm=NONE ctermbg=DarkGrey"), FALSE, TRUE);
7183     else
7184     {
7185 	do_highlight((char_u *)"Visual cterm=reverse ctermbg=NONE",
7186 								 FALSE, TRUE);
7187 	if (*p_bg == 'l')
7188 	    do_highlight((char_u *)"Search ctermfg=black", FALSE, TRUE);
7189     }
7190 
7191 #ifdef FEAT_SYN_HL
7192     /*
7193      * If syntax highlighting is enabled load the highlighting for it.
7194      */
7195     if (get_var_value((char_u *)"g:syntax_on") != NULL)
7196     {
7197 	static int	recursive = 0;
7198 
7199 	if (recursive >= 5)
7200 	    EMSG(_("E679: recursive loop loading syncolor.vim"));
7201 	else
7202 	{
7203 	    ++recursive;
7204 	    (void)source_runtime((char_u *)"syntax/syncolor.vim", DIP_ALL);
7205 	    --recursive;
7206 	}
7207     }
7208 #endif
7209 }
7210 
7211 /*
7212  * Load color file "name".
7213  * Return OK for success, FAIL for failure.
7214  */
7215     int
7216 load_colors(char_u *name)
7217 {
7218     char_u	*buf;
7219     int		retval = FAIL;
7220     static int	recursive = FALSE;
7221 
7222     /* When being called recursively, this is probably because setting
7223      * 'background' caused the highlighting to be reloaded.  This means it is
7224      * working, thus we should return OK. */
7225     if (recursive)
7226 	return OK;
7227 
7228     recursive = TRUE;
7229     buf = alloc((unsigned)(STRLEN(name) + 12));
7230     if (buf != NULL)
7231     {
7232 	sprintf((char *)buf, "colors/%s.vim", name);
7233 	retval = source_runtime(buf, DIP_START + DIP_OPT);
7234 	vim_free(buf);
7235 #ifdef FEAT_AUTOCMD
7236 	apply_autocmds(EVENT_COLORSCHEME, name, curbuf->b_fname, FALSE, curbuf);
7237 #endif
7238     }
7239     recursive = FALSE;
7240 
7241     return retval;
7242 }
7243 
7244 static char *(color_names[28]) = {
7245 	    "Black", "DarkBlue", "DarkGreen", "DarkCyan",
7246 	    "DarkRed", "DarkMagenta", "Brown", "DarkYellow",
7247 	    "Gray", "Grey", "LightGray", "LightGrey",
7248 	    "DarkGray", "DarkGrey",
7249 	    "Blue", "LightBlue", "Green", "LightGreen",
7250 	    "Cyan", "LightCyan", "Red", "LightRed", "Magenta",
7251 	    "LightMagenta", "Yellow", "LightYellow", "White", "NONE"};
7252 	    /* indices:
7253 	     * 0, 1, 2, 3,
7254 	     * 4, 5, 6, 7,
7255 	     * 8, 9, 10, 11,
7256 	     * 12, 13,
7257 	     * 14, 15, 16, 17,
7258 	     * 18, 19, 20, 21, 22,
7259 	     * 23, 24, 25, 26, 27 */
7260 static int color_numbers_16[28] = {0, 1, 2, 3,
7261 				 4, 5, 6, 6,
7262 				 7, 7, 7, 7,
7263 				 8, 8,
7264 				 9, 9, 10, 10,
7265 				 11, 11, 12, 12, 13,
7266 				 13, 14, 14, 15, -1};
7267 /* for xterm with 88 colors... */
7268 static int color_numbers_88[28] = {0, 4, 2, 6,
7269 				 1, 5, 32, 72,
7270 				 84, 84, 7, 7,
7271 				 82, 82,
7272 				 12, 43, 10, 61,
7273 				 14, 63, 9, 74, 13,
7274 				 75, 11, 78, 15, -1};
7275 /* for xterm with 256 colors... */
7276 static int color_numbers_256[28] = {0, 4, 2, 6,
7277 				 1, 5, 130, 130,
7278 				 248, 248, 7, 7,
7279 				 242, 242,
7280 				 12, 81, 10, 121,
7281 				 14, 159, 9, 224, 13,
7282 				 225, 11, 229, 15, -1};
7283 /* for terminals with less than 16 colors... */
7284 static int color_numbers_8[28] = {0, 4, 2, 6,
7285 				 1, 5, 3, 3,
7286 				 7, 7, 7, 7,
7287 				 0+8, 0+8,
7288 				 4+8, 4+8, 2+8, 2+8,
7289 				 6+8, 6+8, 1+8, 1+8, 5+8,
7290 				 5+8, 3+8, 3+8, 7+8, -1};
7291 
7292 /*
7293  * Lookup the "cterm" value to be used for color with index "idx" in
7294  * color_names[].
7295  * "boldp" will be set to TRUE or FALSE for a foreground color when using 8
7296  * colors, otherwise it will be unchanged.
7297  */
7298     int
7299 lookup_color(int idx, int foreground, int *boldp)
7300 {
7301     int		color = color_numbers_16[idx];
7302     char_u	*p;
7303 
7304     /* Use the _16 table to check if it's a valid color name. */
7305     if (color < 0)
7306 	return -1;
7307 
7308     if (t_colors == 8)
7309     {
7310 	/* t_Co is 8: use the 8 colors table */
7311 #if defined(__QNXNTO__)
7312 	color = color_numbers_8_qansi[idx];
7313 #else
7314 	color = color_numbers_8[idx];
7315 #endif
7316 	if (foreground)
7317 	{
7318 	    /* set/reset bold attribute to get light foreground
7319 	     * colors (on some terminals, e.g. "linux") */
7320 	    if (color & 8)
7321 		*boldp = TRUE;
7322 	    else
7323 		*boldp = FALSE;
7324 	}
7325 	color &= 7;	/* truncate to 8 colors */
7326     }
7327     else if (t_colors == 16 || t_colors == 88
7328 					   || t_colors >= 256)
7329     {
7330 	/*
7331 	 * Guess: if the termcap entry ends in 'm', it is
7332 	 * probably an xterm-like terminal.  Use the changed
7333 	 * order for colors.
7334 	 */
7335 	if (*T_CAF != NUL)
7336 	    p = T_CAF;
7337 	else
7338 	    p = T_CSF;
7339 	if (*p != NUL && (t_colors > 256
7340 			      || *(p + STRLEN(p) - 1) == 'm'))
7341 	{
7342 	    if (t_colors == 88)
7343 		color = color_numbers_88[idx];
7344 	    else if (t_colors >= 256)
7345 		color = color_numbers_256[idx];
7346 	    else
7347 		color = color_numbers_8[idx];
7348 	}
7349 #ifdef FEAT_TERMRESPONSE
7350 	if (t_colors >= 256 && color == 15 && is_mac_terminal)
7351 	    /* Terminal.app has a bug: 15 is light grey. Use white
7352 	     * from the color cube instead. */
7353 	    color = 231;
7354 #endif
7355     }
7356     return color;
7357 }
7358 
7359 /*
7360  * Handle the ":highlight .." command.
7361  * When using ":hi clear" this is called recursively for each group with
7362  * "forceit" and "init" both TRUE.
7363  */
7364     void
7365 do_highlight(
7366     char_u	*line,
7367     int		forceit,
7368     int		init)	    /* TRUE when called for initializing */
7369 {
7370     char_u	*name_end;
7371     char_u	*p;
7372     char_u	*linep;
7373     char_u	*key_start;
7374     char_u	*arg_start;
7375     char_u	*key = NULL, *arg = NULL;
7376     long	i;
7377     int		off;
7378     int		len;
7379     int		attr;
7380     int		id;
7381     int		idx;
7382     struct hl_group item_before;
7383     int		did_change = FALSE;
7384     int		dodefault = FALSE;
7385     int		doclear = FALSE;
7386     int		dolink = FALSE;
7387     int		error = FALSE;
7388     int		color;
7389     int		is_normal_group = FALSE;	/* "Normal" group */
7390 #ifdef FEAT_TERMINAL
7391     int		is_terminal_group = FALSE;	/* "Terminal" group */
7392 #endif
7393 #ifdef FEAT_GUI_X11
7394     int		is_menu_group = FALSE;		/* "Menu" group */
7395     int		is_scrollbar_group = FALSE;	/* "Scrollbar" group */
7396     int		is_tooltip_group = FALSE;	/* "Tooltip" group */
7397     int		do_colors = FALSE;		/* need to update colors? */
7398 #else
7399 # define is_menu_group 0
7400 # define is_tooltip_group 0
7401 #endif
7402 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
7403     int		did_highlight_changed = FALSE;
7404 #endif
7405 
7406     /*
7407      * If no argument, list current highlighting.
7408      */
7409     if (ends_excmd(*line))
7410     {
7411 	for (i = 1; i <= highlight_ga.ga_len && !got_int; ++i)
7412 	    /* TODO: only call when the group has attributes set */
7413 	    highlight_list_one((int)i);
7414 	return;
7415     }
7416 
7417     /*
7418      * Isolate the name.
7419      */
7420     name_end = skiptowhite(line);
7421     linep = skipwhite(name_end);
7422 
7423     /*
7424      * Check for "default" argument.
7425      */
7426     if (STRNCMP(line, "default", name_end - line) == 0)
7427     {
7428 	dodefault = TRUE;
7429 	line = linep;
7430 	name_end = skiptowhite(line);
7431 	linep = skipwhite(name_end);
7432     }
7433 
7434     /*
7435      * Check for "clear" or "link" argument.
7436      */
7437     if (STRNCMP(line, "clear", name_end - line) == 0)
7438 	doclear = TRUE;
7439     if (STRNCMP(line, "link", name_end - line) == 0)
7440 	dolink = TRUE;
7441 
7442     /*
7443      * ":highlight {group-name}": list highlighting for one group.
7444      */
7445     if (!doclear && !dolink && ends_excmd(*linep))
7446     {
7447 	id = syn_namen2id(line, (int)(name_end - line));
7448 	if (id == 0)
7449 	    EMSG2(_("E411: highlight group not found: %s"), line);
7450 	else
7451 	    highlight_list_one(id);
7452 	return;
7453     }
7454 
7455     /*
7456      * Handle ":highlight link {from} {to}" command.
7457      */
7458     if (dolink)
7459     {
7460 	char_u	    *from_start = linep;
7461 	char_u	    *from_end;
7462 	char_u	    *to_start;
7463 	char_u	    *to_end;
7464 	int	    from_id;
7465 	int	    to_id;
7466 
7467 	from_end = skiptowhite(from_start);
7468 	to_start = skipwhite(from_end);
7469 	to_end	 = skiptowhite(to_start);
7470 
7471 	if (ends_excmd(*from_start) || ends_excmd(*to_start))
7472 	{
7473 	    EMSG2(_("E412: Not enough arguments: \":highlight link %s\""),
7474 								  from_start);
7475 	    return;
7476 	}
7477 
7478 	if (!ends_excmd(*skipwhite(to_end)))
7479 	{
7480 	    EMSG2(_("E413: Too many arguments: \":highlight link %s\""), from_start);
7481 	    return;
7482 	}
7483 
7484 	from_id = syn_check_group(from_start, (int)(from_end - from_start));
7485 	if (STRNCMP(to_start, "NONE", 4) == 0)
7486 	    to_id = 0;
7487 	else
7488 	    to_id = syn_check_group(to_start, (int)(to_end - to_start));
7489 
7490 	if (from_id > 0 && (!init || HL_TABLE()[from_id - 1].sg_set == 0))
7491 	{
7492 	    /*
7493 	     * Don't allow a link when there already is some highlighting
7494 	     * for the group, unless '!' is used
7495 	     */
7496 	    if (to_id > 0 && !forceit && !init
7497 				   && hl_has_settings(from_id - 1, dodefault))
7498 	    {
7499 		if (sourcing_name == NULL && !dodefault)
7500 		    EMSG(_("E414: group has settings, highlight link ignored"));
7501 	    }
7502 	    else if (HL_TABLE()[from_id - 1].sg_link != to_id
7503 #ifdef FEAT_EVAL
7504 		    || HL_TABLE()[from_id - 1].sg_scriptID != current_SID
7505 #endif
7506 		    || HL_TABLE()[from_id - 1].sg_cleared)
7507 	    {
7508 		if (!init)
7509 		    HL_TABLE()[from_id - 1].sg_set |= SG_LINK;
7510 		HL_TABLE()[from_id - 1].sg_link = to_id;
7511 #ifdef FEAT_EVAL
7512 		HL_TABLE()[from_id - 1].sg_scriptID = current_SID;
7513 #endif
7514 		HL_TABLE()[from_id - 1].sg_cleared = FALSE;
7515 		redraw_all_later(SOME_VALID);
7516 
7517 		/* Only call highlight_changed() once after multiple changes. */
7518 		need_highlight_changed = TRUE;
7519 	    }
7520 	}
7521 
7522 	return;
7523     }
7524 
7525     if (doclear)
7526     {
7527 	/*
7528 	 * ":highlight clear [group]" command.
7529 	 */
7530 	line = linep;
7531 	if (ends_excmd(*line))
7532 	{
7533 #ifdef FEAT_GUI
7534 	    /* First, we do not destroy the old values, but allocate the new
7535 	     * ones and update the display. THEN we destroy the old values.
7536 	     * If we destroy the old values first, then the old values
7537 	     * (such as GuiFont's or GuiFontset's) will still be displayed but
7538 	     * invalid because they were free'd.
7539 	     */
7540 	    if (gui.in_use)
7541 	    {
7542 # ifdef FEAT_BEVAL_TIP
7543 		gui_init_tooltip_font();
7544 # endif
7545 # if defined(FEAT_MENU) && (defined(FEAT_GUI_ATHENA) || defined(FEAT_GUI_MOTIF))
7546 		gui_init_menu_font();
7547 # endif
7548 	    }
7549 # if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_X11)
7550 	    gui_mch_def_colors();
7551 # endif
7552 # ifdef FEAT_GUI_X11
7553 #  ifdef FEAT_MENU
7554 
7555 	    /* This only needs to be done when there is no Menu highlight
7556 	     * group defined by default, which IS currently the case.
7557 	     */
7558 	    gui_mch_new_menu_colors();
7559 #  endif
7560 	    if (gui.in_use)
7561 	    {
7562 		gui_new_scrollbar_colors();
7563 #  ifdef FEAT_BEVAL_GUI
7564 		gui_mch_new_tooltip_colors();
7565 #  endif
7566 #  ifdef FEAT_MENU
7567 		gui_mch_new_menu_font();
7568 #  endif
7569 	    }
7570 # endif
7571 
7572 	    /* Ok, we're done allocating the new default graphics items.
7573 	     * The screen should already be refreshed at this point.
7574 	     * It is now Ok to clear out the old data.
7575 	     */
7576 #endif
7577 #ifdef FEAT_EVAL
7578 	    do_unlet((char_u *)"colors_name", TRUE);
7579 #endif
7580 	    restore_cterm_colors();
7581 
7582 	    /*
7583 	     * Clear all default highlight groups and load the defaults.
7584 	     */
7585 	    for (idx = 0; idx < highlight_ga.ga_len; ++idx)
7586 		highlight_clear(idx);
7587 	    init_highlight(TRUE, TRUE);
7588 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
7589 	    if (USE_24BIT)
7590 		highlight_gui_started();
7591 	    else
7592 #endif
7593 		highlight_changed();
7594 	    redraw_later_clear();
7595 	    return;
7596 	}
7597 	name_end = skiptowhite(line);
7598 	linep = skipwhite(name_end);
7599     }
7600 
7601     /*
7602      * Find the group name in the table.  If it does not exist yet, add it.
7603      */
7604     id = syn_check_group(line, (int)(name_end - line));
7605     if (id == 0)			/* failed (out of memory) */
7606 	return;
7607     idx = id - 1;			/* index is ID minus one */
7608 
7609     /* Return if "default" was used and the group already has settings. */
7610     if (dodefault && hl_has_settings(idx, TRUE))
7611 	return;
7612 
7613     /* Make a copy so we can check if any attribute actually changed. */
7614     item_before = HL_TABLE()[idx];
7615 
7616     if (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0)
7617 	is_normal_group = TRUE;
7618 #ifdef FEAT_TERMINAL
7619     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "TERMINAL") == 0)
7620 	is_terminal_group = TRUE;
7621 #endif
7622 #ifdef FEAT_GUI_X11
7623     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "MENU") == 0)
7624 	is_menu_group = TRUE;
7625     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "SCROLLBAR") == 0)
7626 	is_scrollbar_group = TRUE;
7627     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "TOOLTIP") == 0)
7628 	is_tooltip_group = TRUE;
7629 #endif
7630 
7631     /* Clear the highlighting for ":hi clear {group}" and ":hi clear". */
7632     if (doclear || (forceit && init))
7633     {
7634 	highlight_clear(idx);
7635 	if (!doclear)
7636 	    HL_TABLE()[idx].sg_set = 0;
7637     }
7638 
7639     if (!doclear)
7640       while (!ends_excmd(*linep))
7641       {
7642 	key_start = linep;
7643 	if (*linep == '=')
7644 	{
7645 	    EMSG2(_("E415: unexpected equal sign: %s"), key_start);
7646 	    error = TRUE;
7647 	    break;
7648 	}
7649 
7650 	/*
7651 	 * Isolate the key ("term", "ctermfg", "ctermbg", "font", "guifg" or
7652 	 * "guibg").
7653 	 */
7654 	while (*linep && !VIM_ISWHITE(*linep) && *linep != '=')
7655 	    ++linep;
7656 	vim_free(key);
7657 	key = vim_strnsave_up(key_start, (int)(linep - key_start));
7658 	if (key == NULL)
7659 	{
7660 	    error = TRUE;
7661 	    break;
7662 	}
7663 	linep = skipwhite(linep);
7664 
7665 	if (STRCMP(key, "NONE") == 0)
7666 	{
7667 	    if (!init || HL_TABLE()[idx].sg_set == 0)
7668 	    {
7669 		if (!init)
7670 		    HL_TABLE()[idx].sg_set |= SG_TERM+SG_CTERM+SG_GUI;
7671 		highlight_clear(idx);
7672 	    }
7673 	    continue;
7674 	}
7675 
7676 	/*
7677 	 * Check for the equal sign.
7678 	 */
7679 	if (*linep != '=')
7680 	{
7681 	    EMSG2(_("E416: missing equal sign: %s"), key_start);
7682 	    error = TRUE;
7683 	    break;
7684 	}
7685 	++linep;
7686 
7687 	/*
7688 	 * Isolate the argument.
7689 	 */
7690 	linep = skipwhite(linep);
7691 	if (*linep == '\'')		/* guifg='color name' */
7692 	{
7693 	    arg_start = ++linep;
7694 	    linep = vim_strchr(linep, '\'');
7695 	    if (linep == NULL)
7696 	    {
7697 		EMSG2(_(e_invarg2), key_start);
7698 		error = TRUE;
7699 		break;
7700 	    }
7701 	}
7702 	else
7703 	{
7704 	    arg_start = linep;
7705 	    linep = skiptowhite(linep);
7706 	}
7707 	if (linep == arg_start)
7708 	{
7709 	    EMSG2(_("E417: missing argument: %s"), key_start);
7710 	    error = TRUE;
7711 	    break;
7712 	}
7713 	vim_free(arg);
7714 	arg = vim_strnsave(arg_start, (int)(linep - arg_start));
7715 	if (arg == NULL)
7716 	{
7717 	    error = TRUE;
7718 	    break;
7719 	}
7720 	if (*linep == '\'')
7721 	    ++linep;
7722 
7723 	/*
7724 	 * Store the argument.
7725 	 */
7726 	if (  STRCMP(key, "TERM") == 0
7727 		|| STRCMP(key, "CTERM") == 0
7728 		|| STRCMP(key, "GUI") == 0)
7729 	{
7730 	    attr = 0;
7731 	    off = 0;
7732 	    while (arg[off] != NUL)
7733 	    {
7734 		for (i = sizeof(hl_attr_table) / sizeof(int); --i >= 0; )
7735 		{
7736 		    len = (int)STRLEN(hl_name_table[i]);
7737 		    if (STRNICMP(arg + off, hl_name_table[i], len) == 0)
7738 		    {
7739 			attr |= hl_attr_table[i];
7740 			off += len;
7741 			break;
7742 		    }
7743 		}
7744 		if (i < 0)
7745 		{
7746 		    EMSG2(_("E418: Illegal value: %s"), arg);
7747 		    error = TRUE;
7748 		    break;
7749 		}
7750 		if (arg[off] == ',')		/* another one follows */
7751 		    ++off;
7752 	    }
7753 	    if (error)
7754 		break;
7755 	    if (*key == 'T')
7756 	    {
7757 		if (!init || !(HL_TABLE()[idx].sg_set & SG_TERM))
7758 		{
7759 		    if (!init)
7760 			HL_TABLE()[idx].sg_set |= SG_TERM;
7761 		    HL_TABLE()[idx].sg_term = attr;
7762 		}
7763 	    }
7764 	    else if (*key == 'C')
7765 	    {
7766 		if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM))
7767 		{
7768 		    if (!init)
7769 			HL_TABLE()[idx].sg_set |= SG_CTERM;
7770 		    HL_TABLE()[idx].sg_cterm = attr;
7771 		    HL_TABLE()[idx].sg_cterm_bold = FALSE;
7772 		}
7773 	    }
7774 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
7775 	    else
7776 	    {
7777 		if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
7778 		{
7779 		    if (!init)
7780 			HL_TABLE()[idx].sg_set |= SG_GUI;
7781 		    HL_TABLE()[idx].sg_gui = attr;
7782 		}
7783 	    }
7784 #endif
7785 	}
7786 	else if (STRCMP(key, "FONT") == 0)
7787 	{
7788 	    /* in non-GUI fonts are simply ignored */
7789 #ifdef FEAT_GUI
7790 	    if (HL_TABLE()[idx].sg_font_name != NULL
7791 			     && STRCMP(HL_TABLE()[idx].sg_font_name, arg) == 0)
7792 	    {
7793 		/* Font name didn't change, ignore. */
7794 	    }
7795 	    else if (!gui.shell_created)
7796 	    {
7797 		/* GUI not started yet, always accept the name. */
7798 		vim_free(HL_TABLE()[idx].sg_font_name);
7799 		HL_TABLE()[idx].sg_font_name = vim_strsave(arg);
7800 		did_change = TRUE;
7801 	    }
7802 	    else
7803 	    {
7804 		GuiFont temp_sg_font = HL_TABLE()[idx].sg_font;
7805 # ifdef FEAT_XFONTSET
7806 		GuiFontset temp_sg_fontset = HL_TABLE()[idx].sg_fontset;
7807 # endif
7808 		/* First, save the current font/fontset.
7809 		 * Then try to allocate the font/fontset.
7810 		 * If the allocation fails, HL_TABLE()[idx].sg_font OR
7811 		 * sg_fontset will be set to NOFONT or NOFONTSET respectively.
7812 		 */
7813 
7814 		HL_TABLE()[idx].sg_font = NOFONT;
7815 # ifdef FEAT_XFONTSET
7816 		HL_TABLE()[idx].sg_fontset = NOFONTSET;
7817 # endif
7818 		hl_do_font(idx, arg, is_normal_group, is_menu_group,
7819 						     is_tooltip_group, FALSE);
7820 
7821 # ifdef FEAT_XFONTSET
7822 		if (HL_TABLE()[idx].sg_fontset != NOFONTSET)
7823 		{
7824 		    /* New fontset was accepted. Free the old one, if there
7825 		     * was one. */
7826 		    gui_mch_free_fontset(temp_sg_fontset);
7827 		    vim_free(HL_TABLE()[idx].sg_font_name);
7828 		    HL_TABLE()[idx].sg_font_name = vim_strsave(arg);
7829 		    did_change = TRUE;
7830 		}
7831 		else
7832 		    HL_TABLE()[idx].sg_fontset = temp_sg_fontset;
7833 # endif
7834 		if (HL_TABLE()[idx].sg_font != NOFONT)
7835 		{
7836 		    /* New font was accepted. Free the old one, if there was
7837 		     * one. */
7838 		    gui_mch_free_font(temp_sg_font);
7839 		    vim_free(HL_TABLE()[idx].sg_font_name);
7840 		    HL_TABLE()[idx].sg_font_name = vim_strsave(arg);
7841 		    did_change = TRUE;
7842 		}
7843 		else
7844 		    HL_TABLE()[idx].sg_font = temp_sg_font;
7845 	    }
7846 #endif
7847 	}
7848 	else if (STRCMP(key, "CTERMFG") == 0 || STRCMP(key, "CTERMBG") == 0)
7849 	{
7850 	  if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM))
7851 	  {
7852 	    if (!init)
7853 		HL_TABLE()[idx].sg_set |= SG_CTERM;
7854 
7855 	    /* When setting the foreground color, and previously the "bold"
7856 	     * flag was set for a light color, reset it now */
7857 	    if (key[5] == 'F' && HL_TABLE()[idx].sg_cterm_bold)
7858 	    {
7859 		HL_TABLE()[idx].sg_cterm &= ~HL_BOLD;
7860 		HL_TABLE()[idx].sg_cterm_bold = FALSE;
7861 	    }
7862 
7863 	    if (VIM_ISDIGIT(*arg))
7864 		color = atoi((char *)arg);
7865 	    else if (STRICMP(arg, "fg") == 0)
7866 	    {
7867 		if (cterm_normal_fg_color)
7868 		    color = cterm_normal_fg_color - 1;
7869 		else
7870 		{
7871 		    EMSG(_("E419: FG color unknown"));
7872 		    error = TRUE;
7873 		    break;
7874 		}
7875 	    }
7876 	    else if (STRICMP(arg, "bg") == 0)
7877 	    {
7878 		if (cterm_normal_bg_color > 0)
7879 		    color = cterm_normal_bg_color - 1;
7880 		else
7881 		{
7882 		    EMSG(_("E420: BG color unknown"));
7883 		    error = TRUE;
7884 		    break;
7885 		}
7886 	    }
7887 	    else
7888 	    {
7889 		int bold = MAYBE;
7890 
7891 #if defined(__QNXNTO__)
7892 		static int *color_numbers_8_qansi = color_numbers_8;
7893 		/* On qnx, the 8 & 16 color arrays are the same */
7894 		if (STRNCMP(T_NAME, "qansi", 5) == 0)
7895 		    color_numbers_8_qansi = color_numbers_16;
7896 #endif
7897 
7898 		/* reduce calls to STRICMP a bit, it can be slow */
7899 		off = TOUPPER_ASC(*arg);
7900 		for (i = (sizeof(color_names) / sizeof(char *)); --i >= 0; )
7901 		    if (off == color_names[i][0]
7902 				 && STRICMP(arg + 1, color_names[i] + 1) == 0)
7903 			break;
7904 		if (i < 0)
7905 		{
7906 		    EMSG2(_("E421: Color name or number not recognized: %s"), key_start);
7907 		    error = TRUE;
7908 		    break;
7909 		}
7910 
7911 		color = lookup_color(i, key[5] == 'F', &bold);
7912 
7913 		/* set/reset bold attribute to get light foreground
7914 		 * colors (on some terminals, e.g. "linux") */
7915 		if (bold == TRUE)
7916 		{
7917 		    HL_TABLE()[idx].sg_cterm |= HL_BOLD;
7918 		    HL_TABLE()[idx].sg_cterm_bold = TRUE;
7919 		}
7920 		else if (bold == FALSE)
7921 		    HL_TABLE()[idx].sg_cterm &= ~HL_BOLD;
7922 	    }
7923 
7924 	    /* Add one to the argument, to avoid zero.  Zero is used for
7925 	     * "NONE", then "color" is -1. */
7926 	    if (key[5] == 'F')
7927 	    {
7928 		HL_TABLE()[idx].sg_cterm_fg = color + 1;
7929 		if (is_normal_group)
7930 		{
7931 		    cterm_normal_fg_color = color + 1;
7932 		    cterm_normal_fg_bold = (HL_TABLE()[idx].sg_cterm & HL_BOLD);
7933 #ifdef FEAT_GUI
7934 		    /* Don't do this if the GUI is used. */
7935 		    if (!gui.in_use && !gui.starting)
7936 #endif
7937 		    {
7938 			must_redraw = CLEAR;
7939 			if (termcap_active && color >= 0)
7940 			    term_fg_color(color);
7941 		    }
7942 		}
7943 	    }
7944 	    else
7945 	    {
7946 		HL_TABLE()[idx].sg_cterm_bg = color + 1;
7947 		if (is_normal_group)
7948 		{
7949 		    cterm_normal_bg_color = color + 1;
7950 #ifdef FEAT_GUI
7951 		    /* Don't mess with 'background' if the GUI is used. */
7952 		    if (!gui.in_use && !gui.starting)
7953 #endif
7954 		    {
7955 			must_redraw = CLEAR;
7956 			if (color >= 0)
7957 			{
7958 			    int dark = -1;
7959 
7960 			    if (termcap_active)
7961 				term_bg_color(color);
7962 			    if (t_colors < 16)
7963 				dark = (color == 0 || color == 4);
7964 			    /* Limit the heuristic to the standard 16 colors */
7965 			    else if (color < 16)
7966 				dark = (color < 7 || color == 8);
7967 			    /* Set the 'background' option if the value is
7968 			     * wrong. */
7969 			    if (dark != -1
7970 				    && dark != (*p_bg == 'd')
7971 				    && !option_was_set((char_u *)"bg"))
7972 			    {
7973 				set_option_value((char_u *)"bg", 0L,
7974 				       (char_u *)(dark ? "dark" : "light"), 0);
7975 				reset_option_was_set((char_u *)"bg");
7976 			    }
7977 			}
7978 		    }
7979 		}
7980 	    }
7981 	  }
7982 	}
7983 	else if (STRCMP(key, "GUIFG") == 0)
7984 	{
7985 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
7986 	    char_u **namep = &HL_TABLE()[idx].sg_gui_fg_name;
7987 
7988 	    if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
7989 	    {
7990 		if (!init)
7991 		    HL_TABLE()[idx].sg_set |= SG_GUI;
7992 
7993 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
7994 		/* In GUI guifg colors are only used when recognized */
7995 		i = color_name2handle(arg);
7996 		if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !USE_24BIT)
7997 		{
7998 		    HL_TABLE()[idx].sg_gui_fg = i;
7999 # endif
8000 		    if (*namep == NULL || STRCMP(*namep, arg) != 0)
8001 		    {
8002 			vim_free(*namep);
8003 			if (STRCMP(arg, "NONE") != 0)
8004 			    *namep = vim_strsave(arg);
8005 			else
8006 			    *namep = NULL;
8007 			did_change = TRUE;
8008 		    }
8009 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8010 #  ifdef FEAT_GUI_X11
8011 		    if (is_menu_group && gui.menu_fg_pixel != i)
8012 		    {
8013 			gui.menu_fg_pixel = i;
8014 			do_colors = TRUE;
8015 		    }
8016 		    if (is_scrollbar_group && gui.scroll_fg_pixel != i)
8017 		    {
8018 			gui.scroll_fg_pixel = i;
8019 			do_colors = TRUE;
8020 		    }
8021 #   ifdef FEAT_BEVAL_GUI
8022 		    if (is_tooltip_group && gui.tooltip_fg_pixel != i)
8023 		    {
8024 			gui.tooltip_fg_pixel = i;
8025 			do_colors = TRUE;
8026 		    }
8027 #   endif
8028 #  endif
8029 		}
8030 # endif
8031 	    }
8032 #endif
8033 	}
8034 	else if (STRCMP(key, "GUIBG") == 0)
8035 	{
8036 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
8037 	    char_u **namep = &HL_TABLE()[idx].sg_gui_bg_name;
8038 
8039 	    if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
8040 	    {
8041 		if (!init)
8042 		    HL_TABLE()[idx].sg_set |= SG_GUI;
8043 
8044 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8045 		/* In GUI guifg colors are only used when recognized */
8046 		i = color_name2handle(arg);
8047 		if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !USE_24BIT)
8048 		{
8049 		    HL_TABLE()[idx].sg_gui_bg = i;
8050 # endif
8051 		    if (*namep == NULL || STRCMP(*namep, arg) != 0)
8052 		    {
8053 			vim_free(*namep);
8054 			if (STRCMP(arg, "NONE") != 0)
8055 			    *namep = vim_strsave(arg);
8056 			else
8057 			    *namep = NULL;
8058 			did_change = TRUE;
8059 		    }
8060 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8061 #  ifdef FEAT_GUI_X11
8062 		    if (is_menu_group && gui.menu_bg_pixel != i)
8063 		    {
8064 			gui.menu_bg_pixel = i;
8065 			do_colors = TRUE;
8066 		    }
8067 		    if (is_scrollbar_group && gui.scroll_bg_pixel != i)
8068 		    {
8069 			gui.scroll_bg_pixel = i;
8070 			do_colors = TRUE;
8071 		    }
8072 #   ifdef FEAT_BEVAL_GUI
8073 		    if (is_tooltip_group && gui.tooltip_bg_pixel != i)
8074 		    {
8075 			gui.tooltip_bg_pixel = i;
8076 			do_colors = TRUE;
8077 		    }
8078 #   endif
8079 #  endif
8080 		}
8081 # endif
8082 	    }
8083 #endif
8084 	}
8085 	else if (STRCMP(key, "GUISP") == 0)
8086 	{
8087 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
8088 	    char_u **namep = &HL_TABLE()[idx].sg_gui_sp_name;
8089 
8090 	    if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
8091 	    {
8092 		if (!init)
8093 		    HL_TABLE()[idx].sg_set |= SG_GUI;
8094 
8095 # ifdef FEAT_GUI
8096 		i = color_name2handle(arg);
8097 		if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !gui.in_use)
8098 		{
8099 		    HL_TABLE()[idx].sg_gui_sp = i;
8100 # endif
8101 		    if (*namep == NULL || STRCMP(*namep, arg) != 0)
8102 		    {
8103 			vim_free(*namep);
8104 			if (STRCMP(arg, "NONE") != 0)
8105 			    *namep = vim_strsave(arg);
8106 			else
8107 			    *namep = NULL;
8108 			did_change = TRUE;
8109 		    }
8110 # ifdef FEAT_GUI
8111 		}
8112 # endif
8113 	    }
8114 #endif
8115 	}
8116 	else if (STRCMP(key, "START") == 0 || STRCMP(key, "STOP") == 0)
8117 	{
8118 	    char_u	buf[100];
8119 	    char_u	*tname;
8120 
8121 	    if (!init)
8122 		HL_TABLE()[idx].sg_set |= SG_TERM;
8123 
8124 	    /*
8125 	     * The "start" and "stop"  arguments can be a literal escape
8126 	     * sequence, or a comma separated list of terminal codes.
8127 	     */
8128 	    if (STRNCMP(arg, "t_", 2) == 0)
8129 	    {
8130 		off = 0;
8131 		buf[0] = 0;
8132 		while (arg[off] != NUL)
8133 		{
8134 		    /* Isolate one termcap name */
8135 		    for (len = 0; arg[off + len] &&
8136 						 arg[off + len] != ','; ++len)
8137 			;
8138 		    tname = vim_strnsave(arg + off, len);
8139 		    if (tname == NULL)		/* out of memory */
8140 		    {
8141 			error = TRUE;
8142 			break;
8143 		    }
8144 		    /* lookup the escape sequence for the item */
8145 		    p = get_term_code(tname);
8146 		    vim_free(tname);
8147 		    if (p == NULL)	    /* ignore non-existing things */
8148 			p = (char_u *)"";
8149 
8150 		    /* Append it to the already found stuff */
8151 		    if ((int)(STRLEN(buf) + STRLEN(p)) >= 99)
8152 		    {
8153 			EMSG2(_("E422: terminal code too long: %s"), arg);
8154 			error = TRUE;
8155 			break;
8156 		    }
8157 		    STRCAT(buf, p);
8158 
8159 		    /* Advance to the next item */
8160 		    off += len;
8161 		    if (arg[off] == ',')	    /* another one follows */
8162 			++off;
8163 		}
8164 	    }
8165 	    else
8166 	    {
8167 		/*
8168 		 * Copy characters from arg[] to buf[], translating <> codes.
8169 		 */
8170 		for (p = arg, off = 0; off < 100 - 6 && *p; )
8171 		{
8172 		    len = trans_special(&p, buf + off, FALSE, FALSE);
8173 		    if (len > 0)	    /* recognized special char */
8174 			off += len;
8175 		    else		    /* copy as normal char */
8176 			buf[off++] = *p++;
8177 		}
8178 		buf[off] = NUL;
8179 	    }
8180 	    if (error)
8181 		break;
8182 
8183 	    if (STRCMP(buf, "NONE") == 0)	/* resetting the value */
8184 		p = NULL;
8185 	    else
8186 		p = vim_strsave(buf);
8187 	    if (key[2] == 'A')
8188 	    {
8189 		vim_free(HL_TABLE()[idx].sg_start);
8190 		HL_TABLE()[idx].sg_start = p;
8191 	    }
8192 	    else
8193 	    {
8194 		vim_free(HL_TABLE()[idx].sg_stop);
8195 		HL_TABLE()[idx].sg_stop = p;
8196 	    }
8197 	}
8198 	else
8199 	{
8200 	    EMSG2(_("E423: Illegal argument: %s"), key_start);
8201 	    error = TRUE;
8202 	    break;
8203 	}
8204 	HL_TABLE()[idx].sg_cleared = FALSE;
8205 
8206 	/*
8207 	 * When highlighting has been given for a group, don't link it.
8208 	 */
8209 	if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK))
8210 	    HL_TABLE()[idx].sg_link = 0;
8211 
8212 	/*
8213 	 * Continue with next argument.
8214 	 */
8215 	linep = skipwhite(linep);
8216       }
8217 
8218     /*
8219      * If there is an error, and it's a new entry, remove it from the table.
8220      */
8221     if (error && idx == highlight_ga.ga_len)
8222 	syn_unadd_group();
8223     else
8224     {
8225 	if (is_normal_group)
8226 	{
8227 	    HL_TABLE()[idx].sg_term_attr = 0;
8228 	    HL_TABLE()[idx].sg_cterm_attr = 0;
8229 #ifdef FEAT_GUI
8230 	    HL_TABLE()[idx].sg_gui_attr = 0;
8231 	    /*
8232 	     * Need to update all groups, because they might be using "bg"
8233 	     * and/or "fg", which have been changed now.
8234 	     */
8235 #endif
8236 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8237 	    if (USE_24BIT)
8238 	    {
8239 		highlight_gui_started();
8240 		did_highlight_changed = TRUE;
8241 		redraw_all_later(NOT_VALID);
8242 	    }
8243 #endif
8244 	}
8245 #ifdef FEAT_TERMINAL
8246 	else if (is_terminal_group)
8247 	    set_terminal_default_colors(
8248 		    HL_TABLE()[idx].sg_cterm_fg, HL_TABLE()[idx].sg_cterm_bg);
8249 #endif
8250 #ifdef FEAT_GUI_X11
8251 # ifdef FEAT_MENU
8252 	else if (is_menu_group)
8253 	{
8254 	    if (gui.in_use && do_colors)
8255 		gui_mch_new_menu_colors();
8256 	}
8257 # endif
8258 	else if (is_scrollbar_group)
8259 	{
8260 	    if (gui.in_use && do_colors)
8261 		gui_new_scrollbar_colors();
8262 	}
8263 # ifdef FEAT_BEVAL_GUI
8264 	else if (is_tooltip_group)
8265 	{
8266 	    if (gui.in_use && do_colors)
8267 		gui_mch_new_tooltip_colors();
8268 	}
8269 # endif
8270 #endif
8271 	else
8272 	    set_hl_attr(idx);
8273 #ifdef FEAT_EVAL
8274 	HL_TABLE()[idx].sg_scriptID = current_SID;
8275 #endif
8276     }
8277 
8278     vim_free(key);
8279     vim_free(arg);
8280 
8281     /* Only call highlight_changed() once, after a sequence of highlight
8282      * commands, and only if an attribute actually changed. */
8283     if ((did_change
8284 	   || memcmp(&HL_TABLE()[idx], &item_before, sizeof(item_before)) != 0)
8285 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8286 	    && !did_highlight_changed
8287 #endif
8288        )
8289     {
8290 	/* Do not trigger a redraw when highlighting is changed while
8291 	 * redrawing.  This may happen when evaluating 'statusline' changes the
8292 	 * StatusLine group. */
8293 	if (!updating_screen)
8294 	    redraw_all_later(NOT_VALID);
8295 	need_highlight_changed = TRUE;
8296     }
8297 }
8298 
8299 #if defined(EXITFREE) || defined(PROTO)
8300     void
8301 free_highlight(void)
8302 {
8303     int	    i;
8304 
8305     for (i = 0; i < highlight_ga.ga_len; ++i)
8306     {
8307 	highlight_clear(i);
8308 	vim_free(HL_TABLE()[i].sg_name);
8309 	vim_free(HL_TABLE()[i].sg_name_u);
8310     }
8311     ga_clear(&highlight_ga);
8312 }
8313 #endif
8314 
8315 /*
8316  * Reset the cterm colors to what they were before Vim was started, if
8317  * possible.  Otherwise reset them to zero.
8318  */
8319     void
8320 restore_cterm_colors(void)
8321 {
8322 #if defined(WIN3264) && !defined(FEAT_GUI_W32)
8323     /* Since t_me has been set, this probably means that the user
8324      * wants to use this as default colors.  Need to reset default
8325      * background/foreground colors. */
8326     mch_set_normal_colors();
8327 #else
8328     cterm_normal_fg_color = 0;
8329     cterm_normal_fg_bold = 0;
8330     cterm_normal_bg_color = 0;
8331 # ifdef FEAT_TERMGUICOLORS
8332     cterm_normal_fg_gui_color = INVALCOLOR;
8333     cterm_normal_bg_gui_color = INVALCOLOR;
8334 # endif
8335 #endif
8336 }
8337 
8338 /*
8339  * Return TRUE if highlight group "idx" has any settings.
8340  * When "check_link" is TRUE also check for an existing link.
8341  */
8342     static int
8343 hl_has_settings(int idx, int check_link)
8344 {
8345     return (   HL_TABLE()[idx].sg_term_attr != 0
8346 	    || HL_TABLE()[idx].sg_cterm_attr != 0
8347 	    || HL_TABLE()[idx].sg_cterm_fg != 0
8348 	    || HL_TABLE()[idx].sg_cterm_bg != 0
8349 #ifdef FEAT_GUI
8350 	    || HL_TABLE()[idx].sg_gui_attr != 0
8351 	    || HL_TABLE()[idx].sg_gui_fg_name != NULL
8352 	    || HL_TABLE()[idx].sg_gui_bg_name != NULL
8353 	    || HL_TABLE()[idx].sg_gui_sp_name != NULL
8354 	    || HL_TABLE()[idx].sg_font_name != NULL
8355 #endif
8356 	    || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK)));
8357 }
8358 
8359 /*
8360  * Clear highlighting for one group.
8361  */
8362     static void
8363 highlight_clear(int idx)
8364 {
8365     HL_TABLE()[idx].sg_cleared = TRUE;
8366 
8367     HL_TABLE()[idx].sg_term = 0;
8368     VIM_CLEAR(HL_TABLE()[idx].sg_start);
8369     VIM_CLEAR(HL_TABLE()[idx].sg_stop);
8370     HL_TABLE()[idx].sg_term_attr = 0;
8371     HL_TABLE()[idx].sg_cterm = 0;
8372     HL_TABLE()[idx].sg_cterm_bold = FALSE;
8373     HL_TABLE()[idx].sg_cterm_fg = 0;
8374     HL_TABLE()[idx].sg_cterm_bg = 0;
8375     HL_TABLE()[idx].sg_cterm_attr = 0;
8376 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
8377     HL_TABLE()[idx].sg_gui = 0;
8378     VIM_CLEAR(HL_TABLE()[idx].sg_gui_fg_name);
8379     VIM_CLEAR(HL_TABLE()[idx].sg_gui_bg_name);
8380     VIM_CLEAR(HL_TABLE()[idx].sg_gui_sp_name);
8381 #endif
8382 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8383     HL_TABLE()[idx].sg_gui_fg = INVALCOLOR;
8384     HL_TABLE()[idx].sg_gui_bg = INVALCOLOR;
8385 #endif
8386 #ifdef FEAT_GUI
8387     HL_TABLE()[idx].sg_gui_sp = INVALCOLOR;
8388     gui_mch_free_font(HL_TABLE()[idx].sg_font);
8389     HL_TABLE()[idx].sg_font = NOFONT;
8390 # ifdef FEAT_XFONTSET
8391     gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
8392     HL_TABLE()[idx].sg_fontset = NOFONTSET;
8393 # endif
8394     VIM_CLEAR(HL_TABLE()[idx].sg_font_name);
8395     HL_TABLE()[idx].sg_gui_attr = 0;
8396 #endif
8397 #ifdef FEAT_EVAL
8398     /* Clear the script ID only when there is no link, since that is not
8399      * cleared. */
8400     if (HL_TABLE()[idx].sg_link == 0)
8401 	HL_TABLE()[idx].sg_scriptID = 0;
8402 #endif
8403 }
8404 
8405 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
8406 /*
8407  * Set the normal foreground and background colors according to the "Normal"
8408  * highlighting group.  For X11 also set "Menu", "Scrollbar", and
8409  * "Tooltip" colors.
8410  */
8411     void
8412 set_normal_colors(void)
8413 {
8414 # ifdef FEAT_GUI
8415 #  ifdef FEAT_TERMGUICOLORS
8416     if (gui.in_use)
8417 #  endif
8418     {
8419 	if (set_group_colors((char_u *)"Normal",
8420 				 &gui.norm_pixel, &gui.back_pixel,
8421 				 FALSE, TRUE, FALSE))
8422 	{
8423 	    gui_mch_new_colors();
8424 	    must_redraw = CLEAR;
8425 	}
8426 #  ifdef FEAT_GUI_X11
8427 	if (set_group_colors((char_u *)"Menu",
8428 			     &gui.menu_fg_pixel, &gui.menu_bg_pixel,
8429 			     TRUE, FALSE, FALSE))
8430 	{
8431 #   ifdef FEAT_MENU
8432 	    gui_mch_new_menu_colors();
8433 #   endif
8434 	    must_redraw = CLEAR;
8435 	}
8436 #   ifdef FEAT_BEVAL_GUI
8437 	if (set_group_colors((char_u *)"Tooltip",
8438 			     &gui.tooltip_fg_pixel, &gui.tooltip_bg_pixel,
8439 			     FALSE, FALSE, TRUE))
8440 	{
8441 #    ifdef FEAT_TOOLBAR
8442 	    gui_mch_new_tooltip_colors();
8443 #    endif
8444 	    must_redraw = CLEAR;
8445 	}
8446 #   endif
8447 	if (set_group_colors((char_u *)"Scrollbar",
8448 			&gui.scroll_fg_pixel, &gui.scroll_bg_pixel,
8449 			FALSE, FALSE, FALSE))
8450 	{
8451 	    gui_new_scrollbar_colors();
8452 	    must_redraw = CLEAR;
8453 	}
8454 #  endif
8455     }
8456 # endif
8457 # ifdef FEAT_TERMGUICOLORS
8458 #  ifdef FEAT_GUI
8459     else
8460 #  endif
8461     {
8462 	int		idx;
8463 
8464 	idx = syn_name2id((char_u *)"Normal") - 1;
8465 	if (idx >= 0)
8466 	{
8467 	    gui_do_one_color(idx, FALSE, FALSE);
8468 
8469 	    /* If the normal fg or bg color changed a complete redraw is
8470 	     * required. */
8471 	    if (cterm_normal_fg_gui_color != HL_TABLE()[idx].sg_gui_fg
8472 		    || cterm_normal_bg_gui_color != HL_TABLE()[idx].sg_gui_bg)
8473 	    {
8474 		/* if the GUI color is INVALCOLOR then we use the default cterm
8475 		 * color */
8476 		cterm_normal_fg_gui_color = HL_TABLE()[idx].sg_gui_fg;
8477 		cterm_normal_bg_gui_color = HL_TABLE()[idx].sg_gui_bg;
8478 		must_redraw = CLEAR;
8479 	    }
8480 	}
8481     }
8482 # endif
8483 }
8484 #endif
8485 
8486 #if defined(FEAT_GUI) || defined(PROTO)
8487 /*
8488  * Set the colors for "Normal", "Menu", "Tooltip" or "Scrollbar".
8489  */
8490     static int
8491 set_group_colors(
8492     char_u	*name,
8493     guicolor_T	*fgp,
8494     guicolor_T	*bgp,
8495     int		do_menu,
8496     int		use_norm,
8497     int		do_tooltip)
8498 {
8499     int		idx;
8500 
8501     idx = syn_name2id(name) - 1;
8502     if (idx >= 0)
8503     {
8504 	gui_do_one_color(idx, do_menu, do_tooltip);
8505 
8506 	if (HL_TABLE()[idx].sg_gui_fg != INVALCOLOR)
8507 	    *fgp = HL_TABLE()[idx].sg_gui_fg;
8508 	else if (use_norm)
8509 	    *fgp = gui.def_norm_pixel;
8510 	if (HL_TABLE()[idx].sg_gui_bg != INVALCOLOR)
8511 	    *bgp = HL_TABLE()[idx].sg_gui_bg;
8512 	else if (use_norm)
8513 	    *bgp = gui.def_back_pixel;
8514 	return TRUE;
8515     }
8516     return FALSE;
8517 }
8518 
8519 /*
8520  * Get the font of the "Normal" group.
8521  * Returns "" when it's not found or not set.
8522  */
8523     char_u *
8524 hl_get_font_name(void)
8525 {
8526     int		id;
8527     char_u	*s;
8528 
8529     id = syn_name2id((char_u *)"Normal");
8530     if (id > 0)
8531     {
8532 	s = HL_TABLE()[id - 1].sg_font_name;
8533 	if (s != NULL)
8534 	    return s;
8535     }
8536     return (char_u *)"";
8537 }
8538 
8539 /*
8540  * Set font for "Normal" group.  Called by gui_mch_init_font() when a font has
8541  * actually chosen to be used.
8542  */
8543     void
8544 hl_set_font_name(char_u *font_name)
8545 {
8546     int	    id;
8547 
8548     id = syn_name2id((char_u *)"Normal");
8549     if (id > 0)
8550     {
8551 	vim_free(HL_TABLE()[id - 1].sg_font_name);
8552 	HL_TABLE()[id - 1].sg_font_name = vim_strsave(font_name);
8553     }
8554 }
8555 
8556 /*
8557  * Set background color for "Normal" group.  Called by gui_set_bg_color()
8558  * when the color is known.
8559  */
8560     void
8561 hl_set_bg_color_name(
8562     char_u  *name)	    /* must have been allocated */
8563 {
8564     int	    id;
8565 
8566     if (name != NULL)
8567     {
8568 	id = syn_name2id((char_u *)"Normal");
8569 	if (id > 0)
8570 	{
8571 	    vim_free(HL_TABLE()[id - 1].sg_gui_bg_name);
8572 	    HL_TABLE()[id - 1].sg_gui_bg_name = name;
8573 	}
8574     }
8575 }
8576 
8577 /*
8578  * Set foreground color for "Normal" group.  Called by gui_set_fg_color()
8579  * when the color is known.
8580  */
8581     void
8582 hl_set_fg_color_name(
8583     char_u  *name)	    /* must have been allocated */
8584 {
8585     int	    id;
8586 
8587     if (name != NULL)
8588     {
8589 	id = syn_name2id((char_u *)"Normal");
8590 	if (id > 0)
8591 	{
8592 	    vim_free(HL_TABLE()[id - 1].sg_gui_fg_name);
8593 	    HL_TABLE()[id - 1].sg_gui_fg_name = name;
8594 	}
8595     }
8596 }
8597 
8598 /*
8599  * Return the handle for a font name.
8600  * Returns NOFONT when failed.
8601  */
8602     static GuiFont
8603 font_name2handle(char_u *name)
8604 {
8605     if (STRCMP(name, "NONE") == 0)
8606 	return NOFONT;
8607 
8608     return gui_mch_get_font(name, TRUE);
8609 }
8610 
8611 # ifdef FEAT_XFONTSET
8612 /*
8613  * Return the handle for a fontset name.
8614  * Returns NOFONTSET when failed.
8615  */
8616     static GuiFontset
8617 fontset_name2handle(char_u *name, int fixed_width)
8618 {
8619     if (STRCMP(name, "NONE") == 0)
8620 	return NOFONTSET;
8621 
8622     return gui_mch_get_fontset(name, TRUE, fixed_width);
8623 }
8624 # endif
8625 
8626 /*
8627  * Get the font or fontset for one highlight group.
8628  */
8629     static void
8630 hl_do_font(
8631     int		idx,
8632     char_u	*arg,
8633     int		do_normal,		/* set normal font */
8634     int		do_menu UNUSED,		/* set menu font */
8635     int		do_tooltip UNUSED,	/* set tooltip font */
8636     int		free_font)		/* free current font/fontset */
8637 {
8638 # ifdef FEAT_XFONTSET
8639     /* If 'guifontset' is not empty, first try using the name as a
8640      * fontset.  If that doesn't work, use it as a font name. */
8641     if (*p_guifontset != NUL
8642 #  ifdef FONTSET_ALWAYS
8643 	|| do_menu
8644 #  endif
8645 #  ifdef FEAT_BEVAL_TIP
8646 	/* In Athena & Motif, the Tooltip highlight group is always a fontset */
8647 	|| do_tooltip
8648 #  endif
8649 	    )
8650     {
8651 	if (free_font)
8652 	    gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
8653 	HL_TABLE()[idx].sg_fontset = fontset_name2handle(arg, 0
8654 #  ifdef FONTSET_ALWAYS
8655 		|| do_menu
8656 #  endif
8657 #  ifdef FEAT_BEVAL_TIP
8658 		|| do_tooltip
8659 #  endif
8660 		);
8661     }
8662     if (HL_TABLE()[idx].sg_fontset != NOFONTSET)
8663     {
8664 	/* If it worked and it's the Normal group, use it as the normal
8665 	 * fontset.  Same for the Menu group. */
8666 	if (do_normal)
8667 	    gui_init_font(arg, TRUE);
8668 #   if (defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_ATHENA)) && defined(FEAT_MENU)
8669 	if (do_menu)
8670 	{
8671 #    ifdef FONTSET_ALWAYS
8672 	    gui.menu_fontset = HL_TABLE()[idx].sg_fontset;
8673 #    else
8674 	    /* YIKES!  This is a bug waiting to crash the program */
8675 	    gui.menu_font = HL_TABLE()[idx].sg_fontset;
8676 #    endif
8677 	    gui_mch_new_menu_font();
8678 	}
8679 #    ifdef FEAT_BEVAL_GUI
8680 	if (do_tooltip)
8681 	{
8682 	    /* The Athena widget set cannot currently handle switching between
8683 	     * displaying a single font and a fontset.
8684 	     * If the XtNinternational resource is set to True at widget
8685 	     * creation, then a fontset is always used, otherwise an
8686 	     * XFontStruct is used.
8687 	     */
8688 	    gui.tooltip_fontset = (XFontSet)HL_TABLE()[idx].sg_fontset;
8689 	    gui_mch_new_tooltip_font();
8690 	}
8691 #    endif
8692 #   endif
8693     }
8694     else
8695 # endif
8696     {
8697 	if (free_font)
8698 	    gui_mch_free_font(HL_TABLE()[idx].sg_font);
8699 	HL_TABLE()[idx].sg_font = font_name2handle(arg);
8700 	/* If it worked and it's the Normal group, use it as the
8701 	 * normal font.  Same for the Menu group. */
8702 	if (HL_TABLE()[idx].sg_font != NOFONT)
8703 	{
8704 	    if (do_normal)
8705 		gui_init_font(arg, FALSE);
8706 #ifndef FONTSET_ALWAYS
8707 # if (defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_ATHENA)) && defined(FEAT_MENU)
8708 	    if (do_menu)
8709 	    {
8710 		gui.menu_font = HL_TABLE()[idx].sg_font;
8711 		gui_mch_new_menu_font();
8712 	    }
8713 # endif
8714 #endif
8715 	}
8716     }
8717 }
8718 
8719 #endif /* FEAT_GUI */
8720 
8721 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
8722 /*
8723  * Return the handle for a color name.
8724  * Returns INVALCOLOR when failed.
8725  */
8726     guicolor_T
8727 color_name2handle(char_u *name)
8728 {
8729     if (STRCMP(name, "NONE") == 0)
8730 	return INVALCOLOR;
8731 
8732     if (STRICMP(name, "fg") == 0 || STRICMP(name, "foreground") == 0)
8733     {
8734 #if defined(FEAT_TERMGUICOLORS) && defined(FEAT_GUI)
8735 	if (gui.in_use)
8736 #endif
8737 #ifdef FEAT_GUI
8738 	    return gui.norm_pixel;
8739 #endif
8740 #ifdef FEAT_TERMGUICOLORS
8741 	if (cterm_normal_fg_gui_color != INVALCOLOR)
8742 	    return cterm_normal_fg_gui_color;
8743 	/* Guess that the foreground is black or white. */
8744 	return GUI_GET_COLOR((char_u *)(*p_bg == 'l' ? "black" : "white"));
8745 #endif
8746     }
8747     if (STRICMP(name, "bg") == 0 || STRICMP(name, "background") == 0)
8748     {
8749 #if defined(FEAT_TERMGUICOLORS) && defined(FEAT_GUI)
8750 	if (gui.in_use)
8751 #endif
8752 #ifdef FEAT_GUI
8753 	    return gui.back_pixel;
8754 #endif
8755 #ifdef FEAT_TERMGUICOLORS
8756 	if (cterm_normal_bg_gui_color != INVALCOLOR)
8757 	    return cterm_normal_bg_gui_color;
8758 	/* Guess that the background is white or black. */
8759 	return GUI_GET_COLOR((char_u *)(*p_bg == 'l' ? "white" : "black"));
8760 #endif
8761     }
8762 
8763     return GUI_GET_COLOR(name);
8764 }
8765 #endif
8766 
8767 /*
8768  * Table with the specifications for an attribute number.
8769  * Note that this table is used by ALL buffers.  This is required because the
8770  * GUI can redraw at any time for any buffer.
8771  */
8772 static garray_T	term_attr_table = {0, 0, 0, 0, NULL};
8773 
8774 #define TERM_ATTR_ENTRY(idx) ((attrentry_T *)term_attr_table.ga_data)[idx]
8775 
8776 static garray_T	cterm_attr_table = {0, 0, 0, 0, NULL};
8777 
8778 #define CTERM_ATTR_ENTRY(idx) ((attrentry_T *)cterm_attr_table.ga_data)[idx]
8779 
8780 #ifdef FEAT_GUI
8781 static garray_T	gui_attr_table = {0, 0, 0, 0, NULL};
8782 
8783 #define GUI_ATTR_ENTRY(idx) ((attrentry_T *)gui_attr_table.ga_data)[idx]
8784 #endif
8785 
8786 /*
8787  * Return the attr number for a set of colors and font.
8788  * Add a new entry to the term_attr_table, cterm_attr_table or gui_attr_table
8789  * if the combination is new.
8790  * Return 0 for error (no more room).
8791  */
8792     static int
8793 get_attr_entry(garray_T *table, attrentry_T *aep)
8794 {
8795     int		i;
8796     attrentry_T	*taep;
8797     static int	recursive = FALSE;
8798 
8799     /*
8800      * Init the table, in case it wasn't done yet.
8801      */
8802     table->ga_itemsize = sizeof(attrentry_T);
8803     table->ga_growsize = 7;
8804 
8805     /*
8806      * Try to find an entry with the same specifications.
8807      */
8808     for (i = 0; i < table->ga_len; ++i)
8809     {
8810 	taep = &(((attrentry_T *)table->ga_data)[i]);
8811 	if (	   aep->ae_attr == taep->ae_attr
8812 		&& (
8813 #ifdef FEAT_GUI
8814 		       (table == &gui_attr_table
8815 			&& (aep->ae_u.gui.fg_color == taep->ae_u.gui.fg_color
8816 			    && aep->ae_u.gui.bg_color
8817 						    == taep->ae_u.gui.bg_color
8818 			    && aep->ae_u.gui.sp_color
8819 						    == taep->ae_u.gui.sp_color
8820 			    && aep->ae_u.gui.font == taep->ae_u.gui.font
8821 #  ifdef FEAT_XFONTSET
8822 			    && aep->ae_u.gui.fontset == taep->ae_u.gui.fontset
8823 #  endif
8824 			    ))
8825 		    ||
8826 #endif
8827 		       (table == &term_attr_table
8828 			&& (aep->ae_u.term.start == NULL)
8829 					    == (taep->ae_u.term.start == NULL)
8830 			&& (aep->ae_u.term.start == NULL
8831 			    || STRCMP(aep->ae_u.term.start,
8832 						  taep->ae_u.term.start) == 0)
8833 			&& (aep->ae_u.term.stop == NULL)
8834 					     == (taep->ae_u.term.stop == NULL)
8835 			&& (aep->ae_u.term.stop == NULL
8836 			    || STRCMP(aep->ae_u.term.stop,
8837 						  taep->ae_u.term.stop) == 0))
8838 		    || (table == &cterm_attr_table
8839 			    && aep->ae_u.cterm.fg_color
8840 						  == taep->ae_u.cterm.fg_color
8841 			    && aep->ae_u.cterm.bg_color
8842 						  == taep->ae_u.cterm.bg_color
8843 #ifdef FEAT_TERMGUICOLORS
8844 			    && aep->ae_u.cterm.fg_rgb
8845 						    == taep->ae_u.cterm.fg_rgb
8846 			    && aep->ae_u.cterm.bg_rgb
8847 						    == taep->ae_u.cterm.bg_rgb
8848 #endif
8849 		       )))
8850 
8851 	return i + ATTR_OFF;
8852     }
8853 
8854     if (table->ga_len + ATTR_OFF > MAX_TYPENR)
8855     {
8856 	/*
8857 	 * Running out of attribute entries!  remove all attributes, and
8858 	 * compute new ones for all groups.
8859 	 * When called recursively, we are really out of numbers.
8860 	 */
8861 	if (recursive)
8862 	{
8863 	    EMSG(_("E424: Too many different highlighting attributes in use"));
8864 	    return 0;
8865 	}
8866 	recursive = TRUE;
8867 
8868 	clear_hl_tables();
8869 
8870 	must_redraw = CLEAR;
8871 
8872 	for (i = 0; i < highlight_ga.ga_len; ++i)
8873 	    set_hl_attr(i);
8874 
8875 	recursive = FALSE;
8876     }
8877 
8878     /*
8879      * This is a new combination of colors and font, add an entry.
8880      */
8881     if (ga_grow(table, 1) == FAIL)
8882 	return 0;
8883 
8884     taep = &(((attrentry_T *)table->ga_data)[table->ga_len]);
8885     vim_memset(taep, 0, sizeof(attrentry_T));
8886     taep->ae_attr = aep->ae_attr;
8887 #ifdef FEAT_GUI
8888     if (table == &gui_attr_table)
8889     {
8890 	taep->ae_u.gui.fg_color = aep->ae_u.gui.fg_color;
8891 	taep->ae_u.gui.bg_color = aep->ae_u.gui.bg_color;
8892 	taep->ae_u.gui.sp_color = aep->ae_u.gui.sp_color;
8893 	taep->ae_u.gui.font = aep->ae_u.gui.font;
8894 # ifdef FEAT_XFONTSET
8895 	taep->ae_u.gui.fontset = aep->ae_u.gui.fontset;
8896 # endif
8897     }
8898 #endif
8899     if (table == &term_attr_table)
8900     {
8901 	if (aep->ae_u.term.start == NULL)
8902 	    taep->ae_u.term.start = NULL;
8903 	else
8904 	    taep->ae_u.term.start = vim_strsave(aep->ae_u.term.start);
8905 	if (aep->ae_u.term.stop == NULL)
8906 	    taep->ae_u.term.stop = NULL;
8907 	else
8908 	    taep->ae_u.term.stop = vim_strsave(aep->ae_u.term.stop);
8909     }
8910     else if (table == &cterm_attr_table)
8911     {
8912 	taep->ae_u.cterm.fg_color = aep->ae_u.cterm.fg_color;
8913 	taep->ae_u.cterm.bg_color = aep->ae_u.cterm.bg_color;
8914 #ifdef FEAT_TERMGUICOLORS
8915 	taep->ae_u.cterm.fg_rgb = aep->ae_u.cterm.fg_rgb;
8916 	taep->ae_u.cterm.bg_rgb = aep->ae_u.cterm.bg_rgb;
8917 #endif
8918     }
8919     ++table->ga_len;
8920     return (table->ga_len - 1 + ATTR_OFF);
8921 }
8922 
8923 /*
8924  * Get an attribute index for a cterm entry.
8925  * Uses an existing entry when possible or adds one when needed.
8926  */
8927     int
8928 get_cterm_attr_idx(int attr, int fg, int bg)
8929 {
8930     attrentry_T		at_en;
8931 
8932     vim_memset(&at_en, 0, sizeof(attrentry_T));
8933 #ifdef FEAT_TERMGUICOLORS
8934     at_en.ae_u.cterm.fg_rgb = INVALCOLOR;
8935     at_en.ae_u.cterm.bg_rgb = INVALCOLOR;
8936 #endif
8937     at_en.ae_attr = attr;
8938     at_en.ae_u.cterm.fg_color = fg;
8939     at_en.ae_u.cterm.bg_color = bg;
8940     return get_attr_entry(&cterm_attr_table, &at_en);
8941 }
8942 
8943 #if defined(FEAT_TERMGUICOLORS) || defined(PROTO)
8944 /*
8945  * Get an attribute index for a 'termguicolors' entry.
8946  * Uses an existing entry when possible or adds one when needed.
8947  */
8948     int
8949 get_tgc_attr_idx(int attr, guicolor_T fg, guicolor_T bg)
8950 {
8951     attrentry_T		at_en;
8952 
8953     vim_memset(&at_en, 0, sizeof(attrentry_T));
8954     at_en.ae_attr = attr;
8955     if (fg == INVALCOLOR && bg == INVALCOLOR)
8956     {
8957 	/* If both GUI colors are not set fall back to the cterm colors.  Helps
8958 	 * if the GUI only has an attribute, such as undercurl. */
8959 	at_en.ae_u.cterm.fg_rgb = CTERMCOLOR;
8960 	at_en.ae_u.cterm.bg_rgb = CTERMCOLOR;
8961     }
8962     else
8963     {
8964 	at_en.ae_u.cterm.fg_rgb = fg;
8965 	at_en.ae_u.cterm.bg_rgb = bg;
8966     }
8967     return get_attr_entry(&cterm_attr_table, &at_en);
8968 }
8969 #endif
8970 
8971 #if defined(FEAT_GUI) || defined(PROTO)
8972 /*
8973  * Get an attribute index for a cterm entry.
8974  * Uses an existing entry when possible or adds one when needed.
8975  */
8976     int
8977 get_gui_attr_idx(int attr, guicolor_T fg, guicolor_T bg)
8978 {
8979     attrentry_T		at_en;
8980 
8981     vim_memset(&at_en, 0, sizeof(attrentry_T));
8982     at_en.ae_attr = attr;
8983     at_en.ae_u.gui.fg_color = fg;
8984     at_en.ae_u.gui.bg_color = bg;
8985     return get_attr_entry(&gui_attr_table, &at_en);
8986 }
8987 #endif
8988 
8989 /*
8990  * Clear all highlight tables.
8991  */
8992     void
8993 clear_hl_tables(void)
8994 {
8995     int		i;
8996     attrentry_T	*taep;
8997 
8998 #ifdef FEAT_GUI
8999     ga_clear(&gui_attr_table);
9000 #endif
9001     for (i = 0; i < term_attr_table.ga_len; ++i)
9002     {
9003 	taep = &(((attrentry_T *)term_attr_table.ga_data)[i]);
9004 	vim_free(taep->ae_u.term.start);
9005 	vim_free(taep->ae_u.term.stop);
9006     }
9007     ga_clear(&term_attr_table);
9008     ga_clear(&cterm_attr_table);
9009 }
9010 
9011 #if defined(FEAT_SYN_HL) || defined(FEAT_SPELL) || defined(PROTO)
9012 /*
9013  * Combine special attributes (e.g., for spelling) with other attributes
9014  * (e.g., for syntax highlighting).
9015  * "prim_attr" overrules "char_attr".
9016  * This creates a new group when required.
9017  * Since we expect there to be few spelling mistakes we don't cache the
9018  * result.
9019  * Return the resulting attributes.
9020  */
9021     int
9022 hl_combine_attr(int char_attr, int prim_attr)
9023 {
9024     attrentry_T *char_aep = NULL;
9025     attrentry_T *spell_aep;
9026     attrentry_T new_en;
9027 
9028     if (char_attr == 0)
9029 	return prim_attr;
9030     if (char_attr <= HL_ALL && prim_attr <= HL_ALL)
9031 	return ATTR_COMBINE(char_attr, prim_attr);
9032 #ifdef FEAT_GUI
9033     if (gui.in_use)
9034     {
9035 	if (char_attr > HL_ALL)
9036 	    char_aep = syn_gui_attr2entry(char_attr);
9037 	if (char_aep != NULL)
9038 	    new_en = *char_aep;
9039 	else
9040 	{
9041 	    vim_memset(&new_en, 0, sizeof(new_en));
9042 	    new_en.ae_u.gui.fg_color = INVALCOLOR;
9043 	    new_en.ae_u.gui.bg_color = INVALCOLOR;
9044 	    new_en.ae_u.gui.sp_color = INVALCOLOR;
9045 	    if (char_attr <= HL_ALL)
9046 		new_en.ae_attr = char_attr;
9047 	}
9048 
9049 	if (prim_attr <= HL_ALL)
9050 	    new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, prim_attr);
9051 	else
9052 	{
9053 	    spell_aep = syn_gui_attr2entry(prim_attr);
9054 	    if (spell_aep != NULL)
9055 	    {
9056 		new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr,
9057 							   spell_aep->ae_attr);
9058 		if (spell_aep->ae_u.gui.fg_color != INVALCOLOR)
9059 		    new_en.ae_u.gui.fg_color = spell_aep->ae_u.gui.fg_color;
9060 		if (spell_aep->ae_u.gui.bg_color != INVALCOLOR)
9061 		    new_en.ae_u.gui.bg_color = spell_aep->ae_u.gui.bg_color;
9062 		if (spell_aep->ae_u.gui.sp_color != INVALCOLOR)
9063 		    new_en.ae_u.gui.sp_color = spell_aep->ae_u.gui.sp_color;
9064 		if (spell_aep->ae_u.gui.font != NOFONT)
9065 		    new_en.ae_u.gui.font = spell_aep->ae_u.gui.font;
9066 # ifdef FEAT_XFONTSET
9067 		if (spell_aep->ae_u.gui.fontset != NOFONTSET)
9068 		    new_en.ae_u.gui.fontset = spell_aep->ae_u.gui.fontset;
9069 # endif
9070 	    }
9071 	}
9072 	return get_attr_entry(&gui_attr_table, &new_en);
9073     }
9074 #endif
9075 
9076     if (IS_CTERM)
9077     {
9078 	if (char_attr > HL_ALL)
9079 	    char_aep = syn_cterm_attr2entry(char_attr);
9080 	if (char_aep != NULL)
9081 	    new_en = *char_aep;
9082 	else
9083 	{
9084 	    vim_memset(&new_en, 0, sizeof(new_en));
9085 #ifdef FEAT_TERMGUICOLORS
9086 	    new_en.ae_u.cterm.bg_rgb = INVALCOLOR;
9087 	    new_en.ae_u.cterm.fg_rgb = INVALCOLOR;
9088 #endif
9089 	    if (char_attr <= HL_ALL)
9090 		new_en.ae_attr = char_attr;
9091 	}
9092 
9093 	if (prim_attr <= HL_ALL)
9094 		new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, prim_attr);
9095 	else
9096 	{
9097 	    spell_aep = syn_cterm_attr2entry(prim_attr);
9098 	    if (spell_aep != NULL)
9099 	    {
9100 		new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr,
9101 							   spell_aep->ae_attr);
9102 		if (spell_aep->ae_u.cterm.fg_color > 0)
9103 		    new_en.ae_u.cterm.fg_color = spell_aep->ae_u.cterm.fg_color;
9104 		if (spell_aep->ae_u.cterm.bg_color > 0)
9105 		    new_en.ae_u.cterm.bg_color = spell_aep->ae_u.cterm.bg_color;
9106 #ifdef FEAT_TERMGUICOLORS
9107 		/* If both fg and bg are not set fall back to cterm colors.
9108 		 * Helps for SpellBad which uses undercurl in the GUI. */
9109 		if (COLOR_INVALID(spell_aep->ae_u.cterm.fg_rgb)
9110 			&& COLOR_INVALID(spell_aep->ae_u.cterm.bg_rgb))
9111 		{
9112 		    if (spell_aep->ae_u.cterm.fg_color > 0)
9113 			new_en.ae_u.cterm.fg_rgb = CTERMCOLOR;
9114 		    if (spell_aep->ae_u.cterm.bg_color > 0)
9115 			new_en.ae_u.cterm.bg_rgb = CTERMCOLOR;
9116 		}
9117 		else
9118 		{
9119 		    if (spell_aep->ae_u.cterm.fg_rgb != INVALCOLOR)
9120 			new_en.ae_u.cterm.fg_rgb = spell_aep->ae_u.cterm.fg_rgb;
9121 		    if (spell_aep->ae_u.cterm.bg_rgb != INVALCOLOR)
9122 			new_en.ae_u.cterm.bg_rgb = spell_aep->ae_u.cterm.bg_rgb;
9123 		}
9124 #endif
9125 	    }
9126 	}
9127 	return get_attr_entry(&cterm_attr_table, &new_en);
9128     }
9129 
9130     if (char_attr > HL_ALL)
9131 	char_aep = syn_term_attr2entry(char_attr);
9132     if (char_aep != NULL)
9133 	new_en = *char_aep;
9134     else
9135     {
9136 	vim_memset(&new_en, 0, sizeof(new_en));
9137 	if (char_attr <= HL_ALL)
9138 	    new_en.ae_attr = char_attr;
9139     }
9140 
9141     if (prim_attr <= HL_ALL)
9142 	new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, prim_attr);
9143     else
9144     {
9145 	spell_aep = syn_term_attr2entry(prim_attr);
9146 	if (spell_aep != NULL)
9147 	{
9148 	    new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, spell_aep->ae_attr);
9149 	    if (spell_aep->ae_u.term.start != NULL)
9150 	    {
9151 		new_en.ae_u.term.start = spell_aep->ae_u.term.start;
9152 		new_en.ae_u.term.stop = spell_aep->ae_u.term.stop;
9153 	    }
9154 	}
9155     }
9156     return get_attr_entry(&term_attr_table, &new_en);
9157 }
9158 #endif
9159 
9160 #ifdef FEAT_GUI
9161 
9162     attrentry_T *
9163 syn_gui_attr2entry(int attr)
9164 {
9165     attr -= ATTR_OFF;
9166     if (attr >= gui_attr_table.ga_len)	    /* did ":syntax clear" */
9167 	return NULL;
9168     return &(GUI_ATTR_ENTRY(attr));
9169 }
9170 #endif /* FEAT_GUI */
9171 
9172 /*
9173  * Get the highlight attributes (HL_BOLD etc.) from an attribute nr.
9174  * Only to be used when "attr" > HL_ALL.
9175  */
9176     int
9177 syn_attr2attr(int attr)
9178 {
9179     attrentry_T	*aep;
9180 
9181 #ifdef FEAT_GUI
9182     if (gui.in_use)
9183 	aep = syn_gui_attr2entry(attr);
9184     else
9185 #endif
9186 	if (IS_CTERM)
9187 	    aep = syn_cterm_attr2entry(attr);
9188 	else
9189 	    aep = syn_term_attr2entry(attr);
9190 
9191     if (aep == NULL)	    /* highlighting not set */
9192 	return 0;
9193     return aep->ae_attr;
9194 }
9195 
9196 
9197     attrentry_T *
9198 syn_term_attr2entry(int attr)
9199 {
9200     attr -= ATTR_OFF;
9201     if (attr >= term_attr_table.ga_len)	    /* did ":syntax clear" */
9202 	return NULL;
9203     return &(TERM_ATTR_ENTRY(attr));
9204 }
9205 
9206     attrentry_T *
9207 syn_cterm_attr2entry(int attr)
9208 {
9209     attr -= ATTR_OFF;
9210     if (attr >= cterm_attr_table.ga_len)	/* did ":syntax clear" */
9211 	return NULL;
9212     return &(CTERM_ATTR_ENTRY(attr));
9213 }
9214 
9215 #define LIST_ATTR   1
9216 #define LIST_STRING 2
9217 #define LIST_INT    3
9218 
9219     static void
9220 highlight_list_one(int id)
9221 {
9222     struct hl_group	*sgp;
9223     int			didh = FALSE;
9224 
9225     sgp = &HL_TABLE()[id - 1];	    /* index is ID minus one */
9226 
9227     didh = highlight_list_arg(id, didh, LIST_ATTR,
9228 				    sgp->sg_term, NULL, "term");
9229     didh = highlight_list_arg(id, didh, LIST_STRING,
9230 				    0, sgp->sg_start, "start");
9231     didh = highlight_list_arg(id, didh, LIST_STRING,
9232 				    0, sgp->sg_stop, "stop");
9233 
9234     didh = highlight_list_arg(id, didh, LIST_ATTR,
9235 				    sgp->sg_cterm, NULL, "cterm");
9236     didh = highlight_list_arg(id, didh, LIST_INT,
9237 				    sgp->sg_cterm_fg, NULL, "ctermfg");
9238     didh = highlight_list_arg(id, didh, LIST_INT,
9239 				    sgp->sg_cterm_bg, NULL, "ctermbg");
9240 
9241 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
9242     didh = highlight_list_arg(id, didh, LIST_ATTR,
9243 				    sgp->sg_gui, NULL, "gui");
9244     didh = highlight_list_arg(id, didh, LIST_STRING,
9245 				    0, sgp->sg_gui_fg_name, "guifg");
9246     didh = highlight_list_arg(id, didh, LIST_STRING,
9247 				    0, sgp->sg_gui_bg_name, "guibg");
9248     didh = highlight_list_arg(id, didh, LIST_STRING,
9249 				    0, sgp->sg_gui_sp_name, "guisp");
9250 #endif
9251 #ifdef FEAT_GUI
9252     didh = highlight_list_arg(id, didh, LIST_STRING,
9253 				    0, sgp->sg_font_name, "font");
9254 #endif
9255 
9256     if (sgp->sg_link && !got_int)
9257     {
9258 	(void)syn_list_header(didh, 9999, id);
9259 	didh = TRUE;
9260 	msg_puts_attr((char_u *)"links to", HL_ATTR(HLF_D));
9261 	msg_putchar(' ');
9262 	msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name);
9263     }
9264 
9265     if (!didh)
9266 	highlight_list_arg(id, didh, LIST_STRING, 0, (char_u *)"cleared", "");
9267 #ifdef FEAT_EVAL
9268     if (p_verbose > 0)
9269 	last_set_msg(sgp->sg_scriptID);
9270 #endif
9271 }
9272 
9273     static int
9274 highlight_list_arg(
9275     int		id,
9276     int		didh,
9277     int		type,
9278     int		iarg,
9279     char_u	*sarg,
9280     char	*name)
9281 {
9282     char_u	buf[100];
9283     char_u	*ts;
9284     int		i;
9285 
9286     if (got_int)
9287 	return FALSE;
9288     if (type == LIST_STRING ? (sarg != NULL) : (iarg != 0))
9289     {
9290 	ts = buf;
9291 	if (type == LIST_INT)
9292 	    sprintf((char *)buf, "%d", iarg - 1);
9293 	else if (type == LIST_STRING)
9294 	    ts = sarg;
9295 	else /* type == LIST_ATTR */
9296 	{
9297 	    buf[0] = NUL;
9298 	    for (i = 0; hl_attr_table[i] != 0; ++i)
9299 	    {
9300 		if (iarg & hl_attr_table[i])
9301 		{
9302 		    if (buf[0] != NUL)
9303 			vim_strcat(buf, (char_u *)",", 100);
9304 		    vim_strcat(buf, (char_u *)hl_name_table[i], 100);
9305 		    iarg &= ~hl_attr_table[i];	    /* don't want "inverse" */
9306 		}
9307 	    }
9308 	}
9309 
9310 	(void)syn_list_header(didh,
9311 			       (int)(vim_strsize(ts) + STRLEN(name) + 1), id);
9312 	didh = TRUE;
9313 	if (!got_int)
9314 	{
9315 	    if (*name != NUL)
9316 	    {
9317 		MSG_PUTS_ATTR(name, HL_ATTR(HLF_D));
9318 		MSG_PUTS_ATTR("=", HL_ATTR(HLF_D));
9319 	    }
9320 	    msg_outtrans(ts);
9321 	}
9322     }
9323     return didh;
9324 }
9325 
9326 #if (((defined(FEAT_EVAL) || defined(FEAT_PRINTER))) && defined(FEAT_SYN_HL)) || defined(PROTO)
9327 /*
9328  * Return "1" if highlight group "id" has attribute "flag".
9329  * Return NULL otherwise.
9330  */
9331     char_u *
9332 highlight_has_attr(
9333     int		id,
9334     int		flag,
9335     int		modec)	/* 'g' for GUI, 'c' for cterm, 't' for term */
9336 {
9337     int		attr;
9338 
9339     if (id <= 0 || id > highlight_ga.ga_len)
9340 	return NULL;
9341 
9342 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
9343     if (modec == 'g')
9344 	attr = HL_TABLE()[id - 1].sg_gui;
9345     else
9346 #endif
9347 	 if (modec == 'c')
9348 	attr = HL_TABLE()[id - 1].sg_cterm;
9349     else
9350 	attr = HL_TABLE()[id - 1].sg_term;
9351 
9352     if (attr & flag)
9353 	return (char_u *)"1";
9354     return NULL;
9355 }
9356 #endif
9357 
9358 #if (defined(FEAT_SYN_HL) && defined(FEAT_EVAL)) || defined(PROTO)
9359 /*
9360  * Return color name of highlight group "id".
9361  */
9362     char_u *
9363 highlight_color(
9364     int		id,
9365     char_u	*what,	/* "font", "fg", "bg", "sp", "fg#", "bg#" or "sp#" */
9366     int		modec)	/* 'g' for GUI, 'c' for cterm, 't' for term */
9367 {
9368     static char_u	name[20];
9369     int			n;
9370     int			fg = FALSE;
9371     int			sp = FALSE;
9372     int			font = FALSE;
9373 
9374     if (id <= 0 || id > highlight_ga.ga_len)
9375 	return NULL;
9376 
9377     if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'g')
9378 	fg = TRUE;
9379     else if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'o'
9380 	     && TOLOWER_ASC(what[2]) == 'n' && TOLOWER_ASC(what[3]) == 't')
9381 	font = TRUE;
9382     else if (TOLOWER_ASC(what[0]) == 's' && TOLOWER_ASC(what[1]) == 'p')
9383 	sp = TRUE;
9384     else if (!(TOLOWER_ASC(what[0]) == 'b' && TOLOWER_ASC(what[1]) == 'g'))
9385 	return NULL;
9386     if (modec == 'g')
9387     {
9388 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
9389 #  ifdef FEAT_GUI
9390 	/* return font name */
9391 	if (font)
9392 	    return HL_TABLE()[id - 1].sg_font_name;
9393 #  endif
9394 
9395 	/* return #RRGGBB form (only possible when GUI is running) */
9396 	if ((USE_24BIT) && what[2] == '#')
9397 	{
9398 	    guicolor_T		color;
9399 	    long_u		rgb;
9400 	    static char_u	buf[10];
9401 
9402 	    if (fg)
9403 		color = HL_TABLE()[id - 1].sg_gui_fg;
9404 	    else if (sp)
9405 #  ifdef FEAT_GUI
9406 		color = HL_TABLE()[id - 1].sg_gui_sp;
9407 #  else
9408 		color = INVALCOLOR;
9409 #  endif
9410 	    else
9411 		color = HL_TABLE()[id - 1].sg_gui_bg;
9412 	    if (color == INVALCOLOR)
9413 		return NULL;
9414 	    rgb = (long_u)GUI_MCH_GET_RGB(color);
9415 	    sprintf((char *)buf, "#%02x%02x%02x",
9416 				      (unsigned)(rgb >> 16),
9417 				      (unsigned)(rgb >> 8) & 255,
9418 				      (unsigned)rgb & 255);
9419 	    return buf;
9420 	}
9421 # endif
9422 	if (fg)
9423 	    return (HL_TABLE()[id - 1].sg_gui_fg_name);
9424 	if (sp)
9425 	    return (HL_TABLE()[id - 1].sg_gui_sp_name);
9426 	return (HL_TABLE()[id - 1].sg_gui_bg_name);
9427     }
9428     if (font || sp)
9429 	return NULL;
9430     if (modec == 'c')
9431     {
9432 	if (fg)
9433 	    n = HL_TABLE()[id - 1].sg_cterm_fg - 1;
9434 	else
9435 	    n = HL_TABLE()[id - 1].sg_cterm_bg - 1;
9436 	if (n < 0)
9437 	    return NULL;
9438 	sprintf((char *)name, "%d", n);
9439 	return name;
9440     }
9441     /* term doesn't have color */
9442     return NULL;
9443 }
9444 #endif
9445 
9446 #if (defined(FEAT_SYN_HL) \
9447 	    && (defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)) \
9448 	&& defined(FEAT_PRINTER)) || defined(PROTO)
9449 /*
9450  * Return color name of highlight group "id" as RGB value.
9451  */
9452     long_u
9453 highlight_gui_color_rgb(
9454     int		id,
9455     int		fg)	/* TRUE = fg, FALSE = bg */
9456 {
9457     guicolor_T	color;
9458 
9459     if (id <= 0 || id > highlight_ga.ga_len)
9460 	return 0L;
9461 
9462     if (fg)
9463 	color = HL_TABLE()[id - 1].sg_gui_fg;
9464     else
9465 	color = HL_TABLE()[id - 1].sg_gui_bg;
9466 
9467     if (color == INVALCOLOR)
9468 	return 0L;
9469 
9470     return GUI_MCH_GET_RGB(color);
9471 }
9472 #endif
9473 
9474 /*
9475  * Output the syntax list header.
9476  * Return TRUE when started a new line.
9477  */
9478     static int
9479 syn_list_header(
9480     int	    did_header,		/* did header already */
9481     int	    outlen,		/* length of string that comes */
9482     int	    id)			/* highlight group id */
9483 {
9484     int	    endcol = 19;
9485     int	    newline = TRUE;
9486 
9487     if (!did_header)
9488     {
9489 	msg_putchar('\n');
9490 	if (got_int)
9491 	    return TRUE;
9492 	msg_outtrans(HL_TABLE()[id - 1].sg_name);
9493 	endcol = 15;
9494     }
9495     else if (msg_col + outlen + 1 >= Columns)
9496     {
9497 	msg_putchar('\n');
9498 	if (got_int)
9499 	    return TRUE;
9500     }
9501     else
9502     {
9503 	if (msg_col >= endcol)	/* wrap around is like starting a new line */
9504 	    newline = FALSE;
9505     }
9506 
9507     if (msg_col >= endcol)	/* output at least one space */
9508 	endcol = msg_col + 1;
9509     if (Columns <= endcol)	/* avoid hang for tiny window */
9510 	endcol = Columns - 1;
9511 
9512     msg_advance(endcol);
9513 
9514     /* Show "xxx" with the attributes. */
9515     if (!did_header)
9516     {
9517 	msg_puts_attr((char_u *)"xxx", syn_id2attr(id));
9518 	msg_putchar(' ');
9519     }
9520 
9521     return newline;
9522 }
9523 
9524 /*
9525  * Set the attribute numbers for a highlight group.
9526  * Called after one of the attributes has changed.
9527  */
9528     static void
9529 set_hl_attr(
9530     int		idx)	    /* index in array */
9531 {
9532     attrentry_T		at_en;
9533     struct hl_group	*sgp = HL_TABLE() + idx;
9534 
9535     /* The "Normal" group doesn't need an attribute number */
9536     if (sgp->sg_name_u != NULL && STRCMP(sgp->sg_name_u, "NORMAL") == 0)
9537 	return;
9538 
9539 #ifdef FEAT_GUI
9540     /*
9541      * For the GUI mode: If there are other than "normal" highlighting
9542      * attributes, need to allocate an attr number.
9543      */
9544     if (sgp->sg_gui_fg == INVALCOLOR
9545 	    && sgp->sg_gui_bg == INVALCOLOR
9546 	    && sgp->sg_gui_sp == INVALCOLOR
9547 	    && sgp->sg_font == NOFONT
9548 # ifdef FEAT_XFONTSET
9549 	    && sgp->sg_fontset == NOFONTSET
9550 # endif
9551 	    )
9552     {
9553 	sgp->sg_gui_attr = sgp->sg_gui;
9554     }
9555     else
9556     {
9557 	at_en.ae_attr = sgp->sg_gui;
9558 	at_en.ae_u.gui.fg_color = sgp->sg_gui_fg;
9559 	at_en.ae_u.gui.bg_color = sgp->sg_gui_bg;
9560 	at_en.ae_u.gui.sp_color = sgp->sg_gui_sp;
9561 	at_en.ae_u.gui.font = sgp->sg_font;
9562 # ifdef FEAT_XFONTSET
9563 	at_en.ae_u.gui.fontset = sgp->sg_fontset;
9564 # endif
9565 	sgp->sg_gui_attr = get_attr_entry(&gui_attr_table, &at_en);
9566     }
9567 #endif
9568     /*
9569      * For the term mode: If there are other than "normal" highlighting
9570      * attributes, need to allocate an attr number.
9571      */
9572     if (sgp->sg_start == NULL && sgp->sg_stop == NULL)
9573 	sgp->sg_term_attr = sgp->sg_term;
9574     else
9575     {
9576 	at_en.ae_attr = sgp->sg_term;
9577 	at_en.ae_u.term.start = sgp->sg_start;
9578 	at_en.ae_u.term.stop = sgp->sg_stop;
9579 	sgp->sg_term_attr = get_attr_entry(&term_attr_table, &at_en);
9580     }
9581 
9582     /*
9583      * For the color term mode: If there are other than "normal"
9584      * highlighting attributes, need to allocate an attr number.
9585      */
9586     if (sgp->sg_cterm_fg == 0 && sgp->sg_cterm_bg == 0
9587 # ifdef FEAT_TERMGUICOLORS
9588 	    && sgp->sg_gui_fg == INVALCOLOR
9589 	    && sgp->sg_gui_bg == INVALCOLOR
9590 # endif
9591 	    )
9592 	sgp->sg_cterm_attr = sgp->sg_cterm;
9593     else
9594     {
9595 	at_en.ae_attr = sgp->sg_cterm;
9596 	at_en.ae_u.cterm.fg_color = sgp->sg_cterm_fg;
9597 	at_en.ae_u.cterm.bg_color = sgp->sg_cterm_bg;
9598 # ifdef FEAT_TERMGUICOLORS
9599 #  ifdef WIN3264
9600 	{
9601 	    int id;
9602 	    guicolor_T fg, bg;
9603 
9604 	    id = syn_name2id((char_u *)"Normal");
9605 	    if (id > 0)
9606 	    {
9607 		syn_id2colors(id, &fg, &bg);
9608 		if (sgp->sg_gui_fg == INVALCOLOR)
9609 		    sgp->sg_gui_fg = fg;
9610 		if (sgp->sg_gui_bg == INVALCOLOR)
9611 		    sgp->sg_gui_bg = bg;
9612 	    }
9613 
9614 	}
9615 #  endif
9616 	at_en.ae_u.cterm.fg_rgb = GUI_MCH_GET_RGB2(sgp->sg_gui_fg);
9617 	at_en.ae_u.cterm.bg_rgb = GUI_MCH_GET_RGB2(sgp->sg_gui_bg);
9618 	if (at_en.ae_u.cterm.fg_rgb == INVALCOLOR
9619 		&& at_en.ae_u.cterm.bg_rgb == INVALCOLOR)
9620 	{
9621 	    /* If both fg and bg are invalid fall back to the cterm colors.
9622 	     * Helps when the GUI only uses an attribute, e.g. undercurl. */
9623 	    at_en.ae_u.cterm.fg_rgb = CTERMCOLOR;
9624 	    at_en.ae_u.cterm.bg_rgb = CTERMCOLOR;
9625 	}
9626 # endif
9627 	sgp->sg_cterm_attr = get_attr_entry(&cterm_attr_table, &at_en);
9628     }
9629 }
9630 
9631 /*
9632  * Lookup a highlight group name and return its ID.
9633  * If it is not found, 0 is returned.
9634  */
9635     int
9636 syn_name2id(char_u *name)
9637 {
9638     int		i;
9639     char_u	name_u[200];
9640 
9641     /* Avoid using stricmp() too much, it's slow on some systems */
9642     /* Avoid alloc()/free(), these are slow too.  ID names over 200 chars
9643      * don't deserve to be found! */
9644     vim_strncpy(name_u, name, 199);
9645     vim_strup(name_u);
9646     for (i = highlight_ga.ga_len; --i >= 0; )
9647 	if (HL_TABLE()[i].sg_name_u != NULL
9648 		&& STRCMP(name_u, HL_TABLE()[i].sg_name_u) == 0)
9649 	    break;
9650     return i + 1;
9651 }
9652 
9653 /*
9654  * Lookup a highlight group name and return its attributes.
9655  * Return zero if not found.
9656  */
9657     int
9658 syn_name2attr(char_u *name)
9659 {
9660     int id = syn_name2id(name);
9661 
9662     if (id != 0)
9663 	return syn_id2attr(id);
9664     return 0;
9665 }
9666 
9667 #if defined(FEAT_EVAL) || defined(PROTO)
9668 /*
9669  * Return TRUE if highlight group "name" exists.
9670  */
9671     int
9672 highlight_exists(char_u *name)
9673 {
9674     return (syn_name2id(name) > 0);
9675 }
9676 
9677 # if defined(FEAT_SEARCH_EXTRA) || defined(PROTO)
9678 /*
9679  * Return the name of highlight group "id".
9680  * When not a valid ID return an empty string.
9681  */
9682     char_u *
9683 syn_id2name(int id)
9684 {
9685     if (id <= 0 || id > highlight_ga.ga_len)
9686 	return (char_u *)"";
9687     return HL_TABLE()[id - 1].sg_name;
9688 }
9689 # endif
9690 #endif
9691 
9692 /*
9693  * Like syn_name2id(), but take a pointer + length argument.
9694  */
9695     int
9696 syn_namen2id(char_u *linep, int len)
9697 {
9698     char_u  *name;
9699     int	    id = 0;
9700 
9701     name = vim_strnsave(linep, len);
9702     if (name != NULL)
9703     {
9704 	id = syn_name2id(name);
9705 	vim_free(name);
9706     }
9707     return id;
9708 }
9709 
9710 /*
9711  * Find highlight group name in the table and return its ID.
9712  * The argument is a pointer to the name and the length of the name.
9713  * If it doesn't exist yet, a new entry is created.
9714  * Return 0 for failure.
9715  */
9716     int
9717 syn_check_group(char_u *pp, int len)
9718 {
9719     int	    id;
9720     char_u  *name;
9721 
9722     name = vim_strnsave(pp, len);
9723     if (name == NULL)
9724 	return 0;
9725 
9726     id = syn_name2id(name);
9727     if (id == 0)			/* doesn't exist yet */
9728 	id = syn_add_group(name);
9729     else
9730 	vim_free(name);
9731     return id;
9732 }
9733 
9734 /*
9735  * Add new highlight group and return its ID.
9736  * "name" must be an allocated string, it will be consumed.
9737  * Return 0 for failure.
9738  */
9739     static int
9740 syn_add_group(char_u *name)
9741 {
9742     char_u	*p;
9743 
9744     /* Check that the name is ASCII letters, digits and underscore. */
9745     for (p = name; *p != NUL; ++p)
9746     {
9747 	if (!vim_isprintc(*p))
9748 	{
9749 	    EMSG(_("E669: Unprintable character in group name"));
9750 	    vim_free(name);
9751 	    return 0;
9752 	}
9753 	else if (!ASCII_ISALNUM(*p) && *p != '_')
9754 	{
9755 	    /* This is an error, but since there previously was no check only
9756 	     * give a warning. */
9757 	    msg_source(HL_ATTR(HLF_W));
9758 	    MSG(_("W18: Invalid character in group name"));
9759 	    break;
9760 	}
9761     }
9762 
9763     /*
9764      * First call for this growarray: init growing array.
9765      */
9766     if (highlight_ga.ga_data == NULL)
9767     {
9768 	highlight_ga.ga_itemsize = sizeof(struct hl_group);
9769 	highlight_ga.ga_growsize = 10;
9770     }
9771 
9772     if (highlight_ga.ga_len >= MAX_HL_ID)
9773     {
9774 	EMSG(_("E849: Too many highlight and syntax groups"));
9775 	vim_free(name);
9776 	return 0;
9777     }
9778 
9779     /*
9780      * Make room for at least one other syntax_highlight entry.
9781      */
9782     if (ga_grow(&highlight_ga, 1) == FAIL)
9783     {
9784 	vim_free(name);
9785 	return 0;
9786     }
9787 
9788     vim_memset(&(HL_TABLE()[highlight_ga.ga_len]), 0, sizeof(struct hl_group));
9789     HL_TABLE()[highlight_ga.ga_len].sg_name = name;
9790     HL_TABLE()[highlight_ga.ga_len].sg_name_u = vim_strsave_up(name);
9791 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
9792     HL_TABLE()[highlight_ga.ga_len].sg_gui_bg = INVALCOLOR;
9793     HL_TABLE()[highlight_ga.ga_len].sg_gui_fg = INVALCOLOR;
9794 # ifdef FEAT_GUI
9795     HL_TABLE()[highlight_ga.ga_len].sg_gui_sp = INVALCOLOR;
9796 # endif
9797 #endif
9798     ++highlight_ga.ga_len;
9799 
9800     return highlight_ga.ga_len;		    /* ID is index plus one */
9801 }
9802 
9803 /*
9804  * When, just after calling syn_add_group(), an error is discovered, this
9805  * function deletes the new name.
9806  */
9807     static void
9808 syn_unadd_group(void)
9809 {
9810     --highlight_ga.ga_len;
9811     vim_free(HL_TABLE()[highlight_ga.ga_len].sg_name);
9812     vim_free(HL_TABLE()[highlight_ga.ga_len].sg_name_u);
9813 }
9814 
9815 /*
9816  * Translate a group ID to highlight attributes.
9817  */
9818     int
9819 syn_id2attr(int hl_id)
9820 {
9821     int			attr;
9822     struct hl_group	*sgp;
9823 
9824     hl_id = syn_get_final_id(hl_id);
9825     sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
9826 
9827 #ifdef FEAT_GUI
9828     /*
9829      * Only use GUI attr when the GUI is being used.
9830      */
9831     if (gui.in_use)
9832 	attr = sgp->sg_gui_attr;
9833     else
9834 #endif
9835 	if (IS_CTERM)
9836 	    attr = sgp->sg_cterm_attr;
9837 	else
9838 	    attr = sgp->sg_term_attr;
9839 
9840     return attr;
9841 }
9842 
9843 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
9844 /*
9845  * Get the GUI colors and attributes for a group ID.
9846  * NOTE: the colors will be INVALCOLOR when not set, the color otherwise.
9847  */
9848     int
9849 syn_id2colors(int hl_id, guicolor_T *fgp, guicolor_T *bgp)
9850 {
9851     struct hl_group	*sgp;
9852 
9853     hl_id = syn_get_final_id(hl_id);
9854     sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
9855 
9856     *fgp = sgp->sg_gui_fg;
9857     *bgp = sgp->sg_gui_bg;
9858     return sgp->sg_gui;
9859 }
9860 #endif
9861 
9862 #if defined(FEAT_TERMINAL) || defined(PROTO)
9863     void
9864 syn_id2cterm_bg(int hl_id, int *fgp, int *bgp)
9865 {
9866     struct hl_group	*sgp;
9867 
9868     hl_id = syn_get_final_id(hl_id);
9869     sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
9870     *fgp = sgp->sg_cterm_fg - 1;
9871     *bgp = sgp->sg_cterm_bg - 1;
9872 }
9873 #endif
9874 
9875 /*
9876  * Translate a group ID to the final group ID (following links).
9877  */
9878     int
9879 syn_get_final_id(int hl_id)
9880 {
9881     int			count;
9882     struct hl_group	*sgp;
9883 
9884     if (hl_id > highlight_ga.ga_len || hl_id < 1)
9885 	return 0;			/* Can be called from eval!! */
9886 
9887     /*
9888      * Follow links until there is no more.
9889      * Look out for loops!  Break after 100 links.
9890      */
9891     for (count = 100; --count >= 0; )
9892     {
9893 	sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
9894 	if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len)
9895 	    break;
9896 	hl_id = sgp->sg_link;
9897     }
9898 
9899     return hl_id;
9900 }
9901 
9902 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
9903 /*
9904  * Call this function just after the GUI has started.
9905  * Also called when 'termguicolors' was set, gui.in_use will be FALSE then.
9906  * It finds the font and color handles for the highlighting groups.
9907  */
9908     void
9909 highlight_gui_started(void)
9910 {
9911     int	    idx;
9912 
9913     /* First get the colors from the "Normal" and "Menu" group, if set */
9914     if (USE_24BIT)
9915 	set_normal_colors();
9916 
9917     for (idx = 0; idx < highlight_ga.ga_len; ++idx)
9918 	gui_do_one_color(idx, FALSE, FALSE);
9919 
9920     highlight_changed();
9921 }
9922 
9923     static void
9924 gui_do_one_color(
9925     int		idx,
9926     int		do_menu UNUSED,	   /* TRUE: might set the menu font */
9927     int		do_tooltip UNUSED) /* TRUE: might set the tooltip font */
9928 {
9929     int		didit = FALSE;
9930 
9931 # ifdef FEAT_GUI
9932 #  ifdef FEAT_TERMGUICOLORS
9933     if (gui.in_use)
9934 #  endif
9935 	if (HL_TABLE()[idx].sg_font_name != NULL)
9936 	{
9937 	    hl_do_font(idx, HL_TABLE()[idx].sg_font_name, FALSE, do_menu,
9938 							    do_tooltip, TRUE);
9939 	    didit = TRUE;
9940 	}
9941 # endif
9942     if (HL_TABLE()[idx].sg_gui_fg_name != NULL)
9943     {
9944 	HL_TABLE()[idx].sg_gui_fg =
9945 			    color_name2handle(HL_TABLE()[idx].sg_gui_fg_name);
9946 	didit = TRUE;
9947     }
9948     if (HL_TABLE()[idx].sg_gui_bg_name != NULL)
9949     {
9950 	HL_TABLE()[idx].sg_gui_bg =
9951 			    color_name2handle(HL_TABLE()[idx].sg_gui_bg_name);
9952 	didit = TRUE;
9953     }
9954 # ifdef FEAT_GUI
9955     if (HL_TABLE()[idx].sg_gui_sp_name != NULL)
9956     {
9957 	HL_TABLE()[idx].sg_gui_sp =
9958 			    color_name2handle(HL_TABLE()[idx].sg_gui_sp_name);
9959 	didit = TRUE;
9960     }
9961 # endif
9962     if (didit)	/* need to get a new attr number */
9963 	set_hl_attr(idx);
9964 }
9965 #endif
9966 
9967 #if defined(USER_HIGHLIGHT) && defined(FEAT_STL_OPT)
9968 /*
9969  * Apply difference between User[1-9] and HLF_S to HLF_SNC, HLF_ST or HLF_STNC.
9970  */
9971     static void
9972 combine_stl_hlt(
9973 	int id,
9974 	int id_S,
9975 	int id_alt,
9976 	int hlcnt,
9977 	int i,
9978 	int hlf,
9979 	int *table)
9980 {
9981     struct hl_group *hlt = HL_TABLE();
9982 
9983     if (id_alt == 0)
9984     {
9985 	vim_memset(&hlt[hlcnt + i], 0, sizeof(struct hl_group));
9986 	hlt[hlcnt + i].sg_term = highlight_attr[hlf];
9987 	hlt[hlcnt + i].sg_cterm = highlight_attr[hlf];
9988 #  if defined(FEAT_GUI) || defined(FEAT_EVAL)
9989 	hlt[hlcnt + i].sg_gui = highlight_attr[hlf];
9990 #  endif
9991     }
9992     else
9993 	mch_memmove(&hlt[hlcnt + i],
9994 		    &hlt[id_alt - 1],
9995 		    sizeof(struct hl_group));
9996     hlt[hlcnt + i].sg_link = 0;
9997 
9998     hlt[hlcnt + i].sg_term ^=
9999 	hlt[id - 1].sg_term ^ hlt[id_S - 1].sg_term;
10000     if (hlt[id - 1].sg_start != hlt[id_S - 1].sg_start)
10001 	hlt[hlcnt + i].sg_start = hlt[id - 1].sg_start;
10002     if (hlt[id - 1].sg_stop != hlt[id_S - 1].sg_stop)
10003 	hlt[hlcnt + i].sg_stop = hlt[id - 1].sg_stop;
10004     hlt[hlcnt + i].sg_cterm ^=
10005 	hlt[id - 1].sg_cterm ^ hlt[id_S - 1].sg_cterm;
10006     if (hlt[id - 1].sg_cterm_fg != hlt[id_S - 1].sg_cterm_fg)
10007 	hlt[hlcnt + i].sg_cterm_fg = hlt[id - 1].sg_cterm_fg;
10008     if (hlt[id - 1].sg_cterm_bg != hlt[id_S - 1].sg_cterm_bg)
10009 	hlt[hlcnt + i].sg_cterm_bg = hlt[id - 1].sg_cterm_bg;
10010 #  if defined(FEAT_GUI) || defined(FEAT_EVAL)
10011     hlt[hlcnt + i].sg_gui ^=
10012 	hlt[id - 1].sg_gui ^ hlt[id_S - 1].sg_gui;
10013 #  endif
10014 #  ifdef FEAT_GUI
10015     if (hlt[id - 1].sg_gui_fg != hlt[id_S - 1].sg_gui_fg)
10016 	hlt[hlcnt + i].sg_gui_fg = hlt[id - 1].sg_gui_fg;
10017     if (hlt[id - 1].sg_gui_bg != hlt[id_S - 1].sg_gui_bg)
10018 	hlt[hlcnt + i].sg_gui_bg = hlt[id - 1].sg_gui_bg;
10019     if (hlt[id - 1].sg_gui_sp != hlt[id_S - 1].sg_gui_sp)
10020 	hlt[hlcnt + i].sg_gui_sp = hlt[id - 1].sg_gui_sp;
10021     if (hlt[id - 1].sg_font != hlt[id_S - 1].sg_font)
10022 	hlt[hlcnt + i].sg_font = hlt[id - 1].sg_font;
10023 #   ifdef FEAT_XFONTSET
10024     if (hlt[id - 1].sg_fontset != hlt[id_S - 1].sg_fontset)
10025 	hlt[hlcnt + i].sg_fontset = hlt[id - 1].sg_fontset;
10026 #   endif
10027 #  endif
10028     highlight_ga.ga_len = hlcnt + i + 1;
10029     set_hl_attr(hlcnt + i);	/* At long last we can apply */
10030     table[i] = syn_id2attr(hlcnt + i + 1);
10031 }
10032 #endif
10033 
10034 /*
10035  * Translate the 'highlight' option into attributes in highlight_attr[] and
10036  * set up the user highlights User1..9.  If FEAT_STL_OPT is in use, a set of
10037  * corresponding highlights to use on top of HLF_SNC is computed.
10038  * Called only when the 'highlight' option has been changed and upon first
10039  * screen redraw after any :highlight command.
10040  * Return FAIL when an invalid flag is found in 'highlight'.  OK otherwise.
10041  */
10042     int
10043 highlight_changed(void)
10044 {
10045     int		hlf;
10046     int		i;
10047     char_u	*p;
10048     int		attr;
10049     char_u	*end;
10050     int		id;
10051 #ifdef USER_HIGHLIGHT
10052     char_u      userhl[10];
10053 # ifdef FEAT_STL_OPT
10054     int		id_SNC = -1;
10055     int		id_S = -1;
10056 #  ifdef FEAT_TERMINAL
10057     int		id_ST = -1;
10058     int		id_STNC = -1;
10059 #  endif
10060     int		hlcnt;
10061 # endif
10062 #endif
10063     static int	hl_flags[HLF_COUNT] = HL_FLAGS;
10064 
10065     need_highlight_changed = FALSE;
10066 
10067     /*
10068      * Clear all attributes.
10069      */
10070     for (hlf = 0; hlf < (int)HLF_COUNT; ++hlf)
10071 	highlight_attr[hlf] = 0;
10072 
10073     /*
10074      * First set all attributes to their default value.
10075      * Then use the attributes from the 'highlight' option.
10076      */
10077     for (i = 0; i < 2; ++i)
10078     {
10079 	if (i)
10080 	    p = p_hl;
10081 	else
10082 	    p = get_highlight_default();
10083 	if (p == NULL)	    /* just in case */
10084 	    continue;
10085 
10086 	while (*p)
10087 	{
10088 	    for (hlf = 0; hlf < (int)HLF_COUNT; ++hlf)
10089 		if (hl_flags[hlf] == *p)
10090 		    break;
10091 	    ++p;
10092 	    if (hlf == (int)HLF_COUNT || *p == NUL)
10093 		return FAIL;
10094 
10095 	    /*
10096 	     * Allow several hl_flags to be combined, like "bu" for
10097 	     * bold-underlined.
10098 	     */
10099 	    attr = 0;
10100 	    for ( ; *p && *p != ','; ++p)	    /* parse upto comma */
10101 	    {
10102 		if (VIM_ISWHITE(*p))		    /* ignore white space */
10103 		    continue;
10104 
10105 		if (attr > HL_ALL)  /* Combination with ':' is not allowed. */
10106 		    return FAIL;
10107 
10108 		switch (*p)
10109 		{
10110 		    case 'b':	attr |= HL_BOLD;
10111 				break;
10112 		    case 'i':	attr |= HL_ITALIC;
10113 				break;
10114 		    case '-':
10115 		    case 'n':			    /* no highlighting */
10116 				break;
10117 		    case 'r':	attr |= HL_INVERSE;
10118 				break;
10119 		    case 's':	attr |= HL_STANDOUT;
10120 				break;
10121 		    case 'u':	attr |= HL_UNDERLINE;
10122 				break;
10123 		    case 'c':	attr |= HL_UNDERCURL;
10124 				break;
10125 		    case 't':	attr |= HL_STRIKETHROUGH;
10126 				break;
10127 		    case ':':	++p;		    /* highlight group name */
10128 				if (attr || *p == NUL)	 /* no combinations */
10129 				    return FAIL;
10130 				end = vim_strchr(p, ',');
10131 				if (end == NULL)
10132 				    end = p + STRLEN(p);
10133 				id = syn_check_group(p, (int)(end - p));
10134 				if (id == 0)
10135 				    return FAIL;
10136 				attr = syn_id2attr(id);
10137 				p = end - 1;
10138 #if defined(FEAT_STL_OPT) && defined(USER_HIGHLIGHT)
10139 				if (hlf == (int)HLF_SNC)
10140 				    id_SNC = syn_get_final_id(id);
10141 # ifdef FEAT_TERMINAL
10142 				else if (hlf == (int)HLF_ST)
10143 				    id_ST = syn_get_final_id(id);
10144 				else if (hlf == (int)HLF_STNC)
10145 				    id_STNC = syn_get_final_id(id);
10146 # endif
10147 				else if (hlf == (int)HLF_S)
10148 				    id_S = syn_get_final_id(id);
10149 #endif
10150 				break;
10151 		    default:	return FAIL;
10152 		}
10153 	    }
10154 	    highlight_attr[hlf] = attr;
10155 
10156 	    p = skip_to_option_part(p);	    /* skip comma and spaces */
10157 	}
10158     }
10159 
10160 #ifdef USER_HIGHLIGHT
10161     /* Setup the user highlights
10162      *
10163      * Temporarily utilize 28 more hl entries:
10164      * 9 for User1-User9 combined with StatusLineNC
10165      * 9 for User1-User9 combined with StatusLineTerm
10166      * 9 for User1-User9 combined with StatusLineTermNC
10167      * 1 for StatusLine default
10168      * Have to be in there simultaneously in case of table overflows in
10169      * get_attr_entry()
10170      */
10171 # ifdef FEAT_STL_OPT
10172     if (ga_grow(&highlight_ga, 28) == FAIL)
10173 	return FAIL;
10174     hlcnt = highlight_ga.ga_len;
10175     if (id_S == -1)
10176     {
10177 	/* Make sure id_S is always valid to simplify code below. Use the last
10178 	 * entry. */
10179 	vim_memset(&HL_TABLE()[hlcnt + 27], 0, sizeof(struct hl_group));
10180 	HL_TABLE()[hlcnt + 18].sg_term = highlight_attr[HLF_S];
10181 	id_S = hlcnt + 19;
10182     }
10183 # endif
10184     for (i = 0; i < 9; i++)
10185     {
10186 	sprintf((char *)userhl, "User%d", i + 1);
10187 	id = syn_name2id(userhl);
10188 	if (id == 0)
10189 	{
10190 	    highlight_user[i] = 0;
10191 # ifdef FEAT_STL_OPT
10192 	    highlight_stlnc[i] = 0;
10193 #  ifdef FEAT_TERMINAL
10194 	    highlight_stlterm[i] = 0;
10195 	    highlight_stltermnc[i] = 0;
10196 #  endif
10197 # endif
10198 	}
10199 	else
10200 	{
10201 	    highlight_user[i] = syn_id2attr(id);
10202 # ifdef FEAT_STL_OPT
10203 	    combine_stl_hlt(id, id_S, id_SNC, hlcnt, i,
10204 						     HLF_SNC, highlight_stlnc);
10205 #  ifdef FEAT_TERMINAL
10206 	    combine_stl_hlt(id, id_S, id_ST, hlcnt + 9, i,
10207 						    HLF_ST, highlight_stlterm);
10208 	    combine_stl_hlt(id, id_S, id_STNC, hlcnt + 18, i,
10209 						HLF_STNC, highlight_stltermnc);
10210 #  endif
10211 # endif
10212 	}
10213     }
10214 # ifdef FEAT_STL_OPT
10215     highlight_ga.ga_len = hlcnt;
10216 # endif
10217 
10218 #endif /* USER_HIGHLIGHT */
10219 
10220     return OK;
10221 }
10222 
10223 #if defined(FEAT_CMDL_COMPL) || defined(PROTO)
10224 
10225 static void highlight_list(void);
10226 static void highlight_list_two(int cnt, int attr);
10227 
10228 /*
10229  * Handle command line completion for :highlight command.
10230  */
10231     void
10232 set_context_in_highlight_cmd(expand_T *xp, char_u *arg)
10233 {
10234     char_u	*p;
10235 
10236     /* Default: expand group names */
10237     xp->xp_context = EXPAND_HIGHLIGHT;
10238     xp->xp_pattern = arg;
10239     include_link = 2;
10240     include_default = 1;
10241 
10242     /* (part of) subcommand already typed */
10243     if (*arg != NUL)
10244     {
10245 	p = skiptowhite(arg);
10246 	if (*p != NUL)			/* past "default" or group name */
10247 	{
10248 	    include_default = 0;
10249 	    if (STRNCMP("default", arg, p - arg) == 0)
10250 	    {
10251 		arg = skipwhite(p);
10252 		xp->xp_pattern = arg;
10253 		p = skiptowhite(arg);
10254 	    }
10255 	    if (*p != NUL)			/* past group name */
10256 	    {
10257 		include_link = 0;
10258 		if (arg[1] == 'i' && arg[0] == 'N')
10259 		    highlight_list();
10260 		if (STRNCMP("link", arg, p - arg) == 0
10261 			|| STRNCMP("clear", arg, p - arg) == 0)
10262 		{
10263 		    xp->xp_pattern = skipwhite(p);
10264 		    p = skiptowhite(xp->xp_pattern);
10265 		    if (*p != NUL)		/* past first group name */
10266 		    {
10267 			xp->xp_pattern = skipwhite(p);
10268 			p = skiptowhite(xp->xp_pattern);
10269 		    }
10270 		}
10271 		if (*p != NUL)			/* past group name(s) */
10272 		    xp->xp_context = EXPAND_NOTHING;
10273 	    }
10274 	}
10275     }
10276 }
10277 
10278 /*
10279  * List highlighting matches in a nice way.
10280  */
10281     static void
10282 highlight_list(void)
10283 {
10284     int		i;
10285 
10286     for (i = 10; --i >= 0; )
10287 	highlight_list_two(i, HL_ATTR(HLF_D));
10288     for (i = 40; --i >= 0; )
10289 	highlight_list_two(99, 0);
10290 }
10291 
10292     static void
10293 highlight_list_two(int cnt, int attr)
10294 {
10295     msg_puts_attr((char_u *)&("N \bI \b!  \b"[cnt / 11]), attr);
10296     msg_clr_eos();
10297     out_flush();
10298     ui_delay(cnt == 99 ? 40L : (long)cnt * 50L, FALSE);
10299 }
10300 
10301 #endif /* FEAT_CMDL_COMPL */
10302 
10303 #if defined(FEAT_CMDL_COMPL) || (defined(FEAT_SYN_HL) && defined(FEAT_EVAL)) \
10304     || defined(FEAT_SIGNS) || defined(PROTO)
10305 /*
10306  * Function given to ExpandGeneric() to obtain the list of group names.
10307  */
10308     char_u *
10309 get_highlight_name(expand_T *xp UNUSED, int idx)
10310 {
10311     return get_highlight_name_ext(xp, idx, TRUE);
10312 }
10313 
10314 /*
10315  * Obtain a highlight group name.
10316  * When "skip_cleared" is TRUE don't return a cleared entry.
10317  */
10318     char_u *
10319 get_highlight_name_ext(expand_T *xp UNUSED, int idx, int skip_cleared)
10320 {
10321     if (idx < 0)
10322 	return NULL;
10323 
10324     /* Items are never removed from the table, skip the ones that were
10325      * cleared. */
10326     if (skip_cleared && idx < highlight_ga.ga_len && HL_TABLE()[idx].sg_cleared)
10327 	return (char_u *)"";
10328 
10329 #ifdef FEAT_CMDL_COMPL
10330     if (idx == highlight_ga.ga_len && include_none != 0)
10331 	return (char_u *)"none";
10332     if (idx == highlight_ga.ga_len + include_none && include_default != 0)
10333 	return (char_u *)"default";
10334     if (idx == highlight_ga.ga_len + include_none + include_default
10335 							 && include_link != 0)
10336 	return (char_u *)"link";
10337     if (idx == highlight_ga.ga_len + include_none + include_default + 1
10338 							 && include_link != 0)
10339 	return (char_u *)"clear";
10340 #endif
10341     if (idx >= highlight_ga.ga_len)
10342 	return NULL;
10343     return HL_TABLE()[idx].sg_name;
10344 }
10345 #endif
10346 
10347 #if defined(FEAT_GUI) || defined(PROTO)
10348 /*
10349  * Free all the highlight group fonts.
10350  * Used when quitting for systems which need it.
10351  */
10352     void
10353 free_highlight_fonts(void)
10354 {
10355     int	    idx;
10356 
10357     for (idx = 0; idx < highlight_ga.ga_len; ++idx)
10358     {
10359 	gui_mch_free_font(HL_TABLE()[idx].sg_font);
10360 	HL_TABLE()[idx].sg_font = NOFONT;
10361 # ifdef FEAT_XFONTSET
10362 	gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
10363 	HL_TABLE()[idx].sg_fontset = NOFONTSET;
10364 # endif
10365     }
10366 
10367     gui_mch_free_font(gui.norm_font);
10368 # ifdef FEAT_XFONTSET
10369     gui_mch_free_fontset(gui.fontset);
10370 # endif
10371 # ifndef FEAT_GUI_GTK
10372     gui_mch_free_font(gui.bold_font);
10373     gui_mch_free_font(gui.ital_font);
10374     gui_mch_free_font(gui.boldital_font);
10375 # endif
10376 }
10377 #endif
10378 
10379 /**************************************
10380  *  End of Highlighting stuff	      *
10381  **************************************/
10382