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