xref: /vim-8.2.3635/src/syntax.c (revision 3d1d6475)
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 	    && (line = syn_getcurline())[current_col] != NUL
2474 	    && line[current_col + 1] == NUL
2475 	    && !(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY)))
2476 	current_next_list = NULL;
2477 
2478     if (zero_width_next_ga.ga_len > 0)
2479 	ga_clear(&zero_width_next_ga);
2480 
2481     /* No longer need external matches.  But keep next_match_extmatch. */
2482     unref_extmatch(re_extmatch_out);
2483     re_extmatch_out = NULL;
2484     unref_extmatch(cur_extmatch);
2485 
2486     return current_attr;
2487 }
2488 
2489 
2490 /*
2491  * Check if we already matched pattern "idx" at the current column.
2492  */
2493     static int
2494 did_match_already(int idx, garray_T *gap)
2495 {
2496     int		i;
2497 
2498     for (i = current_state.ga_len; --i >= 0; )
2499 	if (CUR_STATE(i).si_m_startcol == (int)current_col
2500 		&& CUR_STATE(i).si_m_lnum == (int)current_lnum
2501 		&& CUR_STATE(i).si_idx == idx)
2502 	    return TRUE;
2503 
2504     /* Zero-width matches with a nextgroup argument are not put on the syntax
2505      * stack, and can only be matched once anyway. */
2506     for (i = gap->ga_len; --i >= 0; )
2507 	if (((int *)(gap->ga_data))[i] == idx)
2508 	    return TRUE;
2509 
2510     return FALSE;
2511 }
2512 
2513 /*
2514  * Push the next match onto the stack.
2515  */
2516     static stateitem_T *
2517 push_next_match(stateitem_T *cur_si)
2518 {
2519     synpat_T	*spp;
2520 #ifdef FEAT_CONCEAL
2521     int		 save_flags;
2522 #endif
2523 
2524     spp = &(SYN_ITEMS(syn_block)[next_match_idx]);
2525 
2526     /*
2527      * Push the item in current_state stack;
2528      */
2529     if (push_current_state(next_match_idx) == OK)
2530     {
2531 	/*
2532 	 * If it's a start-skip-end type that crosses lines, figure out how
2533 	 * much it continues in this line.  Otherwise just fill in the length.
2534 	 */
2535 	cur_si = &CUR_STATE(current_state.ga_len - 1);
2536 	cur_si->si_h_startpos = next_match_h_startpos;
2537 	cur_si->si_m_startcol = current_col;
2538 	cur_si->si_m_lnum = current_lnum;
2539 	cur_si->si_flags = spp->sp_flags;
2540 #ifdef FEAT_CONCEAL
2541 	cur_si->si_seqnr = next_seqnr++;
2542 	cur_si->si_cchar = spp->sp_cchar;
2543 	if (current_state.ga_len > 1)
2544 	    cur_si->si_flags |=
2545 		    CUR_STATE(current_state.ga_len - 2).si_flags & HL_CONCEAL;
2546 #endif
2547 	cur_si->si_next_list = spp->sp_next_list;
2548 	cur_si->si_extmatch = ref_extmatch(next_match_extmatch);
2549 	if (spp->sp_type == SPTYPE_START && !(spp->sp_flags & HL_ONELINE))
2550 	{
2551 	    /* Try to find the end pattern in the current line */
2552 	    update_si_end(cur_si, (int)(next_match_m_endpos.col), TRUE);
2553 	    check_keepend();
2554 	}
2555 	else
2556 	{
2557 	    cur_si->si_m_endpos = next_match_m_endpos;
2558 	    cur_si->si_h_endpos = next_match_h_endpos;
2559 	    cur_si->si_ends = TRUE;
2560 	    cur_si->si_flags |= next_match_flags;
2561 	    cur_si->si_eoe_pos = next_match_eoe_pos;
2562 	    cur_si->si_end_idx = next_match_end_idx;
2563 	}
2564 	if (keepend_level < 0 && (cur_si->si_flags & HL_KEEPEND))
2565 	    keepend_level = current_state.ga_len - 1;
2566 	check_keepend();
2567 	update_si_attr(current_state.ga_len - 1);
2568 
2569 #ifdef FEAT_CONCEAL
2570 	save_flags = cur_si->si_flags & (HL_CONCEAL | HL_CONCEALENDS);
2571 #endif
2572 	/*
2573 	 * If the start pattern has another highlight group, push another item
2574 	 * on the stack for the start pattern.
2575 	 */
2576 	if (	   spp->sp_type == SPTYPE_START
2577 		&& spp->sp_syn_match_id != 0
2578 		&& push_current_state(next_match_idx) == OK)
2579 	{
2580 	    cur_si = &CUR_STATE(current_state.ga_len - 1);
2581 	    cur_si->si_h_startpos = next_match_h_startpos;
2582 	    cur_si->si_m_startcol = current_col;
2583 	    cur_si->si_m_lnum = current_lnum;
2584 	    cur_si->si_m_endpos = next_match_eos_pos;
2585 	    cur_si->si_h_endpos = next_match_eos_pos;
2586 	    cur_si->si_ends = TRUE;
2587 	    cur_si->si_end_idx = 0;
2588 	    cur_si->si_flags = HL_MATCH;
2589 #ifdef FEAT_CONCEAL
2590 	    cur_si->si_seqnr = next_seqnr++;
2591 	    cur_si->si_flags |= save_flags;
2592 	    if (cur_si->si_flags & HL_CONCEALENDS)
2593 		cur_si->si_flags |= HL_CONCEAL;
2594 #endif
2595 	    cur_si->si_next_list = NULL;
2596 	    check_keepend();
2597 	    update_si_attr(current_state.ga_len - 1);
2598 	}
2599     }
2600 
2601     next_match_idx = -1;	/* try other match next time */
2602 
2603     return cur_si;
2604 }
2605 
2606 /*
2607  * Check for end of current state (and the states before it).
2608  */
2609     static void
2610 check_state_ends(void)
2611 {
2612     stateitem_T	*cur_si;
2613     int		had_extend;
2614 
2615     cur_si = &CUR_STATE(current_state.ga_len - 1);
2616     for (;;)
2617     {
2618 	if (cur_si->si_ends
2619 		&& (cur_si->si_m_endpos.lnum < current_lnum
2620 		    || (cur_si->si_m_endpos.lnum == current_lnum
2621 			&& cur_si->si_m_endpos.col <= current_col)))
2622 	{
2623 	    /*
2624 	     * If there is an end pattern group ID, highlight the end pattern
2625 	     * now.  No need to pop the current item from the stack.
2626 	     * Only do this if the end pattern continues beyond the current
2627 	     * position.
2628 	     */
2629 	    if (cur_si->si_end_idx
2630 		    && (cur_si->si_eoe_pos.lnum > current_lnum
2631 			|| (cur_si->si_eoe_pos.lnum == current_lnum
2632 			    && cur_si->si_eoe_pos.col > current_col)))
2633 	    {
2634 		cur_si->si_idx = cur_si->si_end_idx;
2635 		cur_si->si_end_idx = 0;
2636 		cur_si->si_m_endpos = cur_si->si_eoe_pos;
2637 		cur_si->si_h_endpos = cur_si->si_eoe_pos;
2638 		cur_si->si_flags |= HL_MATCH;
2639 #ifdef FEAT_CONCEAL
2640 		cur_si->si_seqnr = next_seqnr++;
2641 		if (cur_si->si_flags & HL_CONCEALENDS)
2642 		    cur_si->si_flags |= HL_CONCEAL;
2643 #endif
2644 		update_si_attr(current_state.ga_len - 1);
2645 
2646 		/* nextgroup= should not match in the end pattern */
2647 		current_next_list = NULL;
2648 
2649 		/* what matches next may be different now, clear it */
2650 		next_match_idx = 0;
2651 		next_match_col = MAXCOL;
2652 		break;
2653 	    }
2654 	    else
2655 	    {
2656 		/* handle next_list, unless at end of line and no "skipnl" or
2657 		 * "skipempty" */
2658 		current_next_list = cur_si->si_next_list;
2659 		current_next_flags = cur_si->si_flags;
2660 		if (!(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY))
2661 			&& syn_getcurline()[current_col] == NUL)
2662 		    current_next_list = NULL;
2663 
2664 		/* When the ended item has "extend", another item with
2665 		 * "keepend" now needs to check for its end. */
2666 		 had_extend = (cur_si->si_flags & HL_EXTEND);
2667 
2668 		pop_current_state();
2669 
2670 		if (current_state.ga_len == 0)
2671 		    break;
2672 
2673 		if (had_extend && keepend_level >= 0)
2674 		{
2675 		    syn_update_ends(FALSE);
2676 		    if (current_state.ga_len == 0)
2677 			break;
2678 		}
2679 
2680 		cur_si = &CUR_STATE(current_state.ga_len - 1);
2681 
2682 		/*
2683 		 * Only for a region the search for the end continues after
2684 		 * the end of the contained item.  If the contained match
2685 		 * included the end-of-line, break here, the region continues.
2686 		 * Don't do this when:
2687 		 * - "keepend" is used for the contained item
2688 		 * - not at the end of the line (could be end="x$"me=e-1).
2689 		 * - "excludenl" is used (HL_HAS_EOL won't be set)
2690 		 */
2691 		if (cur_si->si_idx >= 0
2692 			&& SYN_ITEMS(syn_block)[cur_si->si_idx].sp_type
2693 							       == SPTYPE_START
2694 			&& !(cur_si->si_flags & (HL_MATCH | HL_KEEPEND)))
2695 		{
2696 		    update_si_end(cur_si, (int)current_col, TRUE);
2697 		    check_keepend();
2698 		    if ((current_next_flags & HL_HAS_EOL)
2699 			    && keepend_level < 0
2700 			    && syn_getcurline()[current_col] == NUL)
2701 			break;
2702 		}
2703 	    }
2704 	}
2705 	else
2706 	    break;
2707     }
2708 }
2709 
2710 /*
2711  * Update an entry in the current_state stack for a match or region.  This
2712  * fills in si_attr, si_next_list and si_cont_list.
2713  */
2714     static void
2715 update_si_attr(int idx)
2716 {
2717     stateitem_T	*sip = &CUR_STATE(idx);
2718     synpat_T	*spp;
2719 
2720     /* This should not happen... */
2721     if (sip->si_idx < 0)
2722 	return;
2723 
2724     spp = &(SYN_ITEMS(syn_block)[sip->si_idx]);
2725     if (sip->si_flags & HL_MATCH)
2726 	sip->si_id = spp->sp_syn_match_id;
2727     else
2728 	sip->si_id = spp->sp_syn.id;
2729     sip->si_attr = syn_id2attr(sip->si_id);
2730     sip->si_trans_id = sip->si_id;
2731     if (sip->si_flags & HL_MATCH)
2732 	sip->si_cont_list = NULL;
2733     else
2734 	sip->si_cont_list = spp->sp_cont_list;
2735 
2736     /*
2737      * For transparent items, take attr from outer item.
2738      * Also take cont_list, if there is none.
2739      * Don't do this for the matchgroup of a start or end pattern.
2740      */
2741     if ((spp->sp_flags & HL_TRANSP) && !(sip->si_flags & HL_MATCH))
2742     {
2743 	if (idx == 0)
2744 	{
2745 	    sip->si_attr = 0;
2746 	    sip->si_trans_id = 0;
2747 	    if (sip->si_cont_list == NULL)
2748 		sip->si_cont_list = ID_LIST_ALL;
2749 	}
2750 	else
2751 	{
2752 	    sip->si_attr = CUR_STATE(idx - 1).si_attr;
2753 	    sip->si_trans_id = CUR_STATE(idx - 1).si_trans_id;
2754 	    sip->si_h_startpos = CUR_STATE(idx - 1).si_h_startpos;
2755 	    sip->si_h_endpos = CUR_STATE(idx - 1).si_h_endpos;
2756 	    if (sip->si_cont_list == NULL)
2757 	    {
2758 		sip->si_flags |= HL_TRANS_CONT;
2759 		sip->si_cont_list = CUR_STATE(idx - 1).si_cont_list;
2760 	    }
2761 	}
2762     }
2763 }
2764 
2765 /*
2766  * Check the current stack for patterns with "keepend" flag.
2767  * Propagate the match-end to contained items, until a "skipend" item is found.
2768  */
2769     static void
2770 check_keepend(void)
2771 {
2772     int		i;
2773     lpos_T	maxpos;
2774     lpos_T	maxpos_h;
2775     stateitem_T	*sip;
2776 
2777     /*
2778      * This check can consume a lot of time; only do it from the level where
2779      * there really is a keepend.
2780      */
2781     if (keepend_level < 0)
2782 	return;
2783 
2784     /*
2785      * Find the last index of an "extend" item.  "keepend" items before that
2786      * won't do anything.  If there is no "extend" item "i" will be
2787      * "keepend_level" and all "keepend" items will work normally.
2788      */
2789     for (i = current_state.ga_len - 1; i > keepend_level; --i)
2790 	if (CUR_STATE(i).si_flags & HL_EXTEND)
2791 	    break;
2792 
2793     maxpos.lnum = 0;
2794     maxpos.col = 0;
2795     maxpos_h.lnum = 0;
2796     maxpos_h.col = 0;
2797     for ( ; i < current_state.ga_len; ++i)
2798     {
2799 	sip = &CUR_STATE(i);
2800 	if (maxpos.lnum != 0)
2801 	{
2802 	    limit_pos_zero(&sip->si_m_endpos, &maxpos);
2803 	    limit_pos_zero(&sip->si_h_endpos, &maxpos_h);
2804 	    limit_pos_zero(&sip->si_eoe_pos, &maxpos);
2805 	    sip->si_ends = TRUE;
2806 	}
2807 	if (sip->si_ends && (sip->si_flags & HL_KEEPEND))
2808 	{
2809 	    if (maxpos.lnum == 0
2810 		    || maxpos.lnum > sip->si_m_endpos.lnum
2811 		    || (maxpos.lnum == sip->si_m_endpos.lnum
2812 			&& maxpos.col > sip->si_m_endpos.col))
2813 		maxpos = sip->si_m_endpos;
2814 	    if (maxpos_h.lnum == 0
2815 		    || maxpos_h.lnum > sip->si_h_endpos.lnum
2816 		    || (maxpos_h.lnum == sip->si_h_endpos.lnum
2817 			&& maxpos_h.col > sip->si_h_endpos.col))
2818 		maxpos_h = sip->si_h_endpos;
2819 	}
2820     }
2821 }
2822 
2823 /*
2824  * Update an entry in the current_state stack for a start-skip-end pattern.
2825  * This finds the end of the current item, if it's in the current line.
2826  *
2827  * Return the flags for the matched END.
2828  */
2829     static void
2830 update_si_end(
2831     stateitem_T	*sip,
2832     int		startcol,   /* where to start searching for the end */
2833     int		force)	    /* when TRUE overrule a previous end */
2834 {
2835     lpos_T	startpos;
2836     lpos_T	endpos;
2837     lpos_T	hl_endpos;
2838     lpos_T	end_endpos;
2839     int		end_idx;
2840 
2841     /* return quickly for a keyword */
2842     if (sip->si_idx < 0)
2843 	return;
2844 
2845     /* Don't update when it's already done.  Can be a match of an end pattern
2846      * that started in a previous line.  Watch out: can also be a "keepend"
2847      * from a containing item. */
2848     if (!force && sip->si_m_endpos.lnum >= current_lnum)
2849 	return;
2850 
2851     /*
2852      * We need to find the end of the region.  It may continue in the next
2853      * line.
2854      */
2855     end_idx = 0;
2856     startpos.lnum = current_lnum;
2857     startpos.col = startcol;
2858     find_endpos(sip->si_idx, &startpos, &endpos, &hl_endpos,
2859 		   &(sip->si_flags), &end_endpos, &end_idx, sip->si_extmatch);
2860 
2861     if (endpos.lnum == 0)
2862     {
2863 	/* No end pattern matched. */
2864 	if (SYN_ITEMS(syn_block)[sip->si_idx].sp_flags & HL_ONELINE)
2865 	{
2866 	    /* a "oneline" never continues in the next line */
2867 	    sip->si_ends = TRUE;
2868 	    sip->si_m_endpos.lnum = current_lnum;
2869 	    sip->si_m_endpos.col = (colnr_T)STRLEN(syn_getcurline());
2870 	}
2871 	else
2872 	{
2873 	    /* continues in the next line */
2874 	    sip->si_ends = FALSE;
2875 	    sip->si_m_endpos.lnum = 0;
2876 	}
2877 	sip->si_h_endpos = sip->si_m_endpos;
2878     }
2879     else
2880     {
2881 	/* match within this line */
2882 	sip->si_m_endpos = endpos;
2883 	sip->si_h_endpos = hl_endpos;
2884 	sip->si_eoe_pos = end_endpos;
2885 	sip->si_ends = TRUE;
2886 	sip->si_end_idx = end_idx;
2887     }
2888 }
2889 
2890 /*
2891  * Add a new state to the current state stack.
2892  * It is cleared and the index set to "idx".
2893  * Return FAIL if it's not possible (out of memory).
2894  */
2895     static int
2896 push_current_state(int idx)
2897 {
2898     if (ga_grow(&current_state, 1) == FAIL)
2899 	return FAIL;
2900     vim_memset(&CUR_STATE(current_state.ga_len), 0, sizeof(stateitem_T));
2901     CUR_STATE(current_state.ga_len).si_idx = idx;
2902     ++current_state.ga_len;
2903     return OK;
2904 }
2905 
2906 /*
2907  * Remove a state from the current_state stack.
2908  */
2909     static void
2910 pop_current_state(void)
2911 {
2912     if (current_state.ga_len)
2913     {
2914 	unref_extmatch(CUR_STATE(current_state.ga_len - 1).si_extmatch);
2915 	--current_state.ga_len;
2916     }
2917     /* after the end of a pattern, try matching a keyword or pattern */
2918     next_match_idx = -1;
2919 
2920     /* if first state with "keepend" is popped, reset keepend_level */
2921     if (keepend_level >= current_state.ga_len)
2922 	keepend_level = -1;
2923 }
2924 
2925 /*
2926  * Find the end of a start/skip/end syntax region after "startpos".
2927  * Only checks one line.
2928  * Also handles a match item that continued from a previous line.
2929  * If not found, the syntax item continues in the next line.  m_endpos->lnum
2930  * will be 0.
2931  * If found, the end of the region and the end of the highlighting is
2932  * computed.
2933  */
2934     static void
2935 find_endpos(
2936     int		idx,		/* index of the pattern */
2937     lpos_T	*startpos,	/* where to start looking for an END match */
2938     lpos_T	*m_endpos,	/* return: end of match */
2939     lpos_T	*hl_endpos,	/* return: end of highlighting */
2940     long	*flagsp,	/* return: flags of matching END */
2941     lpos_T	*end_endpos,	/* return: end of end pattern match */
2942     int		*end_idx,	/* return: group ID for end pat. match, or 0 */
2943     reg_extmatch_T *start_ext)	/* submatches from the start pattern */
2944 {
2945     colnr_T	matchcol;
2946     synpat_T	*spp, *spp_skip;
2947     int		start_idx;
2948     int		best_idx;
2949     regmmatch_T	regmatch;
2950     regmmatch_T	best_regmatch;	    /* startpos/endpos of best match */
2951     lpos_T	pos;
2952     char_u	*line;
2953     int		had_match = FALSE;
2954     char_u	buf_chartab[32];  /* chartab array for syn option iskyeyword */
2955 
2956     /* just in case we are invoked for a keyword */
2957     if (idx < 0)
2958 	return;
2959 
2960     /*
2961      * Check for being called with a START pattern.
2962      * Can happen with a match that continues to the next line, because it
2963      * contained a region.
2964      */
2965     spp = &(SYN_ITEMS(syn_block)[idx]);
2966     if (spp->sp_type != SPTYPE_START)
2967     {
2968 	*hl_endpos = *startpos;
2969 	return;
2970     }
2971 
2972     /*
2973      * Find the SKIP or first END pattern after the last START pattern.
2974      */
2975     for (;;)
2976     {
2977 	spp = &(SYN_ITEMS(syn_block)[idx]);
2978 	if (spp->sp_type != SPTYPE_START)
2979 	    break;
2980 	++idx;
2981     }
2982 
2983     /*
2984      *	Lookup the SKIP pattern (if present)
2985      */
2986     if (spp->sp_type == SPTYPE_SKIP)
2987     {
2988 	spp_skip = spp;
2989 	++idx;
2990     }
2991     else
2992 	spp_skip = NULL;
2993 
2994     /* Setup external matches for syn_regexec(). */
2995     unref_extmatch(re_extmatch_in);
2996     re_extmatch_in = ref_extmatch(start_ext);
2997 
2998     matchcol = startpos->col;	/* start looking for a match at sstart */
2999     start_idx = idx;		/* remember the first END pattern. */
3000     best_regmatch.startpos[0].col = 0;		/* avoid compiler warning */
3001 
3002     /* use syntax iskeyword option */
3003     save_chartab(buf_chartab);
3004 
3005     for (;;)
3006     {
3007 	/*
3008 	 * Find end pattern that matches first after "matchcol".
3009 	 */
3010 	best_idx = -1;
3011 	for (idx = start_idx; idx < syn_block->b_syn_patterns.ga_len; ++idx)
3012 	{
3013 	    int lc_col = matchcol;
3014 	    int r;
3015 
3016 	    spp = &(SYN_ITEMS(syn_block)[idx]);
3017 	    if (spp->sp_type != SPTYPE_END)	/* past last END pattern */
3018 		break;
3019 	    lc_col -= spp->sp_offsets[SPO_LC_OFF];
3020 	    if (lc_col < 0)
3021 		lc_col = 0;
3022 
3023 	    regmatch.rmm_ic = spp->sp_ic;
3024 	    regmatch.regprog = spp->sp_prog;
3025 	    r = syn_regexec(&regmatch, startpos->lnum, lc_col,
3026 						  IF_SYN_TIME(&spp->sp_time));
3027 	    spp->sp_prog = regmatch.regprog;
3028 	    if (r)
3029 	    {
3030 		if (best_idx == -1 || regmatch.startpos[0].col
3031 					      < best_regmatch.startpos[0].col)
3032 		{
3033 		    best_idx = idx;
3034 		    best_regmatch.startpos[0] = regmatch.startpos[0];
3035 		    best_regmatch.endpos[0] = regmatch.endpos[0];
3036 		}
3037 	    }
3038 	}
3039 
3040 	/*
3041 	 * If all end patterns have been tried, and there is no match, the
3042 	 * item continues until end-of-line.
3043 	 */
3044 	if (best_idx == -1)
3045 	    break;
3046 
3047 	/*
3048 	 * If the skip pattern matches before the end pattern,
3049 	 * continue searching after the skip pattern.
3050 	 */
3051 	if (spp_skip != NULL)
3052 	{
3053 	    int lc_col = matchcol - spp_skip->sp_offsets[SPO_LC_OFF];
3054 	    int r;
3055 
3056 	    if (lc_col < 0)
3057 		lc_col = 0;
3058 	    regmatch.rmm_ic = spp_skip->sp_ic;
3059 	    regmatch.regprog = spp_skip->sp_prog;
3060 	    r = syn_regexec(&regmatch, startpos->lnum, lc_col,
3061 					      IF_SYN_TIME(&spp_skip->sp_time));
3062 	    spp_skip->sp_prog = regmatch.regprog;
3063 	    if (r && regmatch.startpos[0].col
3064 					     <= best_regmatch.startpos[0].col)
3065 	    {
3066 		int line_len;
3067 
3068 		/* Add offset to skip pattern match */
3069 		syn_add_end_off(&pos, &regmatch, spp_skip, SPO_ME_OFF, 1);
3070 
3071 		/* If the skip pattern goes on to the next line, there is no
3072 		 * match with an end pattern in this line. */
3073 		if (pos.lnum > startpos->lnum)
3074 		    break;
3075 
3076 		line = ml_get_buf(syn_buf, startpos->lnum, FALSE);
3077 		line_len = (int)STRLEN(line);
3078 
3079 		/* take care of an empty match or negative offset */
3080 		if (pos.col <= matchcol)
3081 		    ++matchcol;
3082 		else if (pos.col <= regmatch.endpos[0].col)
3083 		    matchcol = pos.col;
3084 		else
3085 		    /* Be careful not to jump over the NUL at the end-of-line */
3086 		    for (matchcol = regmatch.endpos[0].col;
3087 			    matchcol < line_len && matchcol < pos.col;
3088 								   ++matchcol)
3089 			;
3090 
3091 		/* if the skip pattern includes end-of-line, break here */
3092 		if (matchcol >= line_len)
3093 		    break;
3094 
3095 		continue;	    /* start with first end pattern again */
3096 	    }
3097 	}
3098 
3099 	/*
3100 	 * Match from start pattern to end pattern.
3101 	 * Correct for match and highlight offset of end pattern.
3102 	 */
3103 	spp = &(SYN_ITEMS(syn_block)[best_idx]);
3104 	syn_add_end_off(m_endpos, &best_regmatch, spp, SPO_ME_OFF, 1);
3105 	/* can't end before the start */
3106 	if (m_endpos->lnum == startpos->lnum && m_endpos->col < startpos->col)
3107 	    m_endpos->col = startpos->col;
3108 
3109 	syn_add_end_off(end_endpos, &best_regmatch, spp, SPO_HE_OFF, 1);
3110 	/* can't end before the start */
3111 	if (end_endpos->lnum == startpos->lnum
3112 					   && end_endpos->col < startpos->col)
3113 	    end_endpos->col = startpos->col;
3114 	/* can't end after the match */
3115 	limit_pos(end_endpos, m_endpos);
3116 
3117 	/*
3118 	 * If the end group is highlighted differently, adjust the pointers.
3119 	 */
3120 	if (spp->sp_syn_match_id != spp->sp_syn.id && spp->sp_syn_match_id != 0)
3121 	{
3122 	    *end_idx = best_idx;
3123 	    if (spp->sp_off_flags & (1 << (SPO_RE_OFF + SPO_COUNT)))
3124 	    {
3125 		hl_endpos->lnum = best_regmatch.endpos[0].lnum;
3126 		hl_endpos->col = best_regmatch.endpos[0].col;
3127 	    }
3128 	    else
3129 	    {
3130 		hl_endpos->lnum = best_regmatch.startpos[0].lnum;
3131 		hl_endpos->col = best_regmatch.startpos[0].col;
3132 	    }
3133 	    hl_endpos->col += spp->sp_offsets[SPO_RE_OFF];
3134 
3135 	    /* can't end before the start */
3136 	    if (hl_endpos->lnum == startpos->lnum
3137 					    && hl_endpos->col < startpos->col)
3138 		hl_endpos->col = startpos->col;
3139 	    limit_pos(hl_endpos, m_endpos);
3140 
3141 	    /* now the match ends where the highlighting ends, it is turned
3142 	     * into the matchgroup for the end */
3143 	    *m_endpos = *hl_endpos;
3144 	}
3145 	else
3146 	{
3147 	    *end_idx = 0;
3148 	    *hl_endpos = *end_endpos;
3149 	}
3150 
3151 	*flagsp = spp->sp_flags;
3152 
3153 	had_match = TRUE;
3154 	break;
3155     }
3156 
3157     /* no match for an END pattern in this line */
3158     if (!had_match)
3159 	m_endpos->lnum = 0;
3160 
3161     restore_chartab(buf_chartab);
3162 
3163     /* Remove external matches. */
3164     unref_extmatch(re_extmatch_in);
3165     re_extmatch_in = NULL;
3166 }
3167 
3168 /*
3169  * Limit "pos" not to be after "limit".
3170  */
3171     static void
3172 limit_pos(lpos_T *pos, lpos_T *limit)
3173 {
3174     if (pos->lnum > limit->lnum)
3175 	*pos = *limit;
3176     else if (pos->lnum == limit->lnum && pos->col > limit->col)
3177 	pos->col = limit->col;
3178 }
3179 
3180 /*
3181  * Limit "pos" not to be after "limit", unless pos->lnum is zero.
3182  */
3183     static void
3184 limit_pos_zero(
3185     lpos_T	*pos,
3186     lpos_T	*limit)
3187 {
3188     if (pos->lnum == 0)
3189 	*pos = *limit;
3190     else
3191 	limit_pos(pos, limit);
3192 }
3193 
3194 /*
3195  * Add offset to matched text for end of match or highlight.
3196  */
3197     static void
3198 syn_add_end_off(
3199     lpos_T	*result,	/* returned position */
3200     regmmatch_T	*regmatch,	/* start/end of match */
3201     synpat_T	*spp,		/* matched pattern */
3202     int		idx,		/* index of offset */
3203     int		extra)		/* extra chars for offset to start */
3204 {
3205     int		col;
3206     int		off;
3207     char_u	*base;
3208     char_u	*p;
3209 
3210     if (spp->sp_off_flags & (1 << idx))
3211     {
3212 	result->lnum = regmatch->startpos[0].lnum;
3213 	col = regmatch->startpos[0].col;
3214 	off = spp->sp_offsets[idx] + extra;
3215     }
3216     else
3217     {
3218 	result->lnum = regmatch->endpos[0].lnum;
3219 	col = regmatch->endpos[0].col;
3220 	off = spp->sp_offsets[idx];
3221     }
3222     /* Don't go past the end of the line.  Matters for "rs=e+2" when there
3223      * is a matchgroup. Watch out for match with last NL in the buffer. */
3224     if (result->lnum > syn_buf->b_ml.ml_line_count)
3225 	col = 0;
3226     else if (off != 0)
3227     {
3228 	base = ml_get_buf(syn_buf, result->lnum, FALSE);
3229 	p = base + col;
3230 	if (off > 0)
3231 	{
3232 	    while (off-- > 0 && *p != NUL)
3233 		MB_PTR_ADV(p);
3234 	}
3235 	else if (off < 0)
3236 	{
3237 	    while (off++ < 0 && base < p)
3238 		MB_PTR_BACK(base, p);
3239 	}
3240 	col = (int)(p - base);
3241     }
3242     result->col = col;
3243 }
3244 
3245 /*
3246  * Add offset to matched text for start of match or highlight.
3247  * Avoid resulting column to become negative.
3248  */
3249     static void
3250 syn_add_start_off(
3251     lpos_T	*result,	/* returned position */
3252     regmmatch_T	*regmatch,	/* start/end of match */
3253     synpat_T	*spp,
3254     int		idx,
3255     int		extra)	    /* extra chars for offset to end */
3256 {
3257     int		col;
3258     int		off;
3259     char_u	*base;
3260     char_u	*p;
3261 
3262     if (spp->sp_off_flags & (1 << (idx + SPO_COUNT)))
3263     {
3264 	result->lnum = regmatch->endpos[0].lnum;
3265 	col = regmatch->endpos[0].col;
3266 	off = spp->sp_offsets[idx] + extra;
3267     }
3268     else
3269     {
3270 	result->lnum = regmatch->startpos[0].lnum;
3271 	col = regmatch->startpos[0].col;
3272 	off = spp->sp_offsets[idx];
3273     }
3274     if (result->lnum > syn_buf->b_ml.ml_line_count)
3275     {
3276 	/* a "\n" at the end of the pattern may take us below the last line */
3277 	result->lnum = syn_buf->b_ml.ml_line_count;
3278 	col = (int)STRLEN(ml_get_buf(syn_buf, result->lnum, FALSE));
3279     }
3280     if (off != 0)
3281     {
3282 	base = ml_get_buf(syn_buf, result->lnum, FALSE);
3283 	p = base + col;
3284 	if (off > 0)
3285 	{
3286 	    while (off-- && *p != NUL)
3287 		MB_PTR_ADV(p);
3288 	}
3289 	else if (off < 0)
3290 	{
3291 	    while (off++ && base < p)
3292 		MB_PTR_BACK(base, p);
3293 	}
3294 	col = (int)(p - base);
3295     }
3296     result->col = col;
3297 }
3298 
3299 /*
3300  * Get current line in syntax buffer.
3301  */
3302     static char_u *
3303 syn_getcurline(void)
3304 {
3305     return ml_get_buf(syn_buf, current_lnum, FALSE);
3306 }
3307 
3308 /*
3309  * Call vim_regexec() to find a match with "rmp" in "syn_buf".
3310  * Returns TRUE when there is a match.
3311  */
3312     static int
3313 syn_regexec(
3314     regmmatch_T	*rmp,
3315     linenr_T	lnum,
3316     colnr_T	col,
3317     syn_time_T  *st UNUSED)
3318 {
3319     int r;
3320 #ifdef FEAT_RELTIME
3321     int timed_out = FALSE;
3322 #endif
3323 #ifdef FEAT_PROFILE
3324     proftime_T	pt;
3325 
3326     if (syn_time_on)
3327 	profile_start(&pt);
3328 #endif
3329 
3330     if (rmp->regprog == NULL)
3331 	// This can happen if a previous call to vim_regexec_multi() tried to
3332 	// use the NFA engine, which resulted in NFA_TOO_EXPENSIVE, and
3333 	// compiling the pattern with the other engine fails.
3334 	return FALSE;
3335 
3336     rmp->rmm_maxcol = syn_buf->b_p_smc;
3337     r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col,
3338 #ifdef FEAT_RELTIME
3339 	    syn_tm, &timed_out
3340 #else
3341 	    NULL, NULL
3342 #endif
3343 	    );
3344 
3345 #ifdef FEAT_PROFILE
3346     if (syn_time_on)
3347     {
3348 	profile_end(&pt);
3349 	profile_add(&st->total, &pt);
3350 	if (profile_cmp(&pt, &st->slowest) < 0)
3351 	    st->slowest = pt;
3352 	++st->count;
3353 	if (r > 0)
3354 	    ++st->match;
3355     }
3356 #endif
3357 #ifdef FEAT_RELTIME
3358     if (timed_out)
3359 	syn_win->w_s->b_syn_slow = TRUE;
3360 #endif
3361 
3362     if (r > 0)
3363     {
3364 	rmp->startpos[0].lnum += lnum;
3365 	rmp->endpos[0].lnum += lnum;
3366 	return TRUE;
3367     }
3368     return FALSE;
3369 }
3370 
3371 /*
3372  * Check one position in a line for a matching keyword.
3373  * The caller must check if a keyword can start at startcol.
3374  * Return its ID if found, 0 otherwise.
3375  */
3376     static int
3377 check_keyword_id(
3378     char_u	*line,
3379     int		startcol,	/* position in line to check for keyword */
3380     int		*endcolp,	/* return: character after found keyword */
3381     long	*flagsp,	/* return: flags of matching keyword */
3382     short	**next_listp,	/* return: next_list of matching keyword */
3383     stateitem_T	*cur_si,	/* item at the top of the stack */
3384     int		*ccharp UNUSED)	/* conceal substitution char */
3385 {
3386     keyentry_T	*kp;
3387     char_u	*kwp;
3388     int		round;
3389     int		kwlen;
3390     char_u	keyword[MAXKEYWLEN + 1]; /* assume max. keyword len is 80 */
3391     hashtab_T	*ht;
3392     hashitem_T	*hi;
3393 
3394     /* Find first character after the keyword.  First character was already
3395      * checked. */
3396     kwp = line + startcol;
3397     kwlen = 0;
3398     do
3399     {
3400 #ifdef FEAT_MBYTE
3401 	if (has_mbyte)
3402 	    kwlen += (*mb_ptr2len)(kwp + kwlen);
3403 	else
3404 #endif
3405 	    ++kwlen;
3406     }
3407     while (vim_iswordp_buf(kwp + kwlen, syn_buf));
3408 
3409     if (kwlen > MAXKEYWLEN)
3410 	return 0;
3411 
3412     /*
3413      * Must make a copy of the keyword, so we can add a NUL and make it
3414      * lowercase.
3415      */
3416     vim_strncpy(keyword, kwp, kwlen);
3417 
3418     /*
3419      * Try twice:
3420      * 1. matching case
3421      * 2. ignoring case
3422      */
3423     for (round = 1; round <= 2; ++round)
3424     {
3425 	ht = round == 1 ? &syn_block->b_keywtab : &syn_block->b_keywtab_ic;
3426 	if (ht->ht_used == 0)
3427 	    continue;
3428 	if (round == 2)	/* ignore case */
3429 	    (void)str_foldcase(kwp, kwlen, keyword, MAXKEYWLEN + 1);
3430 
3431 	/*
3432 	 * Find keywords that match.  There can be several with different
3433 	 * attributes.
3434 	 * When current_next_list is non-zero accept only that group, otherwise:
3435 	 *  Accept a not-contained keyword at toplevel.
3436 	 *  Accept a keyword at other levels only if it is in the contains list.
3437 	 */
3438 	hi = hash_find(ht, keyword);
3439 	if (!HASHITEM_EMPTY(hi))
3440 	    for (kp = HI2KE(hi); kp != NULL; kp = kp->ke_next)
3441 	    {
3442 		if (current_next_list != 0
3443 			? in_id_list(NULL, current_next_list, &kp->k_syn, 0)
3444 			: (cur_si == NULL
3445 			    ? !(kp->flags & HL_CONTAINED)
3446 			    : in_id_list(cur_si, cur_si->si_cont_list,
3447 				      &kp->k_syn, kp->flags & HL_CONTAINED)))
3448 		{
3449 		    *endcolp = startcol + kwlen;
3450 		    *flagsp = kp->flags;
3451 		    *next_listp = kp->next_list;
3452 #ifdef FEAT_CONCEAL
3453 		    *ccharp = kp->k_char;
3454 #endif
3455 		    return kp->k_syn.id;
3456 		}
3457 	    }
3458     }
3459     return 0;
3460 }
3461 
3462 /*
3463  * Handle ":syntax conceal" command.
3464  */
3465     static void
3466 syn_cmd_conceal(exarg_T *eap UNUSED, int syncing UNUSED)
3467 {
3468 #ifdef FEAT_CONCEAL
3469     char_u	*arg = eap->arg;
3470     char_u	*next;
3471 
3472     eap->nextcmd = find_nextcmd(arg);
3473     if (eap->skip)
3474 	return;
3475 
3476     next = skiptowhite(arg);
3477     if (*arg == NUL)
3478     {
3479 	if (curwin->w_s->b_syn_conceal)
3480 	    MSG(_("syntax conceal on"));
3481 	else
3482 	    MSG(_("syntax conceal off"));
3483     }
3484     else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2)
3485 	curwin->w_s->b_syn_conceal = TRUE;
3486     else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3)
3487 	curwin->w_s->b_syn_conceal = FALSE;
3488     else
3489 	EMSG2(_("E390: Illegal argument: %s"), arg);
3490 #endif
3491 }
3492 
3493 /*
3494  * Handle ":syntax case" command.
3495  */
3496     static void
3497 syn_cmd_case(exarg_T *eap, int syncing UNUSED)
3498 {
3499     char_u	*arg = eap->arg;
3500     char_u	*next;
3501 
3502     eap->nextcmd = find_nextcmd(arg);
3503     if (eap->skip)
3504 	return;
3505 
3506     next = skiptowhite(arg);
3507     if (*arg == NUL)
3508     {
3509 	if (curwin->w_s->b_syn_ic)
3510 	    MSG(_("syntax case ignore"));
3511 	else
3512 	    MSG(_("syntax case match"));
3513     }
3514     else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5)
3515 	curwin->w_s->b_syn_ic = FALSE;
3516     else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6)
3517 	curwin->w_s->b_syn_ic = TRUE;
3518     else
3519 	EMSG2(_("E390: Illegal argument: %s"), arg);
3520 }
3521 
3522 /*
3523  * Handle ":syntax spell" command.
3524  */
3525     static void
3526 syn_cmd_spell(exarg_T *eap, int syncing UNUSED)
3527 {
3528     char_u	*arg = eap->arg;
3529     char_u	*next;
3530 
3531     eap->nextcmd = find_nextcmd(arg);
3532     if (eap->skip)
3533 	return;
3534 
3535     next = skiptowhite(arg);
3536     if (*arg == NUL)
3537     {
3538 	if (curwin->w_s->b_syn_spell == SYNSPL_TOP)
3539 	    MSG(_("syntax spell toplevel"));
3540 	else if (curwin->w_s->b_syn_spell == SYNSPL_NOTOP)
3541 	    MSG(_("syntax spell notoplevel"));
3542 	else
3543 	    MSG(_("syntax spell default"));
3544     }
3545     else if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8)
3546 	curwin->w_s->b_syn_spell = SYNSPL_TOP;
3547     else if (STRNICMP(arg, "notoplevel", 10) == 0 && next - arg == 10)
3548 	curwin->w_s->b_syn_spell = SYNSPL_NOTOP;
3549     else if (STRNICMP(arg, "default", 7) == 0 && next - arg == 7)
3550 	curwin->w_s->b_syn_spell = SYNSPL_DEFAULT;
3551     else
3552     {
3553 	EMSG2(_("E390: Illegal argument: %s"), arg);
3554 	return;
3555     }
3556 
3557     /* assume spell checking changed, force a redraw */
3558     redraw_win_later(curwin, NOT_VALID);
3559 }
3560 
3561 /*
3562  * Handle ":syntax iskeyword" command.
3563  */
3564     static void
3565 syn_cmd_iskeyword(exarg_T *eap, int syncing UNUSED)
3566 {
3567     char_u	*arg = eap->arg;
3568     char_u	save_chartab[32];
3569     char_u	*save_isk;
3570 
3571     if (eap->skip)
3572 	return;
3573 
3574     arg = skipwhite(arg);
3575     if (*arg == NUL)
3576     {
3577 	MSG_PUTS("\n");
3578 	MSG_PUTS(_("syntax iskeyword "));
3579 	if (curwin->w_s->b_syn_isk != empty_option)
3580 	    msg_outtrans(curwin->w_s->b_syn_isk);
3581 	else
3582 	    msg_outtrans((char_u *)"not set");
3583     }
3584     else
3585     {
3586 	if (STRNICMP(arg, "clear", 5) == 0)
3587 	{
3588 	    mch_memmove(curwin->w_s->b_syn_chartab, curbuf->b_chartab,
3589 								  (size_t)32);
3590 	    clear_string_option(&curwin->w_s->b_syn_isk);
3591 	}
3592 	else
3593 	{
3594 	    mch_memmove(save_chartab, curbuf->b_chartab, (size_t)32);
3595 	    save_isk = curbuf->b_p_isk;
3596 	    curbuf->b_p_isk = vim_strsave(arg);
3597 
3598 	    buf_init_chartab(curbuf, FALSE);
3599 	    mch_memmove(curwin->w_s->b_syn_chartab, curbuf->b_chartab,
3600 								  (size_t)32);
3601 	    mch_memmove(curbuf->b_chartab, save_chartab, (size_t)32);
3602 	    clear_string_option(&curwin->w_s->b_syn_isk);
3603 	    curwin->w_s->b_syn_isk = curbuf->b_p_isk;
3604 	    curbuf->b_p_isk = save_isk;
3605 	}
3606     }
3607     redraw_win_later(curwin, NOT_VALID);
3608 }
3609 
3610 /*
3611  * Clear all syntax info for one buffer.
3612  */
3613     void
3614 syntax_clear(synblock_T *block)
3615 {
3616     int i;
3617 
3618     block->b_syn_error = FALSE;	    /* clear previous error */
3619 #ifdef FEAT_RELTIME
3620     block->b_syn_slow = FALSE;	    /* clear previous timeout */
3621 #endif
3622     block->b_syn_ic = FALSE;	    /* Use case, by default */
3623     block->b_syn_spell = SYNSPL_DEFAULT; /* default spell checking */
3624     block->b_syn_containedin = FALSE;
3625 #ifdef FEAT_CONCEAL
3626     block->b_syn_conceal = FALSE;
3627 #endif
3628 
3629     /* free the keywords */
3630     clear_keywtab(&block->b_keywtab);
3631     clear_keywtab(&block->b_keywtab_ic);
3632 
3633     /* free the syntax patterns */
3634     for (i = block->b_syn_patterns.ga_len; --i >= 0; )
3635 	syn_clear_pattern(block, i);
3636     ga_clear(&block->b_syn_patterns);
3637 
3638     /* free the syntax clusters */
3639     for (i = block->b_syn_clusters.ga_len; --i >= 0; )
3640 	syn_clear_cluster(block, i);
3641     ga_clear(&block->b_syn_clusters);
3642     block->b_spell_cluster_id = 0;
3643     block->b_nospell_cluster_id = 0;
3644 
3645     block->b_syn_sync_flags = 0;
3646     block->b_syn_sync_minlines = 0;
3647     block->b_syn_sync_maxlines = 0;
3648     block->b_syn_sync_linebreaks = 0;
3649 
3650     vim_regfree(block->b_syn_linecont_prog);
3651     block->b_syn_linecont_prog = NULL;
3652     VIM_CLEAR(block->b_syn_linecont_pat);
3653 #ifdef FEAT_FOLDING
3654     block->b_syn_folditems = 0;
3655 #endif
3656     clear_string_option(&block->b_syn_isk);
3657 
3658     /* free the stored states */
3659     syn_stack_free_all(block);
3660     invalidate_current_state();
3661 
3662     /* Reset the counter for ":syn include" */
3663     running_syn_inc_tag = 0;
3664 }
3665 
3666 /*
3667  * Get rid of ownsyntax for window "wp".
3668  */
3669     void
3670 reset_synblock(win_T *wp)
3671 {
3672     if (wp->w_s != &wp->w_buffer->b_s)
3673     {
3674 	syntax_clear(wp->w_s);
3675 	vim_free(wp->w_s);
3676 	wp->w_s = &wp->w_buffer->b_s;
3677     }
3678 }
3679 
3680 /*
3681  * Clear syncing info for one buffer.
3682  */
3683     static void
3684 syntax_sync_clear(void)
3685 {
3686     int i;
3687 
3688     /* free the syntax patterns */
3689     for (i = curwin->w_s->b_syn_patterns.ga_len; --i >= 0; )
3690 	if (SYN_ITEMS(curwin->w_s)[i].sp_syncing)
3691 	    syn_remove_pattern(curwin->w_s, i);
3692 
3693     curwin->w_s->b_syn_sync_flags = 0;
3694     curwin->w_s->b_syn_sync_minlines = 0;
3695     curwin->w_s->b_syn_sync_maxlines = 0;
3696     curwin->w_s->b_syn_sync_linebreaks = 0;
3697 
3698     vim_regfree(curwin->w_s->b_syn_linecont_prog);
3699     curwin->w_s->b_syn_linecont_prog = NULL;
3700     VIM_CLEAR(curwin->w_s->b_syn_linecont_pat);
3701     clear_string_option(&curwin->w_s->b_syn_isk);
3702 
3703     syn_stack_free_all(curwin->w_s);	/* Need to recompute all syntax. */
3704 }
3705 
3706 /*
3707  * Remove one pattern from the buffer's pattern list.
3708  */
3709     static void
3710 syn_remove_pattern(
3711     synblock_T	*block,
3712     int		idx)
3713 {
3714     synpat_T	*spp;
3715 
3716     spp = &(SYN_ITEMS(block)[idx]);
3717 #ifdef FEAT_FOLDING
3718     if (spp->sp_flags & HL_FOLD)
3719 	--block->b_syn_folditems;
3720 #endif
3721     syn_clear_pattern(block, idx);
3722     mch_memmove(spp, spp + 1,
3723 		   sizeof(synpat_T) * (block->b_syn_patterns.ga_len - idx - 1));
3724     --block->b_syn_patterns.ga_len;
3725 }
3726 
3727 /*
3728  * Clear and free one syntax pattern.  When clearing all, must be called from
3729  * last to first!
3730  */
3731     static void
3732 syn_clear_pattern(synblock_T *block, int i)
3733 {
3734     vim_free(SYN_ITEMS(block)[i].sp_pattern);
3735     vim_regfree(SYN_ITEMS(block)[i].sp_prog);
3736     /* Only free sp_cont_list and sp_next_list of first start pattern */
3737     if (i == 0 || SYN_ITEMS(block)[i - 1].sp_type != SPTYPE_START)
3738     {
3739 	vim_free(SYN_ITEMS(block)[i].sp_cont_list);
3740 	vim_free(SYN_ITEMS(block)[i].sp_next_list);
3741 	vim_free(SYN_ITEMS(block)[i].sp_syn.cont_in_list);
3742     }
3743 }
3744 
3745 /*
3746  * Clear and free one syntax cluster.
3747  */
3748     static void
3749 syn_clear_cluster(synblock_T *block, int i)
3750 {
3751     vim_free(SYN_CLSTR(block)[i].scl_name);
3752     vim_free(SYN_CLSTR(block)[i].scl_name_u);
3753     vim_free(SYN_CLSTR(block)[i].scl_list);
3754 }
3755 
3756 /*
3757  * Handle ":syntax clear" command.
3758  */
3759     static void
3760 syn_cmd_clear(exarg_T *eap, int syncing)
3761 {
3762     char_u	*arg = eap->arg;
3763     char_u	*arg_end;
3764     int		id;
3765 
3766     eap->nextcmd = find_nextcmd(arg);
3767     if (eap->skip)
3768 	return;
3769 
3770     /*
3771      * We have to disable this within ":syn include @group filename",
3772      * because otherwise @group would get deleted.
3773      * Only required for Vim 5.x syntax files, 6.0 ones don't contain ":syn
3774      * clear".
3775      */
3776     if (curwin->w_s->b_syn_topgrp != 0)
3777 	return;
3778 
3779     if (ends_excmd(*arg))
3780     {
3781 	/*
3782 	 * No argument: Clear all syntax items.
3783 	 */
3784 	if (syncing)
3785 	    syntax_sync_clear();
3786 	else
3787 	{
3788 	    syntax_clear(curwin->w_s);
3789 	    if (curwin->w_s == &curwin->w_buffer->b_s)
3790 		do_unlet((char_u *)"b:current_syntax", TRUE);
3791 	    do_unlet((char_u *)"w:current_syntax", TRUE);
3792 	}
3793     }
3794     else
3795     {
3796 	/*
3797 	 * Clear the group IDs that are in the argument.
3798 	 */
3799 	while (!ends_excmd(*arg))
3800 	{
3801 	    arg_end = skiptowhite(arg);
3802 	    if (*arg == '@')
3803 	    {
3804 		id = syn_scl_namen2id(arg + 1, (int)(arg_end - arg - 1));
3805 		if (id == 0)
3806 		{
3807 		    EMSG2(_("E391: No such syntax cluster: %s"), arg);
3808 		    break;
3809 		}
3810 		else
3811 		{
3812 		    /*
3813 		     * We can't physically delete a cluster without changing
3814 		     * the IDs of other clusters, so we do the next best thing
3815 		     * and make it empty.
3816 		     */
3817 		    short scl_id = id - SYNID_CLUSTER;
3818 
3819 		    VIM_CLEAR(SYN_CLSTR(curwin->w_s)[scl_id].scl_list);
3820 		}
3821 	    }
3822 	    else
3823 	    {
3824 		id = syn_namen2id(arg, (int)(arg_end - arg));
3825 		if (id == 0)
3826 		{
3827 		    EMSG2(_(e_nogroup), arg);
3828 		    break;
3829 		}
3830 		else
3831 		    syn_clear_one(id, syncing);
3832 	    }
3833 	    arg = skipwhite(arg_end);
3834 	}
3835     }
3836     redraw_curbuf_later(SOME_VALID);
3837     syn_stack_free_all(curwin->w_s);		/* Need to recompute all syntax. */
3838 }
3839 
3840 /*
3841  * Clear one syntax group for the current buffer.
3842  */
3843     static void
3844 syn_clear_one(int id, int syncing)
3845 {
3846     synpat_T	*spp;
3847     int		idx;
3848 
3849     /* Clear keywords only when not ":syn sync clear group-name" */
3850     if (!syncing)
3851     {
3852 	(void)syn_clear_keyword(id, &curwin->w_s->b_keywtab);
3853 	(void)syn_clear_keyword(id, &curwin->w_s->b_keywtab_ic);
3854     }
3855 
3856     /* clear the patterns for "id" */
3857     for (idx = curwin->w_s->b_syn_patterns.ga_len; --idx >= 0; )
3858     {
3859 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
3860 	if (spp->sp_syn.id != id || spp->sp_syncing != syncing)
3861 	    continue;
3862 	syn_remove_pattern(curwin->w_s, idx);
3863     }
3864 }
3865 
3866 /*
3867  * Handle ":syntax on" command.
3868  */
3869     static void
3870 syn_cmd_on(exarg_T *eap, int syncing UNUSED)
3871 {
3872     syn_cmd_onoff(eap, "syntax");
3873 }
3874 
3875 /*
3876  * Handle ":syntax enable" command.
3877  */
3878     static void
3879 syn_cmd_enable(exarg_T *eap, int syncing UNUSED)
3880 {
3881     set_internal_string_var((char_u *)"syntax_cmd", (char_u *)"enable");
3882     syn_cmd_onoff(eap, "syntax");
3883     do_unlet((char_u *)"g:syntax_cmd", TRUE);
3884 }
3885 
3886 /*
3887  * Handle ":syntax reset" command.
3888  * It actually resets highlighting, not syntax.
3889  */
3890     static void
3891 syn_cmd_reset(exarg_T *eap, int syncing UNUSED)
3892 {
3893     eap->nextcmd = check_nextcmd(eap->arg);
3894     if (!eap->skip)
3895     {
3896 	set_internal_string_var((char_u *)"syntax_cmd", (char_u *)"reset");
3897 	do_cmdline_cmd((char_u *)"runtime! syntax/syncolor.vim");
3898 	do_unlet((char_u *)"g:syntax_cmd", TRUE);
3899     }
3900 }
3901 
3902 /*
3903  * Handle ":syntax manual" command.
3904  */
3905     static void
3906 syn_cmd_manual(exarg_T *eap, int syncing UNUSED)
3907 {
3908     syn_cmd_onoff(eap, "manual");
3909 }
3910 
3911 /*
3912  * Handle ":syntax off" command.
3913  */
3914     static void
3915 syn_cmd_off(exarg_T *eap, int syncing UNUSED)
3916 {
3917     syn_cmd_onoff(eap, "nosyntax");
3918 }
3919 
3920     static void
3921 syn_cmd_onoff(exarg_T *eap, char *name)
3922 {
3923     char_u	buf[100];
3924 
3925     eap->nextcmd = check_nextcmd(eap->arg);
3926     if (!eap->skip)
3927     {
3928 	STRCPY(buf, "so ");
3929 	vim_snprintf((char *)buf + 3, sizeof(buf) - 3, SYNTAX_FNAME, name);
3930 	do_cmdline_cmd(buf);
3931     }
3932 }
3933 
3934 /*
3935  * Handle ":syntax [list]" command: list current syntax words.
3936  */
3937     static void
3938 syn_cmd_list(
3939     exarg_T	*eap,
3940     int		syncing)	    /* when TRUE: list syncing items */
3941 {
3942     char_u	*arg = eap->arg;
3943     int		id;
3944     char_u	*arg_end;
3945 
3946     eap->nextcmd = find_nextcmd(arg);
3947     if (eap->skip)
3948 	return;
3949 
3950     if (!syntax_present(curwin))
3951     {
3952 	MSG(_(msg_no_items));
3953 	return;
3954     }
3955 
3956     if (syncing)
3957     {
3958 	if (curwin->w_s->b_syn_sync_flags & SF_CCOMMENT)
3959 	{
3960 	    MSG_PUTS(_("syncing on C-style comments"));
3961 	    syn_lines_msg();
3962 	    syn_match_msg();
3963 	    return;
3964 	}
3965 	else if (!(curwin->w_s->b_syn_sync_flags & SF_MATCH))
3966 	{
3967 	    if (curwin->w_s->b_syn_sync_minlines == 0)
3968 		MSG_PUTS(_("no syncing"));
3969 	    else
3970 	    {
3971 		MSG_PUTS(_("syncing starts "));
3972 		msg_outnum(curwin->w_s->b_syn_sync_minlines);
3973 		MSG_PUTS(_(" lines before top line"));
3974 		syn_match_msg();
3975 	    }
3976 	    return;
3977 	}
3978 	MSG_PUTS_TITLE(_("\n--- Syntax sync items ---"));
3979 	if (curwin->w_s->b_syn_sync_minlines > 0
3980 		|| curwin->w_s->b_syn_sync_maxlines > 0
3981 		|| curwin->w_s->b_syn_sync_linebreaks > 0)
3982 	{
3983 	    MSG_PUTS(_("\nsyncing on items"));
3984 	    syn_lines_msg();
3985 	    syn_match_msg();
3986 	}
3987     }
3988     else
3989 	MSG_PUTS_TITLE(_("\n--- Syntax items ---"));
3990     if (ends_excmd(*arg))
3991     {
3992 	/*
3993 	 * No argument: List all group IDs and all syntax clusters.
3994 	 */
3995 	for (id = 1; id <= highlight_ga.ga_len && !got_int; ++id)
3996 	    syn_list_one(id, syncing, FALSE);
3997 	for (id = 0; id < curwin->w_s->b_syn_clusters.ga_len && !got_int; ++id)
3998 	    syn_list_cluster(id);
3999     }
4000     else
4001     {
4002 	/*
4003 	 * List the group IDs and syntax clusters that are in the argument.
4004 	 */
4005 	while (!ends_excmd(*arg) && !got_int)
4006 	{
4007 	    arg_end = skiptowhite(arg);
4008 	    if (*arg == '@')
4009 	    {
4010 		id = syn_scl_namen2id(arg + 1, (int)(arg_end - arg - 1));
4011 		if (id == 0)
4012 		    EMSG2(_("E392: No such syntax cluster: %s"), arg);
4013 		else
4014 		    syn_list_cluster(id - SYNID_CLUSTER);
4015 	    }
4016 	    else
4017 	    {
4018 		id = syn_namen2id(arg, (int)(arg_end - arg));
4019 		if (id == 0)
4020 		    EMSG2(_(e_nogroup), arg);
4021 		else
4022 		    syn_list_one(id, syncing, TRUE);
4023 	    }
4024 	    arg = skipwhite(arg_end);
4025 	}
4026     }
4027     eap->nextcmd = check_nextcmd(arg);
4028 }
4029 
4030     static void
4031 syn_lines_msg(void)
4032 {
4033     if (curwin->w_s->b_syn_sync_maxlines > 0
4034 				      || curwin->w_s->b_syn_sync_minlines > 0)
4035     {
4036 	MSG_PUTS("; ");
4037 	if (curwin->w_s->b_syn_sync_minlines > 0)
4038 	{
4039 	    MSG_PUTS(_("minimal "));
4040 	    msg_outnum(curwin->w_s->b_syn_sync_minlines);
4041 	    if (curwin->w_s->b_syn_sync_maxlines)
4042 		MSG_PUTS(", ");
4043 	}
4044 	if (curwin->w_s->b_syn_sync_maxlines > 0)
4045 	{
4046 	    MSG_PUTS(_("maximal "));
4047 	    msg_outnum(curwin->w_s->b_syn_sync_maxlines);
4048 	}
4049 	MSG_PUTS(_(" lines before top line"));
4050     }
4051 }
4052 
4053     static void
4054 syn_match_msg(void)
4055 {
4056     if (curwin->w_s->b_syn_sync_linebreaks > 0)
4057     {
4058 	MSG_PUTS(_("; match "));
4059 	msg_outnum(curwin->w_s->b_syn_sync_linebreaks);
4060 	MSG_PUTS(_(" line breaks"));
4061     }
4062 }
4063 
4064 static int  last_matchgroup;
4065 
4066 struct name_list
4067 {
4068     int		flag;
4069     char	*name;
4070 };
4071 
4072 static void syn_list_flags(struct name_list *nl, int flags, int attr);
4073 
4074 /*
4075  * List one syntax item, for ":syntax" or "syntax list syntax_name".
4076  */
4077     static void
4078 syn_list_one(
4079     int		id,
4080     int		syncing,	    /* when TRUE: list syncing items */
4081     int		link_only)	    /* when TRUE; list link-only too */
4082 {
4083     int		attr;
4084     int		idx;
4085     int		did_header = FALSE;
4086     synpat_T	*spp;
4087     static struct name_list namelist1[] =
4088 		{
4089 		    {HL_DISPLAY, "display"},
4090 		    {HL_CONTAINED, "contained"},
4091 		    {HL_ONELINE, "oneline"},
4092 		    {HL_KEEPEND, "keepend"},
4093 		    {HL_EXTEND, "extend"},
4094 		    {HL_EXCLUDENL, "excludenl"},
4095 		    {HL_TRANSP, "transparent"},
4096 		    {HL_FOLD, "fold"},
4097 #ifdef FEAT_CONCEAL
4098 		    {HL_CONCEAL, "conceal"},
4099 		    {HL_CONCEALENDS, "concealends"},
4100 #endif
4101 		    {0, NULL}
4102 		};
4103     static struct name_list namelist2[] =
4104 		{
4105 		    {HL_SKIPWHITE, "skipwhite"},
4106 		    {HL_SKIPNL, "skipnl"},
4107 		    {HL_SKIPEMPTY, "skipempty"},
4108 		    {0, NULL}
4109 		};
4110 
4111     attr = HL_ATTR(HLF_D);		/* highlight like directories */
4112 
4113     /* list the keywords for "id" */
4114     if (!syncing)
4115     {
4116 	did_header = syn_list_keywords(id, &curwin->w_s->b_keywtab, FALSE, attr);
4117 	did_header = syn_list_keywords(id, &curwin->w_s->b_keywtab_ic,
4118 							    did_header, attr);
4119     }
4120 
4121     /* list the patterns for "id" */
4122     for (idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len && !got_int; ++idx)
4123     {
4124 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
4125 	if (spp->sp_syn.id != id || spp->sp_syncing != syncing)
4126 	    continue;
4127 
4128 	(void)syn_list_header(did_header, 999, id);
4129 	did_header = TRUE;
4130 	last_matchgroup = 0;
4131 	if (spp->sp_type == SPTYPE_MATCH)
4132 	{
4133 	    put_pattern("match", ' ', spp, attr);
4134 	    msg_putchar(' ');
4135 	}
4136 	else if (spp->sp_type == SPTYPE_START)
4137 	{
4138 	    while (SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_START)
4139 		put_pattern("start", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr);
4140 	    if (SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_SKIP)
4141 		put_pattern("skip", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr);
4142 	    while (idx < curwin->w_s->b_syn_patterns.ga_len
4143 			      && SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_END)
4144 		put_pattern("end", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr);
4145 	    --idx;
4146 	    msg_putchar(' ');
4147 	}
4148 	syn_list_flags(namelist1, spp->sp_flags, attr);
4149 
4150 	if (spp->sp_cont_list != NULL)
4151 	    put_id_list((char_u *)"contains", spp->sp_cont_list, attr);
4152 
4153 	if (spp->sp_syn.cont_in_list != NULL)
4154 	    put_id_list((char_u *)"containedin",
4155 					      spp->sp_syn.cont_in_list, attr);
4156 
4157 	if (spp->sp_next_list != NULL)
4158 	{
4159 	    put_id_list((char_u *)"nextgroup", spp->sp_next_list, attr);
4160 	    syn_list_flags(namelist2, spp->sp_flags, attr);
4161 	}
4162 	if (spp->sp_flags & (HL_SYNC_HERE|HL_SYNC_THERE))
4163 	{
4164 	    if (spp->sp_flags & HL_SYNC_HERE)
4165 		msg_puts_attr((char_u *)"grouphere", attr);
4166 	    else
4167 		msg_puts_attr((char_u *)"groupthere", attr);
4168 	    msg_putchar(' ');
4169 	    if (spp->sp_sync_idx >= 0)
4170 		msg_outtrans(HL_TABLE()[SYN_ITEMS(curwin->w_s)
4171 				   [spp->sp_sync_idx].sp_syn.id - 1].sg_name);
4172 	    else
4173 		MSG_PUTS("NONE");
4174 	    msg_putchar(' ');
4175 	}
4176     }
4177 
4178     /* list the link, if there is one */
4179     if (HL_TABLE()[id - 1].sg_link && (did_header || link_only) && !got_int)
4180     {
4181 	(void)syn_list_header(did_header, 999, id);
4182 	msg_puts_attr((char_u *)"links to", attr);
4183 	msg_putchar(' ');
4184 	msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name);
4185     }
4186 }
4187 
4188     static void
4189 syn_list_flags(struct name_list *nlist, int flags, int attr)
4190 {
4191     int		i;
4192 
4193     for (i = 0; nlist[i].flag != 0; ++i)
4194 	if (flags & nlist[i].flag)
4195 	{
4196 	    msg_puts_attr((char_u *)nlist[i].name, attr);
4197 	    msg_putchar(' ');
4198 	}
4199 }
4200 
4201 /*
4202  * List one syntax cluster, for ":syntax" or "syntax list syntax_name".
4203  */
4204     static void
4205 syn_list_cluster(int id)
4206 {
4207     int	    endcol = 15;
4208 
4209     /* slight hack:  roughly duplicate the guts of syn_list_header() */
4210     msg_putchar('\n');
4211     msg_outtrans(SYN_CLSTR(curwin->w_s)[id].scl_name);
4212 
4213     if (msg_col >= endcol)	/* output at least one space */
4214 	endcol = msg_col + 1;
4215     if (Columns <= endcol)	/* avoid hang for tiny window */
4216 	endcol = Columns - 1;
4217 
4218     msg_advance(endcol);
4219     if (SYN_CLSTR(curwin->w_s)[id].scl_list != NULL)
4220     {
4221 	put_id_list((char_u *)"cluster", SYN_CLSTR(curwin->w_s)[id].scl_list,
4222 		    HL_ATTR(HLF_D));
4223     }
4224     else
4225     {
4226 	msg_puts_attr((char_u *)"cluster", HL_ATTR(HLF_D));
4227 	msg_puts((char_u *)"=NONE");
4228     }
4229 }
4230 
4231     static void
4232 put_id_list(char_u *name, short *list, int attr)
4233 {
4234     short		*p;
4235 
4236     msg_puts_attr(name, attr);
4237     msg_putchar('=');
4238     for (p = list; *p; ++p)
4239     {
4240 	if (*p >= SYNID_ALLBUT && *p < SYNID_TOP)
4241 	{
4242 	    if (p[1])
4243 		MSG_PUTS("ALLBUT");
4244 	    else
4245 		MSG_PUTS("ALL");
4246 	}
4247 	else if (*p >= SYNID_TOP && *p < SYNID_CONTAINED)
4248 	{
4249 	    MSG_PUTS("TOP");
4250 	}
4251 	else if (*p >= SYNID_CONTAINED && *p < SYNID_CLUSTER)
4252 	{
4253 	    MSG_PUTS("CONTAINED");
4254 	}
4255 	else if (*p >= SYNID_CLUSTER)
4256 	{
4257 	    short scl_id = *p - SYNID_CLUSTER;
4258 
4259 	    msg_putchar('@');
4260 	    msg_outtrans(SYN_CLSTR(curwin->w_s)[scl_id].scl_name);
4261 	}
4262 	else
4263 	    msg_outtrans(HL_TABLE()[*p - 1].sg_name);
4264 	if (p[1])
4265 	    msg_putchar(',');
4266     }
4267     msg_putchar(' ');
4268 }
4269 
4270     static void
4271 put_pattern(
4272     char	*s,
4273     int		c,
4274     synpat_T	*spp,
4275     int		attr)
4276 {
4277     long	n;
4278     int		mask;
4279     int		first;
4280     static char	*sepchars = "/+=-#@\"|'^&";
4281     int		i;
4282 
4283     /* May have to write "matchgroup=group" */
4284     if (last_matchgroup != spp->sp_syn_match_id)
4285     {
4286 	last_matchgroup = spp->sp_syn_match_id;
4287 	msg_puts_attr((char_u *)"matchgroup", attr);
4288 	msg_putchar('=');
4289 	if (last_matchgroup == 0)
4290 	    msg_outtrans((char_u *)"NONE");
4291 	else
4292 	    msg_outtrans(HL_TABLE()[last_matchgroup - 1].sg_name);
4293 	msg_putchar(' ');
4294     }
4295 
4296     /* output the name of the pattern and an '=' or ' ' */
4297     msg_puts_attr((char_u *)s, attr);
4298     msg_putchar(c);
4299 
4300     /* output the pattern, in between a char that is not in the pattern */
4301     for (i = 0; vim_strchr(spp->sp_pattern, sepchars[i]) != NULL; )
4302 	if (sepchars[++i] == NUL)
4303 	{
4304 	    i = 0;	/* no good char found, just use the first one */
4305 	    break;
4306 	}
4307     msg_putchar(sepchars[i]);
4308     msg_outtrans(spp->sp_pattern);
4309     msg_putchar(sepchars[i]);
4310 
4311     /* output any pattern options */
4312     first = TRUE;
4313     for (i = 0; i < SPO_COUNT; ++i)
4314     {
4315 	mask = (1 << i);
4316 	if (spp->sp_off_flags & (mask + (mask << SPO_COUNT)))
4317 	{
4318 	    if (!first)
4319 		msg_putchar(',');	/* separate with commas */
4320 	    msg_puts((char_u *)spo_name_tab[i]);
4321 	    n = spp->sp_offsets[i];
4322 	    if (i != SPO_LC_OFF)
4323 	    {
4324 		if (spp->sp_off_flags & mask)
4325 		    msg_putchar('s');
4326 		else
4327 		    msg_putchar('e');
4328 		if (n > 0)
4329 		    msg_putchar('+');
4330 	    }
4331 	    if (n || i == SPO_LC_OFF)
4332 		msg_outnum(n);
4333 	    first = FALSE;
4334 	}
4335     }
4336     msg_putchar(' ');
4337 }
4338 
4339 /*
4340  * List or clear the keywords for one syntax group.
4341  * Return TRUE if the header has been printed.
4342  */
4343     static int
4344 syn_list_keywords(
4345     int		id,
4346     hashtab_T	*ht,
4347     int		did_header,		/* header has already been printed */
4348     int		attr)
4349 {
4350     int		outlen;
4351     hashitem_T	*hi;
4352     keyentry_T	*kp;
4353     int		todo;
4354     int		prev_contained = 0;
4355     short	*prev_next_list = NULL;
4356     short	*prev_cont_in_list = NULL;
4357     int		prev_skipnl = 0;
4358     int		prev_skipwhite = 0;
4359     int		prev_skipempty = 0;
4360 
4361     /*
4362      * Unfortunately, this list of keywords is not sorted on alphabet but on
4363      * hash value...
4364      */
4365     todo = (int)ht->ht_used;
4366     for (hi = ht->ht_array; todo > 0 && !got_int; ++hi)
4367     {
4368 	if (!HASHITEM_EMPTY(hi))
4369 	{
4370 	    --todo;
4371 	    for (kp = HI2KE(hi); kp != NULL && !got_int; kp = kp->ke_next)
4372 	    {
4373 		if (kp->k_syn.id == id)
4374 		{
4375 		    if (prev_contained != (kp->flags & HL_CONTAINED)
4376 			    || prev_skipnl != (kp->flags & HL_SKIPNL)
4377 			    || prev_skipwhite != (kp->flags & HL_SKIPWHITE)
4378 			    || prev_skipempty != (kp->flags & HL_SKIPEMPTY)
4379 			    || prev_cont_in_list != kp->k_syn.cont_in_list
4380 			    || prev_next_list != kp->next_list)
4381 			outlen = 9999;
4382 		    else
4383 			outlen = (int)STRLEN(kp->keyword);
4384 		    /* output "contained" and "nextgroup" on each line */
4385 		    if (syn_list_header(did_header, outlen, id))
4386 		    {
4387 			prev_contained = 0;
4388 			prev_next_list = NULL;
4389 			prev_cont_in_list = NULL;
4390 			prev_skipnl = 0;
4391 			prev_skipwhite = 0;
4392 			prev_skipempty = 0;
4393 		    }
4394 		    did_header = TRUE;
4395 		    if (prev_contained != (kp->flags & HL_CONTAINED))
4396 		    {
4397 			msg_puts_attr((char_u *)"contained", attr);
4398 			msg_putchar(' ');
4399 			prev_contained = (kp->flags & HL_CONTAINED);
4400 		    }
4401 		    if (kp->k_syn.cont_in_list != prev_cont_in_list)
4402 		    {
4403 			put_id_list((char_u *)"containedin",
4404 						kp->k_syn.cont_in_list, attr);
4405 			msg_putchar(' ');
4406 			prev_cont_in_list = kp->k_syn.cont_in_list;
4407 		    }
4408 		    if (kp->next_list != prev_next_list)
4409 		    {
4410 			put_id_list((char_u *)"nextgroup", kp->next_list, attr);
4411 			msg_putchar(' ');
4412 			prev_next_list = kp->next_list;
4413 			if (kp->flags & HL_SKIPNL)
4414 			{
4415 			    msg_puts_attr((char_u *)"skipnl", attr);
4416 			    msg_putchar(' ');
4417 			    prev_skipnl = (kp->flags & HL_SKIPNL);
4418 			}
4419 			if (kp->flags & HL_SKIPWHITE)
4420 			{
4421 			    msg_puts_attr((char_u *)"skipwhite", attr);
4422 			    msg_putchar(' ');
4423 			    prev_skipwhite = (kp->flags & HL_SKIPWHITE);
4424 			}
4425 			if (kp->flags & HL_SKIPEMPTY)
4426 			{
4427 			    msg_puts_attr((char_u *)"skipempty", attr);
4428 			    msg_putchar(' ');
4429 			    prev_skipempty = (kp->flags & HL_SKIPEMPTY);
4430 			}
4431 		    }
4432 		    msg_outtrans(kp->keyword);
4433 		}
4434 	    }
4435 	}
4436     }
4437 
4438     return did_header;
4439 }
4440 
4441     static void
4442 syn_clear_keyword(int id, hashtab_T *ht)
4443 {
4444     hashitem_T	*hi;
4445     keyentry_T	*kp;
4446     keyentry_T	*kp_prev;
4447     keyentry_T	*kp_next;
4448     int		todo;
4449 
4450     hash_lock(ht);
4451     todo = (int)ht->ht_used;
4452     for (hi = ht->ht_array; todo > 0; ++hi)
4453     {
4454 	if (!HASHITEM_EMPTY(hi))
4455 	{
4456 	    --todo;
4457 	    kp_prev = NULL;
4458 	    for (kp = HI2KE(hi); kp != NULL; )
4459 	    {
4460 		if (kp->k_syn.id == id)
4461 		{
4462 		    kp_next = kp->ke_next;
4463 		    if (kp_prev == NULL)
4464 		    {
4465 			if (kp_next == NULL)
4466 			    hash_remove(ht, hi);
4467 			else
4468 			    hi->hi_key = KE2HIKEY(kp_next);
4469 		    }
4470 		    else
4471 			kp_prev->ke_next = kp_next;
4472 		    vim_free(kp->next_list);
4473 		    vim_free(kp->k_syn.cont_in_list);
4474 		    vim_free(kp);
4475 		    kp = kp_next;
4476 		}
4477 		else
4478 		{
4479 		    kp_prev = kp;
4480 		    kp = kp->ke_next;
4481 		}
4482 	    }
4483 	}
4484     }
4485     hash_unlock(ht);
4486 }
4487 
4488 /*
4489  * Clear a whole keyword table.
4490  */
4491     static void
4492 clear_keywtab(hashtab_T *ht)
4493 {
4494     hashitem_T	*hi;
4495     int		todo;
4496     keyentry_T	*kp;
4497     keyentry_T	*kp_next;
4498 
4499     todo = (int)ht->ht_used;
4500     for (hi = ht->ht_array; todo > 0; ++hi)
4501     {
4502 	if (!HASHITEM_EMPTY(hi))
4503 	{
4504 	    --todo;
4505 	    for (kp = HI2KE(hi); kp != NULL; kp = kp_next)
4506 	    {
4507 		kp_next = kp->ke_next;
4508 		vim_free(kp->next_list);
4509 		vim_free(kp->k_syn.cont_in_list);
4510 		vim_free(kp);
4511 	    }
4512 	}
4513     }
4514     hash_clear(ht);
4515     hash_init(ht);
4516 }
4517 
4518 /*
4519  * Add a keyword to the list of keywords.
4520  */
4521     static void
4522 add_keyword(
4523     char_u	*name,	    /* name of keyword */
4524     int		id,	    /* group ID for this keyword */
4525     int		flags,	    /* flags for this keyword */
4526     short	*cont_in_list, /* containedin for this keyword */
4527     short	*next_list, /* nextgroup for this keyword */
4528     int		conceal_char)
4529 {
4530     keyentry_T	*kp;
4531     hashtab_T	*ht;
4532     hashitem_T	*hi;
4533     char_u	*name_ic;
4534     long_u	hash;
4535     char_u	name_folded[MAXKEYWLEN + 1];
4536 
4537     if (curwin->w_s->b_syn_ic)
4538 	name_ic = str_foldcase(name, (int)STRLEN(name),
4539 						 name_folded, MAXKEYWLEN + 1);
4540     else
4541 	name_ic = name;
4542     kp = (keyentry_T *)alloc((int)(sizeof(keyentry_T) + STRLEN(name_ic)));
4543     if (kp == NULL)
4544 	return;
4545     STRCPY(kp->keyword, name_ic);
4546     kp->k_syn.id = id;
4547     kp->k_syn.inc_tag = current_syn_inc_tag;
4548     kp->flags = flags;
4549     kp->k_char = conceal_char;
4550     kp->k_syn.cont_in_list = copy_id_list(cont_in_list);
4551     if (cont_in_list != NULL)
4552 	curwin->w_s->b_syn_containedin = TRUE;
4553     kp->next_list = copy_id_list(next_list);
4554 
4555     if (curwin->w_s->b_syn_ic)
4556 	ht = &curwin->w_s->b_keywtab_ic;
4557     else
4558 	ht = &curwin->w_s->b_keywtab;
4559 
4560     hash = hash_hash(kp->keyword);
4561     hi = hash_lookup(ht, kp->keyword, hash);
4562     if (HASHITEM_EMPTY(hi))
4563     {
4564 	/* new keyword, add to hashtable */
4565 	kp->ke_next = NULL;
4566 	hash_add_item(ht, hi, kp->keyword, hash);
4567     }
4568     else
4569     {
4570 	/* keyword already exists, prepend to list */
4571 	kp->ke_next = HI2KE(hi);
4572 	hi->hi_key = KE2HIKEY(kp);
4573     }
4574 }
4575 
4576 /*
4577  * Get the start and end of the group name argument.
4578  * Return a pointer to the first argument.
4579  * Return NULL if the end of the command was found instead of further args.
4580  */
4581     static char_u *
4582 get_group_name(
4583     char_u	*arg,		/* start of the argument */
4584     char_u	**name_end)	/* pointer to end of the name */
4585 {
4586     char_u	*rest;
4587 
4588     *name_end = skiptowhite(arg);
4589     rest = skipwhite(*name_end);
4590 
4591     /*
4592      * Check if there are enough arguments.  The first argument may be a
4593      * pattern, where '|' is allowed, so only check for NUL.
4594      */
4595     if (ends_excmd(*arg) || *rest == NUL)
4596 	return NULL;
4597     return rest;
4598 }
4599 
4600 /*
4601  * Check for syntax command option arguments.
4602  * This can be called at any place in the list of arguments, and just picks
4603  * out the arguments that are known.  Can be called several times in a row to
4604  * collect all options in between other arguments.
4605  * Return a pointer to the next argument (which isn't an option).
4606  * Return NULL for any error;
4607  */
4608     static char_u *
4609 get_syn_options(
4610     char_u	    *arg,		/* next argument to be checked */
4611     syn_opt_arg_T   *opt,		/* various things */
4612     int		    *conceal_char UNUSED,
4613     int		    skip)		/* TRUE if skipping over command */
4614 {
4615     char_u	*gname_start, *gname;
4616     int		syn_id;
4617     int		len;
4618     char	*p;
4619     int		i;
4620     int		fidx;
4621     static struct flag
4622     {
4623 	char	*name;
4624 	int	argtype;
4625 	int	flags;
4626     } flagtab[] = { {"cCoOnNtTaAiInNeEdD",	0,	HL_CONTAINED},
4627 		    {"oOnNeElLiInNeE",		0,	HL_ONELINE},
4628 		    {"kKeEeEpPeEnNdD",		0,	HL_KEEPEND},
4629 		    {"eExXtTeEnNdD",		0,	HL_EXTEND},
4630 		    {"eExXcClLuUdDeEnNlL",	0,	HL_EXCLUDENL},
4631 		    {"tTrRaAnNsSpPaArReEnNtT",	0,	HL_TRANSP},
4632 		    {"sSkKiIpPnNlL",		0,	HL_SKIPNL},
4633 		    {"sSkKiIpPwWhHiItTeE",	0,	HL_SKIPWHITE},
4634 		    {"sSkKiIpPeEmMpPtTyY",	0,	HL_SKIPEMPTY},
4635 		    {"gGrRoOuUpPhHeErReE",	0,	HL_SYNC_HERE},
4636 		    {"gGrRoOuUpPtThHeErReE",	0,	HL_SYNC_THERE},
4637 		    {"dDiIsSpPlLaAyY",		0,	HL_DISPLAY},
4638 		    {"fFoOlLdD",		0,	HL_FOLD},
4639 		    {"cCoOnNcCeEaAlL",		0,	HL_CONCEAL},
4640 		    {"cCoOnNcCeEaAlLeEnNdDsS",	0,	HL_CONCEALENDS},
4641 		    {"cCcChHaArR",		11,	0},
4642 		    {"cCoOnNtTaAiInNsS",	1,	0},
4643 		    {"cCoOnNtTaAiInNeEdDiInN",	2,	0},
4644 		    {"nNeExXtTgGrRoOuUpP",	3,	0},
4645 		};
4646     static char *first_letters = "cCoOkKeEtTsSgGdDfFnN";
4647 
4648     if (arg == NULL)		/* already detected error */
4649 	return NULL;
4650 
4651 #ifdef FEAT_CONCEAL
4652     if (curwin->w_s->b_syn_conceal)
4653 	opt->flags |= HL_CONCEAL;
4654 #endif
4655 
4656     for (;;)
4657     {
4658 	/*
4659 	 * This is used very often when a large number of keywords is defined.
4660 	 * Need to skip quickly when no option name is found.
4661 	 * Also avoid tolower(), it's slow.
4662 	 */
4663 	if (strchr(first_letters, *arg) == NULL)
4664 	    break;
4665 
4666 	for (fidx = sizeof(flagtab) / sizeof(struct flag); --fidx >= 0; )
4667 	{
4668 	    p = flagtab[fidx].name;
4669 	    for (i = 0, len = 0; p[i] != NUL; i += 2, ++len)
4670 		if (arg[len] != p[i] && arg[len] != p[i + 1])
4671 		    break;
4672 	    if (p[i] == NUL && (VIM_ISWHITE(arg[len])
4673 				    || (flagtab[fidx].argtype > 0
4674 					 ? arg[len] == '='
4675 					 : ends_excmd(arg[len]))))
4676 	    {
4677 		if (opt->keyword
4678 			&& (flagtab[fidx].flags == HL_DISPLAY
4679 			    || flagtab[fidx].flags == HL_FOLD
4680 			    || flagtab[fidx].flags == HL_EXTEND))
4681 		    /* treat "display", "fold" and "extend" as a keyword */
4682 		    fidx = -1;
4683 		break;
4684 	    }
4685 	}
4686 	if (fidx < 0)	    /* no match found */
4687 	    break;
4688 
4689 	if (flagtab[fidx].argtype == 1)
4690 	{
4691 	    if (!opt->has_cont_list)
4692 	    {
4693 		EMSG(_("E395: contains argument not accepted here"));
4694 		return NULL;
4695 	    }
4696 	    if (get_id_list(&arg, 8, &opt->cont_list, skip) == FAIL)
4697 		return NULL;
4698 	}
4699 	else if (flagtab[fidx].argtype == 2)
4700 	{
4701 	    if (get_id_list(&arg, 11, &opt->cont_in_list, skip) == FAIL)
4702 		return NULL;
4703 	}
4704 	else if (flagtab[fidx].argtype == 3)
4705 	{
4706 	    if (get_id_list(&arg, 9, &opt->next_list, skip) == FAIL)
4707 		return NULL;
4708 	}
4709 	else if (flagtab[fidx].argtype == 11 && arg[5] == '=')
4710 	{
4711 #ifdef FEAT_MBYTE
4712 	    /* cchar=? */
4713 	    if (has_mbyte)
4714 	    {
4715 # ifdef FEAT_CONCEAL
4716 		*conceal_char = mb_ptr2char(arg + 6);
4717 # endif
4718 		arg += mb_ptr2len(arg + 6) - 1;
4719 	    }
4720 	    else
4721 #endif
4722 	    {
4723 #ifdef FEAT_CONCEAL
4724 		*conceal_char = arg[6];
4725 #else
4726 		;
4727 #endif
4728 	    }
4729 #ifdef FEAT_CONCEAL
4730 	    if (!vim_isprintc_strict(*conceal_char))
4731 	    {
4732 		EMSG(_("E844: invalid cchar value"));
4733 		return NULL;
4734 	    }
4735 #endif
4736 	    arg = skipwhite(arg + 7);
4737 	}
4738 	else
4739 	{
4740 	    opt->flags |= flagtab[fidx].flags;
4741 	    arg = skipwhite(arg + len);
4742 
4743 	    if (flagtab[fidx].flags == HL_SYNC_HERE
4744 		    || flagtab[fidx].flags == HL_SYNC_THERE)
4745 	    {
4746 		if (opt->sync_idx == NULL)
4747 		{
4748 		    EMSG(_("E393: group[t]here not accepted here"));
4749 		    return NULL;
4750 		}
4751 		gname_start = arg;
4752 		arg = skiptowhite(arg);
4753 		if (gname_start == arg)
4754 		    return NULL;
4755 		gname = vim_strnsave(gname_start, (int)(arg - gname_start));
4756 		if (gname == NULL)
4757 		    return NULL;
4758 		if (STRCMP(gname, "NONE") == 0)
4759 		    *opt->sync_idx = NONE_IDX;
4760 		else
4761 		{
4762 		    syn_id = syn_name2id(gname);
4763 		    for (i = curwin->w_s->b_syn_patterns.ga_len; --i >= 0; )
4764 			if (SYN_ITEMS(curwin->w_s)[i].sp_syn.id == syn_id
4765 			      && SYN_ITEMS(curwin->w_s)[i].sp_type == SPTYPE_START)
4766 			{
4767 			    *opt->sync_idx = i;
4768 			    break;
4769 			}
4770 		    if (i < 0)
4771 		    {
4772 			EMSG2(_("E394: Didn't find region item for %s"), gname);
4773 			vim_free(gname);
4774 			return NULL;
4775 		    }
4776 		}
4777 
4778 		vim_free(gname);
4779 		arg = skipwhite(arg);
4780 	    }
4781 #ifdef FEAT_FOLDING
4782 	    else if (flagtab[fidx].flags == HL_FOLD
4783 						&& foldmethodIsSyntax(curwin))
4784 		/* Need to update folds later. */
4785 		foldUpdateAll(curwin);
4786 #endif
4787 	}
4788     }
4789 
4790     return arg;
4791 }
4792 
4793 /*
4794  * Adjustments to syntax item when declared in a ":syn include"'d file.
4795  * Set the contained flag, and if the item is not already contained, add it
4796  * to the specified top-level group, if any.
4797  */
4798     static void
4799 syn_incl_toplevel(int id, int *flagsp)
4800 {
4801     if ((*flagsp & HL_CONTAINED) || curwin->w_s->b_syn_topgrp == 0)
4802 	return;
4803     *flagsp |= HL_CONTAINED;
4804     if (curwin->w_s->b_syn_topgrp >= SYNID_CLUSTER)
4805     {
4806 	/* We have to alloc this, because syn_combine_list() will free it. */
4807 	short	    *grp_list = (short *)alloc((unsigned)(2 * sizeof(short)));
4808 	int	    tlg_id = curwin->w_s->b_syn_topgrp - SYNID_CLUSTER;
4809 
4810 	if (grp_list != NULL)
4811 	{
4812 	    grp_list[0] = id;
4813 	    grp_list[1] = 0;
4814 	    syn_combine_list(&SYN_CLSTR(curwin->w_s)[tlg_id].scl_list, &grp_list,
4815 			 CLUSTER_ADD);
4816 	}
4817     }
4818 }
4819 
4820 /*
4821  * Handle ":syntax include [@{group-name}] filename" command.
4822  */
4823     static void
4824 syn_cmd_include(exarg_T *eap, int syncing UNUSED)
4825 {
4826     char_u	*arg = eap->arg;
4827     int		sgl_id = 1;
4828     char_u	*group_name_end;
4829     char_u	*rest;
4830     char_u	*errormsg = NULL;
4831     int		prev_toplvl_grp;
4832     int		prev_syn_inc_tag;
4833     int		source = FALSE;
4834 
4835     eap->nextcmd = find_nextcmd(arg);
4836     if (eap->skip)
4837 	return;
4838 
4839     if (arg[0] == '@')
4840     {
4841 	++arg;
4842 	rest = get_group_name(arg, &group_name_end);
4843 	if (rest == NULL)
4844 	{
4845 	    EMSG((char_u *)_("E397: Filename required"));
4846 	    return;
4847 	}
4848 	sgl_id = syn_check_cluster(arg, (int)(group_name_end - arg));
4849 	if (sgl_id == 0)
4850 	    return;
4851 	/* separate_nextcmd() and expand_filename() depend on this */
4852 	eap->arg = rest;
4853     }
4854 
4855     /*
4856      * Everything that's left, up to the next command, should be the
4857      * filename to include.
4858      */
4859     eap->argt |= (XFILE | NOSPC);
4860     separate_nextcmd(eap);
4861     if (*eap->arg == '<' || *eap->arg == '$' || mch_isFullName(eap->arg))
4862     {
4863 	/* For an absolute path, "$VIM/..." or "<sfile>.." we ":source" the
4864 	 * file.  Need to expand the file name first.  In other cases
4865 	 * ":runtime!" is used. */
4866 	source = TRUE;
4867 	if (expand_filename(eap, syn_cmdlinep, &errormsg) == FAIL)
4868 	{
4869 	    if (errormsg != NULL)
4870 		EMSG(errormsg);
4871 	    return;
4872 	}
4873     }
4874 
4875     /*
4876      * Save and restore the existing top-level grouplist id and ":syn
4877      * include" tag around the actual inclusion.
4878      */
4879     if (running_syn_inc_tag >= MAX_SYN_INC_TAG)
4880     {
4881 	EMSG((char_u *)_("E847: Too many syntax includes"));
4882 	return;
4883     }
4884     prev_syn_inc_tag = current_syn_inc_tag;
4885     current_syn_inc_tag = ++running_syn_inc_tag;
4886     prev_toplvl_grp = curwin->w_s->b_syn_topgrp;
4887     curwin->w_s->b_syn_topgrp = sgl_id;
4888     if (source ? do_source(eap->arg, FALSE, DOSO_NONE) == FAIL
4889 				: source_runtime(eap->arg, DIP_ALL) == FAIL)
4890 	EMSG2(_(e_notopen), eap->arg);
4891     curwin->w_s->b_syn_topgrp = prev_toplvl_grp;
4892     current_syn_inc_tag = prev_syn_inc_tag;
4893 }
4894 
4895 /*
4896  * Handle ":syntax keyword {group-name} [{option}] keyword .." command.
4897  */
4898     static void
4899 syn_cmd_keyword(exarg_T *eap, int syncing UNUSED)
4900 {
4901     char_u	*arg = eap->arg;
4902     char_u	*group_name_end;
4903     int		syn_id;
4904     char_u	*rest;
4905     char_u	*keyword_copy = NULL;
4906     char_u	*p;
4907     char_u	*kw;
4908     syn_opt_arg_T syn_opt_arg;
4909     int		cnt;
4910     int		conceal_char = NUL;
4911 
4912     rest = get_group_name(arg, &group_name_end);
4913 
4914     if (rest != NULL)
4915     {
4916 	if (eap->skip)
4917 	    syn_id = -1;
4918 	else
4919 	    syn_id = syn_check_group(arg, (int)(group_name_end - arg));
4920 	if (syn_id != 0)
4921 	    /* allocate a buffer, for removing backslashes in the keyword */
4922 	    keyword_copy = alloc((unsigned)STRLEN(rest) + 1);
4923 	if (keyword_copy != NULL)
4924 	{
4925 	    syn_opt_arg.flags = 0;
4926 	    syn_opt_arg.keyword = TRUE;
4927 	    syn_opt_arg.sync_idx = NULL;
4928 	    syn_opt_arg.has_cont_list = FALSE;
4929 	    syn_opt_arg.cont_in_list = NULL;
4930 	    syn_opt_arg.next_list = NULL;
4931 
4932 	    /*
4933 	     * The options given apply to ALL keywords, so all options must be
4934 	     * found before keywords can be created.
4935 	     * 1: collect the options and copy the keywords to keyword_copy.
4936 	     */
4937 	    cnt = 0;
4938 	    p = keyword_copy;
4939 	    for ( ; rest != NULL && !ends_excmd(*rest); rest = skipwhite(rest))
4940 	    {
4941 		rest = get_syn_options(rest, &syn_opt_arg, &conceal_char,
4942 								    eap->skip);
4943 		if (rest == NULL || ends_excmd(*rest))
4944 		    break;
4945 		/* Copy the keyword, removing backslashes, and add a NUL. */
4946 		while (*rest != NUL && !VIM_ISWHITE(*rest))
4947 		{
4948 		    if (*rest == '\\' && rest[1] != NUL)
4949 			++rest;
4950 		    *p++ = *rest++;
4951 		}
4952 		*p++ = NUL;
4953 		++cnt;
4954 	    }
4955 
4956 	    if (!eap->skip)
4957 	    {
4958 		/* Adjust flags for use of ":syn include". */
4959 		syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
4960 
4961 		/*
4962 		 * 2: Add an entry for each keyword.
4963 		 */
4964 		for (kw = keyword_copy; --cnt >= 0; kw += STRLEN(kw) + 1)
4965 		{
4966 		    for (p = vim_strchr(kw, '['); ; )
4967 		    {
4968 			if (p != NULL)
4969 			    *p = NUL;
4970 			add_keyword(kw, syn_id, syn_opt_arg.flags,
4971 				syn_opt_arg.cont_in_list,
4972 					 syn_opt_arg.next_list, conceal_char);
4973 			if (p == NULL)
4974 			    break;
4975 			if (p[1] == NUL)
4976 			{
4977 			    EMSG2(_("E789: Missing ']': %s"), kw);
4978 			    goto error;
4979 			}
4980 			if (p[1] == ']')
4981 			{
4982 			    if (p[2] != NUL)
4983 			    {
4984 				EMSG3(_("E890: trailing char after ']': %s]%s"),
4985 								kw, &p[2]);
4986 				goto error;
4987 			    }
4988 			    kw = p + 1;		/* skip over the "]" */
4989 			    break;
4990 			}
4991 #ifdef FEAT_MBYTE
4992 			if (has_mbyte)
4993 			{
4994 			    int l = (*mb_ptr2len)(p + 1);
4995 
4996 			    mch_memmove(p, p + 1, l);
4997 			    p += l;
4998 			}
4999 			else
5000 #endif
5001 			{
5002 			    p[0] = p[1];
5003 			    ++p;
5004 			}
5005 		    }
5006 		}
5007 	    }
5008 error:
5009 	    vim_free(keyword_copy);
5010 	    vim_free(syn_opt_arg.cont_in_list);
5011 	    vim_free(syn_opt_arg.next_list);
5012 	}
5013     }
5014 
5015     if (rest != NULL)
5016 	eap->nextcmd = check_nextcmd(rest);
5017     else
5018 	EMSG2(_(e_invarg2), arg);
5019 
5020     redraw_curbuf_later(SOME_VALID);
5021     syn_stack_free_all(curwin->w_s);		/* Need to recompute all syntax. */
5022 }
5023 
5024 /*
5025  * Handle ":syntax match {name} [{options}] {pattern} [{options}]".
5026  *
5027  * Also ":syntax sync match {name} [[grouphere | groupthere] {group-name}] .."
5028  */
5029     static void
5030 syn_cmd_match(
5031     exarg_T	*eap,
5032     int		syncing)	    /* TRUE for ":syntax sync match .. " */
5033 {
5034     char_u	*arg = eap->arg;
5035     char_u	*group_name_end;
5036     char_u	*rest;
5037     synpat_T	item;		/* the item found in the line */
5038     int		syn_id;
5039     int		idx;
5040     syn_opt_arg_T syn_opt_arg;
5041     int		sync_idx = 0;
5042     int		conceal_char = NUL;
5043 
5044     /* Isolate the group name, check for validity */
5045     rest = get_group_name(arg, &group_name_end);
5046 
5047     /* Get options before the pattern */
5048     syn_opt_arg.flags = 0;
5049     syn_opt_arg.keyword = FALSE;
5050     syn_opt_arg.sync_idx = syncing ? &sync_idx : NULL;
5051     syn_opt_arg.has_cont_list = TRUE;
5052     syn_opt_arg.cont_list = NULL;
5053     syn_opt_arg.cont_in_list = NULL;
5054     syn_opt_arg.next_list = NULL;
5055     rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
5056 
5057     /* get the pattern. */
5058     init_syn_patterns();
5059     vim_memset(&item, 0, sizeof(item));
5060     rest = get_syn_pattern(rest, &item);
5061     if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL))
5062 	syn_opt_arg.flags |= HL_HAS_EOL;
5063 
5064     /* Get options after the pattern */
5065     rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
5066 
5067     if (rest != NULL)		/* all arguments are valid */
5068     {
5069 	/*
5070 	 * Check for trailing command and illegal trailing arguments.
5071 	 */
5072 	eap->nextcmd = check_nextcmd(rest);
5073 	if (!ends_excmd(*rest) || eap->skip)
5074 	    rest = NULL;
5075 	else if (ga_grow(&curwin->w_s->b_syn_patterns, 1) != FAIL
5076 		&& (syn_id = syn_check_group(arg,
5077 					   (int)(group_name_end - arg))) != 0)
5078 	{
5079 	    syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
5080 	    /*
5081 	     * Store the pattern in the syn_items list
5082 	     */
5083 	    idx = curwin->w_s->b_syn_patterns.ga_len;
5084 	    SYN_ITEMS(curwin->w_s)[idx] = item;
5085 	    SYN_ITEMS(curwin->w_s)[idx].sp_syncing = syncing;
5086 	    SYN_ITEMS(curwin->w_s)[idx].sp_type = SPTYPE_MATCH;
5087 	    SYN_ITEMS(curwin->w_s)[idx].sp_syn.id = syn_id;
5088 	    SYN_ITEMS(curwin->w_s)[idx].sp_syn.inc_tag = current_syn_inc_tag;
5089 	    SYN_ITEMS(curwin->w_s)[idx].sp_flags = syn_opt_arg.flags;
5090 	    SYN_ITEMS(curwin->w_s)[idx].sp_sync_idx = sync_idx;
5091 	    SYN_ITEMS(curwin->w_s)[idx].sp_cont_list = syn_opt_arg.cont_list;
5092 	    SYN_ITEMS(curwin->w_s)[idx].sp_syn.cont_in_list =
5093 						     syn_opt_arg.cont_in_list;
5094 #ifdef FEAT_CONCEAL
5095 	    SYN_ITEMS(curwin->w_s)[idx].sp_cchar = conceal_char;
5096 #endif
5097 	    if (syn_opt_arg.cont_in_list != NULL)
5098 		curwin->w_s->b_syn_containedin = TRUE;
5099 	    SYN_ITEMS(curwin->w_s)[idx].sp_next_list = syn_opt_arg.next_list;
5100 	    ++curwin->w_s->b_syn_patterns.ga_len;
5101 
5102 	    /* remember that we found a match for syncing on */
5103 	    if (syn_opt_arg.flags & (HL_SYNC_HERE|HL_SYNC_THERE))
5104 		curwin->w_s->b_syn_sync_flags |= SF_MATCH;
5105 #ifdef FEAT_FOLDING
5106 	    if (syn_opt_arg.flags & HL_FOLD)
5107 		++curwin->w_s->b_syn_folditems;
5108 #endif
5109 
5110 	    redraw_curbuf_later(SOME_VALID);
5111 	    syn_stack_free_all(curwin->w_s);	/* Need to recompute all syntax. */
5112 	    return;	/* don't free the progs and patterns now */
5113 	}
5114     }
5115 
5116     /*
5117      * Something failed, free the allocated memory.
5118      */
5119     vim_regfree(item.sp_prog);
5120     vim_free(item.sp_pattern);
5121     vim_free(syn_opt_arg.cont_list);
5122     vim_free(syn_opt_arg.cont_in_list);
5123     vim_free(syn_opt_arg.next_list);
5124 
5125     if (rest == NULL)
5126 	EMSG2(_(e_invarg2), arg);
5127 }
5128 
5129 /*
5130  * Handle ":syntax region {group-name} [matchgroup={group-name}]
5131  *		start {start} .. [skip {skip}] end {end} .. [{options}]".
5132  */
5133     static void
5134 syn_cmd_region(
5135     exarg_T	*eap,
5136     int		syncing)	    /* TRUE for ":syntax sync region .." */
5137 {
5138     char_u		*arg = eap->arg;
5139     char_u		*group_name_end;
5140     char_u		*rest;			/* next arg, NULL on error */
5141     char_u		*key_end;
5142     char_u		*key = NULL;
5143     char_u		*p;
5144     int			item;
5145 #define ITEM_START	    0
5146 #define ITEM_SKIP	    1
5147 #define ITEM_END	    2
5148 #define ITEM_MATCHGROUP	    3
5149     struct pat_ptr
5150     {
5151 	synpat_T	*pp_synp;		/* pointer to syn_pattern */
5152 	int		pp_matchgroup_id;	/* matchgroup ID */
5153 	struct pat_ptr	*pp_next;		/* pointer to next pat_ptr */
5154     }			*(pat_ptrs[3]);
5155 					/* patterns found in the line */
5156     struct pat_ptr	*ppp;
5157     struct pat_ptr	*ppp_next;
5158     int			pat_count = 0;		/* nr of syn_patterns found */
5159     int			syn_id;
5160     int			matchgroup_id = 0;
5161     int			not_enough = FALSE;	/* not enough arguments */
5162     int			illegal = FALSE;	/* illegal arguments */
5163     int			success = FALSE;
5164     int			idx;
5165     syn_opt_arg_T	syn_opt_arg;
5166     int			conceal_char = NUL;
5167 
5168     /* Isolate the group name, check for validity */
5169     rest = get_group_name(arg, &group_name_end);
5170 
5171     pat_ptrs[0] = NULL;
5172     pat_ptrs[1] = NULL;
5173     pat_ptrs[2] = NULL;
5174 
5175     init_syn_patterns();
5176 
5177     syn_opt_arg.flags = 0;
5178     syn_opt_arg.keyword = FALSE;
5179     syn_opt_arg.sync_idx = NULL;
5180     syn_opt_arg.has_cont_list = TRUE;
5181     syn_opt_arg.cont_list = NULL;
5182     syn_opt_arg.cont_in_list = NULL;
5183     syn_opt_arg.next_list = NULL;
5184 
5185     /*
5186      * get the options, patterns and matchgroup.
5187      */
5188     while (rest != NULL && !ends_excmd(*rest))
5189     {
5190 	/* Check for option arguments */
5191 	rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
5192 	if (rest == NULL || ends_excmd(*rest))
5193 	    break;
5194 
5195 	/* must be a pattern or matchgroup then */
5196 	key_end = rest;
5197 	while (*key_end && !VIM_ISWHITE(*key_end) && *key_end != '=')
5198 	    ++key_end;
5199 	vim_free(key);
5200 	key = vim_strnsave_up(rest, (int)(key_end - rest));
5201 	if (key == NULL)			/* out of memory */
5202 	{
5203 	    rest = NULL;
5204 	    break;
5205 	}
5206 	if (STRCMP(key, "MATCHGROUP") == 0)
5207 	    item = ITEM_MATCHGROUP;
5208 	else if (STRCMP(key, "START") == 0)
5209 	    item = ITEM_START;
5210 	else if (STRCMP(key, "END") == 0)
5211 	    item = ITEM_END;
5212 	else if (STRCMP(key, "SKIP") == 0)
5213 	{
5214 	    if (pat_ptrs[ITEM_SKIP] != NULL)	/* one skip pattern allowed */
5215 	    {
5216 		illegal = TRUE;
5217 		break;
5218 	    }
5219 	    item = ITEM_SKIP;
5220 	}
5221 	else
5222 	    break;
5223 	rest = skipwhite(key_end);
5224 	if (*rest != '=')
5225 	{
5226 	    rest = NULL;
5227 	    EMSG2(_("E398: Missing '=': %s"), arg);
5228 	    break;
5229 	}
5230 	rest = skipwhite(rest + 1);
5231 	if (*rest == NUL)
5232 	{
5233 	    not_enough = TRUE;
5234 	    break;
5235 	}
5236 
5237 	if (item == ITEM_MATCHGROUP)
5238 	{
5239 	    p = skiptowhite(rest);
5240 	    if ((p - rest == 4 && STRNCMP(rest, "NONE", 4) == 0) || eap->skip)
5241 		matchgroup_id = 0;
5242 	    else
5243 	    {
5244 		matchgroup_id = syn_check_group(rest, (int)(p - rest));
5245 		if (matchgroup_id == 0)
5246 		{
5247 		    illegal = TRUE;
5248 		    break;
5249 		}
5250 	    }
5251 	    rest = skipwhite(p);
5252 	}
5253 	else
5254 	{
5255 	    /*
5256 	     * Allocate room for a syn_pattern, and link it in the list of
5257 	     * syn_patterns for this item, at the start (because the list is
5258 	     * used from end to start).
5259 	     */
5260 	    ppp = (struct pat_ptr *)alloc((unsigned)sizeof(struct pat_ptr));
5261 	    if (ppp == NULL)
5262 	    {
5263 		rest = NULL;
5264 		break;
5265 	    }
5266 	    ppp->pp_next = pat_ptrs[item];
5267 	    pat_ptrs[item] = ppp;
5268 	    ppp->pp_synp = (synpat_T *)alloc_clear((unsigned)sizeof(synpat_T));
5269 	    if (ppp->pp_synp == NULL)
5270 	    {
5271 		rest = NULL;
5272 		break;
5273 	    }
5274 
5275 	    /*
5276 	     * Get the syntax pattern and the following offset(s).
5277 	     */
5278 	    /* Enable the appropriate \z specials. */
5279 	    if (item == ITEM_START)
5280 		reg_do_extmatch = REX_SET;
5281 	    else if (item == ITEM_SKIP || item == ITEM_END)
5282 		reg_do_extmatch = REX_USE;
5283 	    rest = get_syn_pattern(rest, ppp->pp_synp);
5284 	    reg_do_extmatch = 0;
5285 	    if (item == ITEM_END && vim_regcomp_had_eol()
5286 				       && !(syn_opt_arg.flags & HL_EXCLUDENL))
5287 		ppp->pp_synp->sp_flags |= HL_HAS_EOL;
5288 	    ppp->pp_matchgroup_id = matchgroup_id;
5289 	    ++pat_count;
5290 	}
5291     }
5292     vim_free(key);
5293     if (illegal || not_enough)
5294 	rest = NULL;
5295 
5296     /*
5297      * Must have a "start" and "end" pattern.
5298      */
5299     if (rest != NULL && (pat_ptrs[ITEM_START] == NULL ||
5300 						  pat_ptrs[ITEM_END] == NULL))
5301     {
5302 	not_enough = TRUE;
5303 	rest = NULL;
5304     }
5305 
5306     if (rest != NULL)
5307     {
5308 	/*
5309 	 * Check for trailing garbage or command.
5310 	 * If OK, add the item.
5311 	 */
5312 	eap->nextcmd = check_nextcmd(rest);
5313 	if (!ends_excmd(*rest) || eap->skip)
5314 	    rest = NULL;
5315 	else if (ga_grow(&(curwin->w_s->b_syn_patterns), pat_count) != FAIL
5316 		&& (syn_id = syn_check_group(arg,
5317 					   (int)(group_name_end - arg))) != 0)
5318 	{
5319 	    syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
5320 	    /*
5321 	     * Store the start/skip/end in the syn_items list
5322 	     */
5323 	    idx = curwin->w_s->b_syn_patterns.ga_len;
5324 	    for (item = ITEM_START; item <= ITEM_END; ++item)
5325 	    {
5326 		for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp->pp_next)
5327 		{
5328 		    SYN_ITEMS(curwin->w_s)[idx] = *(ppp->pp_synp);
5329 		    SYN_ITEMS(curwin->w_s)[idx].sp_syncing = syncing;
5330 		    SYN_ITEMS(curwin->w_s)[idx].sp_type =
5331 			    (item == ITEM_START) ? SPTYPE_START :
5332 			    (item == ITEM_SKIP) ? SPTYPE_SKIP : SPTYPE_END;
5333 		    SYN_ITEMS(curwin->w_s)[idx].sp_flags |= syn_opt_arg.flags;
5334 		    SYN_ITEMS(curwin->w_s)[idx].sp_syn.id = syn_id;
5335 		    SYN_ITEMS(curwin->w_s)[idx].sp_syn.inc_tag =
5336 							  current_syn_inc_tag;
5337 		    SYN_ITEMS(curwin->w_s)[idx].sp_syn_match_id =
5338 							ppp->pp_matchgroup_id;
5339 #ifdef FEAT_CONCEAL
5340 		    SYN_ITEMS(curwin->w_s)[idx].sp_cchar = conceal_char;
5341 #endif
5342 		    if (item == ITEM_START)
5343 		    {
5344 			SYN_ITEMS(curwin->w_s)[idx].sp_cont_list =
5345 							syn_opt_arg.cont_list;
5346 			SYN_ITEMS(curwin->w_s)[idx].sp_syn.cont_in_list =
5347 						     syn_opt_arg.cont_in_list;
5348 			if (syn_opt_arg.cont_in_list != NULL)
5349 			    curwin->w_s->b_syn_containedin = TRUE;
5350 			SYN_ITEMS(curwin->w_s)[idx].sp_next_list =
5351 							syn_opt_arg.next_list;
5352 		    }
5353 		    ++curwin->w_s->b_syn_patterns.ga_len;
5354 		    ++idx;
5355 #ifdef FEAT_FOLDING
5356 		    if (syn_opt_arg.flags & HL_FOLD)
5357 			++curwin->w_s->b_syn_folditems;
5358 #endif
5359 		}
5360 	    }
5361 
5362 	    redraw_curbuf_later(SOME_VALID);
5363 	    syn_stack_free_all(curwin->w_s);	/* Need to recompute all syntax. */
5364 	    success = TRUE;	    /* don't free the progs and patterns now */
5365 	}
5366     }
5367 
5368     /*
5369      * Free the allocated memory.
5370      */
5371     for (item = ITEM_START; item <= ITEM_END; ++item)
5372 	for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp_next)
5373 	{
5374 	    if (!success)
5375 	    {
5376 		vim_regfree(ppp->pp_synp->sp_prog);
5377 		vim_free(ppp->pp_synp->sp_pattern);
5378 	    }
5379 	    vim_free(ppp->pp_synp);
5380 	    ppp_next = ppp->pp_next;
5381 	    vim_free(ppp);
5382 	}
5383 
5384     if (!success)
5385     {
5386 	vim_free(syn_opt_arg.cont_list);
5387 	vim_free(syn_opt_arg.cont_in_list);
5388 	vim_free(syn_opt_arg.next_list);
5389 	if (not_enough)
5390 	    EMSG2(_("E399: Not enough arguments: syntax region %s"), arg);
5391 	else if (illegal || rest == NULL)
5392 	    EMSG2(_(e_invarg2), arg);
5393     }
5394 }
5395 
5396 /*
5397  * A simple syntax group ID comparison function suitable for use in qsort()
5398  */
5399     static int
5400 #ifdef __BORLANDC__
5401 _RTLENTRYF
5402 #endif
5403 syn_compare_stub(const void *v1, const void *v2)
5404 {
5405     const short	*s1 = v1;
5406     const short	*s2 = v2;
5407 
5408     return (*s1 > *s2 ? 1 : *s1 < *s2 ? -1 : 0);
5409 }
5410 
5411 /*
5412  * Combines lists of syntax clusters.
5413  * *clstr1 and *clstr2 must both be allocated memory; they will be consumed.
5414  */
5415     static void
5416 syn_combine_list(short **clstr1, short **clstr2, int list_op)
5417 {
5418     int		count1 = 0;
5419     int		count2 = 0;
5420     short	*g1;
5421     short	*g2;
5422     short	*clstr = NULL;
5423     int		count;
5424     int		round;
5425 
5426     /*
5427      * Handle degenerate cases.
5428      */
5429     if (*clstr2 == NULL)
5430 	return;
5431     if (*clstr1 == NULL || list_op == CLUSTER_REPLACE)
5432     {
5433 	if (list_op == CLUSTER_REPLACE)
5434 	    vim_free(*clstr1);
5435 	if (list_op == CLUSTER_REPLACE || list_op == CLUSTER_ADD)
5436 	    *clstr1 = *clstr2;
5437 	else
5438 	    vim_free(*clstr2);
5439 	return;
5440     }
5441 
5442     for (g1 = *clstr1; *g1; g1++)
5443 	++count1;
5444     for (g2 = *clstr2; *g2; g2++)
5445 	++count2;
5446 
5447     /*
5448      * For speed purposes, sort both lists.
5449      */
5450     qsort(*clstr1, (size_t)count1, sizeof(short), syn_compare_stub);
5451     qsort(*clstr2, (size_t)count2, sizeof(short), syn_compare_stub);
5452 
5453     /*
5454      * We proceed in two passes; in round 1, we count the elements to place
5455      * in the new list, and in round 2, we allocate and populate the new
5456      * list.  For speed, we use a mergesort-like method, adding the smaller
5457      * of the current elements in each list to the new list.
5458      */
5459     for (round = 1; round <= 2; round++)
5460     {
5461 	g1 = *clstr1;
5462 	g2 = *clstr2;
5463 	count = 0;
5464 
5465 	/*
5466 	 * First, loop through the lists until one of them is empty.
5467 	 */
5468 	while (*g1 && *g2)
5469 	{
5470 	    /*
5471 	     * We always want to add from the first list.
5472 	     */
5473 	    if (*g1 < *g2)
5474 	    {
5475 		if (round == 2)
5476 		    clstr[count] = *g1;
5477 		count++;
5478 		g1++;
5479 		continue;
5480 	    }
5481 	    /*
5482 	     * We only want to add from the second list if we're adding the
5483 	     * lists.
5484 	     */
5485 	    if (list_op == CLUSTER_ADD)
5486 	    {
5487 		if (round == 2)
5488 		    clstr[count] = *g2;
5489 		count++;
5490 	    }
5491 	    if (*g1 == *g2)
5492 		g1++;
5493 	    g2++;
5494 	}
5495 
5496 	/*
5497 	 * Now add the leftovers from whichever list didn't get finished
5498 	 * first.  As before, we only want to add from the second list if
5499 	 * we're adding the lists.
5500 	 */
5501 	for (; *g1; g1++, count++)
5502 	    if (round == 2)
5503 		clstr[count] = *g1;
5504 	if (list_op == CLUSTER_ADD)
5505 	    for (; *g2; g2++, count++)
5506 		if (round == 2)
5507 		    clstr[count] = *g2;
5508 
5509 	if (round == 1)
5510 	{
5511 	    /*
5512 	     * If the group ended up empty, we don't need to allocate any
5513 	     * space for it.
5514 	     */
5515 	    if (count == 0)
5516 	    {
5517 		clstr = NULL;
5518 		break;
5519 	    }
5520 	    clstr = (short *)alloc((unsigned)((count + 1) * sizeof(short)));
5521 	    if (clstr == NULL)
5522 		break;
5523 	    clstr[count] = 0;
5524 	}
5525     }
5526 
5527     /*
5528      * Finally, put the new list in place.
5529      */
5530     vim_free(*clstr1);
5531     vim_free(*clstr2);
5532     *clstr1 = clstr;
5533 }
5534 
5535 /*
5536  * Lookup a syntax cluster name and return its ID.
5537  * If it is not found, 0 is returned.
5538  */
5539     static int
5540 syn_scl_name2id(char_u *name)
5541 {
5542     int		i;
5543     char_u	*name_u;
5544 
5545     /* Avoid using stricmp() too much, it's slow on some systems */
5546     name_u = vim_strsave_up(name);
5547     if (name_u == NULL)
5548 	return 0;
5549     for (i = curwin->w_s->b_syn_clusters.ga_len; --i >= 0; )
5550 	if (SYN_CLSTR(curwin->w_s)[i].scl_name_u != NULL
5551 		&& STRCMP(name_u, SYN_CLSTR(curwin->w_s)[i].scl_name_u) == 0)
5552 	    break;
5553     vim_free(name_u);
5554     return (i < 0 ? 0 : i + SYNID_CLUSTER);
5555 }
5556 
5557 /*
5558  * Like syn_scl_name2id(), but take a pointer + length argument.
5559  */
5560     static int
5561 syn_scl_namen2id(char_u *linep, int len)
5562 {
5563     char_u  *name;
5564     int	    id = 0;
5565 
5566     name = vim_strnsave(linep, len);
5567     if (name != NULL)
5568     {
5569 	id = syn_scl_name2id(name);
5570 	vim_free(name);
5571     }
5572     return id;
5573 }
5574 
5575 /*
5576  * Find syntax cluster name in the table and return its ID.
5577  * The argument is a pointer to the name and the length of the name.
5578  * If it doesn't exist yet, a new entry is created.
5579  * Return 0 for failure.
5580  */
5581     static int
5582 syn_check_cluster(char_u *pp, int len)
5583 {
5584     int		id;
5585     char_u	*name;
5586 
5587     name = vim_strnsave(pp, len);
5588     if (name == NULL)
5589 	return 0;
5590 
5591     id = syn_scl_name2id(name);
5592     if (id == 0)			/* doesn't exist yet */
5593 	id = syn_add_cluster(name);
5594     else
5595 	vim_free(name);
5596     return id;
5597 }
5598 
5599 /*
5600  * Add new syntax cluster and return its ID.
5601  * "name" must be an allocated string, it will be consumed.
5602  * Return 0 for failure.
5603  */
5604     static int
5605 syn_add_cluster(char_u *name)
5606 {
5607     int		len;
5608 
5609     /*
5610      * First call for this growarray: init growing array.
5611      */
5612     if (curwin->w_s->b_syn_clusters.ga_data == NULL)
5613     {
5614 	curwin->w_s->b_syn_clusters.ga_itemsize = sizeof(syn_cluster_T);
5615 	curwin->w_s->b_syn_clusters.ga_growsize = 10;
5616     }
5617 
5618     len = curwin->w_s->b_syn_clusters.ga_len;
5619     if (len >= MAX_CLUSTER_ID)
5620     {
5621 	EMSG((char_u *)_("E848: Too many syntax clusters"));
5622 	vim_free(name);
5623 	return 0;
5624     }
5625 
5626     /*
5627      * Make room for at least one other cluster entry.
5628      */
5629     if (ga_grow(&curwin->w_s->b_syn_clusters, 1) == FAIL)
5630     {
5631 	vim_free(name);
5632 	return 0;
5633     }
5634 
5635     vim_memset(&(SYN_CLSTR(curwin->w_s)[len]), 0, sizeof(syn_cluster_T));
5636     SYN_CLSTR(curwin->w_s)[len].scl_name = name;
5637     SYN_CLSTR(curwin->w_s)[len].scl_name_u = vim_strsave_up(name);
5638     SYN_CLSTR(curwin->w_s)[len].scl_list = NULL;
5639     ++curwin->w_s->b_syn_clusters.ga_len;
5640 
5641     if (STRICMP(name, "Spell") == 0)
5642 	curwin->w_s->b_spell_cluster_id = len + SYNID_CLUSTER;
5643     if (STRICMP(name, "NoSpell") == 0)
5644 	curwin->w_s->b_nospell_cluster_id = len + SYNID_CLUSTER;
5645 
5646     return len + SYNID_CLUSTER;
5647 }
5648 
5649 /*
5650  * Handle ":syntax cluster {cluster-name} [contains={groupname},..]
5651  *		[add={groupname},..] [remove={groupname},..]".
5652  */
5653     static void
5654 syn_cmd_cluster(exarg_T *eap, int syncing UNUSED)
5655 {
5656     char_u	*arg = eap->arg;
5657     char_u	*group_name_end;
5658     char_u	*rest;
5659     int		scl_id;
5660     short	*clstr_list;
5661     int		got_clstr = FALSE;
5662     int		opt_len;
5663     int		list_op;
5664 
5665     eap->nextcmd = find_nextcmd(arg);
5666     if (eap->skip)
5667 	return;
5668 
5669     rest = get_group_name(arg, &group_name_end);
5670 
5671     if (rest != NULL)
5672     {
5673 	scl_id = syn_check_cluster(arg, (int)(group_name_end - arg));
5674 	if (scl_id == 0)
5675 	    return;
5676 	scl_id -= SYNID_CLUSTER;
5677 
5678 	for (;;)
5679 	{
5680 	    if (STRNICMP(rest, "add", 3) == 0
5681 		    && (VIM_ISWHITE(rest[3]) || rest[3] == '='))
5682 	    {
5683 		opt_len = 3;
5684 		list_op = CLUSTER_ADD;
5685 	    }
5686 	    else if (STRNICMP(rest, "remove", 6) == 0
5687 		    && (VIM_ISWHITE(rest[6]) || rest[6] == '='))
5688 	    {
5689 		opt_len = 6;
5690 		list_op = CLUSTER_SUBTRACT;
5691 	    }
5692 	    else if (STRNICMP(rest, "contains", 8) == 0
5693 			&& (VIM_ISWHITE(rest[8]) || rest[8] == '='))
5694 	    {
5695 		opt_len = 8;
5696 		list_op = CLUSTER_REPLACE;
5697 	    }
5698 	    else
5699 		break;
5700 
5701 	    clstr_list = NULL;
5702 	    if (get_id_list(&rest, opt_len, &clstr_list, eap->skip) == FAIL)
5703 	    {
5704 		EMSG2(_(e_invarg2), rest);
5705 		break;
5706 	    }
5707 	    if (scl_id >= 0)
5708 		syn_combine_list(&SYN_CLSTR(curwin->w_s)[scl_id].scl_list,
5709 			     &clstr_list, list_op);
5710 	    else
5711 		vim_free(clstr_list);
5712 	    got_clstr = TRUE;
5713 	}
5714 
5715 	if (got_clstr)
5716 	{
5717 	    redraw_curbuf_later(SOME_VALID);
5718 	    syn_stack_free_all(curwin->w_s);	/* Need to recompute all. */
5719 	}
5720     }
5721 
5722     if (!got_clstr)
5723 	EMSG(_("E400: No cluster specified"));
5724     if (rest == NULL || !ends_excmd(*rest))
5725 	EMSG2(_(e_invarg2), arg);
5726 }
5727 
5728 /*
5729  * On first call for current buffer: Init growing array.
5730  */
5731     static void
5732 init_syn_patterns(void)
5733 {
5734     curwin->w_s->b_syn_patterns.ga_itemsize = sizeof(synpat_T);
5735     curwin->w_s->b_syn_patterns.ga_growsize = 10;
5736 }
5737 
5738 /*
5739  * Get one pattern for a ":syntax match" or ":syntax region" command.
5740  * Stores the pattern and program in a synpat_T.
5741  * Returns a pointer to the next argument, or NULL in case of an error.
5742  */
5743     static char_u *
5744 get_syn_pattern(char_u *arg, synpat_T *ci)
5745 {
5746     char_u	*end;
5747     int		*p;
5748     int		idx;
5749     char_u	*cpo_save;
5750 
5751     /* need at least three chars */
5752     if (arg == NULL || arg[0] == NUL || arg[1] == NUL || arg[2] == NUL)
5753 	return NULL;
5754 
5755     end = skip_regexp(arg + 1, *arg, TRUE, NULL);
5756     if (*end != *arg)			    /* end delimiter not found */
5757     {
5758 	EMSG2(_("E401: Pattern delimiter not found: %s"), arg);
5759 	return NULL;
5760     }
5761     /* store the pattern and compiled regexp program */
5762     if ((ci->sp_pattern = vim_strnsave(arg + 1, (int)(end - arg - 1))) == NULL)
5763 	return NULL;
5764 
5765     /* Make 'cpoptions' empty, to avoid the 'l' flag */
5766     cpo_save = p_cpo;
5767     p_cpo = (char_u *)"";
5768     ci->sp_prog = vim_regcomp(ci->sp_pattern, RE_MAGIC);
5769     p_cpo = cpo_save;
5770 
5771     if (ci->sp_prog == NULL)
5772 	return NULL;
5773     ci->sp_ic = curwin->w_s->b_syn_ic;
5774 #ifdef FEAT_PROFILE
5775     syn_clear_time(&ci->sp_time);
5776 #endif
5777 
5778     /*
5779      * Check for a match, highlight or region offset.
5780      */
5781     ++end;
5782     do
5783     {
5784 	for (idx = SPO_COUNT; --idx >= 0; )
5785 	    if (STRNCMP(end, spo_name_tab[idx], 3) == 0)
5786 		break;
5787 	if (idx >= 0)
5788 	{
5789 	    p = &(ci->sp_offsets[idx]);
5790 	    if (idx != SPO_LC_OFF)
5791 		switch (end[3])
5792 		{
5793 		    case 's':   break;
5794 		    case 'b':   break;
5795 		    case 'e':   idx += SPO_COUNT; break;
5796 		    default:    idx = -1; break;
5797 		}
5798 	    if (idx >= 0)
5799 	    {
5800 		ci->sp_off_flags |= (1 << idx);
5801 		if (idx == SPO_LC_OFF)	    /* lc=99 */
5802 		{
5803 		    end += 3;
5804 		    *p = getdigits(&end);
5805 
5806 		    /* "lc=" offset automatically sets "ms=" offset */
5807 		    if (!(ci->sp_off_flags & (1 << SPO_MS_OFF)))
5808 		    {
5809 			ci->sp_off_flags |= (1 << SPO_MS_OFF);
5810 			ci->sp_offsets[SPO_MS_OFF] = *p;
5811 		    }
5812 		}
5813 		else			    /* yy=x+99 */
5814 		{
5815 		    end += 4;
5816 		    if (*end == '+')
5817 		    {
5818 			++end;
5819 			*p = getdigits(&end);		/* positive offset */
5820 		    }
5821 		    else if (*end == '-')
5822 		    {
5823 			++end;
5824 			*p = -getdigits(&end);		/* negative offset */
5825 		    }
5826 		}
5827 		if (*end != ',')
5828 		    break;
5829 		++end;
5830 	    }
5831 	}
5832     } while (idx >= 0);
5833 
5834     if (!ends_excmd(*end) && !VIM_ISWHITE(*end))
5835     {
5836 	EMSG2(_("E402: Garbage after pattern: %s"), arg);
5837 	return NULL;
5838     }
5839     return skipwhite(end);
5840 }
5841 
5842 /*
5843  * Handle ":syntax sync .." command.
5844  */
5845     static void
5846 syn_cmd_sync(exarg_T *eap, int syncing UNUSED)
5847 {
5848     char_u	*arg_start = eap->arg;
5849     char_u	*arg_end;
5850     char_u	*key = NULL;
5851     char_u	*next_arg;
5852     int		illegal = FALSE;
5853     int		finished = FALSE;
5854     long	n;
5855     char_u	*cpo_save;
5856 
5857     if (ends_excmd(*arg_start))
5858     {
5859 	syn_cmd_list(eap, TRUE);
5860 	return;
5861     }
5862 
5863     while (!ends_excmd(*arg_start))
5864     {
5865 	arg_end = skiptowhite(arg_start);
5866 	next_arg = skipwhite(arg_end);
5867 	vim_free(key);
5868 	key = vim_strnsave_up(arg_start, (int)(arg_end - arg_start));
5869 	if (STRCMP(key, "CCOMMENT") == 0)
5870 	{
5871 	    if (!eap->skip)
5872 		curwin->w_s->b_syn_sync_flags |= SF_CCOMMENT;
5873 	    if (!ends_excmd(*next_arg))
5874 	    {
5875 		arg_end = skiptowhite(next_arg);
5876 		if (!eap->skip)
5877 		    curwin->w_s->b_syn_sync_id = syn_check_group(next_arg,
5878 						   (int)(arg_end - next_arg));
5879 		next_arg = skipwhite(arg_end);
5880 	    }
5881 	    else if (!eap->skip)
5882 		curwin->w_s->b_syn_sync_id = syn_name2id((char_u *)"Comment");
5883 	}
5884 	else if (  STRNCMP(key, "LINES", 5) == 0
5885 		|| STRNCMP(key, "MINLINES", 8) == 0
5886 		|| STRNCMP(key, "MAXLINES", 8) == 0
5887 		|| STRNCMP(key, "LINEBREAKS", 10) == 0)
5888 	{
5889 	    if (key[4] == 'S')
5890 		arg_end = key + 6;
5891 	    else if (key[0] == 'L')
5892 		arg_end = key + 11;
5893 	    else
5894 		arg_end = key + 9;
5895 	    if (arg_end[-1] != '=' || !VIM_ISDIGIT(*arg_end))
5896 	    {
5897 		illegal = TRUE;
5898 		break;
5899 	    }
5900 	    n = getdigits(&arg_end);
5901 	    if (!eap->skip)
5902 	    {
5903 		if (key[4] == 'B')
5904 		    curwin->w_s->b_syn_sync_linebreaks = n;
5905 		else if (key[1] == 'A')
5906 		    curwin->w_s->b_syn_sync_maxlines = n;
5907 		else
5908 		    curwin->w_s->b_syn_sync_minlines = n;
5909 	    }
5910 	}
5911 	else if (STRCMP(key, "FROMSTART") == 0)
5912 	{
5913 	    if (!eap->skip)
5914 	    {
5915 		curwin->w_s->b_syn_sync_minlines = MAXLNUM;
5916 		curwin->w_s->b_syn_sync_maxlines = 0;
5917 	    }
5918 	}
5919 	else if (STRCMP(key, "LINECONT") == 0)
5920 	{
5921 	    if (*next_arg == NUL)	   /* missing pattern */
5922 	    {
5923 		illegal = TRUE;
5924 		break;
5925 	    }
5926 	    if (curwin->w_s->b_syn_linecont_pat != NULL)
5927 	    {
5928 		EMSG(_("E403: syntax sync: line continuations pattern specified twice"));
5929 		finished = TRUE;
5930 		break;
5931 	    }
5932 	    arg_end = skip_regexp(next_arg + 1, *next_arg, TRUE, NULL);
5933 	    if (*arg_end != *next_arg)	    /* end delimiter not found */
5934 	    {
5935 		illegal = TRUE;
5936 		break;
5937 	    }
5938 
5939 	    if (!eap->skip)
5940 	    {
5941 		/* store the pattern and compiled regexp program */
5942 		if ((curwin->w_s->b_syn_linecont_pat = vim_strnsave(next_arg + 1,
5943 				      (int)(arg_end - next_arg - 1))) == NULL)
5944 		{
5945 		    finished = TRUE;
5946 		    break;
5947 		}
5948 		curwin->w_s->b_syn_linecont_ic = curwin->w_s->b_syn_ic;
5949 
5950 		/* Make 'cpoptions' empty, to avoid the 'l' flag */
5951 		cpo_save = p_cpo;
5952 		p_cpo = (char_u *)"";
5953 		curwin->w_s->b_syn_linecont_prog =
5954 		       vim_regcomp(curwin->w_s->b_syn_linecont_pat, RE_MAGIC);
5955 		p_cpo = cpo_save;
5956 #ifdef FEAT_PROFILE
5957 		syn_clear_time(&curwin->w_s->b_syn_linecont_time);
5958 #endif
5959 
5960 		if (curwin->w_s->b_syn_linecont_prog == NULL)
5961 		{
5962 		    VIM_CLEAR(curwin->w_s->b_syn_linecont_pat);
5963 		    finished = TRUE;
5964 		    break;
5965 		}
5966 	    }
5967 	    next_arg = skipwhite(arg_end + 1);
5968 	}
5969 	else
5970 	{
5971 	    eap->arg = next_arg;
5972 	    if (STRCMP(key, "MATCH") == 0)
5973 		syn_cmd_match(eap, TRUE);
5974 	    else if (STRCMP(key, "REGION") == 0)
5975 		syn_cmd_region(eap, TRUE);
5976 	    else if (STRCMP(key, "CLEAR") == 0)
5977 		syn_cmd_clear(eap, TRUE);
5978 	    else
5979 		illegal = TRUE;
5980 	    finished = TRUE;
5981 	    break;
5982 	}
5983 	arg_start = next_arg;
5984     }
5985     vim_free(key);
5986     if (illegal)
5987 	EMSG2(_("E404: Illegal arguments: %s"), arg_start);
5988     else if (!finished)
5989     {
5990 	eap->nextcmd = check_nextcmd(arg_start);
5991 	redraw_curbuf_later(SOME_VALID);
5992 	syn_stack_free_all(curwin->w_s);	/* Need to recompute all syntax. */
5993     }
5994 }
5995 
5996 /*
5997  * Convert a line of highlight group names into a list of group ID numbers.
5998  * "arg" should point to the "contains" or "nextgroup" keyword.
5999  * "arg" is advanced to after the last group name.
6000  * Careful: the argument is modified (NULs added).
6001  * returns FAIL for some error, OK for success.
6002  */
6003     static int
6004 get_id_list(
6005     char_u	**arg,
6006     int		keylen,		/* length of keyword */
6007     short	**list,		/* where to store the resulting list, if not
6008 				   NULL, the list is silently skipped! */
6009     int		skip)
6010 {
6011     char_u	*p = NULL;
6012     char_u	*end;
6013     int		round;
6014     int		count;
6015     int		total_count = 0;
6016     short	*retval = NULL;
6017     char_u	*name;
6018     regmatch_T	regmatch;
6019     int		id;
6020     int		i;
6021     int		failed = FALSE;
6022 
6023     /*
6024      * We parse the list twice:
6025      * round == 1: count the number of items, allocate the array.
6026      * round == 2: fill the array with the items.
6027      * In round 1 new groups may be added, causing the number of items to
6028      * grow when a regexp is used.  In that case round 1 is done once again.
6029      */
6030     for (round = 1; round <= 2; ++round)
6031     {
6032 	/*
6033 	 * skip "contains"
6034 	 */
6035 	p = skipwhite(*arg + keylen);
6036 	if (*p != '=')
6037 	{
6038 	    EMSG2(_("E405: Missing equal sign: %s"), *arg);
6039 	    break;
6040 	}
6041 	p = skipwhite(p + 1);
6042 	if (ends_excmd(*p))
6043 	{
6044 	    EMSG2(_("E406: Empty argument: %s"), *arg);
6045 	    break;
6046 	}
6047 
6048 	/*
6049 	 * parse the arguments after "contains"
6050 	 */
6051 	count = 0;
6052 	while (!ends_excmd(*p))
6053 	{
6054 	    for (end = p; *end && !VIM_ISWHITE(*end) && *end != ','; ++end)
6055 		;
6056 	    name = alloc((int)(end - p + 3));	    /* leave room for "^$" */
6057 	    if (name == NULL)
6058 	    {
6059 		failed = TRUE;
6060 		break;
6061 	    }
6062 	    vim_strncpy(name + 1, p, end - p);
6063 	    if (       STRCMP(name + 1, "ALLBUT") == 0
6064 		    || STRCMP(name + 1, "ALL") == 0
6065 		    || STRCMP(name + 1, "TOP") == 0
6066 		    || STRCMP(name + 1, "CONTAINED") == 0)
6067 	    {
6068 		if (TOUPPER_ASC(**arg) != 'C')
6069 		{
6070 		    EMSG2(_("E407: %s not allowed here"), name + 1);
6071 		    failed = TRUE;
6072 		    vim_free(name);
6073 		    break;
6074 		}
6075 		if (count != 0)
6076 		{
6077 		    EMSG2(_("E408: %s must be first in contains list"),
6078 								     name + 1);
6079 		    failed = TRUE;
6080 		    vim_free(name);
6081 		    break;
6082 		}
6083 		if (name[1] == 'A')
6084 		    id = SYNID_ALLBUT;
6085 		else if (name[1] == 'T')
6086 		    id = SYNID_TOP;
6087 		else
6088 		    id = SYNID_CONTAINED;
6089 		id += current_syn_inc_tag;
6090 	    }
6091 	    else if (name[1] == '@')
6092 	    {
6093 		if (skip)
6094 		    id = -1;
6095 		else
6096 		    id = syn_check_cluster(name + 2, (int)(end - p - 1));
6097 	    }
6098 	    else
6099 	    {
6100 		/*
6101 		 * Handle full group name.
6102 		 */
6103 		if (vim_strpbrk(name + 1, (char_u *)"\\.*^$~[") == NULL)
6104 		    id = syn_check_group(name + 1, (int)(end - p));
6105 		else
6106 		{
6107 		    /*
6108 		     * Handle match of regexp with group names.
6109 		     */
6110 		    *name = '^';
6111 		    STRCAT(name, "$");
6112 		    regmatch.regprog = vim_regcomp(name, RE_MAGIC);
6113 		    if (regmatch.regprog == NULL)
6114 		    {
6115 			failed = TRUE;
6116 			vim_free(name);
6117 			break;
6118 		    }
6119 
6120 		    regmatch.rm_ic = TRUE;
6121 		    id = 0;
6122 		    for (i = highlight_ga.ga_len; --i >= 0; )
6123 		    {
6124 			if (vim_regexec(&regmatch, HL_TABLE()[i].sg_name,
6125 								  (colnr_T)0))
6126 			{
6127 			    if (round == 2)
6128 			    {
6129 				/* Got more items than expected; can happen
6130 				 * when adding items that match:
6131 				 * "contains=a.*b,axb".
6132 				 * Go back to first round */
6133 				if (count >= total_count)
6134 				{
6135 				    vim_free(retval);
6136 				    round = 1;
6137 				}
6138 				else
6139 				    retval[count] = i + 1;
6140 			    }
6141 			    ++count;
6142 			    id = -1;	    /* remember that we found one */
6143 			}
6144 		    }
6145 		    vim_regfree(regmatch.regprog);
6146 		}
6147 	    }
6148 	    vim_free(name);
6149 	    if (id == 0)
6150 	    {
6151 		EMSG2(_("E409: Unknown group name: %s"), p);
6152 		failed = TRUE;
6153 		break;
6154 	    }
6155 	    if (id > 0)
6156 	    {
6157 		if (round == 2)
6158 		{
6159 		    /* Got more items than expected, go back to first round */
6160 		    if (count >= total_count)
6161 		    {
6162 			vim_free(retval);
6163 			round = 1;
6164 		    }
6165 		    else
6166 			retval[count] = id;
6167 		}
6168 		++count;
6169 	    }
6170 	    p = skipwhite(end);
6171 	    if (*p != ',')
6172 		break;
6173 	    p = skipwhite(p + 1);	/* skip comma in between arguments */
6174 	}
6175 	if (failed)
6176 	    break;
6177 	if (round == 1)
6178 	{
6179 	    retval = (short *)alloc((unsigned)((count + 1) * sizeof(short)));
6180 	    if (retval == NULL)
6181 		break;
6182 	    retval[count] = 0;	    /* zero means end of the list */
6183 	    total_count = count;
6184 	}
6185     }
6186 
6187     *arg = p;
6188     if (failed || retval == NULL)
6189     {
6190 	vim_free(retval);
6191 	return FAIL;
6192     }
6193 
6194     if (*list == NULL)
6195 	*list = retval;
6196     else
6197 	vim_free(retval);	/* list already found, don't overwrite it */
6198 
6199     return OK;
6200 }
6201 
6202 /*
6203  * Make a copy of an ID list.
6204  */
6205     static short *
6206 copy_id_list(short *list)
6207 {
6208     int	    len;
6209     int	    count;
6210     short   *retval;
6211 
6212     if (list == NULL)
6213 	return NULL;
6214 
6215     for (count = 0; list[count]; ++count)
6216 	;
6217     len = (count + 1) * sizeof(short);
6218     retval = (short *)alloc((unsigned)len);
6219     if (retval != NULL)
6220 	mch_memmove(retval, list, (size_t)len);
6221 
6222     return retval;
6223 }
6224 
6225 /*
6226  * Check if syntax group "ssp" is in the ID list "list" of "cur_si".
6227  * "cur_si" can be NULL if not checking the "containedin" list.
6228  * Used to check if a syntax item is in the "contains" or "nextgroup" list of
6229  * the current item.
6230  * This function is called very often, keep it fast!!
6231  */
6232     static int
6233 in_id_list(
6234     stateitem_T	*cur_si,	/* current item or NULL */
6235     short	*list,		/* id list */
6236     struct sp_syn *ssp,		/* group id and ":syn include" tag of group */
6237     int		contained)	/* group id is contained */
6238 {
6239     int		retval;
6240     short	*scl_list;
6241     short	item;
6242     short	id = ssp->id;
6243     static int	depth = 0;
6244     int		r;
6245 
6246     /* If ssp has a "containedin" list and "cur_si" is in it, return TRUE. */
6247     if (cur_si != NULL && ssp->cont_in_list != NULL
6248 					    && !(cur_si->si_flags & HL_MATCH))
6249     {
6250 	/* Ignore transparent items without a contains argument.  Double check
6251 	 * that we don't go back past the first one. */
6252 	while ((cur_si->si_flags & HL_TRANS_CONT)
6253 		&& cur_si > (stateitem_T *)(current_state.ga_data))
6254 	    --cur_si;
6255 	/* cur_si->si_idx is -1 for keywords, these never contain anything. */
6256 	if (cur_si->si_idx >= 0 && in_id_list(NULL, ssp->cont_in_list,
6257 		&(SYN_ITEMS(syn_block)[cur_si->si_idx].sp_syn),
6258 		  SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags & HL_CONTAINED))
6259 	    return TRUE;
6260     }
6261 
6262     if (list == NULL)
6263 	return FALSE;
6264 
6265     /*
6266      * If list is ID_LIST_ALL, we are in a transparent item that isn't
6267      * inside anything.  Only allow not-contained groups.
6268      */
6269     if (list == ID_LIST_ALL)
6270 	return !contained;
6271 
6272     /*
6273      * If the first item is "ALLBUT", return TRUE if "id" is NOT in the
6274      * contains list.  We also require that "id" is at the same ":syn include"
6275      * level as the list.
6276      */
6277     item = *list;
6278     if (item >= SYNID_ALLBUT && item < SYNID_CLUSTER)
6279     {
6280 	if (item < SYNID_TOP)
6281 	{
6282 	    /* ALL or ALLBUT: accept all groups in the same file */
6283 	    if (item - SYNID_ALLBUT != ssp->inc_tag)
6284 		return FALSE;
6285 	}
6286 	else if (item < SYNID_CONTAINED)
6287 	{
6288 	    /* TOP: accept all not-contained groups in the same file */
6289 	    if (item - SYNID_TOP != ssp->inc_tag || contained)
6290 		return FALSE;
6291 	}
6292 	else
6293 	{
6294 	    /* CONTAINED: accept all contained groups in the same file */
6295 	    if (item - SYNID_CONTAINED != ssp->inc_tag || !contained)
6296 		return FALSE;
6297 	}
6298 	item = *++list;
6299 	retval = FALSE;
6300     }
6301     else
6302 	retval = TRUE;
6303 
6304     /*
6305      * Return "retval" if id is in the contains list.
6306      */
6307     while (item != 0)
6308     {
6309 	if (item == id)
6310 	    return retval;
6311 	if (item >= SYNID_CLUSTER)
6312 	{
6313 	    scl_list = SYN_CLSTR(syn_block)[item - SYNID_CLUSTER].scl_list;
6314 	    /* restrict recursiveness to 30 to avoid an endless loop for a
6315 	     * cluster that includes itself (indirectly) */
6316 	    if (scl_list != NULL && depth < 30)
6317 	    {
6318 		++depth;
6319 		r = in_id_list(NULL, scl_list, ssp, contained);
6320 		--depth;
6321 		if (r)
6322 		    return retval;
6323 	    }
6324 	}
6325 	item = *++list;
6326     }
6327     return !retval;
6328 }
6329 
6330 struct subcommand
6331 {
6332     char    *name;			/* subcommand name */
6333     void    (*func)(exarg_T *, int);	/* function to call */
6334 };
6335 
6336 static struct subcommand subcommands[] =
6337 {
6338     {"case",		syn_cmd_case},
6339     {"clear",		syn_cmd_clear},
6340     {"cluster",		syn_cmd_cluster},
6341     {"conceal",		syn_cmd_conceal},
6342     {"enable",		syn_cmd_enable},
6343     {"include",		syn_cmd_include},
6344     {"iskeyword",	syn_cmd_iskeyword},
6345     {"keyword",		syn_cmd_keyword},
6346     {"list",		syn_cmd_list},
6347     {"manual",		syn_cmd_manual},
6348     {"match",		syn_cmd_match},
6349     {"on",		syn_cmd_on},
6350     {"off",		syn_cmd_off},
6351     {"region",		syn_cmd_region},
6352     {"reset",		syn_cmd_reset},
6353     {"spell",		syn_cmd_spell},
6354     {"sync",		syn_cmd_sync},
6355     {"",		syn_cmd_list},
6356     {NULL, NULL}
6357 };
6358 
6359 /*
6360  * ":syntax".
6361  * This searches the subcommands[] table for the subcommand name, and calls a
6362  * syntax_subcommand() function to do the rest.
6363  */
6364     void
6365 ex_syntax(exarg_T *eap)
6366 {
6367     char_u	*arg = eap->arg;
6368     char_u	*subcmd_end;
6369     char_u	*subcmd_name;
6370     int		i;
6371 
6372     syn_cmdlinep = eap->cmdlinep;
6373 
6374     /* isolate subcommand name */
6375     for (subcmd_end = arg; ASCII_ISALPHA(*subcmd_end); ++subcmd_end)
6376 	;
6377     subcmd_name = vim_strnsave(arg, (int)(subcmd_end - arg));
6378     if (subcmd_name != NULL)
6379     {
6380 	if (eap->skip)		/* skip error messages for all subcommands */
6381 	    ++emsg_skip;
6382 	for (i = 0; ; ++i)
6383 	{
6384 	    if (subcommands[i].name == NULL)
6385 	    {
6386 		EMSG2(_("E410: Invalid :syntax subcommand: %s"), subcmd_name);
6387 		break;
6388 	    }
6389 	    if (STRCMP(subcmd_name, (char_u *)subcommands[i].name) == 0)
6390 	    {
6391 		eap->arg = skipwhite(subcmd_end);
6392 		(subcommands[i].func)(eap, FALSE);
6393 		break;
6394 	    }
6395 	}
6396 	vim_free(subcmd_name);
6397 	if (eap->skip)
6398 	    --emsg_skip;
6399     }
6400 }
6401 
6402     void
6403 ex_ownsyntax(exarg_T *eap)
6404 {
6405     char_u	*old_value;
6406     char_u	*new_value;
6407 
6408     if (curwin->w_s == &curwin->w_buffer->b_s)
6409     {
6410 	curwin->w_s = (synblock_T *)alloc(sizeof(synblock_T));
6411 	memset(curwin->w_s, 0, sizeof(synblock_T));
6412 	hash_init(&curwin->w_s->b_keywtab);
6413 	hash_init(&curwin->w_s->b_keywtab_ic);
6414 #ifdef FEAT_SPELL
6415 	/* TODO: keep the spell checking as it was. */
6416 	curwin->w_p_spell = FALSE;	/* No spell checking */
6417 	clear_string_option(&curwin->w_s->b_p_spc);
6418 	clear_string_option(&curwin->w_s->b_p_spf);
6419 	clear_string_option(&curwin->w_s->b_p_spl);
6420 #endif
6421 	clear_string_option(&curwin->w_s->b_syn_isk);
6422     }
6423 
6424     /* save value of b:current_syntax */
6425     old_value = get_var_value((char_u *)"b:current_syntax");
6426     if (old_value != NULL)
6427 	old_value = vim_strsave(old_value);
6428 
6429     /* Apply the "syntax" autocommand event, this finds and loads the syntax
6430      * file. */
6431     apply_autocmds(EVENT_SYNTAX, eap->arg, curbuf->b_fname, TRUE, curbuf);
6432 
6433     /* move value of b:current_syntax to w:current_syntax */
6434     new_value = get_var_value((char_u *)"b:current_syntax");
6435     if (new_value != NULL)
6436 	set_internal_string_var((char_u *)"w:current_syntax", new_value);
6437 
6438     /* restore value of b:current_syntax */
6439     if (old_value == NULL)
6440 	do_unlet((char_u *)"b:current_syntax", TRUE);
6441     else
6442     {
6443 	set_internal_string_var((char_u *)"b:current_syntax", old_value);
6444 	vim_free(old_value);
6445     }
6446 }
6447 
6448     int
6449 syntax_present(win_T *win)
6450 {
6451     return (win->w_s->b_syn_patterns.ga_len != 0
6452 	    || win->w_s->b_syn_clusters.ga_len != 0
6453 	    || win->w_s->b_keywtab.ht_used > 0
6454 	    || win->w_s->b_keywtab_ic.ht_used > 0);
6455 }
6456 
6457 #if defined(FEAT_CMDL_COMPL) || defined(PROTO)
6458 
6459 static enum
6460 {
6461     EXP_SUBCMD,	    /* expand ":syn" sub-commands */
6462     EXP_CASE,	    /* expand ":syn case" arguments */
6463     EXP_SPELL,	    /* expand ":syn spell" arguments */
6464     EXP_SYNC	    /* expand ":syn sync" arguments */
6465 } expand_what;
6466 
6467 /*
6468  * Reset include_link, include_default, include_none to 0.
6469  * Called when we are done expanding.
6470  */
6471     void
6472 reset_expand_highlight(void)
6473 {
6474     include_link = include_default = include_none = 0;
6475 }
6476 
6477 /*
6478  * Handle command line completion for :match and :echohl command: Add "None"
6479  * as highlight group.
6480  */
6481     void
6482 set_context_in_echohl_cmd(expand_T *xp, char_u *arg)
6483 {
6484     xp->xp_context = EXPAND_HIGHLIGHT;
6485     xp->xp_pattern = arg;
6486     include_none = 1;
6487 }
6488 
6489 /*
6490  * Handle command line completion for :syntax command.
6491  */
6492     void
6493 set_context_in_syntax_cmd(expand_T *xp, char_u *arg)
6494 {
6495     char_u	*p;
6496 
6497     /* Default: expand subcommands */
6498     xp->xp_context = EXPAND_SYNTAX;
6499     expand_what = EXP_SUBCMD;
6500     xp->xp_pattern = arg;
6501     include_link = 0;
6502     include_default = 0;
6503 
6504     /* (part of) subcommand already typed */
6505     if (*arg != NUL)
6506     {
6507 	p = skiptowhite(arg);
6508 	if (*p != NUL)		    /* past first word */
6509 	{
6510 	    xp->xp_pattern = skipwhite(p);
6511 	    if (*skiptowhite(xp->xp_pattern) != NUL)
6512 		xp->xp_context = EXPAND_NOTHING;
6513 	    else if (STRNICMP(arg, "case", p - arg) == 0)
6514 		expand_what = EXP_CASE;
6515 	    else if (STRNICMP(arg, "spell", p - arg) == 0)
6516 		expand_what = EXP_SPELL;
6517 	    else if (STRNICMP(arg, "sync", p - arg) == 0)
6518 		expand_what = EXP_SYNC;
6519 	    else if (  STRNICMP(arg, "keyword", p - arg) == 0
6520 		    || STRNICMP(arg, "region", p - arg) == 0
6521 		    || STRNICMP(arg, "match", p - arg) == 0
6522 		    || STRNICMP(arg, "list", p - arg) == 0)
6523 		xp->xp_context = EXPAND_HIGHLIGHT;
6524 	    else
6525 		xp->xp_context = EXPAND_NOTHING;
6526 	}
6527     }
6528 }
6529 
6530 /*
6531  * Function given to ExpandGeneric() to obtain the list syntax names for
6532  * expansion.
6533  */
6534     char_u *
6535 get_syntax_name(expand_T *xp UNUSED, int idx)
6536 {
6537     switch (expand_what)
6538     {
6539 	case EXP_SUBCMD:
6540 	    return (char_u *)subcommands[idx].name;
6541 	case EXP_CASE:
6542 	{
6543 	    static char *case_args[] = {"match", "ignore", NULL};
6544 	    return (char_u *)case_args[idx];
6545 	}
6546 	case EXP_SPELL:
6547 	{
6548 	    static char *spell_args[] =
6549 		{"toplevel", "notoplevel", "default", NULL};
6550 	    return (char_u *)spell_args[idx];
6551 	}
6552 	case EXP_SYNC:
6553 	{
6554 	    static char *sync_args[] =
6555 		{"ccomment", "clear", "fromstart",
6556 		 "linebreaks=", "linecont", "lines=", "match",
6557 		 "maxlines=", "minlines=", "region", NULL};
6558 	    return (char_u *)sync_args[idx];
6559 	}
6560     }
6561     return NULL;
6562 }
6563 
6564 #endif /* FEAT_CMDL_COMPL */
6565 
6566 /*
6567  * Function called for expression evaluation: get syntax ID at file position.
6568  */
6569     int
6570 syn_get_id(
6571     win_T	*wp,
6572     long	lnum,
6573     colnr_T	col,
6574     int		trans,	     /* remove transparency */
6575     int		*spellp,     /* return: can do spell checking */
6576     int		keep_state)  /* keep state of char at "col" */
6577 {
6578     /* When the position is not after the current position and in the same
6579      * line of the same buffer, need to restart parsing. */
6580     if (wp->w_buffer != syn_buf
6581 	    || lnum != current_lnum
6582 	    || col < current_col)
6583 	syntax_start(wp, lnum);
6584     else if (wp->w_buffer == syn_buf
6585 	    && lnum == current_lnum
6586 	    && col > current_col)
6587 	/* next_match may not be correct when moving around, e.g. with the
6588 	 * "skip" expression in searchpair() */
6589 	next_match_idx = -1;
6590 
6591     (void)get_syntax_attr(col, spellp, keep_state);
6592 
6593     return (trans ? current_trans_id : current_id);
6594 }
6595 
6596 #if defined(FEAT_CONCEAL) || defined(PROTO)
6597 /*
6598  * Get extra information about the syntax item.  Must be called right after
6599  * get_syntax_attr().
6600  * Stores the current item sequence nr in "*seqnrp".
6601  * Returns the current flags.
6602  */
6603     int
6604 get_syntax_info(int *seqnrp)
6605 {
6606     *seqnrp = current_seqnr;
6607     return current_flags;
6608 }
6609 
6610 /*
6611  * Return conceal substitution character
6612  */
6613     int
6614 syn_get_sub_char(void)
6615 {
6616     return current_sub_char;
6617 }
6618 #endif
6619 
6620 #if defined(FEAT_EVAL) || defined(PROTO)
6621 /*
6622  * Return the syntax ID at position "i" in the current stack.
6623  * The caller must have called syn_get_id() before to fill the stack.
6624  * Returns -1 when "i" is out of range.
6625  */
6626     int
6627 syn_get_stack_item(int i)
6628 {
6629     if (i >= current_state.ga_len)
6630     {
6631 	/* Need to invalidate the state, because we didn't properly finish it
6632 	 * for the last character, "keep_state" was TRUE. */
6633 	invalidate_current_state();
6634 	current_col = MAXCOL;
6635 	return -1;
6636     }
6637     return CUR_STATE(i).si_id;
6638 }
6639 #endif
6640 
6641 #if defined(FEAT_FOLDING) || defined(PROTO)
6642 /*
6643  * Function called to get folding level for line "lnum" in window "wp".
6644  */
6645     int
6646 syn_get_foldlevel(win_T *wp, long lnum)
6647 {
6648     int		level = 0;
6649     int		i;
6650 
6651     /* Return quickly when there are no fold items at all. */
6652     if (wp->w_s->b_syn_folditems != 0
6653 	    && !wp->w_s->b_syn_error
6654 # ifdef SYN_TIME_LIMIT
6655 	    && !wp->w_s->b_syn_slow
6656 # endif
6657 	    )
6658     {
6659 	syntax_start(wp, lnum);
6660 
6661 	for (i = 0; i < current_state.ga_len; ++i)
6662 	    if (CUR_STATE(i).si_flags & HL_FOLD)
6663 		++level;
6664     }
6665     if (level > wp->w_p_fdn)
6666     {
6667 	level = wp->w_p_fdn;
6668 	if (level < 0)
6669 	    level = 0;
6670     }
6671     return level;
6672 }
6673 #endif
6674 
6675 #if defined(FEAT_PROFILE) || defined(PROTO)
6676 /*
6677  * ":syntime".
6678  */
6679     void
6680 ex_syntime(exarg_T *eap)
6681 {
6682     if (STRCMP(eap->arg, "on") == 0)
6683 	syn_time_on = TRUE;
6684     else if (STRCMP(eap->arg, "off") == 0)
6685 	syn_time_on = FALSE;
6686     else if (STRCMP(eap->arg, "clear") == 0)
6687 	syntime_clear();
6688     else if (STRCMP(eap->arg, "report") == 0)
6689 	syntime_report();
6690     else
6691 	EMSG2(_(e_invarg2), eap->arg);
6692 }
6693 
6694     static void
6695 syn_clear_time(syn_time_T *st)
6696 {
6697     profile_zero(&st->total);
6698     profile_zero(&st->slowest);
6699     st->count = 0;
6700     st->match = 0;
6701 }
6702 
6703 /*
6704  * Clear the syntax timing for the current buffer.
6705  */
6706     static void
6707 syntime_clear(void)
6708 {
6709     int		idx;
6710     synpat_T	*spp;
6711 
6712     if (!syntax_present(curwin))
6713     {
6714 	MSG(_(msg_no_items));
6715 	return;
6716     }
6717     for (idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx)
6718     {
6719 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
6720 	syn_clear_time(&spp->sp_time);
6721     }
6722 }
6723 
6724 #if defined(FEAT_CMDL_COMPL) || defined(PROTO)
6725 /*
6726  * Function given to ExpandGeneric() to obtain the possible arguments of the
6727  * ":syntime {on,off,clear,report}" command.
6728  */
6729     char_u *
6730 get_syntime_arg(expand_T *xp UNUSED, int idx)
6731 {
6732     switch (idx)
6733     {
6734 	case 0: return (char_u *)"on";
6735 	case 1: return (char_u *)"off";
6736 	case 2: return (char_u *)"clear";
6737 	case 3: return (char_u *)"report";
6738     }
6739     return NULL;
6740 }
6741 #endif
6742 
6743 typedef struct
6744 {
6745     proftime_T	total;
6746     int		count;
6747     int		match;
6748     proftime_T	slowest;
6749     proftime_T	average;
6750     int		id;
6751     char_u	*pattern;
6752 } time_entry_T;
6753 
6754     static int
6755 #ifdef __BORLANDC__
6756 _RTLENTRYF
6757 #endif
6758 syn_compare_syntime(const void *v1, const void *v2)
6759 {
6760     const time_entry_T	*s1 = v1;
6761     const time_entry_T	*s2 = v2;
6762 
6763     return profile_cmp(&s1->total, &s2->total);
6764 }
6765 
6766 /*
6767  * Clear the syntax timing for the current buffer.
6768  */
6769     static void
6770 syntime_report(void)
6771 {
6772     int		idx;
6773     synpat_T	*spp;
6774 # ifdef FEAT_FLOAT
6775     proftime_T	tm;
6776 # endif
6777     int		len;
6778     proftime_T	total_total;
6779     int		total_count = 0;
6780     garray_T    ga;
6781     time_entry_T *p;
6782 
6783     if (!syntax_present(curwin))
6784     {
6785 	MSG(_(msg_no_items));
6786 	return;
6787     }
6788 
6789     ga_init2(&ga, sizeof(time_entry_T), 50);
6790     profile_zero(&total_total);
6791     for (idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx)
6792     {
6793 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
6794 	if (spp->sp_time.count > 0)
6795 	{
6796 	    (void)ga_grow(&ga, 1);
6797 	    p = ((time_entry_T *)ga.ga_data) + ga.ga_len;
6798 	    p->total = spp->sp_time.total;
6799 	    profile_add(&total_total, &spp->sp_time.total);
6800 	    p->count = spp->sp_time.count;
6801 	    p->match = spp->sp_time.match;
6802 	    total_count += spp->sp_time.count;
6803 	    p->slowest = spp->sp_time.slowest;
6804 # ifdef FEAT_FLOAT
6805 	    profile_divide(&spp->sp_time.total, spp->sp_time.count, &tm);
6806 	    p->average = tm;
6807 # endif
6808 	    p->id = spp->sp_syn.id;
6809 	    p->pattern = spp->sp_pattern;
6810 	    ++ga.ga_len;
6811 	}
6812     }
6813 
6814     /* Sort on total time. Skip if there are no items to avoid passing NULL
6815      * pointer to qsort(). */
6816     if (ga.ga_len > 1)
6817 	qsort(ga.ga_data, (size_t)ga.ga_len, sizeof(time_entry_T),
6818 							 syn_compare_syntime);
6819 
6820     MSG_PUTS_TITLE(_("  TOTAL      COUNT  MATCH   SLOWEST     AVERAGE   NAME               PATTERN"));
6821     MSG_PUTS("\n");
6822     for (idx = 0; idx < ga.ga_len && !got_int; ++idx)
6823     {
6824 	p = ((time_entry_T *)ga.ga_data) + idx;
6825 
6826 	MSG_PUTS(profile_msg(&p->total));
6827 	MSG_PUTS(" "); /* make sure there is always a separating space */
6828 	msg_advance(13);
6829 	msg_outnum(p->count);
6830 	MSG_PUTS(" ");
6831 	msg_advance(20);
6832 	msg_outnum(p->match);
6833 	MSG_PUTS(" ");
6834 	msg_advance(26);
6835 	MSG_PUTS(profile_msg(&p->slowest));
6836 	MSG_PUTS(" ");
6837 	msg_advance(38);
6838 # ifdef FEAT_FLOAT
6839 	MSG_PUTS(profile_msg(&p->average));
6840 	MSG_PUTS(" ");
6841 # endif
6842 	msg_advance(50);
6843 	msg_outtrans(HL_TABLE()[p->id - 1].sg_name);
6844 	MSG_PUTS(" ");
6845 
6846 	msg_advance(69);
6847 	if (Columns < 80)
6848 	    len = 20; /* will wrap anyway */
6849 	else
6850 	    len = Columns - 70;
6851 	if (len > (int)STRLEN(p->pattern))
6852 	    len = (int)STRLEN(p->pattern);
6853 	msg_outtrans_len(p->pattern, len);
6854 	MSG_PUTS("\n");
6855     }
6856     ga_clear(&ga);
6857     if (!got_int)
6858     {
6859 	MSG_PUTS("\n");
6860 	MSG_PUTS(profile_msg(&total_total));
6861 	msg_advance(13);
6862 	msg_outnum(total_count);
6863 	MSG_PUTS("\n");
6864     }
6865 }
6866 #endif
6867 
6868 #endif /* FEAT_SYN_HL */
6869 
6870 /**************************************
6871  *  Highlighting stuff		      *
6872  **************************************/
6873 
6874 /*
6875  * The default highlight groups.  These are compiled-in for fast startup and
6876  * they still work when the runtime files can't be found.
6877  * When making changes here, also change runtime/colors/default.vim!
6878  * The #ifdefs are needed to reduce the amount of static data.  Helps to make
6879  * the 16 bit DOS (museum) version compile.
6880  */
6881 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
6882 # define CENT(a, b) b
6883 #else
6884 # define CENT(a, b) a
6885 #endif
6886 static char *(highlight_init_both[]) = {
6887     CENT("ErrorMsg term=standout ctermbg=DarkRed ctermfg=White",
6888 	 "ErrorMsg term=standout ctermbg=DarkRed ctermfg=White guibg=Red guifg=White"),
6889     CENT("IncSearch term=reverse cterm=reverse",
6890 	 "IncSearch term=reverse cterm=reverse gui=reverse"),
6891     CENT("ModeMsg term=bold cterm=bold",
6892 	 "ModeMsg term=bold cterm=bold gui=bold"),
6893     CENT("NonText term=bold ctermfg=Blue",
6894 	 "NonText term=bold ctermfg=Blue gui=bold guifg=Blue"),
6895     CENT("StatusLine term=reverse,bold cterm=reverse,bold",
6896 	 "StatusLine term=reverse,bold cterm=reverse,bold gui=reverse,bold"),
6897     CENT("StatusLineNC term=reverse cterm=reverse",
6898 	 "StatusLineNC term=reverse cterm=reverse gui=reverse"),
6899     "default link EndOfBuffer NonText",
6900     CENT("VertSplit term=reverse cterm=reverse",
6901 	 "VertSplit term=reverse cterm=reverse gui=reverse"),
6902 #ifdef FEAT_CLIPBOARD
6903     CENT("VisualNOS term=underline,bold cterm=underline,bold",
6904 	 "VisualNOS term=underline,bold cterm=underline,bold gui=underline,bold"),
6905 #endif
6906 #ifdef FEAT_DIFF
6907     CENT("DiffText term=reverse cterm=bold ctermbg=Red",
6908 	 "DiffText term=reverse cterm=bold ctermbg=Red gui=bold guibg=Red"),
6909 #endif
6910 #ifdef FEAT_INS_EXPAND
6911     CENT("PmenuSbar ctermbg=Grey",
6912 	 "PmenuSbar ctermbg=Grey guibg=Grey"),
6913 #endif
6914     CENT("TabLineSel term=bold cterm=bold",
6915 	 "TabLineSel term=bold cterm=bold gui=bold"),
6916     CENT("TabLineFill term=reverse cterm=reverse",
6917 	 "TabLineFill term=reverse cterm=reverse gui=reverse"),
6918 #ifdef FEAT_GUI
6919     "Cursor guibg=fg guifg=bg",
6920     "lCursor guibg=fg guifg=bg", /* should be different, but what? */
6921 #endif
6922     "default link QuickFixLine Search",
6923     NULL
6924 };
6925 
6926 /* Default colors only used with a light background. */
6927 static char *(highlight_init_light[]) = {
6928     CENT("Directory term=bold ctermfg=DarkBlue",
6929 	 "Directory term=bold ctermfg=DarkBlue guifg=Blue"),
6930     CENT("LineNr term=underline ctermfg=Brown",
6931 	 "LineNr term=underline ctermfg=Brown guifg=Brown"),
6932     CENT("CursorLineNr term=bold ctermfg=Brown",
6933 	 "CursorLineNr term=bold ctermfg=Brown gui=bold guifg=Brown"),
6934     CENT("MoreMsg term=bold ctermfg=DarkGreen",
6935 	 "MoreMsg term=bold ctermfg=DarkGreen gui=bold guifg=SeaGreen"),
6936     CENT("Question term=standout ctermfg=DarkGreen",
6937 	 "Question term=standout ctermfg=DarkGreen gui=bold guifg=SeaGreen"),
6938     CENT("Search term=reverse ctermbg=Yellow ctermfg=NONE",
6939 	 "Search term=reverse ctermbg=Yellow ctermfg=NONE guibg=Yellow guifg=NONE"),
6940 #ifdef FEAT_SPELL
6941     CENT("SpellBad term=reverse ctermbg=LightRed",
6942 	 "SpellBad term=reverse ctermbg=LightRed guisp=Red gui=undercurl"),
6943     CENT("SpellCap term=reverse ctermbg=LightBlue",
6944 	 "SpellCap term=reverse ctermbg=LightBlue guisp=Blue gui=undercurl"),
6945     CENT("SpellRare term=reverse ctermbg=LightMagenta",
6946 	 "SpellRare term=reverse ctermbg=LightMagenta guisp=Magenta gui=undercurl"),
6947     CENT("SpellLocal term=underline ctermbg=Cyan",
6948 	 "SpellLocal term=underline ctermbg=Cyan guisp=DarkCyan gui=undercurl"),
6949 #endif
6950 #ifdef FEAT_INS_EXPAND
6951     CENT("PmenuThumb ctermbg=Black",
6952 	 "PmenuThumb ctermbg=Black guibg=Black"),
6953     CENT("Pmenu ctermbg=LightMagenta ctermfg=Black",
6954 	 "Pmenu ctermbg=LightMagenta ctermfg=Black guibg=LightMagenta"),
6955     CENT("PmenuSel ctermbg=LightGrey ctermfg=Black",
6956 	 "PmenuSel ctermbg=LightGrey ctermfg=Black guibg=Grey"),
6957 #endif
6958     CENT("SpecialKey term=bold ctermfg=DarkBlue",
6959 	 "SpecialKey term=bold ctermfg=DarkBlue guifg=Blue"),
6960     CENT("Title term=bold ctermfg=DarkMagenta",
6961 	 "Title term=bold ctermfg=DarkMagenta gui=bold guifg=Magenta"),
6962     CENT("WarningMsg term=standout ctermfg=DarkRed",
6963 	 "WarningMsg term=standout ctermfg=DarkRed guifg=Red"),
6964 #ifdef FEAT_WILDMENU
6965     CENT("WildMenu term=standout ctermbg=Yellow ctermfg=Black",
6966 	 "WildMenu term=standout ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black"),
6967 #endif
6968 #ifdef FEAT_FOLDING
6969     CENT("Folded term=standout ctermbg=Grey ctermfg=DarkBlue",
6970 	 "Folded term=standout ctermbg=Grey ctermfg=DarkBlue guibg=LightGrey guifg=DarkBlue"),
6971     CENT("FoldColumn term=standout ctermbg=Grey ctermfg=DarkBlue",
6972 	 "FoldColumn term=standout ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue"),
6973 #endif
6974 #ifdef FEAT_SIGNS
6975     CENT("SignColumn term=standout ctermbg=Grey ctermfg=DarkBlue",
6976 	 "SignColumn term=standout ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue"),
6977 #endif
6978     CENT("Visual term=reverse",
6979 	 "Visual term=reverse guibg=LightGrey"),
6980 #ifdef FEAT_DIFF
6981     CENT("DiffAdd term=bold ctermbg=LightBlue",
6982 	 "DiffAdd term=bold ctermbg=LightBlue guibg=LightBlue"),
6983     CENT("DiffChange term=bold ctermbg=LightMagenta",
6984 	 "DiffChange term=bold ctermbg=LightMagenta guibg=LightMagenta"),
6985     CENT("DiffDelete term=bold ctermfg=Blue ctermbg=LightCyan",
6986 	 "DiffDelete term=bold ctermfg=Blue ctermbg=LightCyan gui=bold guifg=Blue guibg=LightCyan"),
6987 #endif
6988     CENT("TabLine term=underline cterm=underline ctermfg=black ctermbg=LightGrey",
6989 	 "TabLine term=underline cterm=underline ctermfg=black ctermbg=LightGrey gui=underline guibg=LightGrey"),
6990 #ifdef FEAT_SYN_HL
6991     CENT("CursorColumn term=reverse ctermbg=LightGrey",
6992 	 "CursorColumn term=reverse ctermbg=LightGrey guibg=Grey90"),
6993     CENT("CursorLine term=underline cterm=underline",
6994 	 "CursorLine term=underline cterm=underline guibg=Grey90"),
6995     CENT("ColorColumn term=reverse ctermbg=LightRed",
6996 	 "ColorColumn term=reverse ctermbg=LightRed guibg=LightRed"),
6997 #endif
6998 #ifdef FEAT_CONCEAL
6999     CENT("Conceal ctermbg=DarkGrey ctermfg=LightGrey",
7000 	 "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey"),
7001 #endif
7002     CENT("MatchParen term=reverse ctermbg=Cyan",
7003 	 "MatchParen term=reverse ctermbg=Cyan guibg=Cyan"),
7004 #ifdef FEAT_GUI
7005     "Normal gui=NONE",
7006 #endif
7007 #ifdef FEAT_TERMINAL
7008     CENT("StatusLineTerm term=reverse,bold cterm=bold ctermfg=White ctermbg=DarkGreen",
7009 	 "StatusLineTerm term=reverse,bold cterm=bold ctermfg=White ctermbg=DarkGreen gui=bold guifg=bg guibg=DarkGreen"),
7010     CENT("StatusLineTermNC term=reverse ctermfg=White ctermbg=DarkGreen",
7011 	 "StatusLineTermNC term=reverse ctermfg=White ctermbg=DarkGreen guifg=bg guibg=DarkGreen"),
7012 #endif
7013 #ifdef FEAT_MENU
7014     CENT("ToolbarLine term=underline ctermbg=LightGrey",
7015 	 "ToolbarLine term=underline ctermbg=LightGrey guibg=LightGrey"),
7016     CENT("ToolbarButton cterm=bold ctermfg=White ctermbg=DarkGrey",
7017 	 "ToolbarButton cterm=bold ctermfg=White ctermbg=DarkGrey gui=bold guifg=White guibg=Grey40"),
7018 #endif
7019     NULL
7020 };
7021 
7022 /* Default colors only used with a dark background. */
7023 static char *(highlight_init_dark[]) = {
7024     CENT("Directory term=bold ctermfg=LightCyan",
7025 	 "Directory term=bold ctermfg=LightCyan guifg=Cyan"),
7026     CENT("LineNr term=underline ctermfg=Yellow",
7027 	 "LineNr term=underline ctermfg=Yellow guifg=Yellow"),
7028     CENT("CursorLineNr term=bold ctermfg=Yellow",
7029 	 "CursorLineNr term=bold ctermfg=Yellow gui=bold guifg=Yellow"),
7030     CENT("MoreMsg term=bold ctermfg=LightGreen",
7031 	 "MoreMsg term=bold ctermfg=LightGreen gui=bold guifg=SeaGreen"),
7032     CENT("Question term=standout ctermfg=LightGreen",
7033 	 "Question term=standout ctermfg=LightGreen gui=bold guifg=Green"),
7034     CENT("Search term=reverse ctermbg=Yellow ctermfg=Black",
7035 	 "Search term=reverse ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black"),
7036     CENT("SpecialKey term=bold ctermfg=LightBlue",
7037 	 "SpecialKey term=bold ctermfg=LightBlue guifg=Cyan"),
7038 #ifdef FEAT_SPELL
7039     CENT("SpellBad term=reverse ctermbg=Red",
7040 	 "SpellBad term=reverse ctermbg=Red guisp=Red gui=undercurl"),
7041     CENT("SpellCap term=reverse ctermbg=Blue",
7042 	 "SpellCap term=reverse ctermbg=Blue guisp=Blue gui=undercurl"),
7043     CENT("SpellRare term=reverse ctermbg=Magenta",
7044 	 "SpellRare term=reverse ctermbg=Magenta guisp=Magenta gui=undercurl"),
7045     CENT("SpellLocal term=underline ctermbg=Cyan",
7046 	 "SpellLocal term=underline ctermbg=Cyan guisp=Cyan gui=undercurl"),
7047 #endif
7048 #ifdef FEAT_INS_EXPAND
7049     CENT("PmenuThumb ctermbg=White",
7050 	 "PmenuThumb ctermbg=White guibg=White"),
7051     CENT("Pmenu ctermbg=Magenta ctermfg=Black",
7052 	 "Pmenu ctermbg=Magenta ctermfg=Black guibg=Magenta"),
7053     CENT("PmenuSel ctermbg=Black ctermfg=DarkGrey",
7054 	 "PmenuSel ctermbg=Black ctermfg=DarkGrey guibg=DarkGrey"),
7055 #endif
7056     CENT("Title term=bold ctermfg=LightMagenta",
7057 	 "Title term=bold ctermfg=LightMagenta gui=bold guifg=Magenta"),
7058     CENT("WarningMsg term=standout ctermfg=LightRed",
7059 	 "WarningMsg term=standout ctermfg=LightRed guifg=Red"),
7060 #ifdef FEAT_WILDMENU
7061     CENT("WildMenu term=standout ctermbg=Yellow ctermfg=Black",
7062 	 "WildMenu term=standout ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black"),
7063 #endif
7064 #ifdef FEAT_FOLDING
7065     CENT("Folded term=standout ctermbg=DarkGrey ctermfg=Cyan",
7066 	 "Folded term=standout ctermbg=DarkGrey ctermfg=Cyan guibg=DarkGrey guifg=Cyan"),
7067     CENT("FoldColumn term=standout ctermbg=DarkGrey ctermfg=Cyan",
7068 	 "FoldColumn term=standout ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan"),
7069 #endif
7070 #ifdef FEAT_SIGNS
7071     CENT("SignColumn term=standout ctermbg=DarkGrey ctermfg=Cyan",
7072 	 "SignColumn term=standout ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan"),
7073 #endif
7074     CENT("Visual term=reverse",
7075 	 "Visual term=reverse guibg=DarkGrey"),
7076 #ifdef FEAT_DIFF
7077     CENT("DiffAdd term=bold ctermbg=DarkBlue",
7078 	 "DiffAdd term=bold ctermbg=DarkBlue guibg=DarkBlue"),
7079     CENT("DiffChange term=bold ctermbg=DarkMagenta",
7080 	 "DiffChange term=bold ctermbg=DarkMagenta guibg=DarkMagenta"),
7081     CENT("DiffDelete term=bold ctermfg=Blue ctermbg=DarkCyan",
7082 	 "DiffDelete term=bold ctermfg=Blue ctermbg=DarkCyan gui=bold guifg=Blue guibg=DarkCyan"),
7083 #endif
7084     CENT("TabLine term=underline cterm=underline ctermfg=white ctermbg=DarkGrey",
7085 	 "TabLine term=underline cterm=underline ctermfg=white ctermbg=DarkGrey gui=underline guibg=DarkGrey"),
7086 #ifdef FEAT_SYN_HL
7087     CENT("CursorColumn term=reverse ctermbg=DarkGrey",
7088 	 "CursorColumn term=reverse ctermbg=DarkGrey guibg=Grey40"),
7089     CENT("CursorLine term=underline cterm=underline",
7090 	 "CursorLine term=underline cterm=underline guibg=Grey40"),
7091     CENT("ColorColumn term=reverse ctermbg=DarkRed",
7092 	 "ColorColumn term=reverse ctermbg=DarkRed guibg=DarkRed"),
7093 #endif
7094     CENT("MatchParen term=reverse ctermbg=DarkCyan",
7095 	 "MatchParen term=reverse ctermbg=DarkCyan guibg=DarkCyan"),
7096 #ifdef FEAT_CONCEAL
7097     CENT("Conceal ctermbg=DarkGrey ctermfg=LightGrey",
7098 	 "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey"),
7099 #endif
7100 #ifdef FEAT_GUI
7101     "Normal gui=NONE",
7102 #endif
7103 #ifdef FEAT_TERMINAL
7104     CENT("StatusLineTerm term=reverse,bold cterm=bold ctermfg=Black ctermbg=LightGreen",
7105 	 "StatusLineTerm term=reverse,bold cterm=bold ctermfg=Black ctermbg=LightGreen gui=bold guifg=bg guibg=LightGreen"),
7106     CENT("StatusLineTermNC term=reverse ctermfg=Black ctermbg=LightGreen",
7107 	 "StatusLineTermNC term=reverse ctermfg=Black ctermbg=LightGreen guifg=bg guibg=LightGreen"),
7108 #endif
7109 #ifdef FEAT_MENU
7110     CENT("ToolbarLine term=underline ctermbg=DarkGrey",
7111 	 "ToolbarLine term=underline ctermbg=DarkGrey guibg=Grey50"),
7112     CENT("ToolbarButton cterm=bold ctermfg=Black ctermbg=LightGrey",
7113 	 "ToolbarButton cterm=bold ctermfg=Black ctermbg=LightGrey gui=bold guifg=Black guibg=LightGrey"),
7114 #endif
7115     NULL
7116 };
7117 
7118     void
7119 init_highlight(
7120     int		both,	    /* include groups where 'bg' doesn't matter */
7121     int		reset)	    /* clear group first */
7122 {
7123     int		i;
7124     char	**pp;
7125     static int	had_both = FALSE;
7126 #ifdef FEAT_EVAL
7127     char_u	*p;
7128 
7129     /*
7130      * Try finding the color scheme file.  Used when a color file was loaded
7131      * and 'background' or 't_Co' is changed.
7132      */
7133     p = get_var_value((char_u *)"g:colors_name");
7134     if (p != NULL)
7135     {
7136        /* The value of g:colors_name could be freed when sourcing the script,
7137 	* making "p" invalid, so copy it. */
7138        char_u *copy_p = vim_strsave(p);
7139        int    r;
7140 
7141        if (copy_p != NULL)
7142        {
7143 	   r = load_colors(copy_p);
7144 	   vim_free(copy_p);
7145 	   if (r == OK)
7146 	       return;
7147        }
7148     }
7149 
7150 #endif
7151 
7152     /*
7153      * Didn't use a color file, use the compiled-in colors.
7154      */
7155     if (both)
7156     {
7157 	had_both = TRUE;
7158 	pp = highlight_init_both;
7159 	for (i = 0; pp[i] != NULL; ++i)
7160 	    do_highlight((char_u *)pp[i], reset, TRUE);
7161     }
7162     else if (!had_both)
7163 	/* Don't do anything before the call with both == TRUE from main().
7164 	 * Not everything has been setup then, and that call will overrule
7165 	 * everything anyway. */
7166 	return;
7167 
7168     if (*p_bg == 'l')
7169 	pp = highlight_init_light;
7170     else
7171 	pp = highlight_init_dark;
7172     for (i = 0; pp[i] != NULL; ++i)
7173 	do_highlight((char_u *)pp[i], reset, TRUE);
7174 
7175     /* Reverse looks ugly, but grey may not work for 8 colors.  Thus let it
7176      * depend on the number of colors available.
7177      * With 8 colors brown is equal to yellow, need to use black for Search fg
7178      * to avoid Statement highlighted text disappears.
7179      * Clear the attributes, needed when changing the t_Co value. */
7180     if (t_colors > 8)
7181 	do_highlight((char_u *)(*p_bg == 'l'
7182 		    ? "Visual cterm=NONE ctermbg=LightGrey"
7183 		    : "Visual cterm=NONE ctermbg=DarkGrey"), FALSE, TRUE);
7184     else
7185     {
7186 	do_highlight((char_u *)"Visual cterm=reverse ctermbg=NONE",
7187 								 FALSE, TRUE);
7188 	if (*p_bg == 'l')
7189 	    do_highlight((char_u *)"Search ctermfg=black", FALSE, TRUE);
7190     }
7191 
7192 #ifdef FEAT_SYN_HL
7193     /*
7194      * If syntax highlighting is enabled load the highlighting for it.
7195      */
7196     if (get_var_value((char_u *)"g:syntax_on") != NULL)
7197     {
7198 	static int	recursive = 0;
7199 
7200 	if (recursive >= 5)
7201 	    EMSG(_("E679: recursive loop loading syncolor.vim"));
7202 	else
7203 	{
7204 	    ++recursive;
7205 	    (void)source_runtime((char_u *)"syntax/syncolor.vim", DIP_ALL);
7206 	    --recursive;
7207 	}
7208     }
7209 #endif
7210 }
7211 
7212 /*
7213  * Load color file "name".
7214  * Return OK for success, FAIL for failure.
7215  */
7216     int
7217 load_colors(char_u *name)
7218 {
7219     char_u	*buf;
7220     int		retval = FAIL;
7221     static int	recursive = FALSE;
7222 
7223     /* When being called recursively, this is probably because setting
7224      * 'background' caused the highlighting to be reloaded.  This means it is
7225      * working, thus we should return OK. */
7226     if (recursive)
7227 	return OK;
7228 
7229     recursive = TRUE;
7230     buf = alloc((unsigned)(STRLEN(name) + 12));
7231     if (buf != NULL)
7232     {
7233 	apply_autocmds(EVENT_COLORSCHEMEPRE, name,
7234 					       curbuf->b_fname, FALSE, curbuf);
7235 	sprintf((char *)buf, "colors/%s.vim", name);
7236 	retval = source_runtime(buf, DIP_START + DIP_OPT);
7237 	vim_free(buf);
7238 	apply_autocmds(EVENT_COLORSCHEME, name, curbuf->b_fname, FALSE, curbuf);
7239     }
7240     recursive = FALSE;
7241 
7242     return retval;
7243 }
7244 
7245 static char *(color_names[28]) = {
7246 	    "Black", "DarkBlue", "DarkGreen", "DarkCyan",
7247 	    "DarkRed", "DarkMagenta", "Brown", "DarkYellow",
7248 	    "Gray", "Grey", "LightGray", "LightGrey",
7249 	    "DarkGray", "DarkGrey",
7250 	    "Blue", "LightBlue", "Green", "LightGreen",
7251 	    "Cyan", "LightCyan", "Red", "LightRed", "Magenta",
7252 	    "LightMagenta", "Yellow", "LightYellow", "White", "NONE"};
7253 	    /* indices:
7254 	     * 0, 1, 2, 3,
7255 	     * 4, 5, 6, 7,
7256 	     * 8, 9, 10, 11,
7257 	     * 12, 13,
7258 	     * 14, 15, 16, 17,
7259 	     * 18, 19, 20, 21, 22,
7260 	     * 23, 24, 25, 26, 27 */
7261 static int color_numbers_16[28] = {0, 1, 2, 3,
7262 				 4, 5, 6, 6,
7263 				 7, 7, 7, 7,
7264 				 8, 8,
7265 				 9, 9, 10, 10,
7266 				 11, 11, 12, 12, 13,
7267 				 13, 14, 14, 15, -1};
7268 /* for xterm with 88 colors... */
7269 static int color_numbers_88[28] = {0, 4, 2, 6,
7270 				 1, 5, 32, 72,
7271 				 84, 84, 7, 7,
7272 				 82, 82,
7273 				 12, 43, 10, 61,
7274 				 14, 63, 9, 74, 13,
7275 				 75, 11, 78, 15, -1};
7276 /* for xterm with 256 colors... */
7277 static int color_numbers_256[28] = {0, 4, 2, 6,
7278 				 1, 5, 130, 130,
7279 				 248, 248, 7, 7,
7280 				 242, 242,
7281 				 12, 81, 10, 121,
7282 				 14, 159, 9, 224, 13,
7283 				 225, 11, 229, 15, -1};
7284 /* for terminals with less than 16 colors... */
7285 static int color_numbers_8[28] = {0, 4, 2, 6,
7286 				 1, 5, 3, 3,
7287 				 7, 7, 7, 7,
7288 				 0+8, 0+8,
7289 				 4+8, 4+8, 2+8, 2+8,
7290 				 6+8, 6+8, 1+8, 1+8, 5+8,
7291 				 5+8, 3+8, 3+8, 7+8, -1};
7292 
7293 /*
7294  * Lookup the "cterm" value to be used for color with index "idx" in
7295  * color_names[].
7296  * "boldp" will be set to TRUE or FALSE for a foreground color when using 8
7297  * colors, otherwise it will be unchanged.
7298  */
7299     int
7300 lookup_color(int idx, int foreground, int *boldp)
7301 {
7302     int		color = color_numbers_16[idx];
7303     char_u	*p;
7304 
7305     /* Use the _16 table to check if it's a valid color name. */
7306     if (color < 0)
7307 	return -1;
7308 
7309     if (t_colors == 8)
7310     {
7311 	/* t_Co is 8: use the 8 colors table */
7312 #if defined(__QNXNTO__)
7313 	color = color_numbers_8_qansi[idx];
7314 #else
7315 	color = color_numbers_8[idx];
7316 #endif
7317 	if (foreground)
7318 	{
7319 	    /* set/reset bold attribute to get light foreground
7320 	     * colors (on some terminals, e.g. "linux") */
7321 	    if (color & 8)
7322 		*boldp = TRUE;
7323 	    else
7324 		*boldp = FALSE;
7325 	}
7326 	color &= 7;	/* truncate to 8 colors */
7327     }
7328     else if (t_colors == 16 || t_colors == 88
7329 					   || t_colors >= 256)
7330     {
7331 	/*
7332 	 * Guess: if the termcap entry ends in 'm', it is
7333 	 * probably an xterm-like terminal.  Use the changed
7334 	 * order for colors.
7335 	 */
7336 	if (*T_CAF != NUL)
7337 	    p = T_CAF;
7338 	else
7339 	    p = T_CSF;
7340 	if (*p != NUL && (t_colors > 256
7341 			      || *(p + STRLEN(p) - 1) == 'm'))
7342 	{
7343 	    if (t_colors == 88)
7344 		color = color_numbers_88[idx];
7345 	    else if (t_colors >= 256)
7346 		color = color_numbers_256[idx];
7347 	    else
7348 		color = color_numbers_8[idx];
7349 	}
7350 #ifdef FEAT_TERMRESPONSE
7351 	if (t_colors >= 256 && color == 15 && is_mac_terminal)
7352 	    /* Terminal.app has a bug: 15 is light grey. Use white
7353 	     * from the color cube instead. */
7354 	    color = 231;
7355 #endif
7356     }
7357     return color;
7358 }
7359 
7360 /*
7361  * Handle the ":highlight .." command.
7362  * When using ":hi clear" this is called recursively for each group with
7363  * "forceit" and "init" both TRUE.
7364  */
7365     void
7366 do_highlight(
7367     char_u	*line,
7368     int		forceit,
7369     int		init)	    /* TRUE when called for initializing */
7370 {
7371     char_u	*name_end;
7372     char_u	*p;
7373     char_u	*linep;
7374     char_u	*key_start;
7375     char_u	*arg_start;
7376     char_u	*key = NULL, *arg = NULL;
7377     long	i;
7378     int		off;
7379     int		len;
7380     int		attr;
7381     int		id;
7382     int		idx;
7383     struct hl_group item_before;
7384     int		did_change = FALSE;
7385     int		dodefault = FALSE;
7386     int		doclear = FALSE;
7387     int		dolink = FALSE;
7388     int		error = FALSE;
7389     int		color;
7390     int		is_normal_group = FALSE;	/* "Normal" group */
7391 #ifdef FEAT_TERMINAL
7392     int		is_terminal_group = FALSE;	/* "Terminal" group */
7393 #endif
7394 #ifdef FEAT_GUI_X11
7395     int		is_menu_group = FALSE;		/* "Menu" group */
7396     int		is_scrollbar_group = FALSE;	/* "Scrollbar" group */
7397     int		is_tooltip_group = FALSE;	/* "Tooltip" group */
7398     int		do_colors = FALSE;		/* need to update colors? */
7399 #else
7400 # define is_menu_group 0
7401 # define is_tooltip_group 0
7402 #endif
7403 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
7404     int		did_highlight_changed = FALSE;
7405 #endif
7406 
7407     /*
7408      * If no argument, list current highlighting.
7409      */
7410     if (ends_excmd(*line))
7411     {
7412 	for (i = 1; i <= highlight_ga.ga_len && !got_int; ++i)
7413 	    /* TODO: only call when the group has attributes set */
7414 	    highlight_list_one((int)i);
7415 	return;
7416     }
7417 
7418     /*
7419      * Isolate the name.
7420      */
7421     name_end = skiptowhite(line);
7422     linep = skipwhite(name_end);
7423 
7424     /*
7425      * Check for "default" argument.
7426      */
7427     if (STRNCMP(line, "default", name_end - line) == 0)
7428     {
7429 	dodefault = TRUE;
7430 	line = linep;
7431 	name_end = skiptowhite(line);
7432 	linep = skipwhite(name_end);
7433     }
7434 
7435     /*
7436      * Check for "clear" or "link" argument.
7437      */
7438     if (STRNCMP(line, "clear", name_end - line) == 0)
7439 	doclear = TRUE;
7440     if (STRNCMP(line, "link", name_end - line) == 0)
7441 	dolink = TRUE;
7442 
7443     /*
7444      * ":highlight {group-name}": list highlighting for one group.
7445      */
7446     if (!doclear && !dolink && ends_excmd(*linep))
7447     {
7448 	id = syn_namen2id(line, (int)(name_end - line));
7449 	if (id == 0)
7450 	    EMSG2(_("E411: highlight group not found: %s"), line);
7451 	else
7452 	    highlight_list_one(id);
7453 	return;
7454     }
7455 
7456     /*
7457      * Handle ":highlight link {from} {to}" command.
7458      */
7459     if (dolink)
7460     {
7461 	char_u	    *from_start = linep;
7462 	char_u	    *from_end;
7463 	char_u	    *to_start;
7464 	char_u	    *to_end;
7465 	int	    from_id;
7466 	int	    to_id;
7467 
7468 	from_end = skiptowhite(from_start);
7469 	to_start = skipwhite(from_end);
7470 	to_end	 = skiptowhite(to_start);
7471 
7472 	if (ends_excmd(*from_start) || ends_excmd(*to_start))
7473 	{
7474 	    EMSG2(_("E412: Not enough arguments: \":highlight link %s\""),
7475 								  from_start);
7476 	    return;
7477 	}
7478 
7479 	if (!ends_excmd(*skipwhite(to_end)))
7480 	{
7481 	    EMSG2(_("E413: Too many arguments: \":highlight link %s\""), from_start);
7482 	    return;
7483 	}
7484 
7485 	from_id = syn_check_group(from_start, (int)(from_end - from_start));
7486 	if (STRNCMP(to_start, "NONE", 4) == 0)
7487 	    to_id = 0;
7488 	else
7489 	    to_id = syn_check_group(to_start, (int)(to_end - to_start));
7490 
7491 	if (from_id > 0 && (!init || HL_TABLE()[from_id - 1].sg_set == 0))
7492 	{
7493 	    /*
7494 	     * Don't allow a link when there already is some highlighting
7495 	     * for the group, unless '!' is used
7496 	     */
7497 	    if (to_id > 0 && !forceit && !init
7498 				   && hl_has_settings(from_id - 1, dodefault))
7499 	    {
7500 		if (sourcing_name == NULL && !dodefault)
7501 		    EMSG(_("E414: group has settings, highlight link ignored"));
7502 	    }
7503 	    else if (HL_TABLE()[from_id - 1].sg_link != to_id
7504 #ifdef FEAT_EVAL
7505 		    || HL_TABLE()[from_id - 1].sg_scriptID != current_SID
7506 #endif
7507 		    || HL_TABLE()[from_id - 1].sg_cleared)
7508 	    {
7509 		if (!init)
7510 		    HL_TABLE()[from_id - 1].sg_set |= SG_LINK;
7511 		HL_TABLE()[from_id - 1].sg_link = to_id;
7512 #ifdef FEAT_EVAL
7513 		HL_TABLE()[from_id - 1].sg_scriptID = current_SID;
7514 #endif
7515 		HL_TABLE()[from_id - 1].sg_cleared = FALSE;
7516 		redraw_all_later(SOME_VALID);
7517 
7518 		/* Only call highlight_changed() once after multiple changes. */
7519 		need_highlight_changed = TRUE;
7520 	    }
7521 	}
7522 
7523 	return;
7524     }
7525 
7526     if (doclear)
7527     {
7528 	/*
7529 	 * ":highlight clear [group]" command.
7530 	 */
7531 	line = linep;
7532 	if (ends_excmd(*line))
7533 	{
7534 #ifdef FEAT_GUI
7535 	    /* First, we do not destroy the old values, but allocate the new
7536 	     * ones and update the display. THEN we destroy the old values.
7537 	     * If we destroy the old values first, then the old values
7538 	     * (such as GuiFont's or GuiFontset's) will still be displayed but
7539 	     * invalid because they were free'd.
7540 	     */
7541 	    if (gui.in_use)
7542 	    {
7543 # ifdef FEAT_BEVAL_TIP
7544 		gui_init_tooltip_font();
7545 # endif
7546 # if defined(FEAT_MENU) && (defined(FEAT_GUI_ATHENA) || defined(FEAT_GUI_MOTIF))
7547 		gui_init_menu_font();
7548 # endif
7549 	    }
7550 # if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_X11)
7551 	    gui_mch_def_colors();
7552 # endif
7553 # ifdef FEAT_GUI_X11
7554 #  ifdef FEAT_MENU
7555 
7556 	    /* This only needs to be done when there is no Menu highlight
7557 	     * group defined by default, which IS currently the case.
7558 	     */
7559 	    gui_mch_new_menu_colors();
7560 #  endif
7561 	    if (gui.in_use)
7562 	    {
7563 		gui_new_scrollbar_colors();
7564 #  ifdef FEAT_BEVAL_GUI
7565 		gui_mch_new_tooltip_colors();
7566 #  endif
7567 #  ifdef FEAT_MENU
7568 		gui_mch_new_menu_font();
7569 #  endif
7570 	    }
7571 # endif
7572 
7573 	    /* Ok, we're done allocating the new default graphics items.
7574 	     * The screen should already be refreshed at this point.
7575 	     * It is now Ok to clear out the old data.
7576 	     */
7577 #endif
7578 #ifdef FEAT_EVAL
7579 	    do_unlet((char_u *)"colors_name", TRUE);
7580 #endif
7581 	    restore_cterm_colors();
7582 
7583 	    /*
7584 	     * Clear all default highlight groups and load the defaults.
7585 	     */
7586 	    for (idx = 0; idx < highlight_ga.ga_len; ++idx)
7587 		highlight_clear(idx);
7588 	    init_highlight(TRUE, TRUE);
7589 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
7590 	    if (USE_24BIT)
7591 		highlight_gui_started();
7592 	    else
7593 #endif
7594 		highlight_changed();
7595 	    redraw_later_clear();
7596 	    return;
7597 	}
7598 	name_end = skiptowhite(line);
7599 	linep = skipwhite(name_end);
7600     }
7601 
7602     /*
7603      * Find the group name in the table.  If it does not exist yet, add it.
7604      */
7605     id = syn_check_group(line, (int)(name_end - line));
7606     if (id == 0)			/* failed (out of memory) */
7607 	return;
7608     idx = id - 1;			/* index is ID minus one */
7609 
7610     /* Return if "default" was used and the group already has settings. */
7611     if (dodefault && hl_has_settings(idx, TRUE))
7612 	return;
7613 
7614     /* Make a copy so we can check if any attribute actually changed. */
7615     item_before = HL_TABLE()[idx];
7616 
7617     if (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0)
7618 	is_normal_group = TRUE;
7619 #ifdef FEAT_TERMINAL
7620     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "TERMINAL") == 0)
7621 	is_terminal_group = TRUE;
7622 #endif
7623 #ifdef FEAT_GUI_X11
7624     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "MENU") == 0)
7625 	is_menu_group = TRUE;
7626     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "SCROLLBAR") == 0)
7627 	is_scrollbar_group = TRUE;
7628     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "TOOLTIP") == 0)
7629 	is_tooltip_group = TRUE;
7630 #endif
7631 
7632     /* Clear the highlighting for ":hi clear {group}" and ":hi clear". */
7633     if (doclear || (forceit && init))
7634     {
7635 	highlight_clear(idx);
7636 	if (!doclear)
7637 	    HL_TABLE()[idx].sg_set = 0;
7638     }
7639 
7640     if (!doclear)
7641       while (!ends_excmd(*linep))
7642       {
7643 	key_start = linep;
7644 	if (*linep == '=')
7645 	{
7646 	    EMSG2(_("E415: unexpected equal sign: %s"), key_start);
7647 	    error = TRUE;
7648 	    break;
7649 	}
7650 
7651 	/*
7652 	 * Isolate the key ("term", "ctermfg", "ctermbg", "font", "guifg" or
7653 	 * "guibg").
7654 	 */
7655 	while (*linep && !VIM_ISWHITE(*linep) && *linep != '=')
7656 	    ++linep;
7657 	vim_free(key);
7658 	key = vim_strnsave_up(key_start, (int)(linep - key_start));
7659 	if (key == NULL)
7660 	{
7661 	    error = TRUE;
7662 	    break;
7663 	}
7664 	linep = skipwhite(linep);
7665 
7666 	if (STRCMP(key, "NONE") == 0)
7667 	{
7668 	    if (!init || HL_TABLE()[idx].sg_set == 0)
7669 	    {
7670 		if (!init)
7671 		    HL_TABLE()[idx].sg_set |= SG_TERM+SG_CTERM+SG_GUI;
7672 		highlight_clear(idx);
7673 	    }
7674 	    continue;
7675 	}
7676 
7677 	/*
7678 	 * Check for the equal sign.
7679 	 */
7680 	if (*linep != '=')
7681 	{
7682 	    EMSG2(_("E416: missing equal sign: %s"), key_start);
7683 	    error = TRUE;
7684 	    break;
7685 	}
7686 	++linep;
7687 
7688 	/*
7689 	 * Isolate the argument.
7690 	 */
7691 	linep = skipwhite(linep);
7692 	if (*linep == '\'')		/* guifg='color name' */
7693 	{
7694 	    arg_start = ++linep;
7695 	    linep = vim_strchr(linep, '\'');
7696 	    if (linep == NULL)
7697 	    {
7698 		EMSG2(_(e_invarg2), key_start);
7699 		error = TRUE;
7700 		break;
7701 	    }
7702 	}
7703 	else
7704 	{
7705 	    arg_start = linep;
7706 	    linep = skiptowhite(linep);
7707 	}
7708 	if (linep == arg_start)
7709 	{
7710 	    EMSG2(_("E417: missing argument: %s"), key_start);
7711 	    error = TRUE;
7712 	    break;
7713 	}
7714 	vim_free(arg);
7715 	arg = vim_strnsave(arg_start, (int)(linep - arg_start));
7716 	if (arg == NULL)
7717 	{
7718 	    error = TRUE;
7719 	    break;
7720 	}
7721 	if (*linep == '\'')
7722 	    ++linep;
7723 
7724 	/*
7725 	 * Store the argument.
7726 	 */
7727 	if (  STRCMP(key, "TERM") == 0
7728 		|| STRCMP(key, "CTERM") == 0
7729 		|| STRCMP(key, "GUI") == 0)
7730 	{
7731 	    attr = 0;
7732 	    off = 0;
7733 	    while (arg[off] != NUL)
7734 	    {
7735 		for (i = sizeof(hl_attr_table) / sizeof(int); --i >= 0; )
7736 		{
7737 		    len = (int)STRLEN(hl_name_table[i]);
7738 		    if (STRNICMP(arg + off, hl_name_table[i], len) == 0)
7739 		    {
7740 			attr |= hl_attr_table[i];
7741 			off += len;
7742 			break;
7743 		    }
7744 		}
7745 		if (i < 0)
7746 		{
7747 		    EMSG2(_("E418: Illegal value: %s"), arg);
7748 		    error = TRUE;
7749 		    break;
7750 		}
7751 		if (arg[off] == ',')		/* another one follows */
7752 		    ++off;
7753 	    }
7754 	    if (error)
7755 		break;
7756 	    if (*key == 'T')
7757 	    {
7758 		if (!init || !(HL_TABLE()[idx].sg_set & SG_TERM))
7759 		{
7760 		    if (!init)
7761 			HL_TABLE()[idx].sg_set |= SG_TERM;
7762 		    HL_TABLE()[idx].sg_term = attr;
7763 		}
7764 	    }
7765 	    else if (*key == 'C')
7766 	    {
7767 		if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM))
7768 		{
7769 		    if (!init)
7770 			HL_TABLE()[idx].sg_set |= SG_CTERM;
7771 		    HL_TABLE()[idx].sg_cterm = attr;
7772 		    HL_TABLE()[idx].sg_cterm_bold = FALSE;
7773 		}
7774 	    }
7775 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
7776 	    else
7777 	    {
7778 		if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
7779 		{
7780 		    if (!init)
7781 			HL_TABLE()[idx].sg_set |= SG_GUI;
7782 		    HL_TABLE()[idx].sg_gui = attr;
7783 		}
7784 	    }
7785 #endif
7786 	}
7787 	else if (STRCMP(key, "FONT") == 0)
7788 	{
7789 	    /* in non-GUI fonts are simply ignored */
7790 #ifdef FEAT_GUI
7791 	    if (HL_TABLE()[idx].sg_font_name != NULL
7792 			     && STRCMP(HL_TABLE()[idx].sg_font_name, arg) == 0)
7793 	    {
7794 		/* Font name didn't change, ignore. */
7795 	    }
7796 	    else if (!gui.shell_created)
7797 	    {
7798 		/* GUI not started yet, always accept the name. */
7799 		vim_free(HL_TABLE()[idx].sg_font_name);
7800 		HL_TABLE()[idx].sg_font_name = vim_strsave(arg);
7801 		did_change = TRUE;
7802 	    }
7803 	    else
7804 	    {
7805 		GuiFont temp_sg_font = HL_TABLE()[idx].sg_font;
7806 # ifdef FEAT_XFONTSET
7807 		GuiFontset temp_sg_fontset = HL_TABLE()[idx].sg_fontset;
7808 # endif
7809 		/* First, save the current font/fontset.
7810 		 * Then try to allocate the font/fontset.
7811 		 * If the allocation fails, HL_TABLE()[idx].sg_font OR
7812 		 * sg_fontset will be set to NOFONT or NOFONTSET respectively.
7813 		 */
7814 
7815 		HL_TABLE()[idx].sg_font = NOFONT;
7816 # ifdef FEAT_XFONTSET
7817 		HL_TABLE()[idx].sg_fontset = NOFONTSET;
7818 # endif
7819 		hl_do_font(idx, arg, is_normal_group, is_menu_group,
7820 						     is_tooltip_group, FALSE);
7821 
7822 # ifdef FEAT_XFONTSET
7823 		if (HL_TABLE()[idx].sg_fontset != NOFONTSET)
7824 		{
7825 		    /* New fontset was accepted. Free the old one, if there
7826 		     * was one. */
7827 		    gui_mch_free_fontset(temp_sg_fontset);
7828 		    vim_free(HL_TABLE()[idx].sg_font_name);
7829 		    HL_TABLE()[idx].sg_font_name = vim_strsave(arg);
7830 		    did_change = TRUE;
7831 		}
7832 		else
7833 		    HL_TABLE()[idx].sg_fontset = temp_sg_fontset;
7834 # endif
7835 		if (HL_TABLE()[idx].sg_font != NOFONT)
7836 		{
7837 		    /* New font was accepted. Free the old one, if there was
7838 		     * one. */
7839 		    gui_mch_free_font(temp_sg_font);
7840 		    vim_free(HL_TABLE()[idx].sg_font_name);
7841 		    HL_TABLE()[idx].sg_font_name = vim_strsave(arg);
7842 		    did_change = TRUE;
7843 		}
7844 		else
7845 		    HL_TABLE()[idx].sg_font = temp_sg_font;
7846 	    }
7847 #endif
7848 	}
7849 	else if (STRCMP(key, "CTERMFG") == 0 || STRCMP(key, "CTERMBG") == 0)
7850 	{
7851 	  if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM))
7852 	  {
7853 	    if (!init)
7854 		HL_TABLE()[idx].sg_set |= SG_CTERM;
7855 
7856 	    /* When setting the foreground color, and previously the "bold"
7857 	     * flag was set for a light color, reset it now */
7858 	    if (key[5] == 'F' && HL_TABLE()[idx].sg_cterm_bold)
7859 	    {
7860 		HL_TABLE()[idx].sg_cterm &= ~HL_BOLD;
7861 		HL_TABLE()[idx].sg_cterm_bold = FALSE;
7862 	    }
7863 
7864 	    if (VIM_ISDIGIT(*arg))
7865 		color = atoi((char *)arg);
7866 	    else if (STRICMP(arg, "fg") == 0)
7867 	    {
7868 		if (cterm_normal_fg_color)
7869 		    color = cterm_normal_fg_color - 1;
7870 		else
7871 		{
7872 		    EMSG(_("E419: FG color unknown"));
7873 		    error = TRUE;
7874 		    break;
7875 		}
7876 	    }
7877 	    else if (STRICMP(arg, "bg") == 0)
7878 	    {
7879 		if (cterm_normal_bg_color > 0)
7880 		    color = cterm_normal_bg_color - 1;
7881 		else
7882 		{
7883 		    EMSG(_("E420: BG color unknown"));
7884 		    error = TRUE;
7885 		    break;
7886 		}
7887 	    }
7888 	    else
7889 	    {
7890 		int bold = MAYBE;
7891 
7892 #if defined(__QNXNTO__)
7893 		static int *color_numbers_8_qansi = color_numbers_8;
7894 		/* On qnx, the 8 & 16 color arrays are the same */
7895 		if (STRNCMP(T_NAME, "qansi", 5) == 0)
7896 		    color_numbers_8_qansi = color_numbers_16;
7897 #endif
7898 
7899 		/* reduce calls to STRICMP a bit, it can be slow */
7900 		off = TOUPPER_ASC(*arg);
7901 		for (i = (sizeof(color_names) / sizeof(char *)); --i >= 0; )
7902 		    if (off == color_names[i][0]
7903 				 && STRICMP(arg + 1, color_names[i] + 1) == 0)
7904 			break;
7905 		if (i < 0)
7906 		{
7907 		    EMSG2(_("E421: Color name or number not recognized: %s"), key_start);
7908 		    error = TRUE;
7909 		    break;
7910 		}
7911 
7912 		color = lookup_color(i, key[5] == 'F', &bold);
7913 
7914 		/* set/reset bold attribute to get light foreground
7915 		 * colors (on some terminals, e.g. "linux") */
7916 		if (bold == TRUE)
7917 		{
7918 		    HL_TABLE()[idx].sg_cterm |= HL_BOLD;
7919 		    HL_TABLE()[idx].sg_cterm_bold = TRUE;
7920 		}
7921 		else if (bold == FALSE)
7922 		    HL_TABLE()[idx].sg_cterm &= ~HL_BOLD;
7923 	    }
7924 
7925 	    /* Add one to the argument, to avoid zero.  Zero is used for
7926 	     * "NONE", then "color" is -1. */
7927 	    if (key[5] == 'F')
7928 	    {
7929 		HL_TABLE()[idx].sg_cterm_fg = color + 1;
7930 		if (is_normal_group)
7931 		{
7932 		    cterm_normal_fg_color = color + 1;
7933 		    cterm_normal_fg_bold = (HL_TABLE()[idx].sg_cterm & HL_BOLD);
7934 #ifdef FEAT_GUI
7935 		    /* Don't do this if the GUI is used. */
7936 		    if (!gui.in_use && !gui.starting)
7937 #endif
7938 		    {
7939 			must_redraw = CLEAR;
7940 			if (termcap_active && color >= 0)
7941 			    term_fg_color(color);
7942 		    }
7943 		}
7944 	    }
7945 	    else
7946 	    {
7947 		HL_TABLE()[idx].sg_cterm_bg = color + 1;
7948 		if (is_normal_group)
7949 		{
7950 		    cterm_normal_bg_color = color + 1;
7951 #ifdef FEAT_GUI
7952 		    /* Don't mess with 'background' if the GUI is used. */
7953 		    if (!gui.in_use && !gui.starting)
7954 #endif
7955 		    {
7956 			must_redraw = CLEAR;
7957 			if (color >= 0)
7958 			{
7959 			    int dark = -1;
7960 
7961 			    if (termcap_active)
7962 				term_bg_color(color);
7963 			    if (t_colors < 16)
7964 				dark = (color == 0 || color == 4);
7965 			    /* Limit the heuristic to the standard 16 colors */
7966 			    else if (color < 16)
7967 				dark = (color < 7 || color == 8);
7968 			    /* Set the 'background' option if the value is
7969 			     * wrong. */
7970 			    if (dark != -1
7971 				    && dark != (*p_bg == 'd')
7972 				    && !option_was_set((char_u *)"bg"))
7973 			    {
7974 				set_option_value((char_u *)"bg", 0L,
7975 				       (char_u *)(dark ? "dark" : "light"), 0);
7976 				reset_option_was_set((char_u *)"bg");
7977 			    }
7978 			}
7979 		    }
7980 		}
7981 	    }
7982 	  }
7983 	}
7984 	else if (STRCMP(key, "GUIFG") == 0)
7985 	{
7986 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
7987 	    char_u **namep = &HL_TABLE()[idx].sg_gui_fg_name;
7988 
7989 	    if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
7990 	    {
7991 		if (!init)
7992 		    HL_TABLE()[idx].sg_set |= SG_GUI;
7993 
7994 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
7995 		/* In GUI guifg colors are only used when recognized */
7996 		i = color_name2handle(arg);
7997 		if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !USE_24BIT)
7998 		{
7999 		    HL_TABLE()[idx].sg_gui_fg = i;
8000 # endif
8001 		    if (*namep == NULL || STRCMP(*namep, arg) != 0)
8002 		    {
8003 			vim_free(*namep);
8004 			if (STRCMP(arg, "NONE") != 0)
8005 			    *namep = vim_strsave(arg);
8006 			else
8007 			    *namep = NULL;
8008 			did_change = TRUE;
8009 		    }
8010 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8011 #  ifdef FEAT_GUI_X11
8012 		    if (is_menu_group && gui.menu_fg_pixel != i)
8013 		    {
8014 			gui.menu_fg_pixel = i;
8015 			do_colors = TRUE;
8016 		    }
8017 		    if (is_scrollbar_group && gui.scroll_fg_pixel != i)
8018 		    {
8019 			gui.scroll_fg_pixel = i;
8020 			do_colors = TRUE;
8021 		    }
8022 #   ifdef FEAT_BEVAL_GUI
8023 		    if (is_tooltip_group && gui.tooltip_fg_pixel != i)
8024 		    {
8025 			gui.tooltip_fg_pixel = i;
8026 			do_colors = TRUE;
8027 		    }
8028 #   endif
8029 #  endif
8030 		}
8031 # endif
8032 	    }
8033 #endif
8034 	}
8035 	else if (STRCMP(key, "GUIBG") == 0)
8036 	{
8037 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
8038 	    char_u **namep = &HL_TABLE()[idx].sg_gui_bg_name;
8039 
8040 	    if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
8041 	    {
8042 		if (!init)
8043 		    HL_TABLE()[idx].sg_set |= SG_GUI;
8044 
8045 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8046 		/* In GUI guifg colors are only used when recognized */
8047 		i = color_name2handle(arg);
8048 		if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !USE_24BIT)
8049 		{
8050 		    HL_TABLE()[idx].sg_gui_bg = i;
8051 # endif
8052 		    if (*namep == NULL || STRCMP(*namep, arg) != 0)
8053 		    {
8054 			vim_free(*namep);
8055 			if (STRCMP(arg, "NONE") != 0)
8056 			    *namep = vim_strsave(arg);
8057 			else
8058 			    *namep = NULL;
8059 			did_change = TRUE;
8060 		    }
8061 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8062 #  ifdef FEAT_GUI_X11
8063 		    if (is_menu_group && gui.menu_bg_pixel != i)
8064 		    {
8065 			gui.menu_bg_pixel = i;
8066 			do_colors = TRUE;
8067 		    }
8068 		    if (is_scrollbar_group && gui.scroll_bg_pixel != i)
8069 		    {
8070 			gui.scroll_bg_pixel = i;
8071 			do_colors = TRUE;
8072 		    }
8073 #   ifdef FEAT_BEVAL_GUI
8074 		    if (is_tooltip_group && gui.tooltip_bg_pixel != i)
8075 		    {
8076 			gui.tooltip_bg_pixel = i;
8077 			do_colors = TRUE;
8078 		    }
8079 #   endif
8080 #  endif
8081 		}
8082 # endif
8083 	    }
8084 #endif
8085 	}
8086 	else if (STRCMP(key, "GUISP") == 0)
8087 	{
8088 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
8089 	    char_u **namep = &HL_TABLE()[idx].sg_gui_sp_name;
8090 
8091 	    if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
8092 	    {
8093 		if (!init)
8094 		    HL_TABLE()[idx].sg_set |= SG_GUI;
8095 
8096 # ifdef FEAT_GUI
8097 		i = color_name2handle(arg);
8098 		if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !gui.in_use)
8099 		{
8100 		    HL_TABLE()[idx].sg_gui_sp = i;
8101 # endif
8102 		    if (*namep == NULL || STRCMP(*namep, arg) != 0)
8103 		    {
8104 			vim_free(*namep);
8105 			if (STRCMP(arg, "NONE") != 0)
8106 			    *namep = vim_strsave(arg);
8107 			else
8108 			    *namep = NULL;
8109 			did_change = TRUE;
8110 		    }
8111 # ifdef FEAT_GUI
8112 		}
8113 # endif
8114 	    }
8115 #endif
8116 	}
8117 	else if (STRCMP(key, "START") == 0 || STRCMP(key, "STOP") == 0)
8118 	{
8119 	    char_u	buf[100];
8120 	    char_u	*tname;
8121 
8122 	    if (!init)
8123 		HL_TABLE()[idx].sg_set |= SG_TERM;
8124 
8125 	    /*
8126 	     * The "start" and "stop"  arguments can be a literal escape
8127 	     * sequence, or a comma separated list of terminal codes.
8128 	     */
8129 	    if (STRNCMP(arg, "t_", 2) == 0)
8130 	    {
8131 		off = 0;
8132 		buf[0] = 0;
8133 		while (arg[off] != NUL)
8134 		{
8135 		    /* Isolate one termcap name */
8136 		    for (len = 0; arg[off + len] &&
8137 						 arg[off + len] != ','; ++len)
8138 			;
8139 		    tname = vim_strnsave(arg + off, len);
8140 		    if (tname == NULL)		/* out of memory */
8141 		    {
8142 			error = TRUE;
8143 			break;
8144 		    }
8145 		    /* lookup the escape sequence for the item */
8146 		    p = get_term_code(tname);
8147 		    vim_free(tname);
8148 		    if (p == NULL)	    /* ignore non-existing things */
8149 			p = (char_u *)"";
8150 
8151 		    /* Append it to the already found stuff */
8152 		    if ((int)(STRLEN(buf) + STRLEN(p)) >= 99)
8153 		    {
8154 			EMSG2(_("E422: terminal code too long: %s"), arg);
8155 			error = TRUE;
8156 			break;
8157 		    }
8158 		    STRCAT(buf, p);
8159 
8160 		    /* Advance to the next item */
8161 		    off += len;
8162 		    if (arg[off] == ',')	    /* another one follows */
8163 			++off;
8164 		}
8165 	    }
8166 	    else
8167 	    {
8168 		/*
8169 		 * Copy characters from arg[] to buf[], translating <> codes.
8170 		 */
8171 		for (p = arg, off = 0; off < 100 - 6 && *p; )
8172 		{
8173 		    len = trans_special(&p, buf + off, FALSE, FALSE);
8174 		    if (len > 0)	    /* recognized special char */
8175 			off += len;
8176 		    else		    /* copy as normal char */
8177 			buf[off++] = *p++;
8178 		}
8179 		buf[off] = NUL;
8180 	    }
8181 	    if (error)
8182 		break;
8183 
8184 	    if (STRCMP(buf, "NONE") == 0)	/* resetting the value */
8185 		p = NULL;
8186 	    else
8187 		p = vim_strsave(buf);
8188 	    if (key[2] == 'A')
8189 	    {
8190 		vim_free(HL_TABLE()[idx].sg_start);
8191 		HL_TABLE()[idx].sg_start = p;
8192 	    }
8193 	    else
8194 	    {
8195 		vim_free(HL_TABLE()[idx].sg_stop);
8196 		HL_TABLE()[idx].sg_stop = p;
8197 	    }
8198 	}
8199 	else
8200 	{
8201 	    EMSG2(_("E423: Illegal argument: %s"), key_start);
8202 	    error = TRUE;
8203 	    break;
8204 	}
8205 	HL_TABLE()[idx].sg_cleared = FALSE;
8206 
8207 	/*
8208 	 * When highlighting has been given for a group, don't link it.
8209 	 */
8210 	if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK))
8211 	    HL_TABLE()[idx].sg_link = 0;
8212 
8213 	/*
8214 	 * Continue with next argument.
8215 	 */
8216 	linep = skipwhite(linep);
8217       }
8218 
8219     /*
8220      * If there is an error, and it's a new entry, remove it from the table.
8221      */
8222     if (error && idx == highlight_ga.ga_len)
8223 	syn_unadd_group();
8224     else
8225     {
8226 	if (is_normal_group)
8227 	{
8228 	    HL_TABLE()[idx].sg_term_attr = 0;
8229 	    HL_TABLE()[idx].sg_cterm_attr = 0;
8230 #ifdef FEAT_GUI
8231 	    HL_TABLE()[idx].sg_gui_attr = 0;
8232 	    /*
8233 	     * Need to update all groups, because they might be using "bg"
8234 	     * and/or "fg", which have been changed now.
8235 	     */
8236 #endif
8237 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8238 	    if (USE_24BIT)
8239 	    {
8240 		highlight_gui_started();
8241 		did_highlight_changed = TRUE;
8242 		redraw_all_later(NOT_VALID);
8243 	    }
8244 #endif
8245 	}
8246 #ifdef FEAT_TERMINAL
8247 	else if (is_terminal_group)
8248 	    set_terminal_default_colors(
8249 		    HL_TABLE()[idx].sg_cterm_fg, HL_TABLE()[idx].sg_cterm_bg);
8250 #endif
8251 #ifdef FEAT_GUI_X11
8252 # ifdef FEAT_MENU
8253 	else if (is_menu_group)
8254 	{
8255 	    if (gui.in_use && do_colors)
8256 		gui_mch_new_menu_colors();
8257 	}
8258 # endif
8259 	else if (is_scrollbar_group)
8260 	{
8261 	    if (gui.in_use && do_colors)
8262 		gui_new_scrollbar_colors();
8263 	}
8264 # ifdef FEAT_BEVAL_GUI
8265 	else if (is_tooltip_group)
8266 	{
8267 	    if (gui.in_use && do_colors)
8268 		gui_mch_new_tooltip_colors();
8269 	}
8270 # endif
8271 #endif
8272 	else
8273 	    set_hl_attr(idx);
8274 #ifdef FEAT_EVAL
8275 	HL_TABLE()[idx].sg_scriptID = current_SID;
8276 #endif
8277     }
8278 
8279     vim_free(key);
8280     vim_free(arg);
8281 
8282     /* Only call highlight_changed() once, after a sequence of highlight
8283      * commands, and only if an attribute actually changed. */
8284     if ((did_change
8285 	   || memcmp(&HL_TABLE()[idx], &item_before, sizeof(item_before)) != 0)
8286 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8287 	    && !did_highlight_changed
8288 #endif
8289        )
8290     {
8291 	/* Do not trigger a redraw when highlighting is changed while
8292 	 * redrawing.  This may happen when evaluating 'statusline' changes the
8293 	 * StatusLine group. */
8294 	if (!updating_screen)
8295 	    redraw_all_later(NOT_VALID);
8296 	need_highlight_changed = TRUE;
8297     }
8298 }
8299 
8300 #if defined(EXITFREE) || defined(PROTO)
8301     void
8302 free_highlight(void)
8303 {
8304     int	    i;
8305 
8306     for (i = 0; i < highlight_ga.ga_len; ++i)
8307     {
8308 	highlight_clear(i);
8309 	vim_free(HL_TABLE()[i].sg_name);
8310 	vim_free(HL_TABLE()[i].sg_name_u);
8311     }
8312     ga_clear(&highlight_ga);
8313 }
8314 #endif
8315 
8316 /*
8317  * Reset the cterm colors to what they were before Vim was started, if
8318  * possible.  Otherwise reset them to zero.
8319  */
8320     void
8321 restore_cterm_colors(void)
8322 {
8323 #if defined(WIN3264) && !defined(FEAT_GUI_W32)
8324     /* Since t_me has been set, this probably means that the user
8325      * wants to use this as default colors.  Need to reset default
8326      * background/foreground colors. */
8327     mch_set_normal_colors();
8328 #else
8329     cterm_normal_fg_color = 0;
8330     cterm_normal_fg_bold = 0;
8331     cterm_normal_bg_color = 0;
8332 # ifdef FEAT_TERMGUICOLORS
8333     cterm_normal_fg_gui_color = INVALCOLOR;
8334     cterm_normal_bg_gui_color = INVALCOLOR;
8335 # endif
8336 #endif
8337 }
8338 
8339 /*
8340  * Return TRUE if highlight group "idx" has any settings.
8341  * When "check_link" is TRUE also check for an existing link.
8342  */
8343     static int
8344 hl_has_settings(int idx, int check_link)
8345 {
8346     return (   HL_TABLE()[idx].sg_term_attr != 0
8347 	    || HL_TABLE()[idx].sg_cterm_attr != 0
8348 	    || HL_TABLE()[idx].sg_cterm_fg != 0
8349 	    || HL_TABLE()[idx].sg_cterm_bg != 0
8350 #ifdef FEAT_GUI
8351 	    || HL_TABLE()[idx].sg_gui_attr != 0
8352 	    || HL_TABLE()[idx].sg_gui_fg_name != NULL
8353 	    || HL_TABLE()[idx].sg_gui_bg_name != NULL
8354 	    || HL_TABLE()[idx].sg_gui_sp_name != NULL
8355 	    || HL_TABLE()[idx].sg_font_name != NULL
8356 #endif
8357 	    || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK)));
8358 }
8359 
8360 /*
8361  * Clear highlighting for one group.
8362  */
8363     static void
8364 highlight_clear(int idx)
8365 {
8366     HL_TABLE()[idx].sg_cleared = TRUE;
8367 
8368     HL_TABLE()[idx].sg_term = 0;
8369     VIM_CLEAR(HL_TABLE()[idx].sg_start);
8370     VIM_CLEAR(HL_TABLE()[idx].sg_stop);
8371     HL_TABLE()[idx].sg_term_attr = 0;
8372     HL_TABLE()[idx].sg_cterm = 0;
8373     HL_TABLE()[idx].sg_cterm_bold = FALSE;
8374     HL_TABLE()[idx].sg_cterm_fg = 0;
8375     HL_TABLE()[idx].sg_cterm_bg = 0;
8376     HL_TABLE()[idx].sg_cterm_attr = 0;
8377 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
8378     HL_TABLE()[idx].sg_gui = 0;
8379     VIM_CLEAR(HL_TABLE()[idx].sg_gui_fg_name);
8380     VIM_CLEAR(HL_TABLE()[idx].sg_gui_bg_name);
8381     VIM_CLEAR(HL_TABLE()[idx].sg_gui_sp_name);
8382 #endif
8383 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8384     HL_TABLE()[idx].sg_gui_fg = INVALCOLOR;
8385     HL_TABLE()[idx].sg_gui_bg = INVALCOLOR;
8386 #endif
8387 #ifdef FEAT_GUI
8388     HL_TABLE()[idx].sg_gui_sp = INVALCOLOR;
8389     gui_mch_free_font(HL_TABLE()[idx].sg_font);
8390     HL_TABLE()[idx].sg_font = NOFONT;
8391 # ifdef FEAT_XFONTSET
8392     gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
8393     HL_TABLE()[idx].sg_fontset = NOFONTSET;
8394 # endif
8395     VIM_CLEAR(HL_TABLE()[idx].sg_font_name);
8396     HL_TABLE()[idx].sg_gui_attr = 0;
8397 #endif
8398 #ifdef FEAT_EVAL
8399     /* Clear the script ID only when there is no link, since that is not
8400      * cleared. */
8401     if (HL_TABLE()[idx].sg_link == 0)
8402 	HL_TABLE()[idx].sg_scriptID = 0;
8403 #endif
8404 }
8405 
8406 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
8407 /*
8408  * Set the normal foreground and background colors according to the "Normal"
8409  * highlighting group.  For X11 also set "Menu", "Scrollbar", and
8410  * "Tooltip" colors.
8411  */
8412     void
8413 set_normal_colors(void)
8414 {
8415 # ifdef FEAT_GUI
8416 #  ifdef FEAT_TERMGUICOLORS
8417     if (gui.in_use)
8418 #  endif
8419     {
8420 	if (set_group_colors((char_u *)"Normal",
8421 				 &gui.norm_pixel, &gui.back_pixel,
8422 				 FALSE, TRUE, FALSE))
8423 	{
8424 	    gui_mch_new_colors();
8425 	    must_redraw = CLEAR;
8426 	}
8427 #  ifdef FEAT_GUI_X11
8428 	if (set_group_colors((char_u *)"Menu",
8429 			     &gui.menu_fg_pixel, &gui.menu_bg_pixel,
8430 			     TRUE, FALSE, FALSE))
8431 	{
8432 #   ifdef FEAT_MENU
8433 	    gui_mch_new_menu_colors();
8434 #   endif
8435 	    must_redraw = CLEAR;
8436 	}
8437 #   ifdef FEAT_BEVAL_GUI
8438 	if (set_group_colors((char_u *)"Tooltip",
8439 			     &gui.tooltip_fg_pixel, &gui.tooltip_bg_pixel,
8440 			     FALSE, FALSE, TRUE))
8441 	{
8442 #    ifdef FEAT_TOOLBAR
8443 	    gui_mch_new_tooltip_colors();
8444 #    endif
8445 	    must_redraw = CLEAR;
8446 	}
8447 #   endif
8448 	if (set_group_colors((char_u *)"Scrollbar",
8449 			&gui.scroll_fg_pixel, &gui.scroll_bg_pixel,
8450 			FALSE, FALSE, FALSE))
8451 	{
8452 	    gui_new_scrollbar_colors();
8453 	    must_redraw = CLEAR;
8454 	}
8455 #  endif
8456     }
8457 # endif
8458 # ifdef FEAT_TERMGUICOLORS
8459 #  ifdef FEAT_GUI
8460     else
8461 #  endif
8462     {
8463 	int		idx;
8464 
8465 	idx = syn_name2id((char_u *)"Normal") - 1;
8466 	if (idx >= 0)
8467 	{
8468 	    gui_do_one_color(idx, FALSE, FALSE);
8469 
8470 	    /* If the normal fg or bg color changed a complete redraw is
8471 	     * required. */
8472 	    if (cterm_normal_fg_gui_color != HL_TABLE()[idx].sg_gui_fg
8473 		    || cterm_normal_bg_gui_color != HL_TABLE()[idx].sg_gui_bg)
8474 	    {
8475 		/* if the GUI color is INVALCOLOR then we use the default cterm
8476 		 * color */
8477 		cterm_normal_fg_gui_color = HL_TABLE()[idx].sg_gui_fg;
8478 		cterm_normal_bg_gui_color = HL_TABLE()[idx].sg_gui_bg;
8479 		must_redraw = CLEAR;
8480 	    }
8481 	}
8482     }
8483 # endif
8484 }
8485 #endif
8486 
8487 #if defined(FEAT_GUI) || defined(PROTO)
8488 /*
8489  * Set the colors for "Normal", "Menu", "Tooltip" or "Scrollbar".
8490  */
8491     static int
8492 set_group_colors(
8493     char_u	*name,
8494     guicolor_T	*fgp,
8495     guicolor_T	*bgp,
8496     int		do_menu,
8497     int		use_norm,
8498     int		do_tooltip)
8499 {
8500     int		idx;
8501 
8502     idx = syn_name2id(name) - 1;
8503     if (idx >= 0)
8504     {
8505 	gui_do_one_color(idx, do_menu, do_tooltip);
8506 
8507 	if (HL_TABLE()[idx].sg_gui_fg != INVALCOLOR)
8508 	    *fgp = HL_TABLE()[idx].sg_gui_fg;
8509 	else if (use_norm)
8510 	    *fgp = gui.def_norm_pixel;
8511 	if (HL_TABLE()[idx].sg_gui_bg != INVALCOLOR)
8512 	    *bgp = HL_TABLE()[idx].sg_gui_bg;
8513 	else if (use_norm)
8514 	    *bgp = gui.def_back_pixel;
8515 	return TRUE;
8516     }
8517     return FALSE;
8518 }
8519 
8520 /*
8521  * Get the font of the "Normal" group.
8522  * Returns "" when it's not found or not set.
8523  */
8524     char_u *
8525 hl_get_font_name(void)
8526 {
8527     int		id;
8528     char_u	*s;
8529 
8530     id = syn_name2id((char_u *)"Normal");
8531     if (id > 0)
8532     {
8533 	s = HL_TABLE()[id - 1].sg_font_name;
8534 	if (s != NULL)
8535 	    return s;
8536     }
8537     return (char_u *)"";
8538 }
8539 
8540 /*
8541  * Set font for "Normal" group.  Called by gui_mch_init_font() when a font has
8542  * actually chosen to be used.
8543  */
8544     void
8545 hl_set_font_name(char_u *font_name)
8546 {
8547     int	    id;
8548 
8549     id = syn_name2id((char_u *)"Normal");
8550     if (id > 0)
8551     {
8552 	vim_free(HL_TABLE()[id - 1].sg_font_name);
8553 	HL_TABLE()[id - 1].sg_font_name = vim_strsave(font_name);
8554     }
8555 }
8556 
8557 /*
8558  * Set background color for "Normal" group.  Called by gui_set_bg_color()
8559  * when the color is known.
8560  */
8561     void
8562 hl_set_bg_color_name(
8563     char_u  *name)	    /* must have been allocated */
8564 {
8565     int	    id;
8566 
8567     if (name != NULL)
8568     {
8569 	id = syn_name2id((char_u *)"Normal");
8570 	if (id > 0)
8571 	{
8572 	    vim_free(HL_TABLE()[id - 1].sg_gui_bg_name);
8573 	    HL_TABLE()[id - 1].sg_gui_bg_name = name;
8574 	}
8575     }
8576 }
8577 
8578 /*
8579  * Set foreground color for "Normal" group.  Called by gui_set_fg_color()
8580  * when the color is known.
8581  */
8582     void
8583 hl_set_fg_color_name(
8584     char_u  *name)	    /* must have been allocated */
8585 {
8586     int	    id;
8587 
8588     if (name != NULL)
8589     {
8590 	id = syn_name2id((char_u *)"Normal");
8591 	if (id > 0)
8592 	{
8593 	    vim_free(HL_TABLE()[id - 1].sg_gui_fg_name);
8594 	    HL_TABLE()[id - 1].sg_gui_fg_name = name;
8595 	}
8596     }
8597 }
8598 
8599 /*
8600  * Return the handle for a font name.
8601  * Returns NOFONT when failed.
8602  */
8603     static GuiFont
8604 font_name2handle(char_u *name)
8605 {
8606     if (STRCMP(name, "NONE") == 0)
8607 	return NOFONT;
8608 
8609     return gui_mch_get_font(name, TRUE);
8610 }
8611 
8612 # ifdef FEAT_XFONTSET
8613 /*
8614  * Return the handle for a fontset name.
8615  * Returns NOFONTSET when failed.
8616  */
8617     static GuiFontset
8618 fontset_name2handle(char_u *name, int fixed_width)
8619 {
8620     if (STRCMP(name, "NONE") == 0)
8621 	return NOFONTSET;
8622 
8623     return gui_mch_get_fontset(name, TRUE, fixed_width);
8624 }
8625 # endif
8626 
8627 /*
8628  * Get the font or fontset for one highlight group.
8629  */
8630     static void
8631 hl_do_font(
8632     int		idx,
8633     char_u	*arg,
8634     int		do_normal,		/* set normal font */
8635     int		do_menu UNUSED,		/* set menu font */
8636     int		do_tooltip UNUSED,	/* set tooltip font */
8637     int		free_font)		/* free current font/fontset */
8638 {
8639 # ifdef FEAT_XFONTSET
8640     /* If 'guifontset' is not empty, first try using the name as a
8641      * fontset.  If that doesn't work, use it as a font name. */
8642     if (*p_guifontset != NUL
8643 #  ifdef FONTSET_ALWAYS
8644 	|| do_menu
8645 #  endif
8646 #  ifdef FEAT_BEVAL_TIP
8647 	/* In Athena & Motif, the Tooltip highlight group is always a fontset */
8648 	|| do_tooltip
8649 #  endif
8650 	    )
8651     {
8652 	if (free_font)
8653 	    gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
8654 	HL_TABLE()[idx].sg_fontset = fontset_name2handle(arg, 0
8655 #  ifdef FONTSET_ALWAYS
8656 		|| do_menu
8657 #  endif
8658 #  ifdef FEAT_BEVAL_TIP
8659 		|| do_tooltip
8660 #  endif
8661 		);
8662     }
8663     if (HL_TABLE()[idx].sg_fontset != NOFONTSET)
8664     {
8665 	/* If it worked and it's the Normal group, use it as the normal
8666 	 * fontset.  Same for the Menu group. */
8667 	if (do_normal)
8668 	    gui_init_font(arg, TRUE);
8669 #   if (defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_ATHENA)) && defined(FEAT_MENU)
8670 	if (do_menu)
8671 	{
8672 #    ifdef FONTSET_ALWAYS
8673 	    gui.menu_fontset = HL_TABLE()[idx].sg_fontset;
8674 #    else
8675 	    /* YIKES!  This is a bug waiting to crash the program */
8676 	    gui.menu_font = HL_TABLE()[idx].sg_fontset;
8677 #    endif
8678 	    gui_mch_new_menu_font();
8679 	}
8680 #    ifdef FEAT_BEVAL_GUI
8681 	if (do_tooltip)
8682 	{
8683 	    /* The Athena widget set cannot currently handle switching between
8684 	     * displaying a single font and a fontset.
8685 	     * If the XtNinternational resource is set to True at widget
8686 	     * creation, then a fontset is always used, otherwise an
8687 	     * XFontStruct is used.
8688 	     */
8689 	    gui.tooltip_fontset = (XFontSet)HL_TABLE()[idx].sg_fontset;
8690 	    gui_mch_new_tooltip_font();
8691 	}
8692 #    endif
8693 #   endif
8694     }
8695     else
8696 # endif
8697     {
8698 	if (free_font)
8699 	    gui_mch_free_font(HL_TABLE()[idx].sg_font);
8700 	HL_TABLE()[idx].sg_font = font_name2handle(arg);
8701 	/* If it worked and it's the Normal group, use it as the
8702 	 * normal font.  Same for the Menu group. */
8703 	if (HL_TABLE()[idx].sg_font != NOFONT)
8704 	{
8705 	    if (do_normal)
8706 		gui_init_font(arg, FALSE);
8707 #ifndef FONTSET_ALWAYS
8708 # if (defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_ATHENA)) && defined(FEAT_MENU)
8709 	    if (do_menu)
8710 	    {
8711 		gui.menu_font = HL_TABLE()[idx].sg_font;
8712 		gui_mch_new_menu_font();
8713 	    }
8714 # endif
8715 #endif
8716 	}
8717     }
8718 }
8719 
8720 #endif /* FEAT_GUI */
8721 
8722 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
8723 /*
8724  * Return the handle for a color name.
8725  * Returns INVALCOLOR when failed.
8726  */
8727     guicolor_T
8728 color_name2handle(char_u *name)
8729 {
8730     if (STRCMP(name, "NONE") == 0)
8731 	return INVALCOLOR;
8732 
8733     if (STRICMP(name, "fg") == 0 || STRICMP(name, "foreground") == 0)
8734     {
8735 #if defined(FEAT_TERMGUICOLORS) && defined(FEAT_GUI)
8736 	if (gui.in_use)
8737 #endif
8738 #ifdef FEAT_GUI
8739 	    return gui.norm_pixel;
8740 #endif
8741 #ifdef FEAT_TERMGUICOLORS
8742 	if (cterm_normal_fg_gui_color != INVALCOLOR)
8743 	    return cterm_normal_fg_gui_color;
8744 	/* Guess that the foreground is black or white. */
8745 	return GUI_GET_COLOR((char_u *)(*p_bg == 'l' ? "black" : "white"));
8746 #endif
8747     }
8748     if (STRICMP(name, "bg") == 0 || STRICMP(name, "background") == 0)
8749     {
8750 #if defined(FEAT_TERMGUICOLORS) && defined(FEAT_GUI)
8751 	if (gui.in_use)
8752 #endif
8753 #ifdef FEAT_GUI
8754 	    return gui.back_pixel;
8755 #endif
8756 #ifdef FEAT_TERMGUICOLORS
8757 	if (cterm_normal_bg_gui_color != INVALCOLOR)
8758 	    return cterm_normal_bg_gui_color;
8759 	/* Guess that the background is white or black. */
8760 	return GUI_GET_COLOR((char_u *)(*p_bg == 'l' ? "white" : "black"));
8761 #endif
8762     }
8763 
8764     return GUI_GET_COLOR(name);
8765 }
8766 #endif
8767 
8768 /*
8769  * Table with the specifications for an attribute number.
8770  * Note that this table is used by ALL buffers.  This is required because the
8771  * GUI can redraw at any time for any buffer.
8772  */
8773 static garray_T	term_attr_table = {0, 0, 0, 0, NULL};
8774 
8775 #define TERM_ATTR_ENTRY(idx) ((attrentry_T *)term_attr_table.ga_data)[idx]
8776 
8777 static garray_T	cterm_attr_table = {0, 0, 0, 0, NULL};
8778 
8779 #define CTERM_ATTR_ENTRY(idx) ((attrentry_T *)cterm_attr_table.ga_data)[idx]
8780 
8781 #ifdef FEAT_GUI
8782 static garray_T	gui_attr_table = {0, 0, 0, 0, NULL};
8783 
8784 #define GUI_ATTR_ENTRY(idx) ((attrentry_T *)gui_attr_table.ga_data)[idx]
8785 #endif
8786 
8787 /*
8788  * Return the attr number for a set of colors and font.
8789  * Add a new entry to the term_attr_table, cterm_attr_table or gui_attr_table
8790  * if the combination is new.
8791  * Return 0 for error (no more room).
8792  */
8793     static int
8794 get_attr_entry(garray_T *table, attrentry_T *aep)
8795 {
8796     int		i;
8797     attrentry_T	*taep;
8798     static int	recursive = FALSE;
8799 
8800     /*
8801      * Init the table, in case it wasn't done yet.
8802      */
8803     table->ga_itemsize = sizeof(attrentry_T);
8804     table->ga_growsize = 7;
8805 
8806     /*
8807      * Try to find an entry with the same specifications.
8808      */
8809     for (i = 0; i < table->ga_len; ++i)
8810     {
8811 	taep = &(((attrentry_T *)table->ga_data)[i]);
8812 	if (	   aep->ae_attr == taep->ae_attr
8813 		&& (
8814 #ifdef FEAT_GUI
8815 		       (table == &gui_attr_table
8816 			&& (aep->ae_u.gui.fg_color == taep->ae_u.gui.fg_color
8817 			    && aep->ae_u.gui.bg_color
8818 						    == taep->ae_u.gui.bg_color
8819 			    && aep->ae_u.gui.sp_color
8820 						    == taep->ae_u.gui.sp_color
8821 			    && aep->ae_u.gui.font == taep->ae_u.gui.font
8822 #  ifdef FEAT_XFONTSET
8823 			    && aep->ae_u.gui.fontset == taep->ae_u.gui.fontset
8824 #  endif
8825 			    ))
8826 		    ||
8827 #endif
8828 		       (table == &term_attr_table
8829 			&& (aep->ae_u.term.start == NULL)
8830 					    == (taep->ae_u.term.start == NULL)
8831 			&& (aep->ae_u.term.start == NULL
8832 			    || STRCMP(aep->ae_u.term.start,
8833 						  taep->ae_u.term.start) == 0)
8834 			&& (aep->ae_u.term.stop == NULL)
8835 					     == (taep->ae_u.term.stop == NULL)
8836 			&& (aep->ae_u.term.stop == NULL
8837 			    || STRCMP(aep->ae_u.term.stop,
8838 						  taep->ae_u.term.stop) == 0))
8839 		    || (table == &cterm_attr_table
8840 			    && aep->ae_u.cterm.fg_color
8841 						  == taep->ae_u.cterm.fg_color
8842 			    && aep->ae_u.cterm.bg_color
8843 						  == taep->ae_u.cterm.bg_color
8844 #ifdef FEAT_TERMGUICOLORS
8845 			    && aep->ae_u.cterm.fg_rgb
8846 						    == taep->ae_u.cterm.fg_rgb
8847 			    && aep->ae_u.cterm.bg_rgb
8848 						    == taep->ae_u.cterm.bg_rgb
8849 #endif
8850 		       )))
8851 
8852 	return i + ATTR_OFF;
8853     }
8854 
8855     if (table->ga_len + ATTR_OFF > MAX_TYPENR)
8856     {
8857 	/*
8858 	 * Running out of attribute entries!  remove all attributes, and
8859 	 * compute new ones for all groups.
8860 	 * When called recursively, we are really out of numbers.
8861 	 */
8862 	if (recursive)
8863 	{
8864 	    EMSG(_("E424: Too many different highlighting attributes in use"));
8865 	    return 0;
8866 	}
8867 	recursive = TRUE;
8868 
8869 	clear_hl_tables();
8870 
8871 	must_redraw = CLEAR;
8872 
8873 	for (i = 0; i < highlight_ga.ga_len; ++i)
8874 	    set_hl_attr(i);
8875 
8876 	recursive = FALSE;
8877     }
8878 
8879     /*
8880      * This is a new combination of colors and font, add an entry.
8881      */
8882     if (ga_grow(table, 1) == FAIL)
8883 	return 0;
8884 
8885     taep = &(((attrentry_T *)table->ga_data)[table->ga_len]);
8886     vim_memset(taep, 0, sizeof(attrentry_T));
8887     taep->ae_attr = aep->ae_attr;
8888 #ifdef FEAT_GUI
8889     if (table == &gui_attr_table)
8890     {
8891 	taep->ae_u.gui.fg_color = aep->ae_u.gui.fg_color;
8892 	taep->ae_u.gui.bg_color = aep->ae_u.gui.bg_color;
8893 	taep->ae_u.gui.sp_color = aep->ae_u.gui.sp_color;
8894 	taep->ae_u.gui.font = aep->ae_u.gui.font;
8895 # ifdef FEAT_XFONTSET
8896 	taep->ae_u.gui.fontset = aep->ae_u.gui.fontset;
8897 # endif
8898     }
8899 #endif
8900     if (table == &term_attr_table)
8901     {
8902 	if (aep->ae_u.term.start == NULL)
8903 	    taep->ae_u.term.start = NULL;
8904 	else
8905 	    taep->ae_u.term.start = vim_strsave(aep->ae_u.term.start);
8906 	if (aep->ae_u.term.stop == NULL)
8907 	    taep->ae_u.term.stop = NULL;
8908 	else
8909 	    taep->ae_u.term.stop = vim_strsave(aep->ae_u.term.stop);
8910     }
8911     else if (table == &cterm_attr_table)
8912     {
8913 	taep->ae_u.cterm.fg_color = aep->ae_u.cterm.fg_color;
8914 	taep->ae_u.cterm.bg_color = aep->ae_u.cterm.bg_color;
8915 #ifdef FEAT_TERMGUICOLORS
8916 	taep->ae_u.cterm.fg_rgb = aep->ae_u.cterm.fg_rgb;
8917 	taep->ae_u.cterm.bg_rgb = aep->ae_u.cterm.bg_rgb;
8918 #endif
8919     }
8920     ++table->ga_len;
8921     return (table->ga_len - 1 + ATTR_OFF);
8922 }
8923 
8924 /*
8925  * Get an attribute index for a cterm entry.
8926  * Uses an existing entry when possible or adds one when needed.
8927  */
8928     int
8929 get_cterm_attr_idx(int attr, int fg, int bg)
8930 {
8931     attrentry_T		at_en;
8932 
8933     vim_memset(&at_en, 0, sizeof(attrentry_T));
8934 #ifdef FEAT_TERMGUICOLORS
8935     at_en.ae_u.cterm.fg_rgb = INVALCOLOR;
8936     at_en.ae_u.cterm.bg_rgb = INVALCOLOR;
8937 #endif
8938     at_en.ae_attr = attr;
8939     at_en.ae_u.cterm.fg_color = fg;
8940     at_en.ae_u.cterm.bg_color = bg;
8941     return get_attr_entry(&cterm_attr_table, &at_en);
8942 }
8943 
8944 #if defined(FEAT_TERMGUICOLORS) || defined(PROTO)
8945 /*
8946  * Get an attribute index for a 'termguicolors' entry.
8947  * Uses an existing entry when possible or adds one when needed.
8948  */
8949     int
8950 get_tgc_attr_idx(int attr, guicolor_T fg, guicolor_T bg)
8951 {
8952     attrentry_T		at_en;
8953 
8954     vim_memset(&at_en, 0, sizeof(attrentry_T));
8955     at_en.ae_attr = attr;
8956     if (fg == INVALCOLOR && bg == INVALCOLOR)
8957     {
8958 	/* If both GUI colors are not set fall back to the cterm colors.  Helps
8959 	 * if the GUI only has an attribute, such as undercurl. */
8960 	at_en.ae_u.cterm.fg_rgb = CTERMCOLOR;
8961 	at_en.ae_u.cterm.bg_rgb = CTERMCOLOR;
8962     }
8963     else
8964     {
8965 	at_en.ae_u.cterm.fg_rgb = fg;
8966 	at_en.ae_u.cterm.bg_rgb = bg;
8967     }
8968     return get_attr_entry(&cterm_attr_table, &at_en);
8969 }
8970 #endif
8971 
8972 #if defined(FEAT_GUI) || defined(PROTO)
8973 /*
8974  * Get an attribute index for a cterm entry.
8975  * Uses an existing entry when possible or adds one when needed.
8976  */
8977     int
8978 get_gui_attr_idx(int attr, guicolor_T fg, guicolor_T bg)
8979 {
8980     attrentry_T		at_en;
8981 
8982     vim_memset(&at_en, 0, sizeof(attrentry_T));
8983     at_en.ae_attr = attr;
8984     at_en.ae_u.gui.fg_color = fg;
8985     at_en.ae_u.gui.bg_color = bg;
8986     return get_attr_entry(&gui_attr_table, &at_en);
8987 }
8988 #endif
8989 
8990 /*
8991  * Clear all highlight tables.
8992  */
8993     void
8994 clear_hl_tables(void)
8995 {
8996     int		i;
8997     attrentry_T	*taep;
8998 
8999 #ifdef FEAT_GUI
9000     ga_clear(&gui_attr_table);
9001 #endif
9002     for (i = 0; i < term_attr_table.ga_len; ++i)
9003     {
9004 	taep = &(((attrentry_T *)term_attr_table.ga_data)[i]);
9005 	vim_free(taep->ae_u.term.start);
9006 	vim_free(taep->ae_u.term.stop);
9007     }
9008     ga_clear(&term_attr_table);
9009     ga_clear(&cterm_attr_table);
9010 }
9011 
9012 #if defined(FEAT_SYN_HL) || defined(FEAT_SPELL) || defined(PROTO)
9013 /*
9014  * Combine special attributes (e.g., for spelling) with other attributes
9015  * (e.g., for syntax highlighting).
9016  * "prim_attr" overrules "char_attr".
9017  * This creates a new group when required.
9018  * Since we expect there to be few spelling mistakes we don't cache the
9019  * result.
9020  * Return the resulting attributes.
9021  */
9022     int
9023 hl_combine_attr(int char_attr, int prim_attr)
9024 {
9025     attrentry_T *char_aep = NULL;
9026     attrentry_T *spell_aep;
9027     attrentry_T new_en;
9028 
9029     if (char_attr == 0)
9030 	return prim_attr;
9031     if (char_attr <= HL_ALL && prim_attr <= HL_ALL)
9032 	return ATTR_COMBINE(char_attr, prim_attr);
9033 #ifdef FEAT_GUI
9034     if (gui.in_use)
9035     {
9036 	if (char_attr > HL_ALL)
9037 	    char_aep = syn_gui_attr2entry(char_attr);
9038 	if (char_aep != NULL)
9039 	    new_en = *char_aep;
9040 	else
9041 	{
9042 	    vim_memset(&new_en, 0, sizeof(new_en));
9043 	    new_en.ae_u.gui.fg_color = INVALCOLOR;
9044 	    new_en.ae_u.gui.bg_color = INVALCOLOR;
9045 	    new_en.ae_u.gui.sp_color = INVALCOLOR;
9046 	    if (char_attr <= HL_ALL)
9047 		new_en.ae_attr = char_attr;
9048 	}
9049 
9050 	if (prim_attr <= HL_ALL)
9051 	    new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, prim_attr);
9052 	else
9053 	{
9054 	    spell_aep = syn_gui_attr2entry(prim_attr);
9055 	    if (spell_aep != NULL)
9056 	    {
9057 		new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr,
9058 							   spell_aep->ae_attr);
9059 		if (spell_aep->ae_u.gui.fg_color != INVALCOLOR)
9060 		    new_en.ae_u.gui.fg_color = spell_aep->ae_u.gui.fg_color;
9061 		if (spell_aep->ae_u.gui.bg_color != INVALCOLOR)
9062 		    new_en.ae_u.gui.bg_color = spell_aep->ae_u.gui.bg_color;
9063 		if (spell_aep->ae_u.gui.sp_color != INVALCOLOR)
9064 		    new_en.ae_u.gui.sp_color = spell_aep->ae_u.gui.sp_color;
9065 		if (spell_aep->ae_u.gui.font != NOFONT)
9066 		    new_en.ae_u.gui.font = spell_aep->ae_u.gui.font;
9067 # ifdef FEAT_XFONTSET
9068 		if (spell_aep->ae_u.gui.fontset != NOFONTSET)
9069 		    new_en.ae_u.gui.fontset = spell_aep->ae_u.gui.fontset;
9070 # endif
9071 	    }
9072 	}
9073 	return get_attr_entry(&gui_attr_table, &new_en);
9074     }
9075 #endif
9076 
9077     if (IS_CTERM)
9078     {
9079 	if (char_attr > HL_ALL)
9080 	    char_aep = syn_cterm_attr2entry(char_attr);
9081 	if (char_aep != NULL)
9082 	    new_en = *char_aep;
9083 	else
9084 	{
9085 	    vim_memset(&new_en, 0, sizeof(new_en));
9086 #ifdef FEAT_TERMGUICOLORS
9087 	    new_en.ae_u.cterm.bg_rgb = INVALCOLOR;
9088 	    new_en.ae_u.cterm.fg_rgb = INVALCOLOR;
9089 #endif
9090 	    if (char_attr <= HL_ALL)
9091 		new_en.ae_attr = char_attr;
9092 	}
9093 
9094 	if (prim_attr <= HL_ALL)
9095 		new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, prim_attr);
9096 	else
9097 	{
9098 	    spell_aep = syn_cterm_attr2entry(prim_attr);
9099 	    if (spell_aep != NULL)
9100 	    {
9101 		new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr,
9102 							   spell_aep->ae_attr);
9103 		if (spell_aep->ae_u.cterm.fg_color > 0)
9104 		    new_en.ae_u.cterm.fg_color = spell_aep->ae_u.cterm.fg_color;
9105 		if (spell_aep->ae_u.cterm.bg_color > 0)
9106 		    new_en.ae_u.cterm.bg_color = spell_aep->ae_u.cterm.bg_color;
9107 #ifdef FEAT_TERMGUICOLORS
9108 		/* If both fg and bg are not set fall back to cterm colors.
9109 		 * Helps for SpellBad which uses undercurl in the GUI. */
9110 		if (COLOR_INVALID(spell_aep->ae_u.cterm.fg_rgb)
9111 			&& COLOR_INVALID(spell_aep->ae_u.cterm.bg_rgb))
9112 		{
9113 		    if (spell_aep->ae_u.cterm.fg_color > 0)
9114 			new_en.ae_u.cterm.fg_rgb = CTERMCOLOR;
9115 		    if (spell_aep->ae_u.cterm.bg_color > 0)
9116 			new_en.ae_u.cterm.bg_rgb = CTERMCOLOR;
9117 		}
9118 		else
9119 		{
9120 		    if (spell_aep->ae_u.cterm.fg_rgb != INVALCOLOR)
9121 			new_en.ae_u.cterm.fg_rgb = spell_aep->ae_u.cterm.fg_rgb;
9122 		    if (spell_aep->ae_u.cterm.bg_rgb != INVALCOLOR)
9123 			new_en.ae_u.cterm.bg_rgb = spell_aep->ae_u.cterm.bg_rgb;
9124 		}
9125 #endif
9126 	    }
9127 	}
9128 	return get_attr_entry(&cterm_attr_table, &new_en);
9129     }
9130 
9131     if (char_attr > HL_ALL)
9132 	char_aep = syn_term_attr2entry(char_attr);
9133     if (char_aep != NULL)
9134 	new_en = *char_aep;
9135     else
9136     {
9137 	vim_memset(&new_en, 0, sizeof(new_en));
9138 	if (char_attr <= HL_ALL)
9139 	    new_en.ae_attr = char_attr;
9140     }
9141 
9142     if (prim_attr <= HL_ALL)
9143 	new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, prim_attr);
9144     else
9145     {
9146 	spell_aep = syn_term_attr2entry(prim_attr);
9147 	if (spell_aep != NULL)
9148 	{
9149 	    new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, spell_aep->ae_attr);
9150 	    if (spell_aep->ae_u.term.start != NULL)
9151 	    {
9152 		new_en.ae_u.term.start = spell_aep->ae_u.term.start;
9153 		new_en.ae_u.term.stop = spell_aep->ae_u.term.stop;
9154 	    }
9155 	}
9156     }
9157     return get_attr_entry(&term_attr_table, &new_en);
9158 }
9159 #endif
9160 
9161 #ifdef FEAT_GUI
9162 
9163     attrentry_T *
9164 syn_gui_attr2entry(int attr)
9165 {
9166     attr -= ATTR_OFF;
9167     if (attr >= gui_attr_table.ga_len)	    /* did ":syntax clear" */
9168 	return NULL;
9169     return &(GUI_ATTR_ENTRY(attr));
9170 }
9171 #endif /* FEAT_GUI */
9172 
9173 /*
9174  * Get the highlight attributes (HL_BOLD etc.) from an attribute nr.
9175  * Only to be used when "attr" > HL_ALL.
9176  */
9177     int
9178 syn_attr2attr(int attr)
9179 {
9180     attrentry_T	*aep;
9181 
9182 #ifdef FEAT_GUI
9183     if (gui.in_use)
9184 	aep = syn_gui_attr2entry(attr);
9185     else
9186 #endif
9187 	if (IS_CTERM)
9188 	    aep = syn_cterm_attr2entry(attr);
9189 	else
9190 	    aep = syn_term_attr2entry(attr);
9191 
9192     if (aep == NULL)	    /* highlighting not set */
9193 	return 0;
9194     return aep->ae_attr;
9195 }
9196 
9197 
9198     attrentry_T *
9199 syn_term_attr2entry(int attr)
9200 {
9201     attr -= ATTR_OFF;
9202     if (attr >= term_attr_table.ga_len)	    /* did ":syntax clear" */
9203 	return NULL;
9204     return &(TERM_ATTR_ENTRY(attr));
9205 }
9206 
9207     attrentry_T *
9208 syn_cterm_attr2entry(int attr)
9209 {
9210     attr -= ATTR_OFF;
9211     if (attr >= cterm_attr_table.ga_len)	/* did ":syntax clear" */
9212 	return NULL;
9213     return &(CTERM_ATTR_ENTRY(attr));
9214 }
9215 
9216 #define LIST_ATTR   1
9217 #define LIST_STRING 2
9218 #define LIST_INT    3
9219 
9220     static void
9221 highlight_list_one(int id)
9222 {
9223     struct hl_group	*sgp;
9224     int			didh = FALSE;
9225 
9226     sgp = &HL_TABLE()[id - 1];	    /* index is ID minus one */
9227 
9228     didh = highlight_list_arg(id, didh, LIST_ATTR,
9229 				    sgp->sg_term, NULL, "term");
9230     didh = highlight_list_arg(id, didh, LIST_STRING,
9231 				    0, sgp->sg_start, "start");
9232     didh = highlight_list_arg(id, didh, LIST_STRING,
9233 				    0, sgp->sg_stop, "stop");
9234 
9235     didh = highlight_list_arg(id, didh, LIST_ATTR,
9236 				    sgp->sg_cterm, NULL, "cterm");
9237     didh = highlight_list_arg(id, didh, LIST_INT,
9238 				    sgp->sg_cterm_fg, NULL, "ctermfg");
9239     didh = highlight_list_arg(id, didh, LIST_INT,
9240 				    sgp->sg_cterm_bg, NULL, "ctermbg");
9241 
9242 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
9243     didh = highlight_list_arg(id, didh, LIST_ATTR,
9244 				    sgp->sg_gui, NULL, "gui");
9245     didh = highlight_list_arg(id, didh, LIST_STRING,
9246 				    0, sgp->sg_gui_fg_name, "guifg");
9247     didh = highlight_list_arg(id, didh, LIST_STRING,
9248 				    0, sgp->sg_gui_bg_name, "guibg");
9249     didh = highlight_list_arg(id, didh, LIST_STRING,
9250 				    0, sgp->sg_gui_sp_name, "guisp");
9251 #endif
9252 #ifdef FEAT_GUI
9253     didh = highlight_list_arg(id, didh, LIST_STRING,
9254 				    0, sgp->sg_font_name, "font");
9255 #endif
9256 
9257     if (sgp->sg_link && !got_int)
9258     {
9259 	(void)syn_list_header(didh, 9999, id);
9260 	didh = TRUE;
9261 	msg_puts_attr((char_u *)"links to", HL_ATTR(HLF_D));
9262 	msg_putchar(' ');
9263 	msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name);
9264     }
9265 
9266     if (!didh)
9267 	highlight_list_arg(id, didh, LIST_STRING, 0, (char_u *)"cleared", "");
9268 #ifdef FEAT_EVAL
9269     if (p_verbose > 0)
9270 	last_set_msg(sgp->sg_scriptID);
9271 #endif
9272 }
9273 
9274     static int
9275 highlight_list_arg(
9276     int		id,
9277     int		didh,
9278     int		type,
9279     int		iarg,
9280     char_u	*sarg,
9281     char	*name)
9282 {
9283     char_u	buf[100];
9284     char_u	*ts;
9285     int		i;
9286 
9287     if (got_int)
9288 	return FALSE;
9289     if (type == LIST_STRING ? (sarg != NULL) : (iarg != 0))
9290     {
9291 	ts = buf;
9292 	if (type == LIST_INT)
9293 	    sprintf((char *)buf, "%d", iarg - 1);
9294 	else if (type == LIST_STRING)
9295 	    ts = sarg;
9296 	else /* type == LIST_ATTR */
9297 	{
9298 	    buf[0] = NUL;
9299 	    for (i = 0; hl_attr_table[i] != 0; ++i)
9300 	    {
9301 		if (iarg & hl_attr_table[i])
9302 		{
9303 		    if (buf[0] != NUL)
9304 			vim_strcat(buf, (char_u *)",", 100);
9305 		    vim_strcat(buf, (char_u *)hl_name_table[i], 100);
9306 		    iarg &= ~hl_attr_table[i];	    /* don't want "inverse" */
9307 		}
9308 	    }
9309 	}
9310 
9311 	(void)syn_list_header(didh,
9312 			       (int)(vim_strsize(ts) + STRLEN(name) + 1), id);
9313 	didh = TRUE;
9314 	if (!got_int)
9315 	{
9316 	    if (*name != NUL)
9317 	    {
9318 		MSG_PUTS_ATTR(name, HL_ATTR(HLF_D));
9319 		MSG_PUTS_ATTR("=", HL_ATTR(HLF_D));
9320 	    }
9321 	    msg_outtrans(ts);
9322 	}
9323     }
9324     return didh;
9325 }
9326 
9327 #if (((defined(FEAT_EVAL) || defined(FEAT_PRINTER))) && defined(FEAT_SYN_HL)) || defined(PROTO)
9328 /*
9329  * Return "1" if highlight group "id" has attribute "flag".
9330  * Return NULL otherwise.
9331  */
9332     char_u *
9333 highlight_has_attr(
9334     int		id,
9335     int		flag,
9336     int		modec)	/* 'g' for GUI, 'c' for cterm, 't' for term */
9337 {
9338     int		attr;
9339 
9340     if (id <= 0 || id > highlight_ga.ga_len)
9341 	return NULL;
9342 
9343 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
9344     if (modec == 'g')
9345 	attr = HL_TABLE()[id - 1].sg_gui;
9346     else
9347 #endif
9348 	 if (modec == 'c')
9349 	attr = HL_TABLE()[id - 1].sg_cterm;
9350     else
9351 	attr = HL_TABLE()[id - 1].sg_term;
9352 
9353     if (attr & flag)
9354 	return (char_u *)"1";
9355     return NULL;
9356 }
9357 #endif
9358 
9359 #if (defined(FEAT_SYN_HL) && defined(FEAT_EVAL)) || defined(PROTO)
9360 /*
9361  * Return color name of highlight group "id".
9362  */
9363     char_u *
9364 highlight_color(
9365     int		id,
9366     char_u	*what,	/* "font", "fg", "bg", "sp", "fg#", "bg#" or "sp#" */
9367     int		modec)	/* 'g' for GUI, 'c' for cterm, 't' for term */
9368 {
9369     static char_u	name[20];
9370     int			n;
9371     int			fg = FALSE;
9372     int			sp = FALSE;
9373     int			font = FALSE;
9374 
9375     if (id <= 0 || id > highlight_ga.ga_len)
9376 	return NULL;
9377 
9378     if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'g')
9379 	fg = TRUE;
9380     else if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'o'
9381 	     && TOLOWER_ASC(what[2]) == 'n' && TOLOWER_ASC(what[3]) == 't')
9382 	font = TRUE;
9383     else if (TOLOWER_ASC(what[0]) == 's' && TOLOWER_ASC(what[1]) == 'p')
9384 	sp = TRUE;
9385     else if (!(TOLOWER_ASC(what[0]) == 'b' && TOLOWER_ASC(what[1]) == 'g'))
9386 	return NULL;
9387     if (modec == 'g')
9388     {
9389 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
9390 #  ifdef FEAT_GUI
9391 	/* return font name */
9392 	if (font)
9393 	    return HL_TABLE()[id - 1].sg_font_name;
9394 #  endif
9395 
9396 	/* return #RRGGBB form (only possible when GUI is running) */
9397 	if ((USE_24BIT) && what[2] == '#')
9398 	{
9399 	    guicolor_T		color;
9400 	    long_u		rgb;
9401 	    static char_u	buf[10];
9402 
9403 	    if (fg)
9404 		color = HL_TABLE()[id - 1].sg_gui_fg;
9405 	    else if (sp)
9406 #  ifdef FEAT_GUI
9407 		color = HL_TABLE()[id - 1].sg_gui_sp;
9408 #  else
9409 		color = INVALCOLOR;
9410 #  endif
9411 	    else
9412 		color = HL_TABLE()[id - 1].sg_gui_bg;
9413 	    if (color == INVALCOLOR)
9414 		return NULL;
9415 	    rgb = (long_u)GUI_MCH_GET_RGB(color);
9416 	    sprintf((char *)buf, "#%02x%02x%02x",
9417 				      (unsigned)(rgb >> 16),
9418 				      (unsigned)(rgb >> 8) & 255,
9419 				      (unsigned)rgb & 255);
9420 	    return buf;
9421 	}
9422 # endif
9423 	if (fg)
9424 	    return (HL_TABLE()[id - 1].sg_gui_fg_name);
9425 	if (sp)
9426 	    return (HL_TABLE()[id - 1].sg_gui_sp_name);
9427 	return (HL_TABLE()[id - 1].sg_gui_bg_name);
9428     }
9429     if (font || sp)
9430 	return NULL;
9431     if (modec == 'c')
9432     {
9433 	if (fg)
9434 	    n = HL_TABLE()[id - 1].sg_cterm_fg - 1;
9435 	else
9436 	    n = HL_TABLE()[id - 1].sg_cterm_bg - 1;
9437 	if (n < 0)
9438 	    return NULL;
9439 	sprintf((char *)name, "%d", n);
9440 	return name;
9441     }
9442     /* term doesn't have color */
9443     return NULL;
9444 }
9445 #endif
9446 
9447 #if (defined(FEAT_SYN_HL) \
9448 	    && (defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)) \
9449 	&& defined(FEAT_PRINTER)) || defined(PROTO)
9450 /*
9451  * Return color name of highlight group "id" as RGB value.
9452  */
9453     long_u
9454 highlight_gui_color_rgb(
9455     int		id,
9456     int		fg)	/* TRUE = fg, FALSE = bg */
9457 {
9458     guicolor_T	color;
9459 
9460     if (id <= 0 || id > highlight_ga.ga_len)
9461 	return 0L;
9462 
9463     if (fg)
9464 	color = HL_TABLE()[id - 1].sg_gui_fg;
9465     else
9466 	color = HL_TABLE()[id - 1].sg_gui_bg;
9467 
9468     if (color == INVALCOLOR)
9469 	return 0L;
9470 
9471     return GUI_MCH_GET_RGB(color);
9472 }
9473 #endif
9474 
9475 /*
9476  * Output the syntax list header.
9477  * Return TRUE when started a new line.
9478  */
9479     static int
9480 syn_list_header(
9481     int	    did_header,		/* did header already */
9482     int	    outlen,		/* length of string that comes */
9483     int	    id)			/* highlight group id */
9484 {
9485     int	    endcol = 19;
9486     int	    newline = TRUE;
9487 
9488     if (!did_header)
9489     {
9490 	msg_putchar('\n');
9491 	if (got_int)
9492 	    return TRUE;
9493 	msg_outtrans(HL_TABLE()[id - 1].sg_name);
9494 	endcol = 15;
9495     }
9496     else if (msg_col + outlen + 1 >= Columns)
9497     {
9498 	msg_putchar('\n');
9499 	if (got_int)
9500 	    return TRUE;
9501     }
9502     else
9503     {
9504 	if (msg_col >= endcol)	/* wrap around is like starting a new line */
9505 	    newline = FALSE;
9506     }
9507 
9508     if (msg_col >= endcol)	/* output at least one space */
9509 	endcol = msg_col + 1;
9510     if (Columns <= endcol)	/* avoid hang for tiny window */
9511 	endcol = Columns - 1;
9512 
9513     msg_advance(endcol);
9514 
9515     /* Show "xxx" with the attributes. */
9516     if (!did_header)
9517     {
9518 	msg_puts_attr((char_u *)"xxx", syn_id2attr(id));
9519 	msg_putchar(' ');
9520     }
9521 
9522     return newline;
9523 }
9524 
9525 /*
9526  * Set the attribute numbers for a highlight group.
9527  * Called after one of the attributes has changed.
9528  */
9529     static void
9530 set_hl_attr(
9531     int		idx)	    /* index in array */
9532 {
9533     attrentry_T		at_en;
9534     struct hl_group	*sgp = HL_TABLE() + idx;
9535 
9536     /* The "Normal" group doesn't need an attribute number */
9537     if (sgp->sg_name_u != NULL && STRCMP(sgp->sg_name_u, "NORMAL") == 0)
9538 	return;
9539 
9540 #ifdef FEAT_GUI
9541     /*
9542      * For the GUI mode: If there are other than "normal" highlighting
9543      * attributes, need to allocate an attr number.
9544      */
9545     if (sgp->sg_gui_fg == INVALCOLOR
9546 	    && sgp->sg_gui_bg == INVALCOLOR
9547 	    && sgp->sg_gui_sp == INVALCOLOR
9548 	    && sgp->sg_font == NOFONT
9549 # ifdef FEAT_XFONTSET
9550 	    && sgp->sg_fontset == NOFONTSET
9551 # endif
9552 	    )
9553     {
9554 	sgp->sg_gui_attr = sgp->sg_gui;
9555     }
9556     else
9557     {
9558 	at_en.ae_attr = sgp->sg_gui;
9559 	at_en.ae_u.gui.fg_color = sgp->sg_gui_fg;
9560 	at_en.ae_u.gui.bg_color = sgp->sg_gui_bg;
9561 	at_en.ae_u.gui.sp_color = sgp->sg_gui_sp;
9562 	at_en.ae_u.gui.font = sgp->sg_font;
9563 # ifdef FEAT_XFONTSET
9564 	at_en.ae_u.gui.fontset = sgp->sg_fontset;
9565 # endif
9566 	sgp->sg_gui_attr = get_attr_entry(&gui_attr_table, &at_en);
9567     }
9568 #endif
9569     /*
9570      * For the term mode: If there are other than "normal" highlighting
9571      * attributes, need to allocate an attr number.
9572      */
9573     if (sgp->sg_start == NULL && sgp->sg_stop == NULL)
9574 	sgp->sg_term_attr = sgp->sg_term;
9575     else
9576     {
9577 	at_en.ae_attr = sgp->sg_term;
9578 	at_en.ae_u.term.start = sgp->sg_start;
9579 	at_en.ae_u.term.stop = sgp->sg_stop;
9580 	sgp->sg_term_attr = get_attr_entry(&term_attr_table, &at_en);
9581     }
9582 
9583     /*
9584      * For the color term mode: If there are other than "normal"
9585      * highlighting attributes, need to allocate an attr number.
9586      */
9587     if (sgp->sg_cterm_fg == 0 && sgp->sg_cterm_bg == 0
9588 # ifdef FEAT_TERMGUICOLORS
9589 	    && sgp->sg_gui_fg == INVALCOLOR
9590 	    && sgp->sg_gui_bg == INVALCOLOR
9591 # endif
9592 	    )
9593 	sgp->sg_cterm_attr = sgp->sg_cterm;
9594     else
9595     {
9596 	at_en.ae_attr = sgp->sg_cterm;
9597 	at_en.ae_u.cterm.fg_color = sgp->sg_cterm_fg;
9598 	at_en.ae_u.cterm.bg_color = sgp->sg_cterm_bg;
9599 # ifdef FEAT_TERMGUICOLORS
9600 #  ifdef WIN3264
9601 	{
9602 	    int id;
9603 	    guicolor_T fg, bg;
9604 
9605 	    id = syn_name2id((char_u *)"Normal");
9606 	    if (id > 0)
9607 	    {
9608 		syn_id2colors(id, &fg, &bg);
9609 		if (sgp->sg_gui_fg == INVALCOLOR)
9610 		    sgp->sg_gui_fg = fg;
9611 		if (sgp->sg_gui_bg == INVALCOLOR)
9612 		    sgp->sg_gui_bg = bg;
9613 	    }
9614 
9615 	}
9616 #  endif
9617 	at_en.ae_u.cterm.fg_rgb = GUI_MCH_GET_RGB2(sgp->sg_gui_fg);
9618 	at_en.ae_u.cterm.bg_rgb = GUI_MCH_GET_RGB2(sgp->sg_gui_bg);
9619 	if (at_en.ae_u.cterm.fg_rgb == INVALCOLOR
9620 		&& at_en.ae_u.cterm.bg_rgb == INVALCOLOR)
9621 	{
9622 	    /* If both fg and bg are invalid fall back to the cterm colors.
9623 	     * Helps when the GUI only uses an attribute, e.g. undercurl. */
9624 	    at_en.ae_u.cterm.fg_rgb = CTERMCOLOR;
9625 	    at_en.ae_u.cterm.bg_rgb = CTERMCOLOR;
9626 	}
9627 # endif
9628 	sgp->sg_cterm_attr = get_attr_entry(&cterm_attr_table, &at_en);
9629     }
9630 }
9631 
9632 /*
9633  * Lookup a highlight group name and return its ID.
9634  * If it is not found, 0 is returned.
9635  */
9636     int
9637 syn_name2id(char_u *name)
9638 {
9639     int		i;
9640     char_u	name_u[200];
9641 
9642     /* Avoid using stricmp() too much, it's slow on some systems */
9643     /* Avoid alloc()/free(), these are slow too.  ID names over 200 chars
9644      * don't deserve to be found! */
9645     vim_strncpy(name_u, name, 199);
9646     vim_strup(name_u);
9647     for (i = highlight_ga.ga_len; --i >= 0; )
9648 	if (HL_TABLE()[i].sg_name_u != NULL
9649 		&& STRCMP(name_u, HL_TABLE()[i].sg_name_u) == 0)
9650 	    break;
9651     return i + 1;
9652 }
9653 
9654 /*
9655  * Lookup a highlight group name and return its attributes.
9656  * Return zero if not found.
9657  */
9658     int
9659 syn_name2attr(char_u *name)
9660 {
9661     int id = syn_name2id(name);
9662 
9663     if (id != 0)
9664 	return syn_id2attr(id);
9665     return 0;
9666 }
9667 
9668 #if defined(FEAT_EVAL) || defined(PROTO)
9669 /*
9670  * Return TRUE if highlight group "name" exists.
9671  */
9672     int
9673 highlight_exists(char_u *name)
9674 {
9675     return (syn_name2id(name) > 0);
9676 }
9677 
9678 # if defined(FEAT_SEARCH_EXTRA) || defined(PROTO)
9679 /*
9680  * Return the name of highlight group "id".
9681  * When not a valid ID return an empty string.
9682  */
9683     char_u *
9684 syn_id2name(int id)
9685 {
9686     if (id <= 0 || id > highlight_ga.ga_len)
9687 	return (char_u *)"";
9688     return HL_TABLE()[id - 1].sg_name;
9689 }
9690 # endif
9691 #endif
9692 
9693 /*
9694  * Like syn_name2id(), but take a pointer + length argument.
9695  */
9696     int
9697 syn_namen2id(char_u *linep, int len)
9698 {
9699     char_u  *name;
9700     int	    id = 0;
9701 
9702     name = vim_strnsave(linep, len);
9703     if (name != NULL)
9704     {
9705 	id = syn_name2id(name);
9706 	vim_free(name);
9707     }
9708     return id;
9709 }
9710 
9711 /*
9712  * Find highlight group name in the table and return its ID.
9713  * The argument is a pointer to the name and the length of the name.
9714  * If it doesn't exist yet, a new entry is created.
9715  * Return 0 for failure.
9716  */
9717     int
9718 syn_check_group(char_u *pp, int len)
9719 {
9720     int	    id;
9721     char_u  *name;
9722 
9723     name = vim_strnsave(pp, len);
9724     if (name == NULL)
9725 	return 0;
9726 
9727     id = syn_name2id(name);
9728     if (id == 0)			/* doesn't exist yet */
9729 	id = syn_add_group(name);
9730     else
9731 	vim_free(name);
9732     return id;
9733 }
9734 
9735 /*
9736  * Add new highlight group and return its ID.
9737  * "name" must be an allocated string, it will be consumed.
9738  * Return 0 for failure.
9739  */
9740     static int
9741 syn_add_group(char_u *name)
9742 {
9743     char_u	*p;
9744 
9745     /* Check that the name is ASCII letters, digits and underscore. */
9746     for (p = name; *p != NUL; ++p)
9747     {
9748 	if (!vim_isprintc(*p))
9749 	{
9750 	    EMSG(_("E669: Unprintable character in group name"));
9751 	    vim_free(name);
9752 	    return 0;
9753 	}
9754 	else if (!ASCII_ISALNUM(*p) && *p != '_')
9755 	{
9756 	    /* This is an error, but since there previously was no check only
9757 	     * give a warning. */
9758 	    msg_source(HL_ATTR(HLF_W));
9759 	    MSG(_("W18: Invalid character in group name"));
9760 	    break;
9761 	}
9762     }
9763 
9764     /*
9765      * First call for this growarray: init growing array.
9766      */
9767     if (highlight_ga.ga_data == NULL)
9768     {
9769 	highlight_ga.ga_itemsize = sizeof(struct hl_group);
9770 	highlight_ga.ga_growsize = 10;
9771     }
9772 
9773     if (highlight_ga.ga_len >= MAX_HL_ID)
9774     {
9775 	EMSG(_("E849: Too many highlight and syntax groups"));
9776 	vim_free(name);
9777 	return 0;
9778     }
9779 
9780     /*
9781      * Make room for at least one other syntax_highlight entry.
9782      */
9783     if (ga_grow(&highlight_ga, 1) == FAIL)
9784     {
9785 	vim_free(name);
9786 	return 0;
9787     }
9788 
9789     vim_memset(&(HL_TABLE()[highlight_ga.ga_len]), 0, sizeof(struct hl_group));
9790     HL_TABLE()[highlight_ga.ga_len].sg_name = name;
9791     HL_TABLE()[highlight_ga.ga_len].sg_name_u = vim_strsave_up(name);
9792 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
9793     HL_TABLE()[highlight_ga.ga_len].sg_gui_bg = INVALCOLOR;
9794     HL_TABLE()[highlight_ga.ga_len].sg_gui_fg = INVALCOLOR;
9795 # ifdef FEAT_GUI
9796     HL_TABLE()[highlight_ga.ga_len].sg_gui_sp = INVALCOLOR;
9797 # endif
9798 #endif
9799     ++highlight_ga.ga_len;
9800 
9801     return highlight_ga.ga_len;		    /* ID is index plus one */
9802 }
9803 
9804 /*
9805  * When, just after calling syn_add_group(), an error is discovered, this
9806  * function deletes the new name.
9807  */
9808     static void
9809 syn_unadd_group(void)
9810 {
9811     --highlight_ga.ga_len;
9812     vim_free(HL_TABLE()[highlight_ga.ga_len].sg_name);
9813     vim_free(HL_TABLE()[highlight_ga.ga_len].sg_name_u);
9814 }
9815 
9816 /*
9817  * Translate a group ID to highlight attributes.
9818  */
9819     int
9820 syn_id2attr(int hl_id)
9821 {
9822     int			attr;
9823     struct hl_group	*sgp;
9824 
9825     hl_id = syn_get_final_id(hl_id);
9826     sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
9827 
9828 #ifdef FEAT_GUI
9829     /*
9830      * Only use GUI attr when the GUI is being used.
9831      */
9832     if (gui.in_use)
9833 	attr = sgp->sg_gui_attr;
9834     else
9835 #endif
9836 	if (IS_CTERM)
9837 	    attr = sgp->sg_cterm_attr;
9838 	else
9839 	    attr = sgp->sg_term_attr;
9840 
9841     return attr;
9842 }
9843 
9844 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
9845 /*
9846  * Get the GUI colors and attributes for a group ID.
9847  * NOTE: the colors will be INVALCOLOR when not set, the color otherwise.
9848  */
9849     int
9850 syn_id2colors(int hl_id, guicolor_T *fgp, guicolor_T *bgp)
9851 {
9852     struct hl_group	*sgp;
9853 
9854     hl_id = syn_get_final_id(hl_id);
9855     sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
9856 
9857     *fgp = sgp->sg_gui_fg;
9858     *bgp = sgp->sg_gui_bg;
9859     return sgp->sg_gui;
9860 }
9861 #endif
9862 
9863 #if (defined(WIN3264) \
9864 	&& !defined(FEAT_GUI_W32) \
9865 	&& defined(FEAT_TERMGUICOLORS)) || defined(PROTO)
9866     void
9867 syn_id2cterm_bg(int hl_id, int *fgp, int *bgp)
9868 {
9869     struct hl_group	*sgp;
9870 
9871     hl_id = syn_get_final_id(hl_id);
9872     sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
9873     *fgp = sgp->sg_cterm_fg - 1;
9874     *bgp = sgp->sg_cterm_bg - 1;
9875 }
9876 #endif
9877 
9878 /*
9879  * Translate a group ID to the final group ID (following links).
9880  */
9881     int
9882 syn_get_final_id(int hl_id)
9883 {
9884     int			count;
9885     struct hl_group	*sgp;
9886 
9887     if (hl_id > highlight_ga.ga_len || hl_id < 1)
9888 	return 0;			/* Can be called from eval!! */
9889 
9890     /*
9891      * Follow links until there is no more.
9892      * Look out for loops!  Break after 100 links.
9893      */
9894     for (count = 100; --count >= 0; )
9895     {
9896 	sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
9897 	if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len)
9898 	    break;
9899 	hl_id = sgp->sg_link;
9900     }
9901 
9902     return hl_id;
9903 }
9904 
9905 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
9906 /*
9907  * Call this function just after the GUI has started.
9908  * Also called when 'termguicolors' was set, gui.in_use will be FALSE then.
9909  * It finds the font and color handles for the highlighting groups.
9910  */
9911     void
9912 highlight_gui_started(void)
9913 {
9914     int	    idx;
9915 
9916     /* First get the colors from the "Normal" and "Menu" group, if set */
9917     if (USE_24BIT)
9918 	set_normal_colors();
9919 
9920     for (idx = 0; idx < highlight_ga.ga_len; ++idx)
9921 	gui_do_one_color(idx, FALSE, FALSE);
9922 
9923     highlight_changed();
9924 }
9925 
9926     static void
9927 gui_do_one_color(
9928     int		idx,
9929     int		do_menu UNUSED,	   /* TRUE: might set the menu font */
9930     int		do_tooltip UNUSED) /* TRUE: might set the tooltip font */
9931 {
9932     int		didit = FALSE;
9933 
9934 # ifdef FEAT_GUI
9935 #  ifdef FEAT_TERMGUICOLORS
9936     if (gui.in_use)
9937 #  endif
9938 	if (HL_TABLE()[idx].sg_font_name != NULL)
9939 	{
9940 	    hl_do_font(idx, HL_TABLE()[idx].sg_font_name, FALSE, do_menu,
9941 							    do_tooltip, TRUE);
9942 	    didit = TRUE;
9943 	}
9944 # endif
9945     if (HL_TABLE()[idx].sg_gui_fg_name != NULL)
9946     {
9947 	HL_TABLE()[idx].sg_gui_fg =
9948 			    color_name2handle(HL_TABLE()[idx].sg_gui_fg_name);
9949 	didit = TRUE;
9950     }
9951     if (HL_TABLE()[idx].sg_gui_bg_name != NULL)
9952     {
9953 	HL_TABLE()[idx].sg_gui_bg =
9954 			    color_name2handle(HL_TABLE()[idx].sg_gui_bg_name);
9955 	didit = TRUE;
9956     }
9957 # ifdef FEAT_GUI
9958     if (HL_TABLE()[idx].sg_gui_sp_name != NULL)
9959     {
9960 	HL_TABLE()[idx].sg_gui_sp =
9961 			    color_name2handle(HL_TABLE()[idx].sg_gui_sp_name);
9962 	didit = TRUE;
9963     }
9964 # endif
9965     if (didit)	/* need to get a new attr number */
9966 	set_hl_attr(idx);
9967 }
9968 #endif
9969 
9970 #if defined(USER_HIGHLIGHT) && defined(FEAT_STL_OPT)
9971 /*
9972  * Apply difference between User[1-9] and HLF_S to HLF_SNC, HLF_ST or HLF_STNC.
9973  */
9974     static void
9975 combine_stl_hlt(
9976 	int id,
9977 	int id_S,
9978 	int id_alt,
9979 	int hlcnt,
9980 	int i,
9981 	int hlf,
9982 	int *table)
9983 {
9984     struct hl_group *hlt = HL_TABLE();
9985 
9986     if (id_alt == 0)
9987     {
9988 	vim_memset(&hlt[hlcnt + i], 0, sizeof(struct hl_group));
9989 	hlt[hlcnt + i].sg_term = highlight_attr[hlf];
9990 	hlt[hlcnt + i].sg_cterm = highlight_attr[hlf];
9991 #  if defined(FEAT_GUI) || defined(FEAT_EVAL)
9992 	hlt[hlcnt + i].sg_gui = highlight_attr[hlf];
9993 #  endif
9994     }
9995     else
9996 	mch_memmove(&hlt[hlcnt + i],
9997 		    &hlt[id_alt - 1],
9998 		    sizeof(struct hl_group));
9999     hlt[hlcnt + i].sg_link = 0;
10000 
10001     hlt[hlcnt + i].sg_term ^=
10002 	hlt[id - 1].sg_term ^ hlt[id_S - 1].sg_term;
10003     if (hlt[id - 1].sg_start != hlt[id_S - 1].sg_start)
10004 	hlt[hlcnt + i].sg_start = hlt[id - 1].sg_start;
10005     if (hlt[id - 1].sg_stop != hlt[id_S - 1].sg_stop)
10006 	hlt[hlcnt + i].sg_stop = hlt[id - 1].sg_stop;
10007     hlt[hlcnt + i].sg_cterm ^=
10008 	hlt[id - 1].sg_cterm ^ hlt[id_S - 1].sg_cterm;
10009     if (hlt[id - 1].sg_cterm_fg != hlt[id_S - 1].sg_cterm_fg)
10010 	hlt[hlcnt + i].sg_cterm_fg = hlt[id - 1].sg_cterm_fg;
10011     if (hlt[id - 1].sg_cterm_bg != hlt[id_S - 1].sg_cterm_bg)
10012 	hlt[hlcnt + i].sg_cterm_bg = hlt[id - 1].sg_cterm_bg;
10013 #  if defined(FEAT_GUI) || defined(FEAT_EVAL)
10014     hlt[hlcnt + i].sg_gui ^=
10015 	hlt[id - 1].sg_gui ^ hlt[id_S - 1].sg_gui;
10016 #  endif
10017 #  ifdef FEAT_GUI
10018     if (hlt[id - 1].sg_gui_fg != hlt[id_S - 1].sg_gui_fg)
10019 	hlt[hlcnt + i].sg_gui_fg = hlt[id - 1].sg_gui_fg;
10020     if (hlt[id - 1].sg_gui_bg != hlt[id_S - 1].sg_gui_bg)
10021 	hlt[hlcnt + i].sg_gui_bg = hlt[id - 1].sg_gui_bg;
10022     if (hlt[id - 1].sg_gui_sp != hlt[id_S - 1].sg_gui_sp)
10023 	hlt[hlcnt + i].sg_gui_sp = hlt[id - 1].sg_gui_sp;
10024     if (hlt[id - 1].sg_font != hlt[id_S - 1].sg_font)
10025 	hlt[hlcnt + i].sg_font = hlt[id - 1].sg_font;
10026 #   ifdef FEAT_XFONTSET
10027     if (hlt[id - 1].sg_fontset != hlt[id_S - 1].sg_fontset)
10028 	hlt[hlcnt + i].sg_fontset = hlt[id - 1].sg_fontset;
10029 #   endif
10030 #  endif
10031     highlight_ga.ga_len = hlcnt + i + 1;
10032     set_hl_attr(hlcnt + i);	/* At long last we can apply */
10033     table[i] = syn_id2attr(hlcnt + i + 1);
10034 }
10035 #endif
10036 
10037 /*
10038  * Translate the 'highlight' option into attributes in highlight_attr[] and
10039  * set up the user highlights User1..9.  If FEAT_STL_OPT is in use, a set of
10040  * corresponding highlights to use on top of HLF_SNC is computed.
10041  * Called only when the 'highlight' option has been changed and upon first
10042  * screen redraw after any :highlight command.
10043  * Return FAIL when an invalid flag is found in 'highlight'.  OK otherwise.
10044  */
10045     int
10046 highlight_changed(void)
10047 {
10048     int		hlf;
10049     int		i;
10050     char_u	*p;
10051     int		attr;
10052     char_u	*end;
10053     int		id;
10054 #ifdef USER_HIGHLIGHT
10055     char_u      userhl[10];
10056 # ifdef FEAT_STL_OPT
10057     int		id_S = -1;
10058     int		id_SNC = 0;
10059 #  ifdef FEAT_TERMINAL
10060     int		id_ST = 0;
10061     int		id_STNC = 0;
10062 #  endif
10063     int		hlcnt;
10064 # endif
10065 #endif
10066     static int	hl_flags[HLF_COUNT] = HL_FLAGS;
10067 
10068     need_highlight_changed = FALSE;
10069 
10070     /*
10071      * Clear all attributes.
10072      */
10073     for (hlf = 0; hlf < (int)HLF_COUNT; ++hlf)
10074 	highlight_attr[hlf] = 0;
10075 
10076     /*
10077      * First set all attributes to their default value.
10078      * Then use the attributes from the 'highlight' option.
10079      */
10080     for (i = 0; i < 2; ++i)
10081     {
10082 	if (i)
10083 	    p = p_hl;
10084 	else
10085 	    p = get_highlight_default();
10086 	if (p == NULL)	    /* just in case */
10087 	    continue;
10088 
10089 	while (*p)
10090 	{
10091 	    for (hlf = 0; hlf < (int)HLF_COUNT; ++hlf)
10092 		if (hl_flags[hlf] == *p)
10093 		    break;
10094 	    ++p;
10095 	    if (hlf == (int)HLF_COUNT || *p == NUL)
10096 		return FAIL;
10097 
10098 	    /*
10099 	     * Allow several hl_flags to be combined, like "bu" for
10100 	     * bold-underlined.
10101 	     */
10102 	    attr = 0;
10103 	    for ( ; *p && *p != ','; ++p)	    /* parse upto comma */
10104 	    {
10105 		if (VIM_ISWHITE(*p))		    /* ignore white space */
10106 		    continue;
10107 
10108 		if (attr > HL_ALL)  /* Combination with ':' is not allowed. */
10109 		    return FAIL;
10110 
10111 		switch (*p)
10112 		{
10113 		    case 'b':	attr |= HL_BOLD;
10114 				break;
10115 		    case 'i':	attr |= HL_ITALIC;
10116 				break;
10117 		    case '-':
10118 		    case 'n':			    /* no highlighting */
10119 				break;
10120 		    case 'r':	attr |= HL_INVERSE;
10121 				break;
10122 		    case 's':	attr |= HL_STANDOUT;
10123 				break;
10124 		    case 'u':	attr |= HL_UNDERLINE;
10125 				break;
10126 		    case 'c':	attr |= HL_UNDERCURL;
10127 				break;
10128 		    case 't':	attr |= HL_STRIKETHROUGH;
10129 				break;
10130 		    case ':':	++p;		    /* highlight group name */
10131 				if (attr || *p == NUL)	 /* no combinations */
10132 				    return FAIL;
10133 				end = vim_strchr(p, ',');
10134 				if (end == NULL)
10135 				    end = p + STRLEN(p);
10136 				id = syn_check_group(p, (int)(end - p));
10137 				if (id == 0)
10138 				    return FAIL;
10139 				attr = syn_id2attr(id);
10140 				p = end - 1;
10141 #if defined(FEAT_STL_OPT) && defined(USER_HIGHLIGHT)
10142 				if (hlf == (int)HLF_SNC)
10143 				    id_SNC = syn_get_final_id(id);
10144 # ifdef FEAT_TERMINAL
10145 				else if (hlf == (int)HLF_ST)
10146 				    id_ST = syn_get_final_id(id);
10147 				else if (hlf == (int)HLF_STNC)
10148 				    id_STNC = syn_get_final_id(id);
10149 # endif
10150 				else if (hlf == (int)HLF_S)
10151 				    id_S = syn_get_final_id(id);
10152 #endif
10153 				break;
10154 		    default:	return FAIL;
10155 		}
10156 	    }
10157 	    highlight_attr[hlf] = attr;
10158 
10159 	    p = skip_to_option_part(p);	    /* skip comma and spaces */
10160 	}
10161     }
10162 
10163 #ifdef USER_HIGHLIGHT
10164     /* Setup the user highlights
10165      *
10166      * Temporarily utilize 28 more hl entries:
10167      * 9 for User1-User9 combined with StatusLineNC
10168      * 9 for User1-User9 combined with StatusLineTerm
10169      * 9 for User1-User9 combined with StatusLineTermNC
10170      * 1 for StatusLine default
10171      * Have to be in there simultaneously in case of table overflows in
10172      * get_attr_entry()
10173      */
10174 # ifdef FEAT_STL_OPT
10175     if (ga_grow(&highlight_ga, 28) == FAIL)
10176 	return FAIL;
10177     hlcnt = highlight_ga.ga_len;
10178     if (id_S == -1)
10179     {
10180 	/* Make sure id_S is always valid to simplify code below. Use the last
10181 	 * entry. */
10182 	vim_memset(&HL_TABLE()[hlcnt + 27], 0, sizeof(struct hl_group));
10183 	HL_TABLE()[hlcnt + 18].sg_term = highlight_attr[HLF_S];
10184 	id_S = hlcnt + 19;
10185     }
10186 # endif
10187     for (i = 0; i < 9; i++)
10188     {
10189 	sprintf((char *)userhl, "User%d", i + 1);
10190 	id = syn_name2id(userhl);
10191 	if (id == 0)
10192 	{
10193 	    highlight_user[i] = 0;
10194 # ifdef FEAT_STL_OPT
10195 	    highlight_stlnc[i] = 0;
10196 #  ifdef FEAT_TERMINAL
10197 	    highlight_stlterm[i] = 0;
10198 	    highlight_stltermnc[i] = 0;
10199 #  endif
10200 # endif
10201 	}
10202 	else
10203 	{
10204 	    highlight_user[i] = syn_id2attr(id);
10205 # ifdef FEAT_STL_OPT
10206 	    combine_stl_hlt(id, id_S, id_SNC, hlcnt, i,
10207 						     HLF_SNC, highlight_stlnc);
10208 #  ifdef FEAT_TERMINAL
10209 	    combine_stl_hlt(id, id_S, id_ST, hlcnt + 9, i,
10210 						    HLF_ST, highlight_stlterm);
10211 	    combine_stl_hlt(id, id_S, id_STNC, hlcnt + 18, i,
10212 						HLF_STNC, highlight_stltermnc);
10213 #  endif
10214 # endif
10215 	}
10216     }
10217 # ifdef FEAT_STL_OPT
10218     highlight_ga.ga_len = hlcnt;
10219 # endif
10220 
10221 #endif /* USER_HIGHLIGHT */
10222 
10223     return OK;
10224 }
10225 
10226 #if defined(FEAT_CMDL_COMPL) || defined(PROTO)
10227 
10228 static void highlight_list(void);
10229 static void highlight_list_two(int cnt, int attr);
10230 
10231 /*
10232  * Handle command line completion for :highlight command.
10233  */
10234     void
10235 set_context_in_highlight_cmd(expand_T *xp, char_u *arg)
10236 {
10237     char_u	*p;
10238 
10239     /* Default: expand group names */
10240     xp->xp_context = EXPAND_HIGHLIGHT;
10241     xp->xp_pattern = arg;
10242     include_link = 2;
10243     include_default = 1;
10244 
10245     /* (part of) subcommand already typed */
10246     if (*arg != NUL)
10247     {
10248 	p = skiptowhite(arg);
10249 	if (*p != NUL)			/* past "default" or group name */
10250 	{
10251 	    include_default = 0;
10252 	    if (STRNCMP("default", arg, p - arg) == 0)
10253 	    {
10254 		arg = skipwhite(p);
10255 		xp->xp_pattern = arg;
10256 		p = skiptowhite(arg);
10257 	    }
10258 	    if (*p != NUL)			/* past group name */
10259 	    {
10260 		include_link = 0;
10261 		if (arg[1] == 'i' && arg[0] == 'N')
10262 		    highlight_list();
10263 		if (STRNCMP("link", arg, p - arg) == 0
10264 			|| STRNCMP("clear", arg, p - arg) == 0)
10265 		{
10266 		    xp->xp_pattern = skipwhite(p);
10267 		    p = skiptowhite(xp->xp_pattern);
10268 		    if (*p != NUL)		/* past first group name */
10269 		    {
10270 			xp->xp_pattern = skipwhite(p);
10271 			p = skiptowhite(xp->xp_pattern);
10272 		    }
10273 		}
10274 		if (*p != NUL)			/* past group name(s) */
10275 		    xp->xp_context = EXPAND_NOTHING;
10276 	    }
10277 	}
10278     }
10279 }
10280 
10281 /*
10282  * List highlighting matches in a nice way.
10283  */
10284     static void
10285 highlight_list(void)
10286 {
10287     int		i;
10288 
10289     for (i = 10; --i >= 0; )
10290 	highlight_list_two(i, HL_ATTR(HLF_D));
10291     for (i = 40; --i >= 0; )
10292 	highlight_list_two(99, 0);
10293 }
10294 
10295     static void
10296 highlight_list_two(int cnt, int attr)
10297 {
10298     msg_puts_attr((char_u *)&("N \bI \b!  \b"[cnt / 11]), attr);
10299     msg_clr_eos();
10300     out_flush();
10301     ui_delay(cnt == 99 ? 40L : (long)cnt * 50L, FALSE);
10302 }
10303 
10304 #endif /* FEAT_CMDL_COMPL */
10305 
10306 #if defined(FEAT_CMDL_COMPL) || (defined(FEAT_SYN_HL) && defined(FEAT_EVAL)) \
10307     || defined(FEAT_SIGNS) || defined(PROTO)
10308 /*
10309  * Function given to ExpandGeneric() to obtain the list of group names.
10310  */
10311     char_u *
10312 get_highlight_name(expand_T *xp UNUSED, int idx)
10313 {
10314     return get_highlight_name_ext(xp, idx, TRUE);
10315 }
10316 
10317 /*
10318  * Obtain a highlight group name.
10319  * When "skip_cleared" is TRUE don't return a cleared entry.
10320  */
10321     char_u *
10322 get_highlight_name_ext(expand_T *xp UNUSED, int idx, int skip_cleared)
10323 {
10324     if (idx < 0)
10325 	return NULL;
10326 
10327     /* Items are never removed from the table, skip the ones that were
10328      * cleared. */
10329     if (skip_cleared && idx < highlight_ga.ga_len && HL_TABLE()[idx].sg_cleared)
10330 	return (char_u *)"";
10331 
10332 #ifdef FEAT_CMDL_COMPL
10333     if (idx == highlight_ga.ga_len && include_none != 0)
10334 	return (char_u *)"none";
10335     if (idx == highlight_ga.ga_len + include_none && include_default != 0)
10336 	return (char_u *)"default";
10337     if (idx == highlight_ga.ga_len + include_none + include_default
10338 							 && include_link != 0)
10339 	return (char_u *)"link";
10340     if (idx == highlight_ga.ga_len + include_none + include_default + 1
10341 							 && include_link != 0)
10342 	return (char_u *)"clear";
10343 #endif
10344     if (idx >= highlight_ga.ga_len)
10345 	return NULL;
10346     return HL_TABLE()[idx].sg_name;
10347 }
10348 #endif
10349 
10350 #if defined(FEAT_GUI) || defined(PROTO)
10351 /*
10352  * Free all the highlight group fonts.
10353  * Used when quitting for systems which need it.
10354  */
10355     void
10356 free_highlight_fonts(void)
10357 {
10358     int	    idx;
10359 
10360     for (idx = 0; idx < highlight_ga.ga_len; ++idx)
10361     {
10362 	gui_mch_free_font(HL_TABLE()[idx].sg_font);
10363 	HL_TABLE()[idx].sg_font = NOFONT;
10364 # ifdef FEAT_XFONTSET
10365 	gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
10366 	HL_TABLE()[idx].sg_fontset = NOFONTSET;
10367 # endif
10368     }
10369 
10370     gui_mch_free_font(gui.norm_font);
10371 # ifdef FEAT_XFONTSET
10372     gui_mch_free_fontset(gui.fontset);
10373 # endif
10374 # ifndef FEAT_GUI_GTK
10375     gui_mch_free_font(gui.bold_font);
10376     gui_mch_free_font(gui.ital_font);
10377     gui_mch_free_font(gui.boldital_font);
10378 # endif
10379 }
10380 #endif
10381 
10382 /**************************************
10383  *  End of Highlighting stuff	      *
10384  **************************************/
10385