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