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