xref: /vim-8.2.3635/src/syntax.c (revision 57e4ee4d)
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, int free_font));
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(nlist, flags, attr)
4010     struct name_list	*nlist;
4011     int			flags;
4012     int			attr;
4013 {
4014     int		i;
4015 
4016     for (i = 0; nlist[i].flag != 0; ++i)
4017 	if (flags & nlist[i].flag)
4018 	{
4019 	    msg_puts_attr((char_u *)nlist[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("PmenuSbar ctermbg=Grey",
6520 	     "PmenuSbar ctermbg=Grey guibg=Grey"),
6521 #endif
6522 #ifdef FEAT_WINDOWS
6523 	CENT("TabLineSel term=bold cterm=bold",
6524 	     "TabLineSel term=bold cterm=bold gui=bold"),
6525 	CENT("TabLineFill term=reverse cterm=reverse",
6526 	     "TabLineFill term=reverse cterm=reverse gui=reverse"),
6527 #endif
6528 #ifdef FEAT_GUI
6529 	"Cursor guibg=fg guifg=bg",
6530 	"lCursor guibg=fg guifg=bg", /* should be different, but what? */
6531 #endif
6532 	NULL
6533     };
6534 
6535 static char *(highlight_init_light[]) =
6536     {
6537 	CENT("Directory term=bold ctermfg=DarkBlue",
6538 	     "Directory term=bold ctermfg=DarkBlue guifg=Blue"),
6539 	CENT("LineNr term=underline ctermfg=Brown",
6540 	     "LineNr term=underline ctermfg=Brown guifg=Brown"),
6541 	CENT("CursorLineNr term=bold ctermfg=Brown",
6542 	     "CursorLineNr term=bold ctermfg=Brown gui=bold 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("PmenuThumb ctermbg=Black",
6561 	     "PmenuThumb ctermbg=Black guibg=Black"),
6562 	CENT("Pmenu ctermbg=LightMagenta ctermfg=Black",
6563 	     "Pmenu ctermbg=LightMagenta ctermfg=Black guibg=LightMagenta"),
6564 	CENT("PmenuSel ctermbg=LightGrey ctermfg=Black",
6565 	     "PmenuSel ctermbg=LightGrey ctermfg=Black guibg=Grey"),
6566 #endif
6567 	CENT("SpecialKey term=bold ctermfg=DarkBlue",
6568 	     "SpecialKey term=bold ctermfg=DarkBlue guifg=Blue"),
6569 	CENT("Title term=bold ctermfg=DarkMagenta",
6570 	     "Title term=bold ctermfg=DarkMagenta gui=bold guifg=Magenta"),
6571 	CENT("WarningMsg term=standout ctermfg=DarkRed",
6572 	     "WarningMsg term=standout ctermfg=DarkRed guifg=Red"),
6573 #ifdef FEAT_WILDMENU
6574 	CENT("WildMenu term=standout ctermbg=Yellow ctermfg=Black",
6575 	     "WildMenu term=standout ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black"),
6576 #endif
6577 #ifdef FEAT_FOLDING
6578 	CENT("Folded term=standout ctermbg=Grey ctermfg=DarkBlue",
6579 	     "Folded term=standout ctermbg=Grey ctermfg=DarkBlue guibg=LightGrey guifg=DarkBlue"),
6580 	CENT("FoldColumn term=standout ctermbg=Grey ctermfg=DarkBlue",
6581 	     "FoldColumn term=standout ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue"),
6582 #endif
6583 #ifdef FEAT_SIGNS
6584 	CENT("SignColumn term=standout ctermbg=Grey ctermfg=DarkBlue",
6585 	     "SignColumn term=standout ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue"),
6586 #endif
6587 #ifdef FEAT_VISUAL
6588 	CENT("Visual term=reverse",
6589 	     "Visual term=reverse guibg=LightGrey"),
6590 #endif
6591 #ifdef FEAT_DIFF
6592 	CENT("DiffAdd term=bold ctermbg=LightBlue",
6593 	     "DiffAdd term=bold ctermbg=LightBlue guibg=LightBlue"),
6594 	CENT("DiffChange term=bold ctermbg=LightMagenta",
6595 	     "DiffChange term=bold ctermbg=LightMagenta guibg=LightMagenta"),
6596 	CENT("DiffDelete term=bold ctermfg=Blue ctermbg=LightCyan",
6597 	     "DiffDelete term=bold ctermfg=Blue ctermbg=LightCyan gui=bold guifg=Blue guibg=LightCyan"),
6598 #endif
6599 #ifdef FEAT_WINDOWS
6600 	CENT("TabLine term=underline cterm=underline ctermfg=black ctermbg=LightGrey",
6601 	     "TabLine term=underline cterm=underline ctermfg=black ctermbg=LightGrey gui=underline guibg=LightGrey"),
6602 #endif
6603 #ifdef FEAT_SYN_HL
6604 	CENT("CursorColumn term=reverse ctermbg=LightGrey",
6605 	     "CursorColumn term=reverse ctermbg=LightGrey guibg=Grey90"),
6606 	CENT("CursorLine term=underline cterm=underline",
6607 	     "CursorLine term=underline cterm=underline guibg=Grey90"),
6608 	CENT("ColorColumn term=reverse ctermbg=LightRed",
6609 	     "ColorColumn term=reverse ctermbg=LightRed guibg=LightRed"),
6610 #endif
6611 #ifdef FEAT_CONCEAL
6612 	CENT("Conceal ctermbg=DarkGrey ctermfg=LightGrey",
6613 	     "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey"),
6614 #endif
6615 #ifdef FEAT_AUTOCMD
6616 	CENT("MatchParen term=reverse ctermbg=Cyan",
6617 	     "MatchParen term=reverse ctermbg=Cyan guibg=Cyan"),
6618 #endif
6619 #ifdef FEAT_GUI
6620 	"Normal gui=NONE",
6621 #endif
6622 	NULL
6623     };
6624 
6625 static char *(highlight_init_dark[]) =
6626     {
6627 	CENT("Directory term=bold ctermfg=LightCyan",
6628 	     "Directory term=bold ctermfg=LightCyan guifg=Cyan"),
6629 	CENT("LineNr term=underline ctermfg=Yellow",
6630 	     "LineNr term=underline ctermfg=Yellow guifg=Yellow"),
6631 	CENT("CursorLineNr term=bold ctermfg=Yellow",
6632 	     "CursorLineNr term=bold ctermfg=Yellow gui=bold guifg=Yellow"),
6633 	CENT("MoreMsg term=bold ctermfg=LightGreen",
6634 	     "MoreMsg term=bold ctermfg=LightGreen gui=bold guifg=SeaGreen"),
6635 	CENT("Question term=standout ctermfg=LightGreen",
6636 	     "Question term=standout ctermfg=LightGreen gui=bold guifg=Green"),
6637 	CENT("Search term=reverse ctermbg=Yellow ctermfg=Black",
6638 	     "Search term=reverse ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black"),
6639 	CENT("SpecialKey term=bold ctermfg=LightBlue",
6640 	     "SpecialKey term=bold ctermfg=LightBlue guifg=Cyan"),
6641 #ifdef FEAT_SPELL
6642 	CENT("SpellBad term=reverse ctermbg=Red",
6643 	     "SpellBad term=reverse ctermbg=Red guisp=Red gui=undercurl"),
6644 	CENT("SpellCap term=reverse ctermbg=Blue",
6645 	     "SpellCap term=reverse ctermbg=Blue guisp=Blue gui=undercurl"),
6646 	CENT("SpellRare term=reverse ctermbg=Magenta",
6647 	     "SpellRare term=reverse ctermbg=Magenta guisp=Magenta gui=undercurl"),
6648 	CENT("SpellLocal term=underline ctermbg=Cyan",
6649 	     "SpellLocal term=underline ctermbg=Cyan guisp=Cyan gui=undercurl"),
6650 #endif
6651 #ifdef FEAT_INS_EXPAND
6652 	CENT("PmenuThumb ctermbg=White",
6653 	     "PmenuThumb ctermbg=White guibg=White"),
6654 	CENT("Pmenu ctermbg=Magenta ctermfg=Black",
6655 	     "Pmenu ctermbg=Magenta ctermfg=Black guibg=Magenta"),
6656 	CENT("PmenuSel ctermbg=Black ctermfg=DarkGrey",
6657 	     "PmenuSel ctermbg=Black ctermfg=DarkGrey guibg=DarkGrey"),
6658 #endif
6659 	CENT("Title term=bold ctermfg=LightMagenta",
6660 	     "Title term=bold ctermfg=LightMagenta gui=bold guifg=Magenta"),
6661 	CENT("WarningMsg term=standout ctermfg=LightRed",
6662 	     "WarningMsg term=standout ctermfg=LightRed guifg=Red"),
6663 #ifdef FEAT_WILDMENU
6664 	CENT("WildMenu term=standout ctermbg=Yellow ctermfg=Black",
6665 	     "WildMenu term=standout ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black"),
6666 #endif
6667 #ifdef FEAT_FOLDING
6668 	CENT("Folded term=standout ctermbg=DarkGrey ctermfg=Cyan",
6669 	     "Folded term=standout ctermbg=DarkGrey ctermfg=Cyan guibg=DarkGrey guifg=Cyan"),
6670 	CENT("FoldColumn term=standout ctermbg=DarkGrey ctermfg=Cyan",
6671 	     "FoldColumn term=standout ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan"),
6672 #endif
6673 #ifdef FEAT_SIGNS
6674 	CENT("SignColumn term=standout ctermbg=DarkGrey ctermfg=Cyan",
6675 	     "SignColumn term=standout ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan"),
6676 #endif
6677 #ifdef FEAT_VISUAL
6678 	CENT("Visual term=reverse",
6679 	     "Visual term=reverse guibg=DarkGrey"),
6680 #endif
6681 #ifdef FEAT_DIFF
6682 	CENT("DiffAdd term=bold ctermbg=DarkBlue",
6683 	     "DiffAdd term=bold ctermbg=DarkBlue guibg=DarkBlue"),
6684 	CENT("DiffChange term=bold ctermbg=DarkMagenta",
6685 	     "DiffChange term=bold ctermbg=DarkMagenta guibg=DarkMagenta"),
6686 	CENT("DiffDelete term=bold ctermfg=Blue ctermbg=DarkCyan",
6687 	     "DiffDelete term=bold ctermfg=Blue ctermbg=DarkCyan gui=bold guifg=Blue guibg=DarkCyan"),
6688 #endif
6689 #ifdef FEAT_WINDOWS
6690 	CENT("TabLine term=underline cterm=underline ctermfg=white ctermbg=DarkGrey",
6691 	     "TabLine term=underline cterm=underline ctermfg=white ctermbg=DarkGrey gui=underline guibg=DarkGrey"),
6692 #endif
6693 #ifdef FEAT_SYN_HL
6694 	CENT("CursorColumn term=reverse ctermbg=DarkGrey",
6695 	     "CursorColumn term=reverse ctermbg=DarkGrey guibg=Grey40"),
6696 	CENT("CursorLine term=underline cterm=underline",
6697 	     "CursorLine term=underline cterm=underline guibg=Grey40"),
6698 	CENT("ColorColumn term=reverse ctermbg=DarkRed",
6699 	     "ColorColumn term=reverse ctermbg=DarkRed guibg=DarkRed"),
6700 #endif
6701 #ifdef FEAT_AUTOCMD
6702 	CENT("MatchParen term=reverse ctermbg=DarkCyan",
6703 	     "MatchParen term=reverse ctermbg=DarkCyan guibg=DarkCyan"),
6704 #endif
6705 #ifdef FEAT_CONCEAL
6706 	CENT("Conceal ctermbg=DarkGrey ctermfg=LightGrey",
6707 	     "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey"),
6708 #endif
6709 #ifdef FEAT_GUI
6710 	"Normal gui=NONE",
6711 #endif
6712 	NULL
6713     };
6714 
6715     void
6716 init_highlight(both, reset)
6717     int		both;	    /* include groups where 'bg' doesn't matter */
6718     int		reset;	    /* clear group first */
6719 {
6720     int		i;
6721     char	**pp;
6722     static int	had_both = FALSE;
6723 #ifdef FEAT_EVAL
6724     char_u	*p;
6725 
6726     /*
6727      * Try finding the color scheme file.  Used when a color file was loaded
6728      * and 'background' or 't_Co' is changed.
6729      */
6730     p = get_var_value((char_u *)"g:colors_name");
6731     if (p != NULL && load_colors(p) == OK)
6732 	return;
6733 #endif
6734 
6735     /*
6736      * Didn't use a color file, use the compiled-in colors.
6737      */
6738     if (both)
6739     {
6740 	had_both = TRUE;
6741 	pp = highlight_init_both;
6742 	for (i = 0; pp[i] != NULL; ++i)
6743 	    do_highlight((char_u *)pp[i], reset, TRUE);
6744     }
6745     else if (!had_both)
6746 	/* Don't do anything before the call with both == TRUE from main().
6747 	 * Not everything has been setup then, and that call will overrule
6748 	 * everything anyway. */
6749 	return;
6750 
6751     if (*p_bg == 'l')
6752 	pp = highlight_init_light;
6753     else
6754 	pp = highlight_init_dark;
6755     for (i = 0; pp[i] != NULL; ++i)
6756 	do_highlight((char_u *)pp[i], reset, TRUE);
6757 
6758     /* Reverse looks ugly, but grey may not work for 8 colors.  Thus let it
6759      * depend on the number of colors available.
6760      * With 8 colors brown is equal to yellow, need to use black for Search fg
6761      * to avoid Statement highlighted text disappears.
6762      * Clear the attributes, needed when changing the t_Co value. */
6763     if (t_colors > 8)
6764 	do_highlight((char_u *)(*p_bg == 'l'
6765 		    ? "Visual cterm=NONE ctermbg=LightGrey"
6766 		    : "Visual cterm=NONE ctermbg=DarkGrey"), FALSE, TRUE);
6767     else
6768     {
6769 	do_highlight((char_u *)"Visual cterm=reverse ctermbg=NONE",
6770 								 FALSE, TRUE);
6771 	if (*p_bg == 'l')
6772 	    do_highlight((char_u *)"Search ctermfg=black", FALSE, TRUE);
6773     }
6774 
6775 #ifdef FEAT_SYN_HL
6776     /*
6777      * If syntax highlighting is enabled load the highlighting for it.
6778      */
6779     if (get_var_value((char_u *)"g:syntax_on") != NULL)
6780     {
6781 	static int	recursive = 0;
6782 
6783 	if (recursive >= 5)
6784 	    EMSG(_("E679: recursive loop loading syncolor.vim"));
6785 	else
6786 	{
6787 	    ++recursive;
6788 	    (void)source_runtime((char_u *)"syntax/syncolor.vim", TRUE);
6789 	    --recursive;
6790 	}
6791     }
6792 #endif
6793 }
6794 
6795 /*
6796  * Load color file "name".
6797  * Return OK for success, FAIL for failure.
6798  */
6799     int
6800 load_colors(name)
6801     char_u	*name;
6802 {
6803     char_u	*buf;
6804     int		retval = FAIL;
6805     static int	recursive = FALSE;
6806 
6807     /* When being called recursively, this is probably because setting
6808      * 'background' caused the highlighting to be reloaded.  This means it is
6809      * working, thus we should return OK. */
6810     if (recursive)
6811 	return OK;
6812 
6813     recursive = TRUE;
6814     buf = alloc((unsigned)(STRLEN(name) + 12));
6815     if (buf != NULL)
6816     {
6817 	sprintf((char *)buf, "colors/%s.vim", name);
6818 	retval = source_runtime(buf, FALSE);
6819 	vim_free(buf);
6820 #ifdef FEAT_AUTOCMD
6821 	apply_autocmds(EVENT_COLORSCHEME, NULL, NULL, FALSE, curbuf);
6822 #endif
6823     }
6824     recursive = FALSE;
6825 
6826     return retval;
6827 }
6828 
6829 /*
6830  * Handle the ":highlight .." command.
6831  * When using ":hi clear" this is called recursively for each group with
6832  * "forceit" and "init" both TRUE.
6833  */
6834     void
6835 do_highlight(line, forceit, init)
6836     char_u	*line;
6837     int		forceit;
6838     int		init;	    /* TRUE when called for initializing */
6839 {
6840     char_u	*name_end;
6841     char_u	*p;
6842     char_u	*linep;
6843     char_u	*key_start;
6844     char_u	*arg_start;
6845     char_u	*key = NULL, *arg = NULL;
6846     long	i;
6847     int		off;
6848     int		len;
6849     int		attr;
6850     int		id;
6851     int		idx;
6852     int		dodefault = FALSE;
6853     int		doclear = FALSE;
6854     int		dolink = FALSE;
6855     int		error = FALSE;
6856     int		color;
6857     int		is_normal_group = FALSE;	/* "Normal" group */
6858 #ifdef FEAT_GUI_X11
6859     int		is_menu_group = FALSE;		/* "Menu" group */
6860     int		is_scrollbar_group = FALSE;	/* "Scrollbar" group */
6861     int		is_tooltip_group = FALSE;	/* "Tooltip" group */
6862     int		do_colors = FALSE;		/* need to update colors? */
6863 #else
6864 # define is_menu_group 0
6865 # define is_tooltip_group 0
6866 #endif
6867 
6868     /*
6869      * If no argument, list current highlighting.
6870      */
6871     if (ends_excmd(*line))
6872     {
6873 	for (i = 1; i <= highlight_ga.ga_len && !got_int; ++i)
6874 	    /* TODO: only call when the group has attributes set */
6875 	    highlight_list_one((int)i);
6876 	return;
6877     }
6878 
6879     /*
6880      * Isolate the name.
6881      */
6882     name_end = skiptowhite(line);
6883     linep = skipwhite(name_end);
6884 
6885     /*
6886      * Check for "default" argument.
6887      */
6888     if (STRNCMP(line, "default", name_end - line) == 0)
6889     {
6890 	dodefault = TRUE;
6891 	line = linep;
6892 	name_end = skiptowhite(line);
6893 	linep = skipwhite(name_end);
6894     }
6895 
6896     /*
6897      * Check for "clear" or "link" argument.
6898      */
6899     if (STRNCMP(line, "clear", name_end - line) == 0)
6900 	doclear = TRUE;
6901     if (STRNCMP(line, "link", name_end - line) == 0)
6902 	dolink = TRUE;
6903 
6904     /*
6905      * ":highlight {group-name}": list highlighting for one group.
6906      */
6907     if (!doclear && !dolink && ends_excmd(*linep))
6908     {
6909 	id = syn_namen2id(line, (int)(name_end - line));
6910 	if (id == 0)
6911 	    EMSG2(_("E411: highlight group not found: %s"), line);
6912 	else
6913 	    highlight_list_one(id);
6914 	return;
6915     }
6916 
6917     /*
6918      * Handle ":highlight link {from} {to}" command.
6919      */
6920     if (dolink)
6921     {
6922 	char_u	    *from_start = linep;
6923 	char_u	    *from_end;
6924 	char_u	    *to_start;
6925 	char_u	    *to_end;
6926 	int	    from_id;
6927 	int	    to_id;
6928 
6929 	from_end = skiptowhite(from_start);
6930 	to_start = skipwhite(from_end);
6931 	to_end	 = skiptowhite(to_start);
6932 
6933 	if (ends_excmd(*from_start) || ends_excmd(*to_start))
6934 	{
6935 	    EMSG2(_("E412: Not enough arguments: \":highlight link %s\""),
6936 								  from_start);
6937 	    return;
6938 	}
6939 
6940 	if (!ends_excmd(*skipwhite(to_end)))
6941 	{
6942 	    EMSG2(_("E413: Too many arguments: \":highlight link %s\""), from_start);
6943 	    return;
6944 	}
6945 
6946 	from_id = syn_check_group(from_start, (int)(from_end - from_start));
6947 	if (STRNCMP(to_start, "NONE", 4) == 0)
6948 	    to_id = 0;
6949 	else
6950 	    to_id = syn_check_group(to_start, (int)(to_end - to_start));
6951 
6952 	if (from_id > 0 && (!init || HL_TABLE()[from_id - 1].sg_set == 0))
6953 	{
6954 	    /*
6955 	     * Don't allow a link when there already is some highlighting
6956 	     * for the group, unless '!' is used
6957 	     */
6958 	    if (to_id > 0 && !forceit && !init
6959 				   && hl_has_settings(from_id - 1, dodefault))
6960 	    {
6961 		if (sourcing_name == NULL && !dodefault)
6962 		    EMSG(_("E414: group has settings, highlight link ignored"));
6963 	    }
6964 	    else
6965 	    {
6966 		if (!init)
6967 		    HL_TABLE()[from_id - 1].sg_set |= SG_LINK;
6968 		HL_TABLE()[from_id - 1].sg_link = to_id;
6969 #ifdef FEAT_EVAL
6970 		HL_TABLE()[from_id - 1].sg_scriptID = current_SID;
6971 #endif
6972 		redraw_all_later(SOME_VALID);
6973 	    }
6974 	}
6975 
6976 	/* Only call highlight_changed() once, after sourcing a syntax file */
6977 	need_highlight_changed = TRUE;
6978 
6979 	return;
6980     }
6981 
6982     if (doclear)
6983     {
6984 	/*
6985 	 * ":highlight clear [group]" command.
6986 	 */
6987 	line = linep;
6988 	if (ends_excmd(*line))
6989 	{
6990 #ifdef FEAT_GUI
6991 	    /* First, we do not destroy the old values, but allocate the new
6992 	     * ones and update the display. THEN we destroy the old values.
6993 	     * If we destroy the old values first, then the old values
6994 	     * (such as GuiFont's or GuiFontset's) will still be displayed but
6995 	     * invalid because they were free'd.
6996 	     */
6997 	    if (gui.in_use)
6998 	    {
6999 # ifdef FEAT_BEVAL_TIP
7000 		gui_init_tooltip_font();
7001 # endif
7002 # if defined(FEAT_MENU) && (defined(FEAT_GUI_ATHENA) || defined(FEAT_GUI_MOTIF))
7003 		gui_init_menu_font();
7004 # endif
7005 	    }
7006 # if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_X11)
7007 	    gui_mch_def_colors();
7008 # endif
7009 # ifdef FEAT_GUI_X11
7010 #  ifdef FEAT_MENU
7011 
7012 	    /* This only needs to be done when there is no Menu highlight
7013 	     * group defined by default, which IS currently the case.
7014 	     */
7015 	    gui_mch_new_menu_colors();
7016 #  endif
7017 	    if (gui.in_use)
7018 	    {
7019 		gui_new_scrollbar_colors();
7020 #  ifdef FEAT_BEVAL
7021 		gui_mch_new_tooltip_colors();
7022 #  endif
7023 #  ifdef FEAT_MENU
7024 		gui_mch_new_menu_font();
7025 #  endif
7026 	    }
7027 # endif
7028 
7029 	    /* Ok, we're done allocating the new default graphics items.
7030 	     * The screen should already be refreshed at this point.
7031 	     * It is now Ok to clear out the old data.
7032 	     */
7033 #endif
7034 #ifdef FEAT_EVAL
7035 	    do_unlet((char_u *)"colors_name", TRUE);
7036 #endif
7037 	    restore_cterm_colors();
7038 
7039 	    /*
7040 	     * Clear all default highlight groups and load the defaults.
7041 	     */
7042 	    for (idx = 0; idx < highlight_ga.ga_len; ++idx)
7043 		highlight_clear(idx);
7044 	    init_highlight(TRUE, TRUE);
7045 #ifdef FEAT_GUI
7046 	    if (gui.in_use)
7047 		highlight_gui_started();
7048 #endif
7049 	    highlight_changed();
7050 	    redraw_later_clear();
7051 	    return;
7052 	}
7053 	name_end = skiptowhite(line);
7054 	linep = skipwhite(name_end);
7055     }
7056 
7057     /*
7058      * Find the group name in the table.  If it does not exist yet, add it.
7059      */
7060     id = syn_check_group(line, (int)(name_end - line));
7061     if (id == 0)			/* failed (out of memory) */
7062 	return;
7063     idx = id - 1;			/* index is ID minus one */
7064 
7065     /* Return if "default" was used and the group already has settings. */
7066     if (dodefault && hl_has_settings(idx, TRUE))
7067 	return;
7068 
7069     if (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0)
7070 	is_normal_group = TRUE;
7071 #ifdef FEAT_GUI_X11
7072     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "MENU") == 0)
7073 	is_menu_group = TRUE;
7074     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "SCROLLBAR") == 0)
7075 	is_scrollbar_group = TRUE;
7076     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "TOOLTIP") == 0)
7077 	is_tooltip_group = TRUE;
7078 #endif
7079 
7080     /* Clear the highlighting for ":hi clear {group}" and ":hi clear". */
7081     if (doclear || (forceit && init))
7082     {
7083 	highlight_clear(idx);
7084 	if (!doclear)
7085 	    HL_TABLE()[idx].sg_set = 0;
7086     }
7087 
7088     if (!doclear)
7089       while (!ends_excmd(*linep))
7090       {
7091 	key_start = linep;
7092 	if (*linep == '=')
7093 	{
7094 	    EMSG2(_("E415: unexpected equal sign: %s"), key_start);
7095 	    error = TRUE;
7096 	    break;
7097 	}
7098 
7099 	/*
7100 	 * Isolate the key ("term", "ctermfg", "ctermbg", "font", "guifg" or
7101 	 * "guibg").
7102 	 */
7103 	while (*linep && !vim_iswhite(*linep) && *linep != '=')
7104 	    ++linep;
7105 	vim_free(key);
7106 	key = vim_strnsave_up(key_start, (int)(linep - key_start));
7107 	if (key == NULL)
7108 	{
7109 	    error = TRUE;
7110 	    break;
7111 	}
7112 	linep = skipwhite(linep);
7113 
7114 	if (STRCMP(key, "NONE") == 0)
7115 	{
7116 	    if (!init || HL_TABLE()[idx].sg_set == 0)
7117 	    {
7118 		if (!init)
7119 		    HL_TABLE()[idx].sg_set |= SG_TERM+SG_CTERM+SG_GUI;
7120 		highlight_clear(idx);
7121 	    }
7122 	    continue;
7123 	}
7124 
7125 	/*
7126 	 * Check for the equal sign.
7127 	 */
7128 	if (*linep != '=')
7129 	{
7130 	    EMSG2(_("E416: missing equal sign: %s"), key_start);
7131 	    error = TRUE;
7132 	    break;
7133 	}
7134 	++linep;
7135 
7136 	/*
7137 	 * Isolate the argument.
7138 	 */
7139 	linep = skipwhite(linep);
7140 	if (*linep == '\'')		/* guifg='color name' */
7141 	{
7142 	    arg_start = ++linep;
7143 	    linep = vim_strchr(linep, '\'');
7144 	    if (linep == NULL)
7145 	    {
7146 		EMSG2(_(e_invarg2), key_start);
7147 		error = TRUE;
7148 		break;
7149 	    }
7150 	}
7151 	else
7152 	{
7153 	    arg_start = linep;
7154 	    linep = skiptowhite(linep);
7155 	}
7156 	if (linep == arg_start)
7157 	{
7158 	    EMSG2(_("E417: missing argument: %s"), key_start);
7159 	    error = TRUE;
7160 	    break;
7161 	}
7162 	vim_free(arg);
7163 	arg = vim_strnsave(arg_start, (int)(linep - arg_start));
7164 	if (arg == NULL)
7165 	{
7166 	    error = TRUE;
7167 	    break;
7168 	}
7169 	if (*linep == '\'')
7170 	    ++linep;
7171 
7172 	/*
7173 	 * Store the argument.
7174 	 */
7175 	if (  STRCMP(key, "TERM") == 0
7176 		|| STRCMP(key, "CTERM") == 0
7177 		|| STRCMP(key, "GUI") == 0)
7178 	{
7179 	    attr = 0;
7180 	    off = 0;
7181 	    while (arg[off] != NUL)
7182 	    {
7183 		for (i = sizeof(hl_attr_table) / sizeof(int); --i >= 0; )
7184 		{
7185 		    len = (int)STRLEN(hl_name_table[i]);
7186 		    if (STRNICMP(arg + off, hl_name_table[i], len) == 0)
7187 		    {
7188 			attr |= hl_attr_table[i];
7189 			off += len;
7190 			break;
7191 		    }
7192 		}
7193 		if (i < 0)
7194 		{
7195 		    EMSG2(_("E418: Illegal value: %s"), arg);
7196 		    error = TRUE;
7197 		    break;
7198 		}
7199 		if (arg[off] == ',')		/* another one follows */
7200 		    ++off;
7201 	    }
7202 	    if (error)
7203 		break;
7204 	    if (*key == 'T')
7205 	    {
7206 		if (!init || !(HL_TABLE()[idx].sg_set & SG_TERM))
7207 		{
7208 		    if (!init)
7209 			HL_TABLE()[idx].sg_set |= SG_TERM;
7210 		    HL_TABLE()[idx].sg_term = attr;
7211 		}
7212 	    }
7213 	    else if (*key == 'C')
7214 	    {
7215 		if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM))
7216 		{
7217 		    if (!init)
7218 			HL_TABLE()[idx].sg_set |= SG_CTERM;
7219 		    HL_TABLE()[idx].sg_cterm = attr;
7220 		    HL_TABLE()[idx].sg_cterm_bold = FALSE;
7221 		}
7222 	    }
7223 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
7224 	    else
7225 	    {
7226 		if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
7227 		{
7228 		    if (!init)
7229 			HL_TABLE()[idx].sg_set |= SG_GUI;
7230 		    HL_TABLE()[idx].sg_gui = attr;
7231 		}
7232 	    }
7233 #endif
7234 	}
7235 	else if (STRCMP(key, "FONT") == 0)
7236 	{
7237 	    /* in non-GUI fonts are simply ignored */
7238 #ifdef FEAT_GUI
7239 	    if (!gui.shell_created)
7240 	    {
7241 		/* GUI not started yet, always accept the name. */
7242 		vim_free(HL_TABLE()[idx].sg_font_name);
7243 		HL_TABLE()[idx].sg_font_name = vim_strsave(arg);
7244 	    }
7245 	    else
7246 	    {
7247 		GuiFont temp_sg_font = HL_TABLE()[idx].sg_font;
7248 # ifdef FEAT_XFONTSET
7249 		GuiFontset temp_sg_fontset = HL_TABLE()[idx].sg_fontset;
7250 # endif
7251 		/* First, save the current font/fontset.
7252 		 * Then try to allocate the font/fontset.
7253 		 * If the allocation fails, HL_TABLE()[idx].sg_font OR
7254 		 * sg_fontset will be set to NOFONT or NOFONTSET respectively.
7255 		 */
7256 
7257 		HL_TABLE()[idx].sg_font = NOFONT;
7258 # ifdef FEAT_XFONTSET
7259 		HL_TABLE()[idx].sg_fontset = NOFONTSET;
7260 # endif
7261 		hl_do_font(idx, arg, is_normal_group, is_menu_group,
7262 						     is_tooltip_group, FALSE);
7263 
7264 # ifdef FEAT_XFONTSET
7265 		if (HL_TABLE()[idx].sg_fontset != NOFONTSET)
7266 		{
7267 		    /* New fontset was accepted. Free the old one, if there
7268 		     * was one. */
7269 		    gui_mch_free_fontset(temp_sg_fontset);
7270 		    vim_free(HL_TABLE()[idx].sg_font_name);
7271 		    HL_TABLE()[idx].sg_font_name = vim_strsave(arg);
7272 		}
7273 		else
7274 		    HL_TABLE()[idx].sg_fontset = temp_sg_fontset;
7275 # endif
7276 		if (HL_TABLE()[idx].sg_font != NOFONT)
7277 		{
7278 		    /* New font was accepted. Free the old one, if there was
7279 		     * one. */
7280 		    gui_mch_free_font(temp_sg_font);
7281 		    vim_free(HL_TABLE()[idx].sg_font_name);
7282 		    HL_TABLE()[idx].sg_font_name = vim_strsave(arg);
7283 		}
7284 		else
7285 		    HL_TABLE()[idx].sg_font = temp_sg_font;
7286 	    }
7287 #endif
7288 	}
7289 	else if (STRCMP(key, "CTERMFG") == 0 || STRCMP(key, "CTERMBG") == 0)
7290 	{
7291 	  if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM))
7292 	  {
7293 	    if (!init)
7294 		HL_TABLE()[idx].sg_set |= SG_CTERM;
7295 
7296 	    /* When setting the foreground color, and previously the "bold"
7297 	     * flag was set for a light color, reset it now */
7298 	    if (key[5] == 'F' && HL_TABLE()[idx].sg_cterm_bold)
7299 	    {
7300 		HL_TABLE()[idx].sg_cterm &= ~HL_BOLD;
7301 		HL_TABLE()[idx].sg_cterm_bold = FALSE;
7302 	    }
7303 
7304 	    if (VIM_ISDIGIT(*arg))
7305 		color = atoi((char *)arg);
7306 	    else if (STRICMP(arg, "fg") == 0)
7307 	    {
7308 		if (cterm_normal_fg_color)
7309 		    color = cterm_normal_fg_color - 1;
7310 		else
7311 		{
7312 		    EMSG(_("E419: FG color unknown"));
7313 		    error = TRUE;
7314 		    break;
7315 		}
7316 	    }
7317 	    else if (STRICMP(arg, "bg") == 0)
7318 	    {
7319 		if (cterm_normal_bg_color > 0)
7320 		    color = cterm_normal_bg_color - 1;
7321 		else
7322 		{
7323 		    EMSG(_("E420: BG color unknown"));
7324 		    error = TRUE;
7325 		    break;
7326 		}
7327 	    }
7328 	    else
7329 	    {
7330 		static char *(color_names[28]) = {
7331 			    "Black", "DarkBlue", "DarkGreen", "DarkCyan",
7332 			    "DarkRed", "DarkMagenta", "Brown", "DarkYellow",
7333 			    "Gray", "Grey",
7334 			    "LightGray", "LightGrey", "DarkGray", "DarkGrey",
7335 			    "Blue", "LightBlue", "Green", "LightGreen",
7336 			    "Cyan", "LightCyan", "Red", "LightRed", "Magenta",
7337 			    "LightMagenta", "Yellow", "LightYellow", "White", "NONE"};
7338 		static int color_numbers_16[28] = {0, 1, 2, 3,
7339 						 4, 5, 6, 6,
7340 						 7, 7,
7341 						 7, 7, 8, 8,
7342 						 9, 9, 10, 10,
7343 						 11, 11, 12, 12, 13,
7344 						 13, 14, 14, 15, -1};
7345 		/* for xterm with 88 colors... */
7346 		static int color_numbers_88[28] = {0, 4, 2, 6,
7347 						 1, 5, 32, 72,
7348 						 84, 84,
7349 						 7, 7, 82, 82,
7350 						 12, 43, 10, 61,
7351 						 14, 63, 9, 74, 13,
7352 						 75, 11, 78, 15, -1};
7353 		/* for xterm with 256 colors... */
7354 		static int color_numbers_256[28] = {0, 4, 2, 6,
7355 						 1, 5, 130, 130,
7356 						 248, 248,
7357 						 7, 7, 242, 242,
7358 						 12, 81, 10, 121,
7359 						 14, 159, 9, 224, 13,
7360 						 225, 11, 229, 15, -1};
7361 		/* for terminals with less than 16 colors... */
7362 		static int color_numbers_8[28] = {0, 4, 2, 6,
7363 						 1, 5, 3, 3,
7364 						 7, 7,
7365 						 7, 7, 0+8, 0+8,
7366 						 4+8, 4+8, 2+8, 2+8,
7367 						 6+8, 6+8, 1+8, 1+8, 5+8,
7368 						 5+8, 3+8, 3+8, 7+8, -1};
7369 #if defined(__QNXNTO__)
7370 		static int *color_numbers_8_qansi = color_numbers_8;
7371 		/* On qnx, the 8 & 16 color arrays are the same */
7372 		if (STRNCMP(T_NAME, "qansi", 5) == 0)
7373 		    color_numbers_8_qansi = color_numbers_16;
7374 #endif
7375 
7376 		/* reduce calls to STRICMP a bit, it can be slow */
7377 		off = TOUPPER_ASC(*arg);
7378 		for (i = (sizeof(color_names) / sizeof(char *)); --i >= 0; )
7379 		    if (off == color_names[i][0]
7380 				 && STRICMP(arg + 1, color_names[i] + 1) == 0)
7381 			break;
7382 		if (i < 0)
7383 		{
7384 		    EMSG2(_("E421: Color name or number not recognized: %s"), key_start);
7385 		    error = TRUE;
7386 		    break;
7387 		}
7388 
7389 		/* Use the _16 table to check if its a valid color name. */
7390 		color = color_numbers_16[i];
7391 		if (color >= 0)
7392 		{
7393 		    if (t_colors == 8)
7394 		    {
7395 			/* t_Co is 8: use the 8 colors table */
7396 #if defined(__QNXNTO__)
7397 			color = color_numbers_8_qansi[i];
7398 #else
7399 			color = color_numbers_8[i];
7400 #endif
7401 			if (key[5] == 'F')
7402 			{
7403 			    /* set/reset bold attribute to get light foreground
7404 			     * colors (on some terminals, e.g. "linux") */
7405 			    if (color & 8)
7406 			    {
7407 				HL_TABLE()[idx].sg_cterm |= HL_BOLD;
7408 				HL_TABLE()[idx].sg_cterm_bold = TRUE;
7409 			    }
7410 			    else
7411 				HL_TABLE()[idx].sg_cterm &= ~HL_BOLD;
7412 			}
7413 			color &= 7;	/* truncate to 8 colors */
7414 		    }
7415 		    else if (t_colors == 16 || t_colors == 88
7416 							   || t_colors == 256)
7417 		    {
7418 			/*
7419 			 * Guess: if the termcap entry ends in 'm', it is
7420 			 * probably an xterm-like terminal.  Use the changed
7421 			 * order for colors.
7422 			 */
7423 			if (*T_CAF != NUL)
7424 			    p = T_CAF;
7425 			else
7426 			    p = T_CSF;
7427 			if (*p != NUL && *(p + STRLEN(p) - 1) == 'm')
7428 			    switch (t_colors)
7429 			    {
7430 				case 16:
7431 				    color = color_numbers_8[i];
7432 				    break;
7433 				case 88:
7434 				    color = color_numbers_88[i];
7435 				    break;
7436 				case 256:
7437 				    color = color_numbers_256[i];
7438 				    break;
7439 			    }
7440 		    }
7441 		}
7442 	    }
7443 	    /* Add one to the argument, to avoid zero.  Zero is used for
7444 	     * "NONE", then "color" is -1. */
7445 	    if (key[5] == 'F')
7446 	    {
7447 		HL_TABLE()[idx].sg_cterm_fg = color + 1;
7448 		if (is_normal_group)
7449 		{
7450 		    cterm_normal_fg_color = color + 1;
7451 		    cterm_normal_fg_bold = (HL_TABLE()[idx].sg_cterm & HL_BOLD);
7452 #ifdef FEAT_GUI
7453 		    /* Don't do this if the GUI is used. */
7454 		    if (!gui.in_use && !gui.starting)
7455 #endif
7456 		    {
7457 			must_redraw = CLEAR;
7458 			if (termcap_active && color >= 0)
7459 			    term_fg_color(color);
7460 		    }
7461 		}
7462 	    }
7463 	    else
7464 	    {
7465 		HL_TABLE()[idx].sg_cterm_bg = color + 1;
7466 		if (is_normal_group)
7467 		{
7468 		    cterm_normal_bg_color = color + 1;
7469 #ifdef FEAT_GUI
7470 		    /* Don't mess with 'background' if the GUI is used. */
7471 		    if (!gui.in_use && !gui.starting)
7472 #endif
7473 		    {
7474 			must_redraw = CLEAR;
7475 			if (color >= 0)
7476 			{
7477 			    if (termcap_active)
7478 				term_bg_color(color);
7479 			    if (t_colors < 16)
7480 				i = (color == 0 || color == 4);
7481 			    else
7482 				i = (color < 7 || color == 8);
7483 			    /* Set the 'background' option if the value is
7484 			     * wrong. */
7485 			    if (i != (*p_bg == 'd'))
7486 				set_option_value((char_u *)"bg", 0L,
7487 					i ?  (char_u *)"dark"
7488 					  : (char_u *)"light", 0);
7489 			}
7490 		    }
7491 		}
7492 	    }
7493 	  }
7494 	}
7495 	else if (STRCMP(key, "GUIFG") == 0)
7496 	{
7497 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
7498 	    if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
7499 	    {
7500 		if (!init)
7501 		    HL_TABLE()[idx].sg_set |= SG_GUI;
7502 
7503 # ifdef FEAT_GUI
7504 		/* In GUI guifg colors are only used when recognized */
7505 		i = color_name2handle(arg);
7506 		if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !gui.in_use)
7507 		{
7508 		    HL_TABLE()[idx].sg_gui_fg = i;
7509 # endif
7510 		    vim_free(HL_TABLE()[idx].sg_gui_fg_name);
7511 		    if (STRCMP(arg, "NONE"))
7512 			HL_TABLE()[idx].sg_gui_fg_name = vim_strsave(arg);
7513 		    else
7514 			HL_TABLE()[idx].sg_gui_fg_name = NULL;
7515 # ifdef FEAT_GUI
7516 #  ifdef FEAT_GUI_X11
7517 		    if (is_menu_group)
7518 			gui.menu_fg_pixel = i;
7519 		    if (is_scrollbar_group)
7520 			gui.scroll_fg_pixel = i;
7521 #   ifdef FEAT_BEVAL
7522 		    if (is_tooltip_group)
7523 			gui.tooltip_fg_pixel = i;
7524 #   endif
7525 		    do_colors = TRUE;
7526 #  endif
7527 		}
7528 # endif
7529 	    }
7530 #endif
7531 	}
7532 	else if (STRCMP(key, "GUIBG") == 0)
7533 	{
7534 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
7535 	    if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
7536 	    {
7537 		if (!init)
7538 		    HL_TABLE()[idx].sg_set |= SG_GUI;
7539 
7540 # ifdef FEAT_GUI
7541 		/* In GUI guifg colors are only used when recognized */
7542 		i = color_name2handle(arg);
7543 		if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !gui.in_use)
7544 		{
7545 		    HL_TABLE()[idx].sg_gui_bg = i;
7546 # endif
7547 		    vim_free(HL_TABLE()[idx].sg_gui_bg_name);
7548 		    if (STRCMP(arg, "NONE") != 0)
7549 			HL_TABLE()[idx].sg_gui_bg_name = vim_strsave(arg);
7550 		    else
7551 			HL_TABLE()[idx].sg_gui_bg_name = NULL;
7552 # ifdef FEAT_GUI
7553 #  ifdef FEAT_GUI_X11
7554 		    if (is_menu_group)
7555 			gui.menu_bg_pixel = i;
7556 		    if (is_scrollbar_group)
7557 			gui.scroll_bg_pixel = i;
7558 #   ifdef FEAT_BEVAL
7559 		    if (is_tooltip_group)
7560 			gui.tooltip_bg_pixel = i;
7561 #   endif
7562 		    do_colors = TRUE;
7563 #  endif
7564 		}
7565 # endif
7566 	    }
7567 #endif
7568 	}
7569 	else if (STRCMP(key, "GUISP") == 0)
7570 	{
7571 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
7572 	    if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
7573 	    {
7574 		if (!init)
7575 		    HL_TABLE()[idx].sg_set |= SG_GUI;
7576 
7577 # ifdef FEAT_GUI
7578 		i = color_name2handle(arg);
7579 		if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !gui.in_use)
7580 		{
7581 		    HL_TABLE()[idx].sg_gui_sp = i;
7582 # endif
7583 		    vim_free(HL_TABLE()[idx].sg_gui_sp_name);
7584 		    if (STRCMP(arg, "NONE") != 0)
7585 			HL_TABLE()[idx].sg_gui_sp_name = vim_strsave(arg);
7586 		    else
7587 			HL_TABLE()[idx].sg_gui_sp_name = NULL;
7588 # ifdef FEAT_GUI
7589 		}
7590 # endif
7591 	    }
7592 #endif
7593 	}
7594 	else if (STRCMP(key, "START") == 0 || STRCMP(key, "STOP") == 0)
7595 	{
7596 	    char_u	buf[100];
7597 	    char_u	*tname;
7598 
7599 	    if (!init)
7600 		HL_TABLE()[idx].sg_set |= SG_TERM;
7601 
7602 	    /*
7603 	     * The "start" and "stop"  arguments can be a literal escape
7604 	     * sequence, or a comma separated list of terminal codes.
7605 	     */
7606 	    if (STRNCMP(arg, "t_", 2) == 0)
7607 	    {
7608 		off = 0;
7609 		buf[0] = 0;
7610 		while (arg[off] != NUL)
7611 		{
7612 		    /* Isolate one termcap name */
7613 		    for (len = 0; arg[off + len] &&
7614 						 arg[off + len] != ','; ++len)
7615 			;
7616 		    tname = vim_strnsave(arg + off, len);
7617 		    if (tname == NULL)		/* out of memory */
7618 		    {
7619 			error = TRUE;
7620 			break;
7621 		    }
7622 		    /* lookup the escape sequence for the item */
7623 		    p = get_term_code(tname);
7624 		    vim_free(tname);
7625 		    if (p == NULL)	    /* ignore non-existing things */
7626 			p = (char_u *)"";
7627 
7628 		    /* Append it to the already found stuff */
7629 		    if ((int)(STRLEN(buf) + STRLEN(p)) >= 99)
7630 		    {
7631 			EMSG2(_("E422: terminal code too long: %s"), arg);
7632 			error = TRUE;
7633 			break;
7634 		    }
7635 		    STRCAT(buf, p);
7636 
7637 		    /* Advance to the next item */
7638 		    off += len;
7639 		    if (arg[off] == ',')	    /* another one follows */
7640 			++off;
7641 		}
7642 	    }
7643 	    else
7644 	    {
7645 		/*
7646 		 * Copy characters from arg[] to buf[], translating <> codes.
7647 		 */
7648 		for (p = arg, off = 0; off < 100 - 6 && *p; )
7649 		{
7650 		    len = trans_special(&p, buf + off, FALSE);
7651 		    if (len > 0)	    /* recognized special char */
7652 			off += len;
7653 		    else		    /* copy as normal char */
7654 			buf[off++] = *p++;
7655 		}
7656 		buf[off] = NUL;
7657 	    }
7658 	    if (error)
7659 		break;
7660 
7661 	    if (STRCMP(buf, "NONE") == 0)	/* resetting the value */
7662 		p = NULL;
7663 	    else
7664 		p = vim_strsave(buf);
7665 	    if (key[2] == 'A')
7666 	    {
7667 		vim_free(HL_TABLE()[idx].sg_start);
7668 		HL_TABLE()[idx].sg_start = p;
7669 	    }
7670 	    else
7671 	    {
7672 		vim_free(HL_TABLE()[idx].sg_stop);
7673 		HL_TABLE()[idx].sg_stop = p;
7674 	    }
7675 	}
7676 	else
7677 	{
7678 	    EMSG2(_("E423: Illegal argument: %s"), key_start);
7679 	    error = TRUE;
7680 	    break;
7681 	}
7682 
7683 	/*
7684 	 * When highlighting has been given for a group, don't link it.
7685 	 */
7686 	if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK))
7687 	    HL_TABLE()[idx].sg_link = 0;
7688 
7689 	/*
7690 	 * Continue with next argument.
7691 	 */
7692 	linep = skipwhite(linep);
7693       }
7694 
7695     /*
7696      * If there is an error, and it's a new entry, remove it from the table.
7697      */
7698     if (error && idx == highlight_ga.ga_len)
7699 	syn_unadd_group();
7700     else
7701     {
7702 	if (is_normal_group)
7703 	{
7704 	    HL_TABLE()[idx].sg_term_attr = 0;
7705 	    HL_TABLE()[idx].sg_cterm_attr = 0;
7706 #ifdef FEAT_GUI
7707 	    HL_TABLE()[idx].sg_gui_attr = 0;
7708 	    /*
7709 	     * Need to update all groups, because they might be using "bg"
7710 	     * and/or "fg", which have been changed now.
7711 	     */
7712 	    if (gui.in_use)
7713 		highlight_gui_started();
7714 #endif
7715 	}
7716 #ifdef FEAT_GUI_X11
7717 # ifdef FEAT_MENU
7718 	else if (is_menu_group)
7719 	{
7720 	    if (gui.in_use && do_colors)
7721 		gui_mch_new_menu_colors();
7722 	}
7723 # endif
7724 	else if (is_scrollbar_group)
7725 	{
7726 	    if (gui.in_use && do_colors)
7727 		gui_new_scrollbar_colors();
7728 	}
7729 # ifdef FEAT_BEVAL
7730 	else if (is_tooltip_group)
7731 	{
7732 	    if (gui.in_use && do_colors)
7733 		gui_mch_new_tooltip_colors();
7734 	}
7735 # endif
7736 #endif
7737 	else
7738 	    set_hl_attr(idx);
7739 #ifdef FEAT_EVAL
7740 	HL_TABLE()[idx].sg_scriptID = current_SID;
7741 #endif
7742 	redraw_all_later(NOT_VALID);
7743     }
7744     vim_free(key);
7745     vim_free(arg);
7746 
7747     /* Only call highlight_changed() once, after sourcing a syntax file */
7748     need_highlight_changed = TRUE;
7749 }
7750 
7751 #if defined(EXITFREE) || defined(PROTO)
7752     void
7753 free_highlight()
7754 {
7755     int	    i;
7756 
7757     for (i = 0; i < highlight_ga.ga_len; ++i)
7758     {
7759 	highlight_clear(i);
7760 	vim_free(HL_TABLE()[i].sg_name);
7761 	vim_free(HL_TABLE()[i].sg_name_u);
7762     }
7763     ga_clear(&highlight_ga);
7764 }
7765 #endif
7766 
7767 /*
7768  * Reset the cterm colors to what they were before Vim was started, if
7769  * possible.  Otherwise reset them to zero.
7770  */
7771     void
7772 restore_cterm_colors()
7773 {
7774 #if defined(MSDOS) || (defined(WIN3264) && !defined(FEAT_GUI_W32))
7775     /* Since t_me has been set, this probably means that the user
7776      * wants to use this as default colors.  Need to reset default
7777      * background/foreground colors. */
7778     mch_set_normal_colors();
7779 #else
7780     cterm_normal_fg_color = 0;
7781     cterm_normal_fg_bold = 0;
7782     cterm_normal_bg_color = 0;
7783 #endif
7784 }
7785 
7786 /*
7787  * Return TRUE if highlight group "idx" has any settings.
7788  * When "check_link" is TRUE also check for an existing link.
7789  */
7790     static int
7791 hl_has_settings(idx, check_link)
7792     int		idx;
7793     int		check_link;
7794 {
7795     return (   HL_TABLE()[idx].sg_term_attr != 0
7796 	    || HL_TABLE()[idx].sg_cterm_attr != 0
7797 #ifdef FEAT_GUI
7798 	    || HL_TABLE()[idx].sg_gui_attr != 0
7799 #endif
7800 	    || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK)));
7801 }
7802 
7803 /*
7804  * Clear highlighting for one group.
7805  */
7806     static void
7807 highlight_clear(idx)
7808     int idx;
7809 {
7810     HL_TABLE()[idx].sg_term = 0;
7811     vim_free(HL_TABLE()[idx].sg_start);
7812     HL_TABLE()[idx].sg_start = NULL;
7813     vim_free(HL_TABLE()[idx].sg_stop);
7814     HL_TABLE()[idx].sg_stop = NULL;
7815     HL_TABLE()[idx].sg_term_attr = 0;
7816     HL_TABLE()[idx].sg_cterm = 0;
7817     HL_TABLE()[idx].sg_cterm_bold = FALSE;
7818     HL_TABLE()[idx].sg_cterm_fg = 0;
7819     HL_TABLE()[idx].sg_cterm_bg = 0;
7820     HL_TABLE()[idx].sg_cterm_attr = 0;
7821 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
7822     HL_TABLE()[idx].sg_gui = 0;
7823     vim_free(HL_TABLE()[idx].sg_gui_fg_name);
7824     HL_TABLE()[idx].sg_gui_fg_name = NULL;
7825     vim_free(HL_TABLE()[idx].sg_gui_bg_name);
7826     HL_TABLE()[idx].sg_gui_bg_name = NULL;
7827     vim_free(HL_TABLE()[idx].sg_gui_sp_name);
7828     HL_TABLE()[idx].sg_gui_sp_name = NULL;
7829 #endif
7830 #ifdef FEAT_GUI
7831     HL_TABLE()[idx].sg_gui_fg = INVALCOLOR;
7832     HL_TABLE()[idx].sg_gui_bg = INVALCOLOR;
7833     HL_TABLE()[idx].sg_gui_sp = INVALCOLOR;
7834     gui_mch_free_font(HL_TABLE()[idx].sg_font);
7835     HL_TABLE()[idx].sg_font = NOFONT;
7836 # ifdef FEAT_XFONTSET
7837     gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
7838     HL_TABLE()[idx].sg_fontset = NOFONTSET;
7839 # endif
7840     vim_free(HL_TABLE()[idx].sg_font_name);
7841     HL_TABLE()[idx].sg_font_name = NULL;
7842     HL_TABLE()[idx].sg_gui_attr = 0;
7843 #endif
7844 #ifdef FEAT_EVAL
7845     /* Clear the script ID only when there is no link, since that is not
7846      * cleared. */
7847     if (HL_TABLE()[idx].sg_link == 0)
7848 	HL_TABLE()[idx].sg_scriptID = 0;
7849 #endif
7850 }
7851 
7852 #if defined(FEAT_GUI) || defined(PROTO)
7853 /*
7854  * Set the normal foreground and background colors according to the "Normal"
7855  * highlighting group.  For X11 also set "Menu", "Scrollbar", and
7856  * "Tooltip" colors.
7857  */
7858     void
7859 set_normal_colors()
7860 {
7861     if (set_group_colors((char_u *)"Normal",
7862 			     &gui.norm_pixel, &gui.back_pixel,
7863 			     FALSE, TRUE, FALSE))
7864     {
7865 	gui_mch_new_colors();
7866 	must_redraw = CLEAR;
7867     }
7868 #ifdef FEAT_GUI_X11
7869     if (set_group_colors((char_u *)"Menu",
7870 			 &gui.menu_fg_pixel, &gui.menu_bg_pixel,
7871 			 TRUE, FALSE, FALSE))
7872     {
7873 # ifdef FEAT_MENU
7874 	gui_mch_new_menu_colors();
7875 # endif
7876 	must_redraw = CLEAR;
7877     }
7878 # ifdef FEAT_BEVAL
7879     if (set_group_colors((char_u *)"Tooltip",
7880 			 &gui.tooltip_fg_pixel, &gui.tooltip_bg_pixel,
7881 			 FALSE, FALSE, TRUE))
7882     {
7883 # ifdef FEAT_TOOLBAR
7884 	gui_mch_new_tooltip_colors();
7885 # endif
7886 	must_redraw = CLEAR;
7887     }
7888 #endif
7889     if (set_group_colors((char_u *)"Scrollbar",
7890 		    &gui.scroll_fg_pixel, &gui.scroll_bg_pixel,
7891 		    FALSE, FALSE, FALSE))
7892     {
7893 	gui_new_scrollbar_colors();
7894 	must_redraw = CLEAR;
7895     }
7896 #endif
7897 }
7898 
7899 /*
7900  * Set the colors for "Normal", "Menu", "Tooltip" or "Scrollbar".
7901  */
7902     static int
7903 set_group_colors(name, fgp, bgp, do_menu, use_norm, do_tooltip)
7904     char_u	*name;
7905     guicolor_T	*fgp;
7906     guicolor_T	*bgp;
7907     int		do_menu;
7908     int		use_norm;
7909     int		do_tooltip;
7910 {
7911     int		idx;
7912 
7913     idx = syn_name2id(name) - 1;
7914     if (idx >= 0)
7915     {
7916 	gui_do_one_color(idx, do_menu, do_tooltip);
7917 
7918 	if (HL_TABLE()[idx].sg_gui_fg != INVALCOLOR)
7919 	    *fgp = HL_TABLE()[idx].sg_gui_fg;
7920 	else if (use_norm)
7921 	    *fgp = gui.def_norm_pixel;
7922 	if (HL_TABLE()[idx].sg_gui_bg != INVALCOLOR)
7923 	    *bgp = HL_TABLE()[idx].sg_gui_bg;
7924 	else if (use_norm)
7925 	    *bgp = gui.def_back_pixel;
7926 	return TRUE;
7927     }
7928     return FALSE;
7929 }
7930 
7931 /*
7932  * Get the font of the "Normal" group.
7933  * Returns "" when it's not found or not set.
7934  */
7935     char_u *
7936 hl_get_font_name()
7937 {
7938     int		id;
7939     char_u	*s;
7940 
7941     id = syn_name2id((char_u *)"Normal");
7942     if (id > 0)
7943     {
7944 	s = HL_TABLE()[id - 1].sg_font_name;
7945 	if (s != NULL)
7946 	    return s;
7947     }
7948     return (char_u *)"";
7949 }
7950 
7951 /*
7952  * Set font for "Normal" group.  Called by gui_mch_init_font() when a font has
7953  * actually chosen to be used.
7954  */
7955     void
7956 hl_set_font_name(font_name)
7957     char_u	*font_name;
7958 {
7959     int	    id;
7960 
7961     id = syn_name2id((char_u *)"Normal");
7962     if (id > 0)
7963     {
7964 	vim_free(HL_TABLE()[id - 1].sg_font_name);
7965 	HL_TABLE()[id - 1].sg_font_name = vim_strsave(font_name);
7966     }
7967 }
7968 
7969 /*
7970  * Set background color for "Normal" group.  Called by gui_set_bg_color()
7971  * when the color is known.
7972  */
7973     void
7974 hl_set_bg_color_name(name)
7975     char_u  *name;	    /* must have been allocated */
7976 {
7977     int	    id;
7978 
7979     if (name != NULL)
7980     {
7981 	id = syn_name2id((char_u *)"Normal");
7982 	if (id > 0)
7983 	{
7984 	    vim_free(HL_TABLE()[id - 1].sg_gui_bg_name);
7985 	    HL_TABLE()[id - 1].sg_gui_bg_name = name;
7986 	}
7987     }
7988 }
7989 
7990 /*
7991  * Set foreground color for "Normal" group.  Called by gui_set_fg_color()
7992  * when the color is known.
7993  */
7994     void
7995 hl_set_fg_color_name(name)
7996     char_u  *name;	    /* must have been allocated */
7997 {
7998     int	    id;
7999 
8000     if (name != NULL)
8001     {
8002 	id = syn_name2id((char_u *)"Normal");
8003 	if (id > 0)
8004 	{
8005 	    vim_free(HL_TABLE()[id - 1].sg_gui_fg_name);
8006 	    HL_TABLE()[id - 1].sg_gui_fg_name = name;
8007 	}
8008     }
8009 }
8010 
8011 /*
8012  * Return the handle for a color name.
8013  * Returns INVALCOLOR when failed.
8014  */
8015     static guicolor_T
8016 color_name2handle(name)
8017     char_u  *name;
8018 {
8019     if (STRCMP(name, "NONE") == 0)
8020 	return INVALCOLOR;
8021 
8022     if (STRICMP(name, "fg") == 0 || STRICMP(name, "foreground") == 0)
8023 	return gui.norm_pixel;
8024     if (STRICMP(name, "bg") == 0 || STRICMP(name, "background") == 0)
8025 	return gui.back_pixel;
8026 
8027     return gui_get_color(name);
8028 }
8029 
8030 /*
8031  * Return the handle for a font name.
8032  * Returns NOFONT when failed.
8033  */
8034     static GuiFont
8035 font_name2handle(name)
8036     char_u  *name;
8037 {
8038     if (STRCMP(name, "NONE") == 0)
8039 	return NOFONT;
8040 
8041     return gui_mch_get_font(name, TRUE);
8042 }
8043 
8044 # ifdef FEAT_XFONTSET
8045 /*
8046  * Return the handle for a fontset name.
8047  * Returns NOFONTSET when failed.
8048  */
8049     static GuiFontset
8050 fontset_name2handle(name, fixed_width)
8051     char_u	*name;
8052     int		fixed_width;
8053 {
8054     if (STRCMP(name, "NONE") == 0)
8055 	return NOFONTSET;
8056 
8057     return gui_mch_get_fontset(name, TRUE, fixed_width);
8058 }
8059 # endif
8060 
8061 /*
8062  * Get the font or fontset for one highlight group.
8063  */
8064     static void
8065 hl_do_font(idx, arg, do_normal, do_menu, do_tooltip, free_font)
8066     int		idx;
8067     char_u	*arg;
8068     int		do_normal;		/* set normal font */
8069     int		do_menu UNUSED;		/* set menu font */
8070     int		do_tooltip UNUSED;	/* set tooltip font */
8071     int		free_font;		/* free current font/fontset */
8072 {
8073 # ifdef FEAT_XFONTSET
8074     /* If 'guifontset' is not empty, first try using the name as a
8075      * fontset.  If that doesn't work, use it as a font name. */
8076     if (*p_guifontset != NUL
8077 #  ifdef FONTSET_ALWAYS
8078 	|| do_menu
8079 #  endif
8080 #  ifdef FEAT_BEVAL_TIP
8081 	/* In Athena & Motif, the Tooltip highlight group is always a fontset */
8082 	|| do_tooltip
8083 #  endif
8084 	    )
8085 	if (free_font)
8086 	    gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
8087 	HL_TABLE()[idx].sg_fontset = fontset_name2handle(arg, 0
8088 #  ifdef FONTSET_ALWAYS
8089 		|| do_menu
8090 #  endif
8091 #  ifdef FEAT_BEVAL_TIP
8092 		|| do_tooltip
8093 #  endif
8094 		);
8095     if (HL_TABLE()[idx].sg_fontset != NOFONTSET)
8096     {
8097 	/* If it worked and it's the Normal group, use it as the normal
8098 	 * fontset.  Same for the Menu group. */
8099 	if (do_normal)
8100 	    gui_init_font(arg, TRUE);
8101 #   if (defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_ATHENA)) && defined(FEAT_MENU)
8102 	if (do_menu)
8103 	{
8104 #    ifdef FONTSET_ALWAYS
8105 	    gui.menu_fontset = HL_TABLE()[idx].sg_fontset;
8106 #    else
8107 	    /* YIKES!  This is a bug waiting to crash the program */
8108 	    gui.menu_font = HL_TABLE()[idx].sg_fontset;
8109 #    endif
8110 	    gui_mch_new_menu_font();
8111 	}
8112 #    ifdef FEAT_BEVAL
8113 	if (do_tooltip)
8114 	{
8115 	    /* The Athena widget set cannot currently handle switching between
8116 	     * displaying a single font and a fontset.
8117 	     * If the XtNinternational resource is set to True at widget
8118 	     * creation, then a fontset is always used, otherwise an
8119 	     * XFontStruct is used.
8120 	     */
8121 	    gui.tooltip_fontset = (XFontSet)HL_TABLE()[idx].sg_fontset;
8122 	    gui_mch_new_tooltip_font();
8123 	}
8124 #    endif
8125 #   endif
8126     }
8127     else
8128 # endif
8129     {
8130 	if (free_font)
8131 	    gui_mch_free_font(HL_TABLE()[idx].sg_font);
8132 	HL_TABLE()[idx].sg_font = font_name2handle(arg);
8133 	/* If it worked and it's the Normal group, use it as the
8134 	 * normal font.  Same for the Menu group. */
8135 	if (HL_TABLE()[idx].sg_font != NOFONT)
8136 	{
8137 	    if (do_normal)
8138 		gui_init_font(arg, FALSE);
8139 #ifndef FONTSET_ALWAYS
8140 # if (defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_ATHENA)) && defined(FEAT_MENU)
8141 	    if (do_menu)
8142 	    {
8143 		gui.menu_font = HL_TABLE()[idx].sg_font;
8144 		gui_mch_new_menu_font();
8145 	    }
8146 # endif
8147 #endif
8148 	}
8149     }
8150 }
8151 
8152 #endif /* FEAT_GUI */
8153 
8154 /*
8155  * Table with the specifications for an attribute number.
8156  * Note that this table is used by ALL buffers.  This is required because the
8157  * GUI can redraw at any time for any buffer.
8158  */
8159 static garray_T	term_attr_table = {0, 0, 0, 0, NULL};
8160 
8161 #define TERM_ATTR_ENTRY(idx) ((attrentry_T *)term_attr_table.ga_data)[idx]
8162 
8163 static garray_T	cterm_attr_table = {0, 0, 0, 0, NULL};
8164 
8165 #define CTERM_ATTR_ENTRY(idx) ((attrentry_T *)cterm_attr_table.ga_data)[idx]
8166 
8167 #ifdef FEAT_GUI
8168 static garray_T	gui_attr_table = {0, 0, 0, 0, NULL};
8169 
8170 #define GUI_ATTR_ENTRY(idx) ((attrentry_T *)gui_attr_table.ga_data)[idx]
8171 #endif
8172 
8173 /*
8174  * Return the attr number for a set of colors and font.
8175  * Add a new entry to the term_attr_table, cterm_attr_table or gui_attr_table
8176  * if the combination is new.
8177  * Return 0 for error (no more room).
8178  */
8179     static int
8180 get_attr_entry(table, aep)
8181     garray_T	*table;
8182     attrentry_T	*aep;
8183 {
8184     int		i;
8185     attrentry_T	*taep;
8186     static int	recursive = FALSE;
8187 
8188     /*
8189      * Init the table, in case it wasn't done yet.
8190      */
8191     table->ga_itemsize = sizeof(attrentry_T);
8192     table->ga_growsize = 7;
8193 
8194     /*
8195      * Try to find an entry with the same specifications.
8196      */
8197     for (i = 0; i < table->ga_len; ++i)
8198     {
8199 	taep = &(((attrentry_T *)table->ga_data)[i]);
8200 	if (	   aep->ae_attr == taep->ae_attr
8201 		&& (
8202 #ifdef FEAT_GUI
8203 		       (table == &gui_attr_table
8204 			&& (aep->ae_u.gui.fg_color == taep->ae_u.gui.fg_color
8205 			    && aep->ae_u.gui.bg_color
8206 						    == taep->ae_u.gui.bg_color
8207 			    && aep->ae_u.gui.sp_color
8208 						    == taep->ae_u.gui.sp_color
8209 			    && aep->ae_u.gui.font == taep->ae_u.gui.font
8210 #  ifdef FEAT_XFONTSET
8211 			    && aep->ae_u.gui.fontset == taep->ae_u.gui.fontset
8212 #  endif
8213 			    ))
8214 		    ||
8215 #endif
8216 		       (table == &term_attr_table
8217 			&& (aep->ae_u.term.start == NULL)
8218 					    == (taep->ae_u.term.start == NULL)
8219 			&& (aep->ae_u.term.start == NULL
8220 			    || STRCMP(aep->ae_u.term.start,
8221 						  taep->ae_u.term.start) == 0)
8222 			&& (aep->ae_u.term.stop == NULL)
8223 					     == (taep->ae_u.term.stop == NULL)
8224 			&& (aep->ae_u.term.stop == NULL
8225 			    || STRCMP(aep->ae_u.term.stop,
8226 						  taep->ae_u.term.stop) == 0))
8227 		    || (table == &cterm_attr_table
8228 			    && aep->ae_u.cterm.fg_color
8229 						  == taep->ae_u.cterm.fg_color
8230 			    && aep->ae_u.cterm.bg_color
8231 						 == taep->ae_u.cterm.bg_color)
8232 		     ))
8233 
8234 	return i + ATTR_OFF;
8235     }
8236 
8237     if (table->ga_len + ATTR_OFF > MAX_TYPENR)
8238     {
8239 	/*
8240 	 * Running out of attribute entries!  remove all attributes, and
8241 	 * compute new ones for all groups.
8242 	 * When called recursively, we are really out of numbers.
8243 	 */
8244 	if (recursive)
8245 	{
8246 	    EMSG(_("E424: Too many different highlighting attributes in use"));
8247 	    return 0;
8248 	}
8249 	recursive = TRUE;
8250 
8251 	clear_hl_tables();
8252 
8253 	must_redraw = CLEAR;
8254 
8255 	for (i = 0; i < highlight_ga.ga_len; ++i)
8256 	    set_hl_attr(i);
8257 
8258 	recursive = FALSE;
8259     }
8260 
8261     /*
8262      * This is a new combination of colors and font, add an entry.
8263      */
8264     if (ga_grow(table, 1) == FAIL)
8265 	return 0;
8266 
8267     taep = &(((attrentry_T *)table->ga_data)[table->ga_len]);
8268     vim_memset(taep, 0, sizeof(attrentry_T));
8269     taep->ae_attr = aep->ae_attr;
8270 #ifdef FEAT_GUI
8271     if (table == &gui_attr_table)
8272     {
8273 	taep->ae_u.gui.fg_color = aep->ae_u.gui.fg_color;
8274 	taep->ae_u.gui.bg_color = aep->ae_u.gui.bg_color;
8275 	taep->ae_u.gui.sp_color = aep->ae_u.gui.sp_color;
8276 	taep->ae_u.gui.font = aep->ae_u.gui.font;
8277 # ifdef FEAT_XFONTSET
8278 	taep->ae_u.gui.fontset = aep->ae_u.gui.fontset;
8279 # endif
8280     }
8281 #endif
8282     if (table == &term_attr_table)
8283     {
8284 	if (aep->ae_u.term.start == NULL)
8285 	    taep->ae_u.term.start = NULL;
8286 	else
8287 	    taep->ae_u.term.start = vim_strsave(aep->ae_u.term.start);
8288 	if (aep->ae_u.term.stop == NULL)
8289 	    taep->ae_u.term.stop = NULL;
8290 	else
8291 	    taep->ae_u.term.stop = vim_strsave(aep->ae_u.term.stop);
8292     }
8293     else if (table == &cterm_attr_table)
8294     {
8295 	taep->ae_u.cterm.fg_color = aep->ae_u.cterm.fg_color;
8296 	taep->ae_u.cterm.bg_color = aep->ae_u.cterm.bg_color;
8297     }
8298     ++table->ga_len;
8299     return (table->ga_len - 1 + ATTR_OFF);
8300 }
8301 
8302 /*
8303  * Clear all highlight tables.
8304  */
8305     void
8306 clear_hl_tables()
8307 {
8308     int		i;
8309     attrentry_T	*taep;
8310 
8311 #ifdef FEAT_GUI
8312     ga_clear(&gui_attr_table);
8313 #endif
8314     for (i = 0; i < term_attr_table.ga_len; ++i)
8315     {
8316 	taep = &(((attrentry_T *)term_attr_table.ga_data)[i]);
8317 	vim_free(taep->ae_u.term.start);
8318 	vim_free(taep->ae_u.term.stop);
8319     }
8320     ga_clear(&term_attr_table);
8321     ga_clear(&cterm_attr_table);
8322 }
8323 
8324 #if defined(FEAT_SYN_HL) || defined(FEAT_SPELL) || defined(PROTO)
8325 /*
8326  * Combine special attributes (e.g., for spelling) with other attributes
8327  * (e.g., for syntax highlighting).
8328  * "prim_attr" overrules "char_attr".
8329  * This creates a new group when required.
8330  * Since we expect there to be few spelling mistakes we don't cache the
8331  * result.
8332  * Return the resulting attributes.
8333  */
8334     int
8335 hl_combine_attr(char_attr, prim_attr)
8336     int	    char_attr;
8337     int	    prim_attr;
8338 {
8339     attrentry_T *char_aep = NULL;
8340     attrentry_T *spell_aep;
8341     attrentry_T new_en;
8342 
8343     if (char_attr == 0)
8344 	return prim_attr;
8345     if (char_attr <= HL_ALL && prim_attr <= HL_ALL)
8346 	return char_attr | prim_attr;
8347 #ifdef FEAT_GUI
8348     if (gui.in_use)
8349     {
8350 	if (char_attr > HL_ALL)
8351 	    char_aep = syn_gui_attr2entry(char_attr);
8352 	if (char_aep != NULL)
8353 	    new_en = *char_aep;
8354 	else
8355 	{
8356 	    vim_memset(&new_en, 0, sizeof(new_en));
8357 	    new_en.ae_u.gui.fg_color = INVALCOLOR;
8358 	    new_en.ae_u.gui.bg_color = INVALCOLOR;
8359 	    new_en.ae_u.gui.sp_color = INVALCOLOR;
8360 	    if (char_attr <= HL_ALL)
8361 		new_en.ae_attr = char_attr;
8362 	}
8363 
8364 	if (prim_attr <= HL_ALL)
8365 	    new_en.ae_attr |= prim_attr;
8366 	else
8367 	{
8368 	    spell_aep = syn_gui_attr2entry(prim_attr);
8369 	    if (spell_aep != NULL)
8370 	    {
8371 		new_en.ae_attr |= spell_aep->ae_attr;
8372 		if (spell_aep->ae_u.gui.fg_color != INVALCOLOR)
8373 		    new_en.ae_u.gui.fg_color = spell_aep->ae_u.gui.fg_color;
8374 		if (spell_aep->ae_u.gui.bg_color != INVALCOLOR)
8375 		    new_en.ae_u.gui.bg_color = spell_aep->ae_u.gui.bg_color;
8376 		if (spell_aep->ae_u.gui.sp_color != INVALCOLOR)
8377 		    new_en.ae_u.gui.sp_color = spell_aep->ae_u.gui.sp_color;
8378 		if (spell_aep->ae_u.gui.font != NOFONT)
8379 		    new_en.ae_u.gui.font = spell_aep->ae_u.gui.font;
8380 # ifdef FEAT_XFONTSET
8381 		if (spell_aep->ae_u.gui.fontset != NOFONTSET)
8382 		    new_en.ae_u.gui.fontset = spell_aep->ae_u.gui.fontset;
8383 # endif
8384 	    }
8385 	}
8386 	return get_attr_entry(&gui_attr_table, &new_en);
8387     }
8388 #endif
8389 
8390     if (t_colors > 1)
8391     {
8392 	if (char_attr > HL_ALL)
8393 	    char_aep = syn_cterm_attr2entry(char_attr);
8394 	if (char_aep != NULL)
8395 	    new_en = *char_aep;
8396 	else
8397 	{
8398 	    vim_memset(&new_en, 0, sizeof(new_en));
8399 	    if (char_attr <= HL_ALL)
8400 		new_en.ae_attr = char_attr;
8401 	}
8402 
8403 	if (prim_attr <= HL_ALL)
8404 	    new_en.ae_attr |= prim_attr;
8405 	else
8406 	{
8407 	    spell_aep = syn_cterm_attr2entry(prim_attr);
8408 	    if (spell_aep != NULL)
8409 	    {
8410 		new_en.ae_attr |= spell_aep->ae_attr;
8411 		if (spell_aep->ae_u.cterm.fg_color > 0)
8412 		    new_en.ae_u.cterm.fg_color = spell_aep->ae_u.cterm.fg_color;
8413 		if (spell_aep->ae_u.cterm.bg_color > 0)
8414 		    new_en.ae_u.cterm.bg_color = spell_aep->ae_u.cterm.bg_color;
8415 	    }
8416 	}
8417 	return get_attr_entry(&cterm_attr_table, &new_en);
8418     }
8419 
8420     if (char_attr > HL_ALL)
8421 	char_aep = syn_term_attr2entry(char_attr);
8422     if (char_aep != NULL)
8423 	new_en = *char_aep;
8424     else
8425     {
8426 	vim_memset(&new_en, 0, sizeof(new_en));
8427 	if (char_attr <= HL_ALL)
8428 	    new_en.ae_attr = char_attr;
8429     }
8430 
8431     if (prim_attr <= HL_ALL)
8432 	new_en.ae_attr |= prim_attr;
8433     else
8434     {
8435 	spell_aep = syn_term_attr2entry(prim_attr);
8436 	if (spell_aep != NULL)
8437 	{
8438 	    new_en.ae_attr |= spell_aep->ae_attr;
8439 	    if (spell_aep->ae_u.term.start != NULL)
8440 	    {
8441 		new_en.ae_u.term.start = spell_aep->ae_u.term.start;
8442 		new_en.ae_u.term.stop = spell_aep->ae_u.term.stop;
8443 	    }
8444 	}
8445     }
8446     return get_attr_entry(&term_attr_table, &new_en);
8447 }
8448 #endif
8449 
8450 #ifdef FEAT_GUI
8451 
8452     attrentry_T *
8453 syn_gui_attr2entry(attr)
8454     int		    attr;
8455 {
8456     attr -= ATTR_OFF;
8457     if (attr >= gui_attr_table.ga_len)	    /* did ":syntax clear" */
8458 	return NULL;
8459     return &(GUI_ATTR_ENTRY(attr));
8460 }
8461 #endif /* FEAT_GUI */
8462 
8463 /*
8464  * Get the highlight attributes (HL_BOLD etc.) from an attribute nr.
8465  * Only to be used when "attr" > HL_ALL.
8466  */
8467     int
8468 syn_attr2attr(attr)
8469     int	    attr;
8470 {
8471     attrentry_T	*aep;
8472 
8473 #ifdef FEAT_GUI
8474     if (gui.in_use)
8475 	aep = syn_gui_attr2entry(attr);
8476     else
8477 #endif
8478 	if (t_colors > 1)
8479 	aep = syn_cterm_attr2entry(attr);
8480     else
8481 	aep = syn_term_attr2entry(attr);
8482 
8483     if (aep == NULL)	    /* highlighting not set */
8484 	return 0;
8485     return aep->ae_attr;
8486 }
8487 
8488 
8489     attrentry_T *
8490 syn_term_attr2entry(attr)
8491     int		    attr;
8492 {
8493     attr -= ATTR_OFF;
8494     if (attr >= term_attr_table.ga_len)	    /* did ":syntax clear" */
8495 	return NULL;
8496     return &(TERM_ATTR_ENTRY(attr));
8497 }
8498 
8499     attrentry_T *
8500 syn_cterm_attr2entry(attr)
8501     int		    attr;
8502 {
8503     attr -= ATTR_OFF;
8504     if (attr >= cterm_attr_table.ga_len)	/* did ":syntax clear" */
8505 	return NULL;
8506     return &(CTERM_ATTR_ENTRY(attr));
8507 }
8508 
8509 #define LIST_ATTR   1
8510 #define LIST_STRING 2
8511 #define LIST_INT    3
8512 
8513     static void
8514 highlight_list_one(id)
8515     int		id;
8516 {
8517     struct hl_group	*sgp;
8518     int			didh = FALSE;
8519 
8520     sgp = &HL_TABLE()[id - 1];	    /* index is ID minus one */
8521 
8522     didh = highlight_list_arg(id, didh, LIST_ATTR,
8523 				    sgp->sg_term, NULL, "term");
8524     didh = highlight_list_arg(id, didh, LIST_STRING,
8525 				    0, sgp->sg_start, "start");
8526     didh = highlight_list_arg(id, didh, LIST_STRING,
8527 				    0, sgp->sg_stop, "stop");
8528 
8529     didh = highlight_list_arg(id, didh, LIST_ATTR,
8530 				    sgp->sg_cterm, NULL, "cterm");
8531     didh = highlight_list_arg(id, didh, LIST_INT,
8532 				    sgp->sg_cterm_fg, NULL, "ctermfg");
8533     didh = highlight_list_arg(id, didh, LIST_INT,
8534 				    sgp->sg_cterm_bg, NULL, "ctermbg");
8535 
8536 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
8537     didh = highlight_list_arg(id, didh, LIST_ATTR,
8538 				    sgp->sg_gui, NULL, "gui");
8539     didh = highlight_list_arg(id, didh, LIST_STRING,
8540 				    0, sgp->sg_gui_fg_name, "guifg");
8541     didh = highlight_list_arg(id, didh, LIST_STRING,
8542 				    0, sgp->sg_gui_bg_name, "guibg");
8543     didh = highlight_list_arg(id, didh, LIST_STRING,
8544 				    0, sgp->sg_gui_sp_name, "guisp");
8545 #endif
8546 #ifdef FEAT_GUI
8547     didh = highlight_list_arg(id, didh, LIST_STRING,
8548 				    0, sgp->sg_font_name, "font");
8549 #endif
8550 
8551     if (sgp->sg_link && !got_int)
8552     {
8553 	(void)syn_list_header(didh, 9999, id);
8554 	didh = TRUE;
8555 	msg_puts_attr((char_u *)"links to", hl_attr(HLF_D));
8556 	msg_putchar(' ');
8557 	msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name);
8558     }
8559 
8560     if (!didh)
8561 	highlight_list_arg(id, didh, LIST_STRING, 0, (char_u *)"cleared", "");
8562 #ifdef FEAT_EVAL
8563     if (p_verbose > 0)
8564 	last_set_msg(sgp->sg_scriptID);
8565 #endif
8566 }
8567 
8568     static int
8569 highlight_list_arg(id, didh, type, iarg, sarg, name)
8570     int		id;
8571     int		didh;
8572     int		type;
8573     int		iarg;
8574     char_u	*sarg;
8575     char	*name;
8576 {
8577     char_u	buf[100];
8578     char_u	*ts;
8579     int		i;
8580 
8581     if (got_int)
8582 	return FALSE;
8583     if (type == LIST_STRING ? (sarg != NULL) : (iarg != 0))
8584     {
8585 	ts = buf;
8586 	if (type == LIST_INT)
8587 	    sprintf((char *)buf, "%d", iarg - 1);
8588 	else if (type == LIST_STRING)
8589 	    ts = sarg;
8590 	else /* type == LIST_ATTR */
8591 	{
8592 	    buf[0] = NUL;
8593 	    for (i = 0; hl_attr_table[i] != 0; ++i)
8594 	    {
8595 		if (iarg & hl_attr_table[i])
8596 		{
8597 		    if (buf[0] != NUL)
8598 			vim_strcat(buf, (char_u *)",", 100);
8599 		    vim_strcat(buf, (char_u *)hl_name_table[i], 100);
8600 		    iarg &= ~hl_attr_table[i];	    /* don't want "inverse" */
8601 		}
8602 	    }
8603 	}
8604 
8605 	(void)syn_list_header(didh,
8606 			       (int)(vim_strsize(ts) + STRLEN(name) + 1), id);
8607 	didh = TRUE;
8608 	if (!got_int)
8609 	{
8610 	    if (*name != NUL)
8611 	    {
8612 		MSG_PUTS_ATTR(name, hl_attr(HLF_D));
8613 		MSG_PUTS_ATTR("=", hl_attr(HLF_D));
8614 	    }
8615 	    msg_outtrans(ts);
8616 	}
8617     }
8618     return didh;
8619 }
8620 
8621 #if (((defined(FEAT_EVAL) || defined(FEAT_PRINTER))) && defined(FEAT_SYN_HL)) || defined(PROTO)
8622 /*
8623  * Return "1" if highlight group "id" has attribute "flag".
8624  * Return NULL otherwise.
8625  */
8626     char_u *
8627 highlight_has_attr(id, flag, modec)
8628     int		id;
8629     int		flag;
8630     int		modec;	/* 'g' for GUI, 'c' for cterm, 't' for term */
8631 {
8632     int		attr;
8633 
8634     if (id <= 0 || id > highlight_ga.ga_len)
8635 	return NULL;
8636 
8637 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
8638     if (modec == 'g')
8639 	attr = HL_TABLE()[id - 1].sg_gui;
8640     else
8641 #endif
8642 	 if (modec == 'c')
8643 	attr = HL_TABLE()[id - 1].sg_cterm;
8644     else
8645 	attr = HL_TABLE()[id - 1].sg_term;
8646 
8647     if (attr & flag)
8648 	return (char_u *)"1";
8649     return NULL;
8650 }
8651 #endif
8652 
8653 #if (defined(FEAT_SYN_HL) && defined(FEAT_EVAL)) || defined(PROTO)
8654 /*
8655  * Return color name of highlight group "id".
8656  */
8657     char_u *
8658 highlight_color(id, what, modec)
8659     int		id;
8660     char_u	*what;	/* "font", "fg", "bg", "sp", "fg#", "bg#" or "sp#" */
8661     int		modec;	/* 'g' for GUI, 'c' for cterm, 't' for term */
8662 {
8663     static char_u	name[20];
8664     int			n;
8665     int			fg = FALSE;
8666     int			sp = FALSE;
8667     int			font = FALSE;
8668 
8669     if (id <= 0 || id > highlight_ga.ga_len)
8670 	return NULL;
8671 
8672     if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'g')
8673 	fg = TRUE;
8674     else if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'o'
8675 	     && TOLOWER_ASC(what[2]) == 'n' && TOLOWER_ASC(what[3]) == 't')
8676 	font = TRUE;
8677     else if (TOLOWER_ASC(what[0]) == 's' && TOLOWER_ASC(what[1]) == 'p')
8678 	sp = TRUE;
8679     else if (!(TOLOWER_ASC(what[0]) == 'b' && TOLOWER_ASC(what[1]) == 'g'))
8680 	return NULL;
8681     if (modec == 'g')
8682     {
8683 # ifdef FEAT_GUI
8684 	/* return font name */
8685 	if (font)
8686 	    return HL_TABLE()[id - 1].sg_font_name;
8687 
8688 	/* return #RRGGBB form (only possible when GUI is running) */
8689 	if (gui.in_use && what[2] == '#')
8690 	{
8691 	    guicolor_T		color;
8692 	    long_u		rgb;
8693 	    static char_u	buf[10];
8694 
8695 	    if (fg)
8696 		color = HL_TABLE()[id - 1].sg_gui_fg;
8697 	    else if (sp)
8698 		color = HL_TABLE()[id - 1].sg_gui_sp;
8699 	    else
8700 		color = HL_TABLE()[id - 1].sg_gui_bg;
8701 	    if (color == INVALCOLOR)
8702 		return NULL;
8703 	    rgb = gui_mch_get_rgb(color);
8704 	    sprintf((char *)buf, "#%02x%02x%02x",
8705 				      (unsigned)(rgb >> 16),
8706 				      (unsigned)(rgb >> 8) & 255,
8707 				      (unsigned)rgb & 255);
8708 	    return buf;
8709 	}
8710 #endif
8711 	if (fg)
8712 	    return (HL_TABLE()[id - 1].sg_gui_fg_name);
8713 	if (sp)
8714 	    return (HL_TABLE()[id - 1].sg_gui_sp_name);
8715 	return (HL_TABLE()[id - 1].sg_gui_bg_name);
8716     }
8717     if (font || sp)
8718 	return NULL;
8719     if (modec == 'c')
8720     {
8721 	if (fg)
8722 	    n = HL_TABLE()[id - 1].sg_cterm_fg - 1;
8723 	else
8724 	    n = HL_TABLE()[id - 1].sg_cterm_bg - 1;
8725 	sprintf((char *)name, "%d", n);
8726 	return name;
8727     }
8728     /* term doesn't have color */
8729     return NULL;
8730 }
8731 #endif
8732 
8733 #if (defined(FEAT_SYN_HL) && defined(FEAT_GUI) && defined(FEAT_PRINTER)) \
8734 	|| defined(PROTO)
8735 /*
8736  * Return color name of highlight group "id" as RGB value.
8737  */
8738     long_u
8739 highlight_gui_color_rgb(id, fg)
8740     int		id;
8741     int		fg;	/* TRUE = fg, FALSE = bg */
8742 {
8743     guicolor_T	color;
8744 
8745     if (id <= 0 || id > highlight_ga.ga_len)
8746 	return 0L;
8747 
8748     if (fg)
8749 	color = HL_TABLE()[id - 1].sg_gui_fg;
8750     else
8751 	color = HL_TABLE()[id - 1].sg_gui_bg;
8752 
8753     if (color == INVALCOLOR)
8754 	return 0L;
8755 
8756     return gui_mch_get_rgb(color);
8757 }
8758 #endif
8759 
8760 /*
8761  * Output the syntax list header.
8762  * Return TRUE when started a new line.
8763  */
8764     static int
8765 syn_list_header(did_header, outlen, id)
8766     int	    did_header;		/* did header already */
8767     int	    outlen;		/* length of string that comes */
8768     int	    id;			/* highlight group id */
8769 {
8770     int	    endcol = 19;
8771     int	    newline = TRUE;
8772 
8773     if (!did_header)
8774     {
8775 	msg_putchar('\n');
8776 	if (got_int)
8777 	    return TRUE;
8778 	msg_outtrans(HL_TABLE()[id - 1].sg_name);
8779 	endcol = 15;
8780     }
8781     else if (msg_col + outlen + 1 >= Columns)
8782     {
8783 	msg_putchar('\n');
8784 	if (got_int)
8785 	    return TRUE;
8786     }
8787     else
8788     {
8789 	if (msg_col >= endcol)	/* wrap around is like starting a new line */
8790 	    newline = FALSE;
8791     }
8792 
8793     if (msg_col >= endcol)	/* output at least one space */
8794 	endcol = msg_col + 1;
8795     if (Columns <= endcol)	/* avoid hang for tiny window */
8796 	endcol = Columns - 1;
8797 
8798     msg_advance(endcol);
8799 
8800     /* Show "xxx" with the attributes. */
8801     if (!did_header)
8802     {
8803 	msg_puts_attr((char_u *)"xxx", syn_id2attr(id));
8804 	msg_putchar(' ');
8805     }
8806 
8807     return newline;
8808 }
8809 
8810 /*
8811  * Set the attribute numbers for a highlight group.
8812  * Called after one of the attributes has changed.
8813  */
8814     static void
8815 set_hl_attr(idx)
8816     int		idx;	    /* index in array */
8817 {
8818     attrentry_T		at_en;
8819     struct hl_group	*sgp = HL_TABLE() + idx;
8820 
8821     /* The "Normal" group doesn't need an attribute number */
8822     if (sgp->sg_name_u != NULL && STRCMP(sgp->sg_name_u, "NORMAL") == 0)
8823 	return;
8824 
8825 #ifdef FEAT_GUI
8826     /*
8827      * For the GUI mode: If there are other than "normal" highlighting
8828      * attributes, need to allocate an attr number.
8829      */
8830     if (sgp->sg_gui_fg == INVALCOLOR
8831 	    && sgp->sg_gui_bg == INVALCOLOR
8832 	    && sgp->sg_gui_sp == INVALCOLOR
8833 	    && sgp->sg_font == NOFONT
8834 # ifdef FEAT_XFONTSET
8835 	    && sgp->sg_fontset == NOFONTSET
8836 # endif
8837 	    )
8838     {
8839 	sgp->sg_gui_attr = sgp->sg_gui;
8840     }
8841     else
8842     {
8843 	at_en.ae_attr = sgp->sg_gui;
8844 	at_en.ae_u.gui.fg_color = sgp->sg_gui_fg;
8845 	at_en.ae_u.gui.bg_color = sgp->sg_gui_bg;
8846 	at_en.ae_u.gui.sp_color = sgp->sg_gui_sp;
8847 	at_en.ae_u.gui.font = sgp->sg_font;
8848 # ifdef FEAT_XFONTSET
8849 	at_en.ae_u.gui.fontset = sgp->sg_fontset;
8850 # endif
8851 	sgp->sg_gui_attr = get_attr_entry(&gui_attr_table, &at_en);
8852     }
8853 #endif
8854     /*
8855      * For the term mode: If there are other than "normal" highlighting
8856      * attributes, need to allocate an attr number.
8857      */
8858     if (sgp->sg_start == NULL && sgp->sg_stop == NULL)
8859 	sgp->sg_term_attr = sgp->sg_term;
8860     else
8861     {
8862 	at_en.ae_attr = sgp->sg_term;
8863 	at_en.ae_u.term.start = sgp->sg_start;
8864 	at_en.ae_u.term.stop = sgp->sg_stop;
8865 	sgp->sg_term_attr = get_attr_entry(&term_attr_table, &at_en);
8866     }
8867 
8868     /*
8869      * For the color term mode: If there are other than "normal"
8870      * highlighting attributes, need to allocate an attr number.
8871      */
8872     if (sgp->sg_cterm_fg == 0 && sgp->sg_cterm_bg == 0)
8873 	sgp->sg_cterm_attr = sgp->sg_cterm;
8874     else
8875     {
8876 	at_en.ae_attr = sgp->sg_cterm;
8877 	at_en.ae_u.cterm.fg_color = sgp->sg_cterm_fg;
8878 	at_en.ae_u.cterm.bg_color = sgp->sg_cterm_bg;
8879 	sgp->sg_cterm_attr = get_attr_entry(&cterm_attr_table, &at_en);
8880     }
8881 }
8882 
8883 /*
8884  * Lookup a highlight group name and return it's ID.
8885  * If it is not found, 0 is returned.
8886  */
8887     int
8888 syn_name2id(name)
8889     char_u	*name;
8890 {
8891     int		i;
8892     char_u	name_u[200];
8893 
8894     /* Avoid using stricmp() too much, it's slow on some systems */
8895     /* Avoid alloc()/free(), these are slow too.  ID names over 200 chars
8896      * don't deserve to be found! */
8897     vim_strncpy(name_u, name, 199);
8898     vim_strup(name_u);
8899     for (i = highlight_ga.ga_len; --i >= 0; )
8900 	if (HL_TABLE()[i].sg_name_u != NULL
8901 		&& STRCMP(name_u, HL_TABLE()[i].sg_name_u) == 0)
8902 	    break;
8903     return i + 1;
8904 }
8905 
8906 #if defined(FEAT_EVAL) || defined(PROTO)
8907 /*
8908  * Return TRUE if highlight group "name" exists.
8909  */
8910     int
8911 highlight_exists(name)
8912     char_u	*name;
8913 {
8914     return (syn_name2id(name) > 0);
8915 }
8916 
8917 # if defined(FEAT_SEARCH_EXTRA) || defined(PROTO)
8918 /*
8919  * Return the name of highlight group "id".
8920  * When not a valid ID return an empty string.
8921  */
8922     char_u *
8923 syn_id2name(id)
8924     int		id;
8925 {
8926     if (id <= 0 || id > highlight_ga.ga_len)
8927 	return (char_u *)"";
8928     return HL_TABLE()[id - 1].sg_name;
8929 }
8930 # endif
8931 #endif
8932 
8933 /*
8934  * Like syn_name2id(), but take a pointer + length argument.
8935  */
8936     int
8937 syn_namen2id(linep, len)
8938     char_u  *linep;
8939     int	    len;
8940 {
8941     char_u  *name;
8942     int	    id = 0;
8943 
8944     name = vim_strnsave(linep, len);
8945     if (name != NULL)
8946     {
8947 	id = syn_name2id(name);
8948 	vim_free(name);
8949     }
8950     return id;
8951 }
8952 
8953 /*
8954  * Find highlight group name in the table and return it's ID.
8955  * The argument is a pointer to the name and the length of the name.
8956  * If it doesn't exist yet, a new entry is created.
8957  * Return 0 for failure.
8958  */
8959     int
8960 syn_check_group(pp, len)
8961     char_u		*pp;
8962     int			len;
8963 {
8964     int	    id;
8965     char_u  *name;
8966 
8967     name = vim_strnsave(pp, len);
8968     if (name == NULL)
8969 	return 0;
8970 
8971     id = syn_name2id(name);
8972     if (id == 0)			/* doesn't exist yet */
8973 	id = syn_add_group(name);
8974     else
8975 	vim_free(name);
8976     return id;
8977 }
8978 
8979 /*
8980  * Add new highlight group and return it's ID.
8981  * "name" must be an allocated string, it will be consumed.
8982  * Return 0 for failure.
8983  */
8984     static int
8985 syn_add_group(name)
8986     char_u	*name;
8987 {
8988     char_u	*p;
8989 
8990     /* Check that the name is ASCII letters, digits and underscore. */
8991     for (p = name; *p != NUL; ++p)
8992     {
8993 	if (!vim_isprintc(*p))
8994 	{
8995 	    EMSG(_("E669: Unprintable character in group name"));
8996 	    vim_free(name);
8997 	    return 0;
8998 	}
8999 	else if (!ASCII_ISALNUM(*p) && *p != '_')
9000 	{
9001 	    /* This is an error, but since there previously was no check only
9002 	     * give a warning. */
9003 	    msg_source(hl_attr(HLF_W));
9004 	    MSG(_("W18: Invalid character in group name"));
9005 	    break;
9006 	}
9007     }
9008 
9009     /*
9010      * First call for this growarray: init growing array.
9011      */
9012     if (highlight_ga.ga_data == NULL)
9013     {
9014 	highlight_ga.ga_itemsize = sizeof(struct hl_group);
9015 	highlight_ga.ga_growsize = 10;
9016     }
9017 
9018     if (highlight_ga.ga_len >= MAX_HL_ID)
9019     {
9020 	EMSG(_("E849: Too many highlight and syntax groups"));
9021 	vim_free(name);
9022 	return 0;
9023     }
9024 
9025     /*
9026      * Make room for at least one other syntax_highlight entry.
9027      */
9028     if (ga_grow(&highlight_ga, 1) == FAIL)
9029     {
9030 	vim_free(name);
9031 	return 0;
9032     }
9033 
9034     vim_memset(&(HL_TABLE()[highlight_ga.ga_len]), 0, sizeof(struct hl_group));
9035     HL_TABLE()[highlight_ga.ga_len].sg_name = name;
9036     HL_TABLE()[highlight_ga.ga_len].sg_name_u = vim_strsave_up(name);
9037 #ifdef FEAT_GUI
9038     HL_TABLE()[highlight_ga.ga_len].sg_gui_bg = INVALCOLOR;
9039     HL_TABLE()[highlight_ga.ga_len].sg_gui_fg = INVALCOLOR;
9040     HL_TABLE()[highlight_ga.ga_len].sg_gui_sp = INVALCOLOR;
9041 #endif
9042     ++highlight_ga.ga_len;
9043 
9044     return highlight_ga.ga_len;		    /* ID is index plus one */
9045 }
9046 
9047 /*
9048  * When, just after calling syn_add_group(), an error is discovered, this
9049  * function deletes the new name.
9050  */
9051     static void
9052 syn_unadd_group()
9053 {
9054     --highlight_ga.ga_len;
9055     vim_free(HL_TABLE()[highlight_ga.ga_len].sg_name);
9056     vim_free(HL_TABLE()[highlight_ga.ga_len].sg_name_u);
9057 }
9058 
9059 /*
9060  * Translate a group ID to highlight attributes.
9061  */
9062     int
9063 syn_id2attr(hl_id)
9064     int			hl_id;
9065 {
9066     int			attr;
9067     struct hl_group	*sgp;
9068 
9069     hl_id = syn_get_final_id(hl_id);
9070     sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
9071 
9072 #ifdef FEAT_GUI
9073     /*
9074      * Only use GUI attr when the GUI is being used.
9075      */
9076     if (gui.in_use)
9077 	attr = sgp->sg_gui_attr;
9078     else
9079 #endif
9080 	if (t_colors > 1)
9081 	    attr = sgp->sg_cterm_attr;
9082 	else
9083 	    attr = sgp->sg_term_attr;
9084 
9085     return attr;
9086 }
9087 
9088 #ifdef FEAT_GUI
9089 /*
9090  * Get the GUI colors and attributes for a group ID.
9091  * NOTE: the colors will be INVALCOLOR when not set, the color otherwise.
9092  */
9093     int
9094 syn_id2colors(hl_id, fgp, bgp)
9095     int		hl_id;
9096     guicolor_T	*fgp;
9097     guicolor_T	*bgp;
9098 {
9099     struct hl_group	*sgp;
9100 
9101     hl_id = syn_get_final_id(hl_id);
9102     sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
9103 
9104     *fgp = sgp->sg_gui_fg;
9105     *bgp = sgp->sg_gui_bg;
9106     return sgp->sg_gui;
9107 }
9108 #endif
9109 
9110 /*
9111  * Translate a group ID to the final group ID (following links).
9112  */
9113     int
9114 syn_get_final_id(hl_id)
9115     int			hl_id;
9116 {
9117     int			count;
9118     struct hl_group	*sgp;
9119 
9120     if (hl_id > highlight_ga.ga_len || hl_id < 1)
9121 	return 0;			/* Can be called from eval!! */
9122 
9123     /*
9124      * Follow links until there is no more.
9125      * Look out for loops!  Break after 100 links.
9126      */
9127     for (count = 100; --count >= 0; )
9128     {
9129 	sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
9130 	if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len)
9131 	    break;
9132 	hl_id = sgp->sg_link;
9133     }
9134 
9135     return hl_id;
9136 }
9137 
9138 #ifdef FEAT_GUI
9139 /*
9140  * Call this function just after the GUI has started.
9141  * It finds the font and color handles for the highlighting groups.
9142  */
9143     void
9144 highlight_gui_started()
9145 {
9146     int	    idx;
9147 
9148     /* First get the colors from the "Normal" and "Menu" group, if set */
9149     set_normal_colors();
9150 
9151     for (idx = 0; idx < highlight_ga.ga_len; ++idx)
9152 	gui_do_one_color(idx, FALSE, FALSE);
9153 
9154     highlight_changed();
9155 }
9156 
9157     static void
9158 gui_do_one_color(idx, do_menu, do_tooltip)
9159     int		idx;
9160     int		do_menu;	/* TRUE: might set the menu font */
9161     int		do_tooltip;	/* TRUE: might set the tooltip font */
9162 {
9163     int		didit = FALSE;
9164 
9165     if (HL_TABLE()[idx].sg_font_name != NULL)
9166     {
9167 	hl_do_font(idx, HL_TABLE()[idx].sg_font_name, FALSE, do_menu,
9168 							    do_tooltip, TRUE);
9169 	didit = TRUE;
9170     }
9171     if (HL_TABLE()[idx].sg_gui_fg_name != NULL)
9172     {
9173 	HL_TABLE()[idx].sg_gui_fg =
9174 			    color_name2handle(HL_TABLE()[idx].sg_gui_fg_name);
9175 	didit = TRUE;
9176     }
9177     if (HL_TABLE()[idx].sg_gui_bg_name != NULL)
9178     {
9179 	HL_TABLE()[idx].sg_gui_bg =
9180 			    color_name2handle(HL_TABLE()[idx].sg_gui_bg_name);
9181 	didit = TRUE;
9182     }
9183     if (HL_TABLE()[idx].sg_gui_sp_name != NULL)
9184     {
9185 	HL_TABLE()[idx].sg_gui_sp =
9186 			    color_name2handle(HL_TABLE()[idx].sg_gui_sp_name);
9187 	didit = TRUE;
9188     }
9189     if (didit)	/* need to get a new attr number */
9190 	set_hl_attr(idx);
9191 }
9192 
9193 #endif
9194 
9195 /*
9196  * Translate the 'highlight' option into attributes in highlight_attr[] and
9197  * set up the user highlights User1..9.  If FEAT_STL_OPT is in use, a set of
9198  * corresponding highlights to use on top of HLF_SNC is computed.
9199  * Called only when the 'highlight' option has been changed and upon first
9200  * screen redraw after any :highlight command.
9201  * Return FAIL when an invalid flag is found in 'highlight'.  OK otherwise.
9202  */
9203     int
9204 highlight_changed()
9205 {
9206     int		hlf;
9207     int		i;
9208     char_u	*p;
9209     int		attr;
9210     char_u	*end;
9211     int		id;
9212 #ifdef USER_HIGHLIGHT
9213     char_u      userhl[10];
9214 # ifdef FEAT_STL_OPT
9215     int		id_SNC = -1;
9216     int		id_S = -1;
9217     int		hlcnt;
9218 # endif
9219 #endif
9220     static int	hl_flags[HLF_COUNT] = HL_FLAGS;
9221 
9222     need_highlight_changed = FALSE;
9223 
9224     /*
9225      * Clear all attributes.
9226      */
9227     for (hlf = 0; hlf < (int)HLF_COUNT; ++hlf)
9228 	highlight_attr[hlf] = 0;
9229 
9230     /*
9231      * First set all attributes to their default value.
9232      * Then use the attributes from the 'highlight' option.
9233      */
9234     for (i = 0; i < 2; ++i)
9235     {
9236 	if (i)
9237 	    p = p_hl;
9238 	else
9239 	    p = get_highlight_default();
9240 	if (p == NULL)	    /* just in case */
9241 	    continue;
9242 
9243 	while (*p)
9244 	{
9245 	    for (hlf = 0; hlf < (int)HLF_COUNT; ++hlf)
9246 		if (hl_flags[hlf] == *p)
9247 		    break;
9248 	    ++p;
9249 	    if (hlf == (int)HLF_COUNT || *p == NUL)
9250 		return FAIL;
9251 
9252 	    /*
9253 	     * Allow several hl_flags to be combined, like "bu" for
9254 	     * bold-underlined.
9255 	     */
9256 	    attr = 0;
9257 	    for ( ; *p && *p != ','; ++p)	    /* parse upto comma */
9258 	    {
9259 		if (vim_iswhite(*p))		    /* ignore white space */
9260 		    continue;
9261 
9262 		if (attr > HL_ALL)  /* Combination with ':' is not allowed. */
9263 		    return FAIL;
9264 
9265 		switch (*p)
9266 		{
9267 		    case 'b':	attr |= HL_BOLD;
9268 				break;
9269 		    case 'i':	attr |= HL_ITALIC;
9270 				break;
9271 		    case '-':
9272 		    case 'n':			    /* no highlighting */
9273 				break;
9274 		    case 'r':	attr |= HL_INVERSE;
9275 				break;
9276 		    case 's':	attr |= HL_STANDOUT;
9277 				break;
9278 		    case 'u':	attr |= HL_UNDERLINE;
9279 				break;
9280 		    case 'c':	attr |= HL_UNDERCURL;
9281 				break;
9282 		    case ':':	++p;		    /* highlight group name */
9283 				if (attr || *p == NUL)	 /* no combinations */
9284 				    return FAIL;
9285 				end = vim_strchr(p, ',');
9286 				if (end == NULL)
9287 				    end = p + STRLEN(p);
9288 				id = syn_check_group(p, (int)(end - p));
9289 				if (id == 0)
9290 				    return FAIL;
9291 				attr = syn_id2attr(id);
9292 				p = end - 1;
9293 #if defined(FEAT_STL_OPT) && defined(USER_HIGHLIGHT)
9294 				if (hlf == (int)HLF_SNC)
9295 				    id_SNC = syn_get_final_id(id);
9296 				else if (hlf == (int)HLF_S)
9297 				    id_S = syn_get_final_id(id);
9298 #endif
9299 				break;
9300 		    default:	return FAIL;
9301 		}
9302 	    }
9303 	    highlight_attr[hlf] = attr;
9304 
9305 	    p = skip_to_option_part(p);	    /* skip comma and spaces */
9306 	}
9307     }
9308 
9309 #ifdef USER_HIGHLIGHT
9310     /* Setup the user highlights
9311      *
9312      * Temporarily  utilize 10 more hl entries.  Have to be in there
9313      * simultaneously in case of table overflows in get_attr_entry()
9314      */
9315 # ifdef FEAT_STL_OPT
9316     if (ga_grow(&highlight_ga, 10) == FAIL)
9317 	return FAIL;
9318     hlcnt = highlight_ga.ga_len;
9319     if (id_S == 0)
9320     {		    /* Make sure id_S is always valid to simplify code below */
9321 	vim_memset(&HL_TABLE()[hlcnt + 9], 0, sizeof(struct hl_group));
9322 	HL_TABLE()[hlcnt + 9].sg_term = highlight_attr[HLF_S];
9323 	id_S = hlcnt + 10;
9324     }
9325 # endif
9326     for (i = 0; i < 9; i++)
9327     {
9328 	sprintf((char *)userhl, "User%d", i + 1);
9329 	id = syn_name2id(userhl);
9330 	if (id == 0)
9331 	{
9332 	    highlight_user[i] = 0;
9333 # ifdef FEAT_STL_OPT
9334 	    highlight_stlnc[i] = 0;
9335 # endif
9336 	}
9337 	else
9338 	{
9339 # ifdef FEAT_STL_OPT
9340 	    struct hl_group *hlt = HL_TABLE();
9341 # endif
9342 
9343 	    highlight_user[i] = syn_id2attr(id);
9344 # ifdef FEAT_STL_OPT
9345 	    if (id_SNC == 0)
9346 	    {
9347 		vim_memset(&hlt[hlcnt + i], 0, sizeof(struct hl_group));
9348 		hlt[hlcnt + i].sg_term = highlight_attr[HLF_SNC];
9349 		hlt[hlcnt + i].sg_cterm = highlight_attr[HLF_SNC];
9350 #  if defined(FEAT_GUI) || defined(FEAT_EVAL)
9351 		hlt[hlcnt + i].sg_gui = highlight_attr[HLF_SNC];
9352 #  endif
9353 	    }
9354 	    else
9355 		mch_memmove(&hlt[hlcnt + i],
9356 			    &hlt[id_SNC - 1],
9357 			    sizeof(struct hl_group));
9358 	    hlt[hlcnt + i].sg_link = 0;
9359 
9360 	    /* Apply difference between UserX and HLF_S to HLF_SNC */
9361 	    hlt[hlcnt + i].sg_term ^=
9362 		hlt[id - 1].sg_term ^ hlt[id_S - 1].sg_term;
9363 	    if (hlt[id - 1].sg_start != hlt[id_S - 1].sg_start)
9364 		hlt[hlcnt + i].sg_start = hlt[id - 1].sg_start;
9365 	    if (hlt[id - 1].sg_stop != hlt[id_S - 1].sg_stop)
9366 		hlt[hlcnt + i].sg_stop = hlt[id - 1].sg_stop;
9367 	    hlt[hlcnt + i].sg_cterm ^=
9368 		hlt[id - 1].sg_cterm ^ hlt[id_S - 1].sg_cterm;
9369 	    if (hlt[id - 1].sg_cterm_fg != hlt[id_S - 1].sg_cterm_fg)
9370 		hlt[hlcnt + i].sg_cterm_fg = hlt[id - 1].sg_cterm_fg;
9371 	    if (hlt[id - 1].sg_cterm_bg != hlt[id_S - 1].sg_cterm_bg)
9372 		hlt[hlcnt + i].sg_cterm_bg = hlt[id - 1].sg_cterm_bg;
9373 #  if defined(FEAT_GUI) || defined(FEAT_EVAL)
9374 	    hlt[hlcnt + i].sg_gui ^=
9375 		hlt[id - 1].sg_gui ^ hlt[id_S - 1].sg_gui;
9376 #  endif
9377 #  ifdef FEAT_GUI
9378 	    if (hlt[id - 1].sg_gui_fg != hlt[id_S - 1].sg_gui_fg)
9379 		hlt[hlcnt + i].sg_gui_fg = hlt[id - 1].sg_gui_fg;
9380 	    if (hlt[id - 1].sg_gui_bg != hlt[id_S - 1].sg_gui_bg)
9381 		hlt[hlcnt + i].sg_gui_bg = hlt[id - 1].sg_gui_bg;
9382 	    if (hlt[id - 1].sg_gui_sp != hlt[id_S - 1].sg_gui_sp)
9383 		hlt[hlcnt + i].sg_gui_sp = hlt[id - 1].sg_gui_sp;
9384 	    if (hlt[id - 1].sg_font != hlt[id_S - 1].sg_font)
9385 		hlt[hlcnt + i].sg_font = hlt[id - 1].sg_font;
9386 #   ifdef FEAT_XFONTSET
9387 	    if (hlt[id - 1].sg_fontset != hlt[id_S - 1].sg_fontset)
9388 		hlt[hlcnt + i].sg_fontset = hlt[id - 1].sg_fontset;
9389 #   endif
9390 #  endif
9391 	    highlight_ga.ga_len = hlcnt + i + 1;
9392 	    set_hl_attr(hlcnt + i);	/* At long last we can apply */
9393 	    highlight_stlnc[i] = syn_id2attr(hlcnt + i + 1);
9394 # endif
9395 	}
9396     }
9397 # ifdef FEAT_STL_OPT
9398     highlight_ga.ga_len = hlcnt;
9399 # endif
9400 
9401 #endif /* USER_HIGHLIGHT */
9402 
9403     return OK;
9404 }
9405 
9406 #if defined(FEAT_CMDL_COMPL) || defined(PROTO)
9407 
9408 static void highlight_list __ARGS((void));
9409 static void highlight_list_two __ARGS((int cnt, int attr));
9410 
9411 /*
9412  * Handle command line completion for :highlight command.
9413  */
9414     void
9415 set_context_in_highlight_cmd(xp, arg)
9416     expand_T	*xp;
9417     char_u	*arg;
9418 {
9419     char_u	*p;
9420 
9421     /* Default: expand group names */
9422     xp->xp_context = EXPAND_HIGHLIGHT;
9423     xp->xp_pattern = arg;
9424     include_link = 2;
9425     include_default = 1;
9426 
9427     /* (part of) subcommand already typed */
9428     if (*arg != NUL)
9429     {
9430 	p = skiptowhite(arg);
9431 	if (*p != NUL)			/* past "default" or group name */
9432 	{
9433 	    include_default = 0;
9434 	    if (STRNCMP("default", arg, p - arg) == 0)
9435 	    {
9436 		arg = skipwhite(p);
9437 		xp->xp_pattern = arg;
9438 		p = skiptowhite(arg);
9439 	    }
9440 	    if (*p != NUL)			/* past group name */
9441 	    {
9442 		include_link = 0;
9443 		if (arg[1] == 'i' && arg[0] == 'N')
9444 		    highlight_list();
9445 		if (STRNCMP("link", arg, p - arg) == 0
9446 			|| STRNCMP("clear", arg, p - arg) == 0)
9447 		{
9448 		    xp->xp_pattern = skipwhite(p);
9449 		    p = skiptowhite(xp->xp_pattern);
9450 		    if (*p != NUL)		/* past first group name */
9451 		    {
9452 			xp->xp_pattern = skipwhite(p);
9453 			p = skiptowhite(xp->xp_pattern);
9454 		    }
9455 		}
9456 		if (*p != NUL)			/* past group name(s) */
9457 		    xp->xp_context = EXPAND_NOTHING;
9458 	    }
9459 	}
9460     }
9461 }
9462 
9463 /*
9464  * List highlighting matches in a nice way.
9465  */
9466     static void
9467 highlight_list()
9468 {
9469     int		i;
9470 
9471     for (i = 10; --i >= 0; )
9472 	highlight_list_two(i, hl_attr(HLF_D));
9473     for (i = 40; --i >= 0; )
9474 	highlight_list_two(99, 0);
9475 }
9476 
9477     static void
9478 highlight_list_two(cnt, attr)
9479     int	    cnt;
9480     int	    attr;
9481 {
9482     msg_puts_attr((char_u *)&("N \bI \b!  \b"[cnt / 11]), attr);
9483     msg_clr_eos();
9484     out_flush();
9485     ui_delay(cnt == 99 ? 40L : (long)cnt * 50L, FALSE);
9486 }
9487 
9488 #endif /* FEAT_CMDL_COMPL */
9489 
9490 #if defined(FEAT_CMDL_COMPL) || (defined(FEAT_SYN_HL) && defined(FEAT_EVAL)) \
9491     || defined(FEAT_SIGNS) || defined(PROTO)
9492 /*
9493  * Function given to ExpandGeneric() to obtain the list of group names.
9494  * Also used for synIDattr() function.
9495  */
9496     char_u *
9497 get_highlight_name(xp, idx)
9498     expand_T	*xp UNUSED;
9499     int		idx;
9500 {
9501 #ifdef FEAT_CMDL_COMPL
9502     if (idx == highlight_ga.ga_len && include_none != 0)
9503 	return (char_u *)"none";
9504     if (idx == highlight_ga.ga_len + include_none && include_default != 0)
9505 	return (char_u *)"default";
9506     if (idx == highlight_ga.ga_len + include_none + include_default
9507 							 && include_link != 0)
9508 	return (char_u *)"link";
9509     if (idx == highlight_ga.ga_len + include_none + include_default + 1
9510 							 && include_link != 0)
9511 	return (char_u *)"clear";
9512 #endif
9513     if (idx < 0 || idx >= highlight_ga.ga_len)
9514 	return NULL;
9515     return HL_TABLE()[idx].sg_name;
9516 }
9517 #endif
9518 
9519 #if defined(FEAT_GUI) || defined(PROTO)
9520 /*
9521  * Free all the highlight group fonts.
9522  * Used when quitting for systems which need it.
9523  */
9524     void
9525 free_highlight_fonts()
9526 {
9527     int	    idx;
9528 
9529     for (idx = 0; idx < highlight_ga.ga_len; ++idx)
9530     {
9531 	gui_mch_free_font(HL_TABLE()[idx].sg_font);
9532 	HL_TABLE()[idx].sg_font = NOFONT;
9533 # ifdef FEAT_XFONTSET
9534 	gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
9535 	HL_TABLE()[idx].sg_fontset = NOFONTSET;
9536 # endif
9537     }
9538 
9539     gui_mch_free_font(gui.norm_font);
9540 # ifdef FEAT_XFONTSET
9541     gui_mch_free_fontset(gui.fontset);
9542 # endif
9543 # ifndef FEAT_GUI_GTK
9544     gui_mch_free_font(gui.bold_font);
9545     gui_mch_free_font(gui.ital_font);
9546     gui_mch_free_font(gui.boldital_font);
9547 # endif
9548 }
9549 #endif
9550 
9551 /**************************************
9552  *  End of Highlighting stuff	      *
9553  **************************************/
9554