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