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