xref: /vim-8.2.3635/src/syntax.c (revision 81ea1dfb)
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 #if defined(FEAT_SYN_HL) || defined(PROTO)
17 
18 #define SYN_NAMELEN	50		// maximum length of a syntax name
19 
20 // different types of offsets that are possible
21 #define SPO_MS_OFF	0	// match  start offset
22 #define SPO_ME_OFF	1	// match  end	offset
23 #define SPO_HS_OFF	2	// highl. start offset
24 #define SPO_HE_OFF	3	// highl. end	offset
25 #define SPO_RS_OFF	4	// region start offset
26 #define SPO_RE_OFF	5	// region end	offset
27 #define SPO_LC_OFF	6	// leading context offset
28 #define SPO_COUNT	7
29 
30 static char *(spo_name_tab[SPO_COUNT]) =
31 	    {"ms=", "me=", "hs=", "he=", "rs=", "re=", "lc="};
32 
33 /*
34  * The patterns that are being searched for are stored in a syn_pattern.
35  * A match item consists of one pattern.
36  * A start/end item consists of n start patterns and m end patterns.
37  * A start/skip/end item consists of n start patterns, one skip pattern and m
38  * end patterns.
39  * For the latter two, the patterns are always consecutive: start-skip-end.
40  *
41  * A character offset can be given for the matched text (_m_start and _m_end)
42  * and for the actually highlighted text (_h_start and _h_end).
43  *
44  * Note that ordering of members is optimized to reduce padding.
45  */
46 typedef struct syn_pattern
47 {
48     char	 sp_type;		// see SPTYPE_ defines below
49     char	 sp_syncing;		// this item used for syncing
50     short	 sp_syn_match_id;	// highlight group ID of pattern
51     short	 sp_off_flags;		// see below
52     int		 sp_offsets[SPO_COUNT];	// offsets
53     int		 sp_flags;		// see HL_ defines below
54 #ifdef FEAT_CONCEAL
55     int		 sp_cchar;		// conceal substitute character
56 #endif
57     int		 sp_ic;			// ignore-case flag for sp_prog
58     int		 sp_sync_idx;		// sync item index (syncing only)
59     int		 sp_line_id;		// ID of last line where tried
60     int		 sp_startcol;		// next match in sp_line_id line
61     short	*sp_cont_list;		// cont. group IDs, if non-zero
62     short	*sp_next_list;		// next group IDs, if non-zero
63     struct sp_syn sp_syn;		// struct passed to in_id_list()
64     char_u	*sp_pattern;		// regexp to match, pattern
65     regprog_T	*sp_prog;		// regexp to match, program
66 #ifdef FEAT_PROFILE
67     syn_time_T	 sp_time;
68 #endif
69 } synpat_T;
70 
71 // The sp_off_flags are computed like this:
72 // offset from the start of the matched text: (1 << SPO_XX_OFF)
73 // offset from the end	 of the matched text: (1 << (SPO_XX_OFF + SPO_COUNT))
74 // When both are present, only one is used.
75 
76 #define SPTYPE_MATCH	1	// match keyword with this group ID
77 #define SPTYPE_START	2	// match a regexp, start of item
78 #define SPTYPE_END	3	// match a regexp, end of item
79 #define SPTYPE_SKIP	4	// match a regexp, skip within item
80 
81 
82 #define SYN_ITEMS(buf)	((synpat_T *)((buf)->b_syn_patterns.ga_data))
83 
84 #define NONE_IDX	-2	// value of sp_sync_idx for "NONE"
85 
86 /*
87  * Flags for b_syn_sync_flags:
88  */
89 #define SF_CCOMMENT	0x01	// sync on a C-style comment
90 #define SF_MATCH	0x02	// sync by matching a pattern
91 
92 #define SYN_STATE_P(ssp)    ((bufstate_T *)((ssp)->ga_data))
93 
94 #define MAXKEYWLEN	80	    // maximum length of a keyword
95 
96 /*
97  * The attributes of the syntax item that has been recognized.
98  */
99 static int current_attr = 0;	    // attr of current syntax word
100 #ifdef FEAT_EVAL
101 static int current_id = 0;	    // ID of current char for syn_get_id()
102 static int current_trans_id = 0;    // idem, transparency removed
103 #endif
104 #ifdef FEAT_CONCEAL
105 static int current_flags = 0;
106 static int current_seqnr = 0;
107 static int current_sub_char = 0;
108 #endif
109 
110 typedef struct syn_cluster_S
111 {
112     char_u	    *scl_name;	    // syntax cluster name
113     char_u	    *scl_name_u;    // uppercase of scl_name
114     short	    *scl_list;	    // IDs in this syntax cluster
115 } syn_cluster_T;
116 
117 /*
118  * Methods of combining two clusters
119  */
120 #define CLUSTER_REPLACE	    1	// replace first list with second
121 #define CLUSTER_ADD	    2	// add second list to first
122 #define CLUSTER_SUBTRACT    3	// subtract second list from first
123 
124 #define SYN_CLSTR(buf)	((syn_cluster_T *)((buf)->b_syn_clusters.ga_data))
125 
126 /*
127  * Syntax group IDs have different types:
128  *     0 - 19999  normal syntax groups
129  * 20000 - 20999  ALLBUT indicator (current_syn_inc_tag added)
130  * 21000 - 21999  TOP indicator (current_syn_inc_tag added)
131  * 22000 - 22999  CONTAINED indicator (current_syn_inc_tag added)
132  * 23000 - 32767  cluster IDs (subtract SYNID_CLUSTER for the cluster ID)
133  */
134 #define SYNID_ALLBUT	MAX_HL_ID   // syntax group ID for contains=ALLBUT
135 #define SYNID_TOP	21000	    // syntax group ID for contains=TOP
136 #define SYNID_CONTAINED	22000	    // syntax group ID for contains=CONTAINED
137 #define SYNID_CLUSTER	23000	    // first syntax group ID for clusters
138 
139 #define MAX_SYN_INC_TAG	999	    // maximum before the above overflow
140 #define MAX_CLUSTER_ID  (32767 - SYNID_CLUSTER)
141 
142 /*
143  * Annoying Hack(TM):  ":syn include" needs this pointer to pass to
144  * expand_filename().  Most of the other syntax commands don't need it, so
145  * instead of passing it to them, we stow it here.
146  */
147 static char_u **syn_cmdlinep;
148 
149 /*
150  * Another Annoying Hack(TM):  To prevent rules from other ":syn include"'d
151  * files from leaking into ALLBUT lists, we assign a unique ID to the
152  * rules in each ":syn include"'d file.
153  */
154 static int current_syn_inc_tag = 0;
155 static int running_syn_inc_tag = 0;
156 
157 /*
158  * In a hashtable item "hi_key" points to "keyword" in a keyentry.
159  * This avoids adding a pointer to the hashtable item.
160  * KE2HIKEY() converts a var pointer to a hashitem key pointer.
161  * HIKEY2KE() converts a hashitem key pointer to a var pointer.
162  * HI2KE() converts a hashitem pointer to a var pointer.
163  */
164 static keyentry_T dumkey;
165 #define KE2HIKEY(kp)  ((kp)->keyword)
166 #define HIKEY2KE(p)   ((keyentry_T *)((p) - (dumkey.keyword - (char_u *)&dumkey)))
167 #define HI2KE(hi)      HIKEY2KE((hi)->hi_key)
168 
169 /*
170  * To reduce the time spent in keepend(), remember at which level in the state
171  * stack the first item with "keepend" is present.  When "-1", there is no
172  * "keepend" on the stack.
173  */
174 static int keepend_level = -1;
175 
176 static char msg_no_items[] = N_("No Syntax items defined for this buffer");
177 
178 /*
179  * For the current state we need to remember more than just the idx.
180  * When si_m_endpos.lnum is 0, the items other than si_idx are unknown.
181  * (The end positions have the column number of the next char)
182  */
183 typedef struct state_item
184 {
185     int		si_idx;			// index of syntax pattern or
186 					// KEYWORD_IDX
187     int		si_id;			// highlight group ID for keywords
188     int		si_trans_id;		// idem, transparency removed
189     int		si_m_lnum;		// lnum of the match
190     int		si_m_startcol;		// starting column of the match
191     lpos_T	si_m_endpos;		// just after end posn of the match
192     lpos_T	si_h_startpos;		// start position of the highlighting
193     lpos_T	si_h_endpos;		// end position of the highlighting
194     lpos_T	si_eoe_pos;		// end position of end pattern
195     int		si_end_idx;		// group ID for end pattern or zero
196     int		si_ends;		// if match ends before si_m_endpos
197     int		si_attr;		// attributes in this state
198     long	si_flags;		// HL_HAS_EOL flag in this state, and
199 					// HL_SKIP* for si_next_list
200 #ifdef FEAT_CONCEAL
201     int		si_seqnr;		// sequence number
202     int		si_cchar;		// substitution character for conceal
203 #endif
204     short	*si_cont_list;		// list of contained groups
205     short	*si_next_list;		// nextgroup IDs after this item ends
206     reg_extmatch_T *si_extmatch;	// \z(...\) matches from start
207 					// pattern
208 } stateitem_T;
209 
210 #define KEYWORD_IDX	-1	    // value of si_idx for keywords
211 #define ID_LIST_ALL	(short *)-1 // valid of si_cont_list for containing all
212 				    // but contained groups
213 
214 #ifdef FEAT_CONCEAL
215 static int next_seqnr = 1;		// value to use for si_seqnr
216 #endif
217 
218 /*
219  * Struct to reduce the number of arguments to get_syn_options(), it's used
220  * very often.
221  */
222 typedef struct
223 {
224     int		flags;		// flags for contained and transparent
225     int		keyword;	// TRUE for ":syn keyword"
226     int		*sync_idx;	// syntax item for "grouphere" argument, NULL
227 				// if not allowed
228     char	has_cont_list;	// TRUE if "cont_list" can be used
229     short	*cont_list;	// group IDs for "contains" argument
230     short	*cont_in_list;	// group IDs for "containedin" argument
231     short	*next_list;	// group IDs for "nextgroup" argument
232 } syn_opt_arg_T;
233 
234 /*
235  * The next possible match in the current line for any pattern is remembered,
236  * to avoid having to try for a match in each column.
237  * If next_match_idx == -1, not tried (in this line) yet.
238  * If next_match_col == MAXCOL, no match found in this line.
239  * (All end positions have the column of the char after the end)
240  */
241 static int next_match_col;		// column for start of next match
242 static lpos_T next_match_m_endpos;	// position for end of next match
243 static lpos_T next_match_h_startpos;	// pos. for highl. start of next match
244 static lpos_T next_match_h_endpos;	// pos. for highl. end of next match
245 static int next_match_idx;		// index of matched item
246 static long next_match_flags;		// flags for next match
247 static lpos_T next_match_eos_pos;	// end of start pattn (start region)
248 static lpos_T next_match_eoe_pos;	// pos. for end of end pattern
249 static int next_match_end_idx;		// ID of group for end pattn or zero
250 static reg_extmatch_T *next_match_extmatch = NULL;
251 
252 /*
253  * A state stack is an array of integers or stateitem_T, stored in a
254  * garray_T.  A state stack is invalid if its itemsize entry is zero.
255  */
256 #define INVALID_STATE(ssp)  ((ssp)->ga_itemsize == 0)
257 #define VALID_STATE(ssp)    ((ssp)->ga_itemsize != 0)
258 
259 #define FOR_ALL_SYNSTATES(sb, sst) \
260     for ((sst) = (sb)->b_sst_first; (sst) != NULL; (sst) = (sst)->sst_next)
261 
262 /*
263  * The current state (within the line) of the recognition engine.
264  * When current_state.ga_itemsize is 0 the current state is invalid.
265  */
266 static win_T	*syn_win;		// current window for highlighting
267 static buf_T	*syn_buf;		// current buffer for highlighting
268 static synblock_T *syn_block;		// current buffer for highlighting
269 #ifdef FEAT_RELTIME
270 static proftime_T *syn_tm;		// timeout limit
271 #endif
272 static linenr_T current_lnum = 0;	// lnum of current state
273 static colnr_T	current_col = 0;	// column of current state
274 static int	current_state_stored = 0; // TRUE if stored current state
275 					  // after setting current_finished
276 static int	current_finished = 0;	// current line has been finished
277 static garray_T current_state		// current stack of state_items
278 		= {0, 0, 0, 0, NULL};
279 static short	*current_next_list = NULL; // when non-zero, nextgroup list
280 static int	current_next_flags = 0; // flags for current_next_list
281 static int	current_line_id = 0;	// unique number for current line
282 
283 #define CUR_STATE(idx)	((stateitem_T *)(current_state.ga_data))[idx]
284 
285 static void syn_sync(win_T *wp, linenr_T lnum, synstate_T *last_valid);
286 static int syn_match_linecont(linenr_T lnum);
287 static void syn_start_line(void);
288 static void syn_update_ends(int startofline);
289 static void syn_stack_alloc(void);
290 static int syn_stack_cleanup(void);
291 static void syn_stack_free_entry(synblock_T *block, synstate_T *p);
292 static synstate_T *syn_stack_find_entry(linenr_T lnum);
293 static synstate_T *store_current_state(void);
294 static void load_current_state(synstate_T *from);
295 static void invalidate_current_state(void);
296 static int syn_stack_equal(synstate_T *sp);
297 static void validate_current_state(void);
298 static int syn_finish_line(int syncing);
299 static int syn_current_attr(int syncing, int displaying, int *can_spell, int keep_state);
300 static int did_match_already(int idx, garray_T *gap);
301 static stateitem_T *push_next_match(stateitem_T *cur_si);
302 static void check_state_ends(void);
303 static void update_si_attr(int idx);
304 static void check_keepend(void);
305 static void update_si_end(stateitem_T *sip, int startcol, int force);
306 static short *copy_id_list(short *list);
307 static int in_id_list(stateitem_T *item, short *cont_list, struct sp_syn *ssp, int contained);
308 static int push_current_state(int idx);
309 static void pop_current_state(void);
310 #ifdef FEAT_PROFILE
311 static void syn_clear_time(syn_time_T *tt);
312 static void syntime_clear(void);
313 static void syntime_report(void);
314 static int syn_time_on = FALSE;
315 # define IF_SYN_TIME(p) (p)
316 #else
317 # define IF_SYN_TIME(p) NULL
318 typedef int syn_time_T;
319 #endif
320 
321 static void syn_stack_apply_changes_block(synblock_T *block, buf_T *buf);
322 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);
323 
324 static void limit_pos(lpos_T *pos, lpos_T *limit);
325 static void limit_pos_zero(lpos_T *pos, lpos_T *limit);
326 static void syn_add_end_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *spp, int idx, int extra);
327 static void syn_add_start_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *spp, int idx, int extra);
328 static char_u *syn_getcurline(void);
329 static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T *st);
330 static int check_keyword_id(char_u *line, int startcol, int *endcol, long *flags, short **next_list, stateitem_T *cur_si, int *ccharp);
331 static void syn_remove_pattern(synblock_T *block, int idx);
332 static void syn_clear_pattern(synblock_T *block, int i);
333 static void syn_clear_cluster(synblock_T *block, int i);
334 static void syn_clear_one(int id, int syncing);
335 static void syn_cmd_onoff(exarg_T *eap, char *name);
336 static void syn_lines_msg(void);
337 static void syn_match_msg(void);
338 static void syn_list_one(int id, int syncing, int link_only);
339 static void syn_list_cluster(int id);
340 static void put_id_list(char_u *name, short *list, int attr);
341 static void put_pattern(char *s, int c, synpat_T *spp, int attr);
342 static int syn_list_keywords(int id, hashtab_T *ht, int did_header, int attr);
343 static void syn_clear_keyword(int id, hashtab_T *ht);
344 static void clear_keywtab(hashtab_T *ht);
345 static int syn_scl_namen2id(char_u *linep, int len);
346 static int syn_check_cluster(char_u *pp, int len);
347 static int syn_add_cluster(char_u *name);
348 static void init_syn_patterns(void);
349 static char_u *get_syn_pattern(char_u *arg, synpat_T *ci);
350 static int get_id_list(char_u **arg, int keylen, short **list, int skip);
351 static void syn_combine_list(short **clstr1, short **clstr2, int list_op);
352 
353 #if defined(FEAT_RELTIME) || defined(PROTO)
354 /*
355  * Set the timeout used for syntax highlighting.
356  * Use NULL to reset, no timeout.
357  */
358     void
359 syn_set_timeout(proftime_T *tm)
360 {
361     syn_tm = tm;
362 }
363 #endif
364 
365 /*
366  * Start the syntax recognition for a line.  This function is normally called
367  * from the screen updating, once for each displayed line.
368  * The buffer is remembered in syn_buf, because get_syntax_attr() doesn't get
369  * it.	Careful: curbuf and curwin are likely to point to another buffer and
370  * window.
371  */
372     void
373 syntax_start(win_T *wp, linenr_T lnum)
374 {
375     synstate_T	*p;
376     synstate_T	*last_valid = NULL;
377     synstate_T	*last_min_valid = NULL;
378     synstate_T	*sp, *prev = NULL;
379     linenr_T	parsed_lnum;
380     linenr_T	first_stored;
381     int		dist;
382     static varnumber_T changedtick = 0;	// remember the last change ID
383 
384 #ifdef FEAT_CONCEAL
385     current_sub_char = NUL;
386 #endif
387 
388     /*
389      * After switching buffers, invalidate current_state.
390      * Also do this when a change was made, the current state may be invalid
391      * then.
392      */
393     if (syn_block != wp->w_s
394 	    || syn_buf != wp->w_buffer
395 	    || changedtick != CHANGEDTICK(syn_buf))
396     {
397 	invalidate_current_state();
398 	syn_buf = wp->w_buffer;
399 	syn_block = wp->w_s;
400     }
401     changedtick = CHANGEDTICK(syn_buf);
402     syn_win = wp;
403 
404     /*
405      * Allocate syntax stack when needed.
406      */
407     syn_stack_alloc();
408     if (syn_block->b_sst_array == NULL)
409 	return;		// out of memory
410     syn_block->b_sst_lasttick = display_tick;
411 
412     /*
413      * If the state of the end of the previous line is useful, store it.
414      */
415     if (VALID_STATE(&current_state)
416 	    && current_lnum < lnum
417 	    && current_lnum < syn_buf->b_ml.ml_line_count)
418     {
419 	(void)syn_finish_line(FALSE);
420 	if (!current_state_stored)
421 	{
422 	    ++current_lnum;
423 	    (void)store_current_state();
424 	}
425 
426 	/*
427 	 * If the current_lnum is now the same as "lnum", keep the current
428 	 * state (this happens very often!).  Otherwise invalidate
429 	 * current_state and figure it out below.
430 	 */
431 	if (current_lnum != lnum)
432 	    invalidate_current_state();
433     }
434     else
435 	invalidate_current_state();
436 
437     /*
438      * Try to synchronize from a saved state in b_sst_array[].
439      * Only do this if lnum is not before and not to far beyond a saved state.
440      */
441     if (INVALID_STATE(&current_state) && syn_block->b_sst_array != NULL)
442     {
443 	// Find last valid saved state before start_lnum.
444 	FOR_ALL_SYNSTATES(syn_block, p)
445 	{
446 	    if (p->sst_lnum > lnum)
447 		break;
448 	    if (p->sst_lnum <= lnum && p->sst_change_lnum == 0)
449 	    {
450 		last_valid = p;
451 		if (p->sst_lnum >= lnum - syn_block->b_syn_sync_minlines)
452 		    last_min_valid = p;
453 	    }
454 	}
455 	if (last_min_valid != NULL)
456 	    load_current_state(last_min_valid);
457     }
458 
459     /*
460      * If "lnum" is before or far beyond a line with a saved state, need to
461      * re-synchronize.
462      */
463     if (INVALID_STATE(&current_state))
464     {
465 	syn_sync(wp, lnum, last_valid);
466 	if (current_lnum == 1)
467 	    // First line is always valid, no matter "minlines".
468 	    first_stored = 1;
469 	else
470 	    // Need to parse "minlines" lines before state can be considered
471 	    // valid to store.
472 	    first_stored = current_lnum + syn_block->b_syn_sync_minlines;
473     }
474     else
475 	first_stored = current_lnum;
476 
477     /*
478      * Advance from the sync point or saved state until the current line.
479      * Save some entries for syncing with later on.
480      */
481     if (syn_block->b_sst_len <= Rows)
482 	dist = 999999;
483     else
484 	dist = syn_buf->b_ml.ml_line_count / (syn_block->b_sst_len - Rows) + 1;
485     while (current_lnum < lnum)
486     {
487 	syn_start_line();
488 	(void)syn_finish_line(FALSE);
489 	++current_lnum;
490 
491 	// If we parsed at least "minlines" lines or started at a valid
492 	// state, the current state is considered valid.
493 	if (current_lnum >= first_stored)
494 	{
495 	    // Check if the saved state entry is for the current line and is
496 	    // equal to the current state.  If so, then validate all saved
497 	    // states that depended on a change before the parsed line.
498 	    if (prev == NULL)
499 		prev = syn_stack_find_entry(current_lnum - 1);
500 	    if (prev == NULL)
501 		sp = syn_block->b_sst_first;
502 	    else
503 		sp = prev;
504 	    while (sp != NULL && sp->sst_lnum < current_lnum)
505 		sp = sp->sst_next;
506 	    if (sp != NULL
507 		    && sp->sst_lnum == current_lnum
508 		    && syn_stack_equal(sp))
509 	    {
510 		parsed_lnum = current_lnum;
511 		prev = sp;
512 		while (sp != NULL && sp->sst_change_lnum <= parsed_lnum)
513 		{
514 		    if (sp->sst_lnum <= lnum)
515 			// valid state before desired line, use this one
516 			prev = sp;
517 		    else if (sp->sst_change_lnum == 0)
518 			// past saved states depending on change, break here.
519 			break;
520 		    sp->sst_change_lnum = 0;
521 		    sp = sp->sst_next;
522 		}
523 		load_current_state(prev);
524 	    }
525 	    // Store the state at this line when it's the first one, the line
526 	    // where we start parsing, or some distance from the previously
527 	    // saved state.  But only when parsed at least 'minlines'.
528 	    else if (prev == NULL
529 			|| current_lnum == lnum
530 			|| current_lnum >= prev->sst_lnum + dist)
531 		prev = store_current_state();
532 	}
533 
534 	// This can take a long time: break when CTRL-C pressed.  The current
535 	// state will be wrong then.
536 	line_breakcheck();
537 	if (got_int)
538 	{
539 	    current_lnum = lnum;
540 	    break;
541 	}
542     }
543 
544     syn_start_line();
545 }
546 
547 /*
548  * We cannot simply discard growarrays full of state_items or buf_states; we
549  * have to manually release their extmatch pointers first.
550  */
551     static void
552 clear_syn_state(synstate_T *p)
553 {
554     int		i;
555     garray_T	*gap;
556 
557     if (p->sst_stacksize > SST_FIX_STATES)
558     {
559 	gap = &(p->sst_union.sst_ga);
560 	for (i = 0; i < gap->ga_len; i++)
561 	    unref_extmatch(SYN_STATE_P(gap)[i].bs_extmatch);
562 	ga_clear(gap);
563     }
564     else
565     {
566 	for (i = 0; i < p->sst_stacksize; i++)
567 	    unref_extmatch(p->sst_union.sst_stack[i].bs_extmatch);
568     }
569 }
570 
571 /*
572  * Cleanup the current_state stack.
573  */
574     static void
575 clear_current_state(void)
576 {
577     int		i;
578     stateitem_T	*sip;
579 
580     sip = (stateitem_T *)(current_state.ga_data);
581     for (i = 0; i < current_state.ga_len; i++)
582 	unref_extmatch(sip[i].si_extmatch);
583     ga_clear(&current_state);
584 }
585 
586 /*
587  * Try to find a synchronisation point for line "lnum".
588  *
589  * This sets current_lnum and the current state.  One of three methods is
590  * used:
591  * 1. Search backwards for the end of a C-comment.
592  * 2. Search backwards for given sync patterns.
593  * 3. Simply start on a given number of lines above "lnum".
594  */
595     static void
596 syn_sync(
597     win_T	*wp,
598     linenr_T	start_lnum,
599     synstate_T	*last_valid)
600 {
601     buf_T	*curbuf_save;
602     win_T	*curwin_save;
603     pos_T	cursor_save;
604     int		idx;
605     linenr_T	lnum;
606     linenr_T	end_lnum;
607     linenr_T	break_lnum;
608     int		had_sync_point;
609     stateitem_T	*cur_si;
610     synpat_T	*spp;
611     char_u	*line;
612     int		found_flags = 0;
613     int		found_match_idx = 0;
614     linenr_T	found_current_lnum = 0;
615     int		found_current_col= 0;
616     lpos_T	found_m_endpos;
617     colnr_T	prev_current_col;
618 
619     /*
620      * Clear any current state that might be hanging around.
621      */
622     invalidate_current_state();
623 
624     /*
625      * Start at least "minlines" back.  Default starting point for parsing is
626      * there.
627      * Start further back, to avoid that scrolling backwards will result in
628      * resyncing for every line.  Now it resyncs only one out of N lines,
629      * where N is minlines * 1.5, or minlines * 2 if minlines is small.
630      * Watch out for overflow when minlines is MAXLNUM.
631      */
632     if (syn_block->b_syn_sync_minlines > start_lnum)
633 	start_lnum = 1;
634     else
635     {
636 	if (syn_block->b_syn_sync_minlines == 1)
637 	    lnum = 1;
638 	else if (syn_block->b_syn_sync_minlines < 10)
639 	    lnum = syn_block->b_syn_sync_minlines * 2;
640 	else
641 	    lnum = syn_block->b_syn_sync_minlines * 3 / 2;
642 	if (syn_block->b_syn_sync_maxlines != 0
643 				     && lnum > syn_block->b_syn_sync_maxlines)
644 	    lnum = syn_block->b_syn_sync_maxlines;
645 	if (lnum >= start_lnum)
646 	    start_lnum = 1;
647 	else
648 	    start_lnum -= lnum;
649     }
650     current_lnum = start_lnum;
651 
652     /*
653      * 1. Search backwards for the end of a C-style comment.
654      */
655     if (syn_block->b_syn_sync_flags & SF_CCOMMENT)
656     {
657 	// Need to make syn_buf the current buffer for a moment, to be able to
658 	// use find_start_comment().
659 	curwin_save = curwin;
660 	curwin = wp;
661 	curbuf_save = curbuf;
662 	curbuf = syn_buf;
663 
664 	/*
665 	 * Skip lines that end in a backslash.
666 	 */
667 	for ( ; start_lnum > 1; --start_lnum)
668 	{
669 	    line = ml_get(start_lnum - 1);
670 	    if (*line == NUL || *(line + STRLEN(line) - 1) != '\\')
671 		break;
672 	}
673 	current_lnum = start_lnum;
674 
675 	// set cursor to start of search
676 	cursor_save = wp->w_cursor;
677 	wp->w_cursor.lnum = start_lnum;
678 	wp->w_cursor.col = 0;
679 
680 	/*
681 	 * If the line is inside a comment, need to find the syntax item that
682 	 * defines the comment.
683 	 * Restrict the search for the end of a comment to b_syn_sync_maxlines.
684 	 */
685 	if (find_start_comment((int)syn_block->b_syn_sync_maxlines) != NULL)
686 	{
687 	    for (idx = syn_block->b_syn_patterns.ga_len; --idx >= 0; )
688 		if (SYN_ITEMS(syn_block)[idx].sp_syn.id
689 						   == syn_block->b_syn_sync_id
690 			&& SYN_ITEMS(syn_block)[idx].sp_type == SPTYPE_START)
691 		{
692 		    validate_current_state();
693 		    if (push_current_state(idx) == OK)
694 			update_si_attr(current_state.ga_len - 1);
695 		    break;
696 		}
697 	}
698 
699 	// restore cursor and buffer
700 	wp->w_cursor = cursor_save;
701 	curwin = curwin_save;
702 	curbuf = curbuf_save;
703     }
704 
705     /*
706      * 2. Search backwards for given sync patterns.
707      */
708     else if (syn_block->b_syn_sync_flags & SF_MATCH)
709     {
710 	if (syn_block->b_syn_sync_maxlines != 0
711 			       && start_lnum > syn_block->b_syn_sync_maxlines)
712 	    break_lnum = start_lnum - syn_block->b_syn_sync_maxlines;
713 	else
714 	    break_lnum = 0;
715 
716 	found_m_endpos.lnum = 0;
717 	found_m_endpos.col = 0;
718 	end_lnum = start_lnum;
719 	lnum = start_lnum;
720 	while (--lnum > break_lnum)
721 	{
722 	    // This can take a long time: break when CTRL-C pressed.
723 	    line_breakcheck();
724 	    if (got_int)
725 	    {
726 		invalidate_current_state();
727 		current_lnum = start_lnum;
728 		break;
729 	    }
730 
731 	    // Check if we have run into a valid saved state stack now.
732 	    if (last_valid != NULL && lnum == last_valid->sst_lnum)
733 	    {
734 		load_current_state(last_valid);
735 		break;
736 	    }
737 
738 	    /*
739 	     * Check if the previous line has the line-continuation pattern.
740 	     */
741 	    if (lnum > 1 && syn_match_linecont(lnum - 1))
742 		continue;
743 
744 	    /*
745 	     * Start with nothing on the state stack
746 	     */
747 	    validate_current_state();
748 
749 	    for (current_lnum = lnum; current_lnum < end_lnum; ++current_lnum)
750 	    {
751 		syn_start_line();
752 		for (;;)
753 		{
754 		    had_sync_point = syn_finish_line(TRUE);
755 		    /*
756 		     * When a sync point has been found, remember where, and
757 		     * continue to look for another one, further on in the line.
758 		     */
759 		    if (had_sync_point && current_state.ga_len)
760 		    {
761 			cur_si = &CUR_STATE(current_state.ga_len - 1);
762 			if (cur_si->si_m_endpos.lnum > start_lnum)
763 			{
764 			    // ignore match that goes to after where started
765 			    current_lnum = end_lnum;
766 			    break;
767 			}
768 			if (cur_si->si_idx < 0)
769 			{
770 			    // Cannot happen?
771 			    found_flags = 0;
772 			    found_match_idx = KEYWORD_IDX;
773 			}
774 			else
775 			{
776 			    spp = &(SYN_ITEMS(syn_block)[cur_si->si_idx]);
777 			    found_flags = spp->sp_flags;
778 			    found_match_idx = spp->sp_sync_idx;
779 			}
780 			found_current_lnum = current_lnum;
781 			found_current_col = current_col;
782 			found_m_endpos = cur_si->si_m_endpos;
783 			/*
784 			 * Continue after the match (be aware of a zero-length
785 			 * match).
786 			 */
787 			if (found_m_endpos.lnum > current_lnum)
788 			{
789 			    current_lnum = found_m_endpos.lnum;
790 			    current_col = found_m_endpos.col;
791 			    if (current_lnum >= end_lnum)
792 				break;
793 			}
794 			else if (found_m_endpos.col > current_col)
795 			    current_col = found_m_endpos.col;
796 			else
797 			    ++current_col;
798 
799 			// syn_current_attr() will have skipped the check for
800 			// an item that ends here, need to do that now.  Be
801 			// careful not to go past the NUL.
802 			prev_current_col = current_col;
803 			if (syn_getcurline()[current_col] != NUL)
804 			    ++current_col;
805 			check_state_ends();
806 			current_col = prev_current_col;
807 		    }
808 		    else
809 			break;
810 		}
811 	    }
812 
813 	    /*
814 	     * If a sync point was encountered, break here.
815 	     */
816 	    if (found_flags)
817 	    {
818 		/*
819 		 * Put the item that was specified by the sync point on the
820 		 * state stack.  If there was no item specified, make the
821 		 * state stack empty.
822 		 */
823 		clear_current_state();
824 		if (found_match_idx >= 0
825 			&& push_current_state(found_match_idx) == OK)
826 		    update_si_attr(current_state.ga_len - 1);
827 
828 		/*
829 		 * When using "grouphere", continue from the sync point
830 		 * match, until the end of the line.  Parsing starts at
831 		 * the next line.
832 		 * For "groupthere" the parsing starts at start_lnum.
833 		 */
834 		if (found_flags & HL_SYNC_HERE)
835 		{
836 		    if (current_state.ga_len)
837 		    {
838 			cur_si = &CUR_STATE(current_state.ga_len - 1);
839 			cur_si->si_h_startpos.lnum = found_current_lnum;
840 			cur_si->si_h_startpos.col = found_current_col;
841 			update_si_end(cur_si, (int)current_col, TRUE);
842 			check_keepend();
843 		    }
844 		    current_col = found_m_endpos.col;
845 		    current_lnum = found_m_endpos.lnum;
846 		    (void)syn_finish_line(FALSE);
847 		    ++current_lnum;
848 		}
849 		else
850 		    current_lnum = start_lnum;
851 
852 		break;
853 	    }
854 
855 	    end_lnum = lnum;
856 	    invalidate_current_state();
857 	}
858 
859 	// Ran into start of the file or exceeded maximum number of lines
860 	if (lnum <= break_lnum)
861 	{
862 	    invalidate_current_state();
863 	    current_lnum = break_lnum + 1;
864 	}
865     }
866 
867     validate_current_state();
868 }
869 
870     static void
871 save_chartab(char_u *chartab)
872 {
873     if (syn_block->b_syn_isk != empty_option)
874     {
875 	mch_memmove(chartab, syn_buf->b_chartab, (size_t)32);
876 	mch_memmove(syn_buf->b_chartab, syn_win->w_s->b_syn_chartab,
877 								  (size_t)32);
878     }
879 }
880 
881     static void
882 restore_chartab(char_u *chartab)
883 {
884     if (syn_win->w_s->b_syn_isk != empty_option)
885 	mch_memmove(syn_buf->b_chartab, chartab, (size_t)32);
886 }
887 
888 /*
889  * Return TRUE if the line-continuation pattern matches in line "lnum".
890  */
891     static int
892 syn_match_linecont(linenr_T lnum)
893 {
894     regmmatch_T regmatch;
895     int r;
896     char_u	buf_chartab[32];  // chartab array for syn iskyeyword
897 
898     if (syn_block->b_syn_linecont_prog != NULL)
899     {
900 	// use syntax iskeyword option
901 	save_chartab(buf_chartab);
902 	regmatch.rmm_ic = syn_block->b_syn_linecont_ic;
903 	regmatch.regprog = syn_block->b_syn_linecont_prog;
904 	r = syn_regexec(&regmatch, lnum, (colnr_T)0,
905 				IF_SYN_TIME(&syn_block->b_syn_linecont_time));
906 	syn_block->b_syn_linecont_prog = regmatch.regprog;
907 	restore_chartab(buf_chartab);
908 	return r;
909     }
910     return FALSE;
911 }
912 
913 /*
914  * Prepare the current state for the start of a line.
915  */
916     static void
917 syn_start_line(void)
918 {
919     current_finished = FALSE;
920     current_col = 0;
921 
922     /*
923      * Need to update the end of a start/skip/end that continues from the
924      * previous line and regions that have "keepend".
925      */
926     if (current_state.ga_len > 0)
927     {
928 	syn_update_ends(TRUE);
929 	check_state_ends();
930     }
931 
932     next_match_idx = -1;
933     ++current_line_id;
934 #ifdef FEAT_CONCEAL
935     next_seqnr = 1;
936 #endif
937 }
938 
939 /*
940  * Check for items in the stack that need their end updated.
941  * When "startofline" is TRUE the last item is always updated.
942  * When "startofline" is FALSE the item with "keepend" is forcefully updated.
943  */
944     static void
945 syn_update_ends(int startofline)
946 {
947     stateitem_T	*cur_si;
948     int		i;
949     int		seen_keepend;
950 
951     if (startofline)
952     {
953 	// Check for a match carried over from a previous line with a
954 	// contained region.  The match ends as soon as the region ends.
955 	for (i = 0; i < current_state.ga_len; ++i)
956 	{
957 	    cur_si = &CUR_STATE(i);
958 	    if (cur_si->si_idx >= 0
959 		    && (SYN_ITEMS(syn_block)[cur_si->si_idx]).sp_type
960 							       == SPTYPE_MATCH
961 		    && cur_si->si_m_endpos.lnum < current_lnum)
962 	    {
963 		cur_si->si_flags |= HL_MATCHCONT;
964 		cur_si->si_m_endpos.lnum = 0;
965 		cur_si->si_m_endpos.col = 0;
966 		cur_si->si_h_endpos = cur_si->si_m_endpos;
967 		cur_si->si_ends = TRUE;
968 	    }
969 	}
970     }
971 
972     /*
973      * Need to update the end of a start/skip/end that continues from the
974      * previous line.  And regions that have "keepend", because they may
975      * influence contained items.  If we've just removed "extend"
976      * (startofline == 0) then we should update ends of normal regions
977      * contained inside "keepend" because "extend" could have extended
978      * these "keepend" regions as well as contained normal regions.
979      * Then check for items ending in column 0.
980      */
981     i = current_state.ga_len - 1;
982     if (keepend_level >= 0)
983 	for ( ; i > keepend_level; --i)
984 	    if (CUR_STATE(i).si_flags & HL_EXTEND)
985 		break;
986 
987     seen_keepend = FALSE;
988     for ( ; i < current_state.ga_len; ++i)
989     {
990 	cur_si = &CUR_STATE(i);
991 	if ((cur_si->si_flags & HL_KEEPEND)
992 			    || (seen_keepend && !startofline)
993 			    || (i == current_state.ga_len - 1 && startofline))
994 	{
995 	    cur_si->si_h_startpos.col = 0;	// start highl. in col 0
996 	    cur_si->si_h_startpos.lnum = current_lnum;
997 
998 	    if (!(cur_si->si_flags & HL_MATCHCONT))
999 		update_si_end(cur_si, (int)current_col, !startofline);
1000 
1001 	    if (!startofline && (cur_si->si_flags & HL_KEEPEND))
1002 		seen_keepend = TRUE;
1003 	}
1004     }
1005     check_keepend();
1006 }
1007 
1008 /////////////////////////////////////////
1009 // Handling of the state stack cache.
1010 
1011 /*
1012  * EXPLANATION OF THE SYNTAX STATE STACK CACHE
1013  *
1014  * To speed up syntax highlighting, the state stack for the start of some
1015  * lines is cached.  These entries can be used to start parsing at that point.
1016  *
1017  * The stack is kept in b_sst_array[] for each buffer.  There is a list of
1018  * valid entries.  b_sst_first points to the first one, then follow sst_next.
1019  * The entries are sorted on line number.  The first entry is often for line 2
1020  * (line 1 always starts with an empty stack).
1021  * There is also a list for free entries.  This construction is used to avoid
1022  * having to allocate and free memory blocks too often.
1023  *
1024  * When making changes to the buffer, this is logged in b_mod_*.  When calling
1025  * update_screen() to update the display, it will call
1026  * syn_stack_apply_changes() for each displayed buffer to adjust the cached
1027  * entries.  The entries which are inside the changed area are removed,
1028  * because they must be recomputed.  Entries below the changed have their line
1029  * number adjusted for deleted/inserted lines, and have their sst_change_lnum
1030  * set to indicate that a check must be made if the changed lines would change
1031  * the cached entry.
1032  *
1033  * When later displaying lines, an entry is stored for each line.  Displayed
1034  * lines are likely to be displayed again, in which case the state at the
1035  * start of the line is needed.
1036  * For not displayed lines, an entry is stored for every so many lines.  These
1037  * entries will be used e.g., when scrolling backwards.  The distance between
1038  * entries depends on the number of lines in the buffer.  For small buffers
1039  * the distance is fixed at SST_DIST, for large buffers there is a fixed
1040  * number of entries SST_MAX_ENTRIES, and the distance is computed.
1041  */
1042 
1043     static void
1044 syn_stack_free_block(synblock_T *block)
1045 {
1046     synstate_T	*p;
1047 
1048     if (block->b_sst_array != NULL)
1049     {
1050 	FOR_ALL_SYNSTATES(block, p)
1051 	    clear_syn_state(p);
1052 	VIM_CLEAR(block->b_sst_array);
1053 	block->b_sst_first = NULL;
1054 	block->b_sst_len = 0;
1055     }
1056 }
1057 /*
1058  * Free b_sst_array[] for buffer "buf".
1059  * Used when syntax items changed to force resyncing everywhere.
1060  */
1061     void
1062 syn_stack_free_all(synblock_T *block)
1063 {
1064 #ifdef FEAT_FOLDING
1065     win_T	*wp;
1066 #endif
1067 
1068     syn_stack_free_block(block);
1069 
1070 #ifdef FEAT_FOLDING
1071     // When using "syntax" fold method, must update all folds.
1072     FOR_ALL_WINDOWS(wp)
1073     {
1074 	if (wp->w_s == block && foldmethodIsSyntax(wp))
1075 	    foldUpdateAll(wp);
1076     }
1077 #endif
1078 }
1079 
1080 /*
1081  * Allocate the syntax state stack for syn_buf when needed.
1082  * If the number of entries in b_sst_array[] is much too big or a bit too
1083  * small, reallocate it.
1084  * Also used to allocate b_sst_array[] for the first time.
1085  */
1086     static void
1087 syn_stack_alloc(void)
1088 {
1089     long	len;
1090     synstate_T	*to, *from;
1091     synstate_T	*sstp;
1092 
1093     len = syn_buf->b_ml.ml_line_count / SST_DIST + Rows * 2;
1094     if (len < SST_MIN_ENTRIES)
1095 	len = SST_MIN_ENTRIES;
1096     else if (len > SST_MAX_ENTRIES)
1097 	len = SST_MAX_ENTRIES;
1098     if (syn_block->b_sst_len > len * 2 || syn_block->b_sst_len < len)
1099     {
1100 	// Allocate 50% too much, to avoid reallocating too often.
1101 	len = syn_buf->b_ml.ml_line_count;
1102 	len = (len + len / 2) / SST_DIST + Rows * 2;
1103 	if (len < SST_MIN_ENTRIES)
1104 	    len = SST_MIN_ENTRIES;
1105 	else if (len > SST_MAX_ENTRIES)
1106 	    len = SST_MAX_ENTRIES;
1107 
1108 	if (syn_block->b_sst_array != NULL)
1109 	{
1110 	    // When shrinking the array, cleanup the existing stack.
1111 	    // Make sure that all valid entries fit in the new array.
1112 	    while (syn_block->b_sst_len - syn_block->b_sst_freecount + 2 > len
1113 		    && syn_stack_cleanup())
1114 		;
1115 	    if (len < syn_block->b_sst_len - syn_block->b_sst_freecount + 2)
1116 		len = syn_block->b_sst_len - syn_block->b_sst_freecount + 2;
1117 	}
1118 
1119 	sstp = ALLOC_CLEAR_MULT(synstate_T, len);
1120 	if (sstp == NULL)	// out of memory!
1121 	    return;
1122 
1123 	to = sstp - 1;
1124 	if (syn_block->b_sst_array != NULL)
1125 	{
1126 	    // Move the states from the old array to the new one.
1127 	    for (from = syn_block->b_sst_first; from != NULL;
1128 							from = from->sst_next)
1129 	    {
1130 		++to;
1131 		*to = *from;
1132 		to->sst_next = to + 1;
1133 	    }
1134 	}
1135 	if (to != sstp - 1)
1136 	{
1137 	    to->sst_next = NULL;
1138 	    syn_block->b_sst_first = sstp;
1139 	    syn_block->b_sst_freecount = len - (int)(to - sstp) - 1;
1140 	}
1141 	else
1142 	{
1143 	    syn_block->b_sst_first = NULL;
1144 	    syn_block->b_sst_freecount = len;
1145 	}
1146 
1147 	// Create the list of free entries.
1148 	syn_block->b_sst_firstfree = to + 1;
1149 	while (++to < sstp + len)
1150 	    to->sst_next = to + 1;
1151 	(sstp + len - 1)->sst_next = NULL;
1152 
1153 	vim_free(syn_block->b_sst_array);
1154 	syn_block->b_sst_array = sstp;
1155 	syn_block->b_sst_len = len;
1156     }
1157 }
1158 
1159 /*
1160  * Check for changes in a buffer to affect stored syntax states.  Uses the
1161  * b_mod_* fields.
1162  * Called from update_screen(), before screen is being updated, once for each
1163  * displayed buffer.
1164  */
1165     void
1166 syn_stack_apply_changes(buf_T *buf)
1167 {
1168     win_T	*wp;
1169 
1170     syn_stack_apply_changes_block(&buf->b_s, buf);
1171 
1172     FOR_ALL_WINDOWS(wp)
1173     {
1174 	if ((wp->w_buffer == buf) && (wp->w_s != &buf->b_s))
1175 	    syn_stack_apply_changes_block(wp->w_s, buf);
1176     }
1177 }
1178 
1179     static void
1180 syn_stack_apply_changes_block(synblock_T *block, buf_T *buf)
1181 {
1182     synstate_T	*p, *prev, *np;
1183     linenr_T	n;
1184 
1185     prev = NULL;
1186     for (p = block->b_sst_first; p != NULL; )
1187     {
1188 	if (p->sst_lnum + block->b_syn_sync_linebreaks > buf->b_mod_top)
1189 	{
1190 	    n = p->sst_lnum + buf->b_mod_xlines;
1191 	    if (n <= buf->b_mod_bot)
1192 	    {
1193 		// this state is inside the changed area, remove it
1194 		np = p->sst_next;
1195 		if (prev == NULL)
1196 		    block->b_sst_first = np;
1197 		else
1198 		    prev->sst_next = np;
1199 		syn_stack_free_entry(block, p);
1200 		p = np;
1201 		continue;
1202 	    }
1203 	    // This state is below the changed area.  Remember the line
1204 	    // that needs to be parsed before this entry can be made valid
1205 	    // again.
1206 	    if (p->sst_change_lnum != 0 && p->sst_change_lnum > buf->b_mod_top)
1207 	    {
1208 		if (p->sst_change_lnum + buf->b_mod_xlines > buf->b_mod_top)
1209 		    p->sst_change_lnum += buf->b_mod_xlines;
1210 		else
1211 		    p->sst_change_lnum = buf->b_mod_top;
1212 	    }
1213 	    if (p->sst_change_lnum == 0
1214 		    || p->sst_change_lnum < buf->b_mod_bot)
1215 		p->sst_change_lnum = buf->b_mod_bot;
1216 
1217 	    p->sst_lnum = n;
1218 	}
1219 	prev = p;
1220 	p = p->sst_next;
1221     }
1222 }
1223 
1224 /*
1225  * Reduce the number of entries in the state stack for syn_buf.
1226  * Returns TRUE if at least one entry was freed.
1227  */
1228     static int
1229 syn_stack_cleanup(void)
1230 {
1231     synstate_T	*p, *prev;
1232     disptick_T	tick;
1233     int		above;
1234     int		dist;
1235     int		retval = FALSE;
1236 
1237     if (syn_block->b_sst_first == NULL)
1238 	return retval;
1239 
1240     // Compute normal distance between non-displayed entries.
1241     if (syn_block->b_sst_len <= Rows)
1242 	dist = 999999;
1243     else
1244 	dist = syn_buf->b_ml.ml_line_count / (syn_block->b_sst_len - Rows) + 1;
1245 
1246     /*
1247      * Go through the list to find the "tick" for the oldest entry that can
1248      * be removed.  Set "above" when the "tick" for the oldest entry is above
1249      * "b_sst_lasttick" (the display tick wraps around).
1250      */
1251     tick = syn_block->b_sst_lasttick;
1252     above = FALSE;
1253     prev = syn_block->b_sst_first;
1254     for (p = prev->sst_next; p != NULL; prev = p, p = p->sst_next)
1255     {
1256 	if (prev->sst_lnum + dist > p->sst_lnum)
1257 	{
1258 	    if (p->sst_tick > syn_block->b_sst_lasttick)
1259 	    {
1260 		if (!above || p->sst_tick < tick)
1261 		    tick = p->sst_tick;
1262 		above = TRUE;
1263 	    }
1264 	    else if (!above && p->sst_tick < tick)
1265 		tick = p->sst_tick;
1266 	}
1267     }
1268 
1269     /*
1270      * Go through the list to make the entries for the oldest tick at an
1271      * interval of several lines.
1272      */
1273     prev = syn_block->b_sst_first;
1274     for (p = prev->sst_next; p != NULL; prev = p, p = p->sst_next)
1275     {
1276 	if (p->sst_tick == tick && prev->sst_lnum + dist > p->sst_lnum)
1277 	{
1278 	    // Move this entry from used list to free list
1279 	    prev->sst_next = p->sst_next;
1280 	    syn_stack_free_entry(syn_block, p);
1281 	    p = prev;
1282 	    retval = TRUE;
1283 	}
1284     }
1285     return retval;
1286 }
1287 
1288 /*
1289  * Free the allocated memory for a syn_state item.
1290  * Move the entry into the free list.
1291  */
1292     static void
1293 syn_stack_free_entry(synblock_T *block, synstate_T *p)
1294 {
1295     clear_syn_state(p);
1296     p->sst_next = block->b_sst_firstfree;
1297     block->b_sst_firstfree = p;
1298     ++block->b_sst_freecount;
1299 }
1300 
1301 /*
1302  * Find an entry in the list of state stacks at or before "lnum".
1303  * Returns NULL when there is no entry or the first entry is after "lnum".
1304  */
1305     static synstate_T *
1306 syn_stack_find_entry(linenr_T lnum)
1307 {
1308     synstate_T	*p, *prev;
1309 
1310     prev = NULL;
1311     for (p = syn_block->b_sst_first; p != NULL; prev = p, p = p->sst_next)
1312     {
1313 	if (p->sst_lnum == lnum)
1314 	    return p;
1315 	if (p->sst_lnum > lnum)
1316 	    break;
1317     }
1318     return prev;
1319 }
1320 
1321 /*
1322  * Try saving the current state in b_sst_array[].
1323  * The current state must be valid for the start of the current_lnum line!
1324  */
1325     static synstate_T *
1326 store_current_state(void)
1327 {
1328     int		i;
1329     synstate_T	*p;
1330     bufstate_T	*bp;
1331     stateitem_T	*cur_si;
1332     synstate_T	*sp = syn_stack_find_entry(current_lnum);
1333 
1334     /*
1335      * If the current state contains a start or end pattern that continues
1336      * from the previous line, we can't use it.  Don't store it then.
1337      */
1338     for (i = current_state.ga_len - 1; i >= 0; --i)
1339     {
1340 	cur_si = &CUR_STATE(i);
1341 	if (cur_si->si_h_startpos.lnum >= current_lnum
1342 		|| cur_si->si_m_endpos.lnum >= current_lnum
1343 		|| cur_si->si_h_endpos.lnum >= current_lnum
1344 		|| (cur_si->si_end_idx
1345 		    && cur_si->si_eoe_pos.lnum >= current_lnum))
1346 	    break;
1347     }
1348     if (i >= 0)
1349     {
1350 	if (sp != NULL)
1351 	{
1352 	    // find "sp" in the list and remove it
1353 	    if (syn_block->b_sst_first == sp)
1354 		// it's the first entry
1355 		syn_block->b_sst_first = sp->sst_next;
1356 	    else
1357 	    {
1358 		// find the entry just before this one to adjust sst_next
1359 		FOR_ALL_SYNSTATES(syn_block, p)
1360 		    if (p->sst_next == sp)
1361 			break;
1362 		if (p != NULL)	// just in case
1363 		    p->sst_next = sp->sst_next;
1364 	    }
1365 	    syn_stack_free_entry(syn_block, sp);
1366 	    sp = NULL;
1367 	}
1368     }
1369     else if (sp == NULL || sp->sst_lnum != current_lnum)
1370     {
1371 	/*
1372 	 * Add a new entry
1373 	 */
1374 	// If no free items, cleanup the array first.
1375 	if (syn_block->b_sst_freecount == 0)
1376 	{
1377 	    (void)syn_stack_cleanup();
1378 	    // "sp" may have been moved to the freelist now
1379 	    sp = syn_stack_find_entry(current_lnum);
1380 	}
1381 	// Still no free items?  Must be a strange problem...
1382 	if (syn_block->b_sst_freecount == 0)
1383 	    sp = NULL;
1384 	else
1385 	{
1386 	    // Take the first item from the free list and put it in the used
1387 	    // list, after *sp
1388 	    p = syn_block->b_sst_firstfree;
1389 	    syn_block->b_sst_firstfree = p->sst_next;
1390 	    --syn_block->b_sst_freecount;
1391 	    if (sp == NULL)
1392 	    {
1393 		// Insert in front of the list
1394 		p->sst_next = syn_block->b_sst_first;
1395 		syn_block->b_sst_first = p;
1396 	    }
1397 	    else
1398 	    {
1399 		// insert in list after *sp
1400 		p->sst_next = sp->sst_next;
1401 		sp->sst_next = p;
1402 	    }
1403 	    sp = p;
1404 	    sp->sst_stacksize = 0;
1405 	    sp->sst_lnum = current_lnum;
1406 	}
1407     }
1408     if (sp != NULL)
1409     {
1410 	// When overwriting an existing state stack, clear it first
1411 	clear_syn_state(sp);
1412 	sp->sst_stacksize = current_state.ga_len;
1413 	if (current_state.ga_len > SST_FIX_STATES)
1414 	{
1415 	    // Need to clear it, might be something remaining from when the
1416 	    // length was less than SST_FIX_STATES.
1417 	    ga_init2(&sp->sst_union.sst_ga, (int)sizeof(bufstate_T), 1);
1418 	    if (ga_grow(&sp->sst_union.sst_ga, current_state.ga_len) == FAIL)
1419 		sp->sst_stacksize = 0;
1420 	    else
1421 		sp->sst_union.sst_ga.ga_len = current_state.ga_len;
1422 	    bp = SYN_STATE_P(&(sp->sst_union.sst_ga));
1423 	}
1424 	else
1425 	    bp = sp->sst_union.sst_stack;
1426 	for (i = 0; i < sp->sst_stacksize; ++i)
1427 	{
1428 	    bp[i].bs_idx = CUR_STATE(i).si_idx;
1429 	    bp[i].bs_flags = CUR_STATE(i).si_flags;
1430 #ifdef FEAT_CONCEAL
1431 	    bp[i].bs_seqnr = CUR_STATE(i).si_seqnr;
1432 	    bp[i].bs_cchar = CUR_STATE(i).si_cchar;
1433 #endif
1434 	    bp[i].bs_extmatch = ref_extmatch(CUR_STATE(i).si_extmatch);
1435 	}
1436 	sp->sst_next_flags = current_next_flags;
1437 	sp->sst_next_list = current_next_list;
1438 	sp->sst_tick = display_tick;
1439 	sp->sst_change_lnum = 0;
1440     }
1441     current_state_stored = TRUE;
1442     return sp;
1443 }
1444 
1445 /*
1446  * Copy a state stack from "from" in b_sst_array[] to current_state;
1447  */
1448     static void
1449 load_current_state(synstate_T *from)
1450 {
1451     int		i;
1452     bufstate_T	*bp;
1453 
1454     clear_current_state();
1455     validate_current_state();
1456     keepend_level = -1;
1457     if (from->sst_stacksize
1458 	    && ga_grow(&current_state, from->sst_stacksize) != FAIL)
1459     {
1460 	if (from->sst_stacksize > SST_FIX_STATES)
1461 	    bp = SYN_STATE_P(&(from->sst_union.sst_ga));
1462 	else
1463 	    bp = from->sst_union.sst_stack;
1464 	for (i = 0; i < from->sst_stacksize; ++i)
1465 	{
1466 	    CUR_STATE(i).si_idx = bp[i].bs_idx;
1467 	    CUR_STATE(i).si_flags = bp[i].bs_flags;
1468 #ifdef FEAT_CONCEAL
1469 	    CUR_STATE(i).si_seqnr = bp[i].bs_seqnr;
1470 	    CUR_STATE(i).si_cchar = bp[i].bs_cchar;
1471 #endif
1472 	    CUR_STATE(i).si_extmatch = ref_extmatch(bp[i].bs_extmatch);
1473 	    if (keepend_level < 0 && (CUR_STATE(i).si_flags & HL_KEEPEND))
1474 		keepend_level = i;
1475 	    CUR_STATE(i).si_ends = FALSE;
1476 	    CUR_STATE(i).si_m_lnum = 0;
1477 	    if (CUR_STATE(i).si_idx >= 0)
1478 		CUR_STATE(i).si_next_list =
1479 		     (SYN_ITEMS(syn_block)[CUR_STATE(i).si_idx]).sp_next_list;
1480 	    else
1481 		CUR_STATE(i).si_next_list = NULL;
1482 	    update_si_attr(i);
1483 	}
1484 	current_state.ga_len = from->sst_stacksize;
1485     }
1486     current_next_list = from->sst_next_list;
1487     current_next_flags = from->sst_next_flags;
1488     current_lnum = from->sst_lnum;
1489 }
1490 
1491 /*
1492  * Compare saved state stack "*sp" with the current state.
1493  * Return TRUE when they are equal.
1494  */
1495     static int
1496 syn_stack_equal(synstate_T *sp)
1497 {
1498     int		i, j;
1499     bufstate_T	*bp;
1500     reg_extmatch_T	*six, *bsx;
1501 
1502     // First a quick check if the stacks have the same size end nextlist.
1503     if (sp->sst_stacksize == current_state.ga_len
1504 	    && sp->sst_next_list == current_next_list)
1505     {
1506 	// Need to compare all states on both stacks.
1507 	if (sp->sst_stacksize > SST_FIX_STATES)
1508 	    bp = SYN_STATE_P(&(sp->sst_union.sst_ga));
1509 	else
1510 	    bp = sp->sst_union.sst_stack;
1511 
1512 	for (i = current_state.ga_len; --i >= 0; )
1513 	{
1514 	    // If the item has another index the state is different.
1515 	    if (bp[i].bs_idx != CUR_STATE(i).si_idx)
1516 		break;
1517 	    if (bp[i].bs_extmatch != CUR_STATE(i).si_extmatch)
1518 	    {
1519 		// When the extmatch pointers are different, the strings in
1520 		// them can still be the same.  Check if the extmatch
1521 		// references are equal.
1522 		bsx = bp[i].bs_extmatch;
1523 		six = CUR_STATE(i).si_extmatch;
1524 		// If one of the extmatch pointers is NULL the states are
1525 		// different.
1526 		if (bsx == NULL || six == NULL)
1527 		    break;
1528 		for (j = 0; j < NSUBEXP; ++j)
1529 		{
1530 		    // Check each referenced match string. They must all be
1531 		    // equal.
1532 		    if (bsx->matches[j] != six->matches[j])
1533 		    {
1534 			// If the pointer is different it can still be the
1535 			// same text.  Compare the strings, ignore case when
1536 			// the start item has the sp_ic flag set.
1537 			if (bsx->matches[j] == NULL
1538 				|| six->matches[j] == NULL)
1539 			    break;
1540 			if ((SYN_ITEMS(syn_block)[CUR_STATE(i).si_idx]).sp_ic
1541 				? MB_STRICMP(bsx->matches[j],
1542 							 six->matches[j]) != 0
1543 				: STRCMP(bsx->matches[j], six->matches[j]) != 0)
1544 			    break;
1545 		    }
1546 		}
1547 		if (j != NSUBEXP)
1548 		    break;
1549 	    }
1550 	}
1551 	if (i < 0)
1552 	    return TRUE;
1553     }
1554     return FALSE;
1555 }
1556 
1557 /*
1558  * We stop parsing syntax above line "lnum".  If the stored state at or below
1559  * this line depended on a change before it, it now depends on the line below
1560  * the last parsed line.
1561  * The window looks like this:
1562  *	    line which changed
1563  *	    displayed line
1564  *	    displayed line
1565  * lnum ->  line below window
1566  */
1567     void
1568 syntax_end_parsing(linenr_T lnum)
1569 {
1570     synstate_T	*sp;
1571 
1572     sp = syn_stack_find_entry(lnum);
1573     if (sp != NULL && sp->sst_lnum < lnum)
1574 	sp = sp->sst_next;
1575 
1576     if (sp != NULL && sp->sst_change_lnum != 0)
1577 	sp->sst_change_lnum = lnum;
1578 }
1579 
1580 /*
1581  * End of handling of the state stack.
1582  ****************************************/
1583 
1584     static void
1585 invalidate_current_state(void)
1586 {
1587     clear_current_state();
1588     current_state.ga_itemsize = 0;	// mark current_state invalid
1589     current_next_list = NULL;
1590     keepend_level = -1;
1591 }
1592 
1593     static void
1594 validate_current_state(void)
1595 {
1596     current_state.ga_itemsize = sizeof(stateitem_T);
1597     current_state.ga_growsize = 3;
1598 }
1599 
1600 /*
1601  * Return TRUE if the syntax at start of lnum changed since last time.
1602  * This will only be called just after get_syntax_attr() for the previous
1603  * line, to check if the next line needs to be redrawn too.
1604  */
1605     int
1606 syntax_check_changed(linenr_T lnum)
1607 {
1608     int		retval = TRUE;
1609     synstate_T	*sp;
1610 
1611     /*
1612      * Check the state stack when:
1613      * - lnum is just below the previously syntaxed line.
1614      * - lnum is not before the lines with saved states.
1615      * - lnum is not past the lines with saved states.
1616      * - lnum is at or before the last changed line.
1617      */
1618     if (VALID_STATE(&current_state) && lnum == current_lnum + 1)
1619     {
1620 	sp = syn_stack_find_entry(lnum);
1621 	if (sp != NULL && sp->sst_lnum == lnum)
1622 	{
1623 	    /*
1624 	     * finish the previous line (needed when not all of the line was
1625 	     * drawn)
1626 	     */
1627 	    (void)syn_finish_line(FALSE);
1628 
1629 	    /*
1630 	     * Compare the current state with the previously saved state of
1631 	     * the line.
1632 	     */
1633 	    if (syn_stack_equal(sp))
1634 		retval = FALSE;
1635 
1636 	    /*
1637 	     * Store the current state in b_sst_array[] for later use.
1638 	     */
1639 	    ++current_lnum;
1640 	    (void)store_current_state();
1641 	}
1642     }
1643 
1644     return retval;
1645 }
1646 
1647 /*
1648  * Finish the current line.
1649  * This doesn't return any attributes, it only gets the state at the end of
1650  * the line.  It can start anywhere in the line, as long as the current state
1651  * is valid.
1652  */
1653     static int
1654 syn_finish_line(
1655     int	    syncing)		// called for syncing
1656 {
1657     stateitem_T	*cur_si;
1658     colnr_T	prev_current_col;
1659 
1660     while (!current_finished)
1661     {
1662 	(void)syn_current_attr(syncing, FALSE, NULL, FALSE);
1663 	/*
1664 	 * When syncing, and found some item, need to check the item.
1665 	 */
1666 	if (syncing && current_state.ga_len)
1667 	{
1668 	    /*
1669 	     * Check for match with sync item.
1670 	     */
1671 	    cur_si = &CUR_STATE(current_state.ga_len - 1);
1672 	    if (cur_si->si_idx >= 0
1673 		    && (SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags
1674 					  & (HL_SYNC_HERE|HL_SYNC_THERE)))
1675 		return TRUE;
1676 
1677 	    // syn_current_attr() will have skipped the check for an item
1678 	    // that ends here, need to do that now.  Be careful not to go
1679 	    // past the NUL.
1680 	    prev_current_col = current_col;
1681 	    if (syn_getcurline()[current_col] != NUL)
1682 		++current_col;
1683 	    check_state_ends();
1684 	    current_col = prev_current_col;
1685 	}
1686 	++current_col;
1687     }
1688     return FALSE;
1689 }
1690 
1691 /*
1692  * Return highlight attributes for next character.
1693  * Must first call syntax_start() once for the line.
1694  * "col" is normally 0 for the first use in a line, and increments by one each
1695  * time.  It's allowed to skip characters and to stop before the end of the
1696  * line.  But only a "col" after a previously used column is allowed.
1697  * When "can_spell" is not NULL set it to TRUE when spell-checking should be
1698  * done.
1699  */
1700     int
1701 get_syntax_attr(
1702     colnr_T	col,
1703     int		*can_spell,
1704     int		keep_state)	// keep state of char at "col"
1705 {
1706     int	    attr = 0;
1707 
1708     if (can_spell != NULL)
1709 	// Default: Only do spelling when there is no @Spell cluster or when
1710 	// ":syn spell toplevel" was used.
1711 	*can_spell = syn_block->b_syn_spell == SYNSPL_DEFAULT
1712 		    ? (syn_block->b_spell_cluster_id == 0)
1713 		    : (syn_block->b_syn_spell == SYNSPL_TOP);
1714 
1715     // check for out of memory situation
1716     if (syn_block->b_sst_array == NULL)
1717 	return 0;
1718 
1719     // After 'synmaxcol' the attribute is always zero.
1720     if (syn_buf->b_p_smc > 0 && col >= (colnr_T)syn_buf->b_p_smc)
1721     {
1722 	clear_current_state();
1723 #ifdef FEAT_EVAL
1724 	current_id = 0;
1725 	current_trans_id = 0;
1726 #endif
1727 #ifdef FEAT_CONCEAL
1728 	current_flags = 0;
1729 	current_seqnr = 0;
1730 #endif
1731 	return 0;
1732     }
1733 
1734     // Make sure current_state is valid
1735     if (INVALID_STATE(&current_state))
1736 	validate_current_state();
1737 
1738     /*
1739      * Skip from the current column to "col", get the attributes for "col".
1740      */
1741     while (current_col <= col)
1742     {
1743 	attr = syn_current_attr(FALSE, TRUE, can_spell,
1744 				     current_col == col ? keep_state : FALSE);
1745 	++current_col;
1746     }
1747 
1748     return attr;
1749 }
1750 
1751 /*
1752  * Get syntax attributes for current_lnum, current_col.
1753  */
1754     static int
1755 syn_current_attr(
1756     int		syncing,		// When 1: called for syncing
1757     int		displaying,		// result will be displayed
1758     int		*can_spell,		// return: do spell checking
1759     int		keep_state)		// keep syntax stack afterwards
1760 {
1761     int		syn_id;
1762     lpos_T	endpos;		// was: char_u *endp;
1763     lpos_T	hl_startpos;	// was: int hl_startcol;
1764     lpos_T	hl_endpos;
1765     lpos_T	eos_pos;	// end-of-start match (start region)
1766     lpos_T	eoe_pos;	// end-of-end pattern
1767     int		end_idx;	// group ID for end pattern
1768     int		idx;
1769     synpat_T	*spp;
1770     stateitem_T	*cur_si, *sip = NULL;
1771     int		startcol;
1772     int		endcol;
1773     long	flags;
1774     int		cchar;
1775     short	*next_list;
1776     int		found_match;		    // found usable match
1777     static int	try_next_column = FALSE;    // must try in next col
1778     int		do_keywords;
1779     regmmatch_T	regmatch;
1780     lpos_T	pos;
1781     int		lc_col;
1782     reg_extmatch_T *cur_extmatch = NULL;
1783     char_u	buf_chartab[32];  // chartab array for syn iskyeyword
1784     char_u	*line;		// current line.  NOTE: becomes invalid after
1785 				// looking for a pattern match!
1786 
1787     // variables for zero-width matches that have a "nextgroup" argument
1788     int		keep_next_list;
1789     int		zero_width_next_list = FALSE;
1790     garray_T	zero_width_next_ga;
1791 
1792     /*
1793      * No character, no attributes!  Past end of line?
1794      * Do try matching with an empty line (could be the start of a region).
1795      */
1796     line = syn_getcurline();
1797     if (line[current_col] == NUL && current_col != 0)
1798     {
1799 	/*
1800 	 * If we found a match after the last column, use it.
1801 	 */
1802 	if (next_match_idx >= 0 && next_match_col >= (int)current_col
1803 						  && next_match_col != MAXCOL)
1804 	    (void)push_next_match(NULL);
1805 
1806 	current_finished = TRUE;
1807 	current_state_stored = FALSE;
1808 	return 0;
1809     }
1810 
1811     // if the current or next character is NUL, we will finish the line now
1812     if (line[current_col] == NUL || line[current_col + 1] == NUL)
1813     {
1814 	current_finished = TRUE;
1815 	current_state_stored = FALSE;
1816     }
1817 
1818     /*
1819      * When in the previous column there was a match but it could not be used
1820      * (empty match or already matched in this column) need to try again in
1821      * the next column.
1822      */
1823     if (try_next_column)
1824     {
1825 	next_match_idx = -1;
1826 	try_next_column = FALSE;
1827     }
1828 
1829     // Only check for keywords when not syncing and there are some.
1830     do_keywords = !syncing
1831 		    && (syn_block->b_keywtab.ht_used > 0
1832 			    || syn_block->b_keywtab_ic.ht_used > 0);
1833 
1834     // Init the list of zero-width matches with a nextlist.  This is used to
1835     // avoid matching the same item in the same position twice.
1836     ga_init2(&zero_width_next_ga, (int)sizeof(int), 10);
1837 
1838     // use syntax iskeyword option
1839     save_chartab(buf_chartab);
1840 
1841     /*
1842      * Repeat matching keywords and patterns, to find contained items at the
1843      * same column.  This stops when there are no extra matches at the current
1844      * column.
1845      */
1846     do
1847     {
1848 	found_match = FALSE;
1849 	keep_next_list = FALSE;
1850 	syn_id = 0;
1851 
1852 
1853 	/*
1854 	 * 1. Check for a current state.
1855 	 *    Only when there is no current state, or if the current state may
1856 	 *    contain other things, we need to check for keywords and patterns.
1857 	 *    Always need to check for contained items if some item has the
1858 	 *    "containedin" argument (takes extra time!).
1859 	 */
1860 	if (current_state.ga_len)
1861 	    cur_si = &CUR_STATE(current_state.ga_len - 1);
1862 	else
1863 	    cur_si = NULL;
1864 
1865 	if (syn_block->b_syn_containedin || cur_si == NULL
1866 					      || cur_si->si_cont_list != NULL)
1867 	{
1868 	    /*
1869 	     * 2. Check for keywords, if on a keyword char after a non-keyword
1870 	     *	  char.  Don't do this when syncing.
1871 	     */
1872 	    if (do_keywords)
1873 	    {
1874 	      line = syn_getcurline();
1875 	      if (vim_iswordp_buf(line + current_col, syn_buf)
1876 		      && (current_col == 0
1877 			  || !vim_iswordp_buf(line + current_col - 1
1878 			      - (has_mbyte
1879 				  ? (*mb_head_off)(line, line + current_col - 1)
1880 				  : 0) , syn_buf)))
1881 	      {
1882 		syn_id = check_keyword_id(line, (int)current_col,
1883 					 &endcol, &flags, &next_list, cur_si,
1884 					 &cchar);
1885 		if (syn_id != 0)
1886 		{
1887 		    if (push_current_state(KEYWORD_IDX) == OK)
1888 		    {
1889 			cur_si = &CUR_STATE(current_state.ga_len - 1);
1890 			cur_si->si_m_startcol = current_col;
1891 			cur_si->si_h_startpos.lnum = current_lnum;
1892 			cur_si->si_h_startpos.col = 0;	// starts right away
1893 			cur_si->si_m_endpos.lnum = current_lnum;
1894 			cur_si->si_m_endpos.col = endcol;
1895 			cur_si->si_h_endpos.lnum = current_lnum;
1896 			cur_si->si_h_endpos.col = endcol;
1897 			cur_si->si_ends = TRUE;
1898 			cur_si->si_end_idx = 0;
1899 			cur_si->si_flags = flags;
1900 #ifdef FEAT_CONCEAL
1901 			cur_si->si_seqnr = next_seqnr++;
1902 			cur_si->si_cchar = cchar;
1903 			if (current_state.ga_len > 1)
1904 			    cur_si->si_flags |=
1905 				  CUR_STATE(current_state.ga_len - 2).si_flags
1906 								 & HL_CONCEAL;
1907 #endif
1908 			cur_si->si_id = syn_id;
1909 			cur_si->si_trans_id = syn_id;
1910 			if (flags & HL_TRANSP)
1911 			{
1912 			    if (current_state.ga_len < 2)
1913 			    {
1914 				cur_si->si_attr = 0;
1915 				cur_si->si_trans_id = 0;
1916 			    }
1917 			    else
1918 			    {
1919 				cur_si->si_attr = CUR_STATE(
1920 					current_state.ga_len - 2).si_attr;
1921 				cur_si->si_trans_id = CUR_STATE(
1922 					current_state.ga_len - 2).si_trans_id;
1923 			    }
1924 			}
1925 			else
1926 			    cur_si->si_attr = syn_id2attr(syn_id);
1927 			cur_si->si_cont_list = NULL;
1928 			cur_si->si_next_list = next_list;
1929 			check_keepend();
1930 		    }
1931 		    else
1932 			vim_free(next_list);
1933 		}
1934 	      }
1935 	    }
1936 
1937 	    /*
1938 	     * 3. Check for patterns (only if no keyword found).
1939 	     */
1940 	    if (syn_id == 0 && syn_block->b_syn_patterns.ga_len)
1941 	    {
1942 		/*
1943 		 * If we didn't check for a match yet, or we are past it, check
1944 		 * for any match with a pattern.
1945 		 */
1946 		if (next_match_idx < 0 || next_match_col < (int)current_col)
1947 		{
1948 		    /*
1949 		     * Check all relevant patterns for a match at this
1950 		     * position.  This is complicated, because matching with a
1951 		     * pattern takes quite a bit of time, thus we want to
1952 		     * avoid doing it when it's not needed.
1953 		     */
1954 		    next_match_idx = 0;		// no match in this line yet
1955 		    next_match_col = MAXCOL;
1956 		    for (idx = syn_block->b_syn_patterns.ga_len; --idx >= 0; )
1957 		    {
1958 			spp = &(SYN_ITEMS(syn_block)[idx]);
1959 			if (	   spp->sp_syncing == syncing
1960 				&& (displaying || !(spp->sp_flags & HL_DISPLAY))
1961 				&& (spp->sp_type == SPTYPE_MATCH
1962 				    || spp->sp_type == SPTYPE_START)
1963 				&& (current_next_list != NULL
1964 				    ? in_id_list(NULL, current_next_list,
1965 							      &spp->sp_syn, 0)
1966 				    : (cur_si == NULL
1967 					? !(spp->sp_flags & HL_CONTAINED)
1968 					: in_id_list(cur_si,
1969 					    cur_si->si_cont_list, &spp->sp_syn,
1970 					    spp->sp_flags & HL_CONTAINED))))
1971 			{
1972 			    int r;
1973 
1974 			    // If we already tried matching in this line, and
1975 			    // there isn't a match before next_match_col, skip
1976 			    // this item.
1977 			    if (spp->sp_line_id == current_line_id
1978 				    && spp->sp_startcol >= next_match_col)
1979 				continue;
1980 			    spp->sp_line_id = current_line_id;
1981 
1982 			    lc_col = current_col - spp->sp_offsets[SPO_LC_OFF];
1983 			    if (lc_col < 0)
1984 				lc_col = 0;
1985 
1986 			    regmatch.rmm_ic = spp->sp_ic;
1987 			    regmatch.regprog = spp->sp_prog;
1988 			    r = syn_regexec(&regmatch,
1989 					     current_lnum,
1990 					     (colnr_T)lc_col,
1991 					     IF_SYN_TIME(&spp->sp_time));
1992 			    spp->sp_prog = regmatch.regprog;
1993 			    if (!r)
1994 			    {
1995 				// no match in this line, try another one
1996 				spp->sp_startcol = MAXCOL;
1997 				continue;
1998 			    }
1999 
2000 			    /*
2001 			     * Compute the first column of the match.
2002 			     */
2003 			    syn_add_start_off(&pos, &regmatch,
2004 							 spp, SPO_MS_OFF, -1);
2005 			    if (pos.lnum > current_lnum)
2006 			    {
2007 				// must have used end of match in a next line,
2008 				// we can't handle that
2009 				spp->sp_startcol = MAXCOL;
2010 				continue;
2011 			    }
2012 			    startcol = pos.col;
2013 
2014 			    // remember the next column where this pattern
2015 			    // matches in the current line
2016 			    spp->sp_startcol = startcol;
2017 
2018 			    /*
2019 			     * If a previously found match starts at a lower
2020 			     * column number, don't use this one.
2021 			     */
2022 			    if (startcol >= next_match_col)
2023 				continue;
2024 
2025 			    /*
2026 			     * If we matched this pattern at this position
2027 			     * before, skip it.  Must retry in the next
2028 			     * column, because it may match from there.
2029 			     */
2030 			    if (did_match_already(idx, &zero_width_next_ga))
2031 			    {
2032 				try_next_column = TRUE;
2033 				continue;
2034 			    }
2035 
2036 			    endpos.lnum = regmatch.endpos[0].lnum;
2037 			    endpos.col = regmatch.endpos[0].col;
2038 
2039 			    // Compute the highlight start.
2040 			    syn_add_start_off(&hl_startpos, &regmatch,
2041 							 spp, SPO_HS_OFF, -1);
2042 
2043 			    // Compute the region start.
2044 			    // Default is to use the end of the match.
2045 			    syn_add_end_off(&eos_pos, &regmatch,
2046 							 spp, SPO_RS_OFF, 0);
2047 
2048 			    /*
2049 			     * Grab the external submatches before they get
2050 			     * overwritten.  Reference count doesn't change.
2051 			     */
2052 			    unref_extmatch(cur_extmatch);
2053 			    cur_extmatch = re_extmatch_out;
2054 			    re_extmatch_out = NULL;
2055 
2056 			    flags = 0;
2057 			    eoe_pos.lnum = 0;	// avoid warning
2058 			    eoe_pos.col = 0;
2059 			    end_idx = 0;
2060 			    hl_endpos.lnum = 0;
2061 
2062 			    /*
2063 			     * For a "oneline" the end must be found in the
2064 			     * same line too.  Search for it after the end of
2065 			     * the match with the start pattern.  Set the
2066 			     * resulting end positions at the same time.
2067 			     */
2068 			    if (spp->sp_type == SPTYPE_START
2069 					      && (spp->sp_flags & HL_ONELINE))
2070 			    {
2071 				lpos_T	startpos;
2072 
2073 				startpos = endpos;
2074 				find_endpos(idx, &startpos, &endpos, &hl_endpos,
2075 				    &flags, &eoe_pos, &end_idx, cur_extmatch);
2076 				if (endpos.lnum == 0)
2077 				    continue;	    // not found
2078 			    }
2079 
2080 			    /*
2081 			     * For a "match" the size must be > 0 after the
2082 			     * end offset needs has been added.  Except when
2083 			     * syncing.
2084 			     */
2085 			    else if (spp->sp_type == SPTYPE_MATCH)
2086 			    {
2087 				syn_add_end_off(&hl_endpos, &regmatch, spp,
2088 							       SPO_HE_OFF, 0);
2089 				syn_add_end_off(&endpos, &regmatch, spp,
2090 							       SPO_ME_OFF, 0);
2091 				if (endpos.lnum == current_lnum
2092 				      && (int)endpos.col + syncing < startcol)
2093 				{
2094 				    /*
2095 				     * If an empty string is matched, may need
2096 				     * to try matching again at next column.
2097 				     */
2098 				    if (regmatch.startpos[0].col
2099 						    == regmatch.endpos[0].col)
2100 					try_next_column = TRUE;
2101 				    continue;
2102 				}
2103 			    }
2104 
2105 			    /*
2106 			     * keep the best match so far in next_match_*
2107 			     */
2108 			    // Highlighting must start after startpos and end
2109 			    // before endpos.
2110 			    if (hl_startpos.lnum == current_lnum
2111 					   && (int)hl_startpos.col < startcol)
2112 				hl_startpos.col = startcol;
2113 			    limit_pos_zero(&hl_endpos, &endpos);
2114 
2115 			    next_match_idx = idx;
2116 			    next_match_col = startcol;
2117 			    next_match_m_endpos = endpos;
2118 			    next_match_h_endpos = hl_endpos;
2119 			    next_match_h_startpos = hl_startpos;
2120 			    next_match_flags = flags;
2121 			    next_match_eos_pos = eos_pos;
2122 			    next_match_eoe_pos = eoe_pos;
2123 			    next_match_end_idx = end_idx;
2124 			    unref_extmatch(next_match_extmatch);
2125 			    next_match_extmatch = cur_extmatch;
2126 			    cur_extmatch = NULL;
2127 			}
2128 		    }
2129 		}
2130 
2131 		/*
2132 		 * If we found a match at the current column, use it.
2133 		 */
2134 		if (next_match_idx >= 0 && next_match_col == (int)current_col)
2135 		{
2136 		    synpat_T	*lspp;
2137 
2138 		    // When a zero-width item matched which has a nextgroup,
2139 		    // don't push the item but set nextgroup.
2140 		    lspp = &(SYN_ITEMS(syn_block)[next_match_idx]);
2141 		    if (next_match_m_endpos.lnum == current_lnum
2142 			    && next_match_m_endpos.col == current_col
2143 			    && lspp->sp_next_list != NULL)
2144 		    {
2145 			current_next_list = lspp->sp_next_list;
2146 			current_next_flags = lspp->sp_flags;
2147 			keep_next_list = TRUE;
2148 			zero_width_next_list = TRUE;
2149 
2150 			// Add the index to a list, so that we can check
2151 			// later that we don't match it again (and cause an
2152 			// endless loop).
2153 			if (ga_grow(&zero_width_next_ga, 1) == OK)
2154 			{
2155 			    ((int *)(zero_width_next_ga.ga_data))
2156 				[zero_width_next_ga.ga_len++] = next_match_idx;
2157 			}
2158 			next_match_idx = -1;
2159 		    }
2160 		    else
2161 			cur_si = push_next_match(cur_si);
2162 		    found_match = TRUE;
2163 		}
2164 	    }
2165 	}
2166 
2167 	/*
2168 	 * Handle searching for nextgroup match.
2169 	 */
2170 	if (current_next_list != NULL && !keep_next_list)
2171 	{
2172 	    /*
2173 	     * If a nextgroup was not found, continue looking for one if:
2174 	     * - this is an empty line and the "skipempty" option was given
2175 	     * - we are on white space and the "skipwhite" option was given
2176 	     */
2177 	    if (!found_match)
2178 	    {
2179 		line = syn_getcurline();
2180 		if (((current_next_flags & HL_SKIPWHITE)
2181 			    && VIM_ISWHITE(line[current_col]))
2182 			|| ((current_next_flags & HL_SKIPEMPTY)
2183 			    && *line == NUL))
2184 		    break;
2185 	    }
2186 
2187 	    /*
2188 	     * If a nextgroup was found: Use it, and continue looking for
2189 	     * contained matches.
2190 	     * If a nextgroup was not found: Continue looking for a normal
2191 	     * match.
2192 	     * When did set current_next_list for a zero-width item and no
2193 	     * match was found don't loop (would get stuck).
2194 	     */
2195 	    current_next_list = NULL;
2196 	    next_match_idx = -1;
2197 	    if (!zero_width_next_list)
2198 		found_match = TRUE;
2199 	}
2200 
2201     } while (found_match);
2202 
2203     restore_chartab(buf_chartab);
2204 
2205     /*
2206      * Use attributes from the current state, if within its highlighting.
2207      * If not, use attributes from the current-but-one state, etc.
2208      */
2209     current_attr = 0;
2210 #ifdef FEAT_EVAL
2211     current_id = 0;
2212     current_trans_id = 0;
2213 #endif
2214 #ifdef FEAT_CONCEAL
2215     current_flags = 0;
2216     current_seqnr = 0;
2217 #endif
2218     if (cur_si != NULL)
2219     {
2220 #ifndef FEAT_EVAL
2221 	int	current_trans_id = 0;
2222 #endif
2223 	for (idx = current_state.ga_len - 1; idx >= 0; --idx)
2224 	{
2225 	    sip = &CUR_STATE(idx);
2226 	    if ((current_lnum > sip->si_h_startpos.lnum
2227 			|| (current_lnum == sip->si_h_startpos.lnum
2228 			    && current_col >= sip->si_h_startpos.col))
2229 		    && (sip->si_h_endpos.lnum == 0
2230 			|| current_lnum < sip->si_h_endpos.lnum
2231 			|| (current_lnum == sip->si_h_endpos.lnum
2232 			    && current_col < sip->si_h_endpos.col)))
2233 	    {
2234 		current_attr = sip->si_attr;
2235 #ifdef FEAT_EVAL
2236 		current_id = sip->si_id;
2237 #endif
2238 		current_trans_id = sip->si_trans_id;
2239 #ifdef FEAT_CONCEAL
2240 		current_flags = sip->si_flags;
2241 		current_seqnr = sip->si_seqnr;
2242 		current_sub_char = sip->si_cchar;
2243 #endif
2244 		break;
2245 	    }
2246 	}
2247 
2248 	if (can_spell != NULL)
2249 	{
2250 	    struct sp_syn   sps;
2251 
2252 	    /*
2253 	     * set "can_spell" to TRUE if spell checking is supposed to be
2254 	     * done in the current item.
2255 	     */
2256 	    if (syn_block->b_spell_cluster_id == 0)
2257 	    {
2258 		// There is no @Spell cluster: Do spelling for items without
2259 		// @NoSpell cluster.
2260 		if (syn_block->b_nospell_cluster_id == 0
2261 						     || current_trans_id == 0)
2262 		    *can_spell = (syn_block->b_syn_spell != SYNSPL_NOTOP);
2263 		else
2264 		{
2265 		    sps.inc_tag = 0;
2266 		    sps.id = syn_block->b_nospell_cluster_id;
2267 		    sps.cont_in_list = NULL;
2268 		    *can_spell = !in_id_list(sip, sip->si_cont_list, &sps, 0);
2269 		}
2270 	    }
2271 	    else
2272 	    {
2273 		// The @Spell cluster is defined: Do spelling in items with
2274 		// the @Spell cluster.  But not when @NoSpell is also there.
2275 		// At the toplevel only spell check when ":syn spell toplevel"
2276 		// was used.
2277 		if (current_trans_id == 0)
2278 		    *can_spell = (syn_block->b_syn_spell == SYNSPL_TOP);
2279 		else
2280 		{
2281 		    sps.inc_tag = 0;
2282 		    sps.id = syn_block->b_spell_cluster_id;
2283 		    sps.cont_in_list = NULL;
2284 		    *can_spell = in_id_list(sip, sip->si_cont_list, &sps, 0);
2285 
2286 		    if (syn_block->b_nospell_cluster_id != 0)
2287 		    {
2288 			sps.id = syn_block->b_nospell_cluster_id;
2289 			if (in_id_list(sip, sip->si_cont_list, &sps, 0))
2290 			    *can_spell = FALSE;
2291 		    }
2292 		}
2293 	    }
2294 	}
2295 
2296 
2297 	/*
2298 	 * Check for end of current state (and the states before it) at the
2299 	 * next column.  Don't do this for syncing, because we would miss a
2300 	 * single character match.
2301 	 * First check if the current state ends at the current column.  It
2302 	 * may be for an empty match and a containing item might end in the
2303 	 * current column.
2304 	 */
2305 	if (!syncing && !keep_state)
2306 	{
2307 	    check_state_ends();
2308 	    if (current_state.ga_len > 0
2309 				      && syn_getcurline()[current_col] != NUL)
2310 	    {
2311 		++current_col;
2312 		check_state_ends();
2313 		--current_col;
2314 	    }
2315 	}
2316     }
2317     else if (can_spell != NULL)
2318 	// Default: Only do spelling when there is no @Spell cluster or when
2319 	// ":syn spell toplevel" was used.
2320 	*can_spell = syn_block->b_syn_spell == SYNSPL_DEFAULT
2321 		    ? (syn_block->b_spell_cluster_id == 0)
2322 		    : (syn_block->b_syn_spell == SYNSPL_TOP);
2323 
2324     // nextgroup ends at end of line, unless "skipnl" or "skipempty" present
2325     if (current_next_list != NULL
2326 	    && (line = syn_getcurline())[current_col] != NUL
2327 	    && line[current_col + 1] == NUL
2328 	    && !(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY)))
2329 	current_next_list = NULL;
2330 
2331     if (zero_width_next_ga.ga_len > 0)
2332 	ga_clear(&zero_width_next_ga);
2333 
2334     // No longer need external matches.  But keep next_match_extmatch.
2335     unref_extmatch(re_extmatch_out);
2336     re_extmatch_out = NULL;
2337     unref_extmatch(cur_extmatch);
2338 
2339     return current_attr;
2340 }
2341 
2342 
2343 /*
2344  * Check if we already matched pattern "idx" at the current column.
2345  */
2346     static int
2347 did_match_already(int idx, garray_T *gap)
2348 {
2349     int		i;
2350 
2351     for (i = current_state.ga_len; --i >= 0; )
2352 	if (CUR_STATE(i).si_m_startcol == (int)current_col
2353 		&& CUR_STATE(i).si_m_lnum == (int)current_lnum
2354 		&& CUR_STATE(i).si_idx == idx)
2355 	    return TRUE;
2356 
2357     // Zero-width matches with a nextgroup argument are not put on the syntax
2358     // stack, and can only be matched once anyway.
2359     for (i = gap->ga_len; --i >= 0; )
2360 	if (((int *)(gap->ga_data))[i] == idx)
2361 	    return TRUE;
2362 
2363     return FALSE;
2364 }
2365 
2366 /*
2367  * Push the next match onto the stack.
2368  */
2369     static stateitem_T *
2370 push_next_match(stateitem_T *cur_si)
2371 {
2372     synpat_T	*spp;
2373 #ifdef FEAT_CONCEAL
2374     int		 save_flags;
2375 #endif
2376 
2377     spp = &(SYN_ITEMS(syn_block)[next_match_idx]);
2378 
2379     /*
2380      * Push the item in current_state stack;
2381      */
2382     if (push_current_state(next_match_idx) == OK)
2383     {
2384 	/*
2385 	 * If it's a start-skip-end type that crosses lines, figure out how
2386 	 * much it continues in this line.  Otherwise just fill in the length.
2387 	 */
2388 	cur_si = &CUR_STATE(current_state.ga_len - 1);
2389 	cur_si->si_h_startpos = next_match_h_startpos;
2390 	cur_si->si_m_startcol = current_col;
2391 	cur_si->si_m_lnum = current_lnum;
2392 	cur_si->si_flags = spp->sp_flags;
2393 #ifdef FEAT_CONCEAL
2394 	cur_si->si_seqnr = next_seqnr++;
2395 	cur_si->si_cchar = spp->sp_cchar;
2396 	if (current_state.ga_len > 1)
2397 	    cur_si->si_flags |=
2398 		    CUR_STATE(current_state.ga_len - 2).si_flags & HL_CONCEAL;
2399 #endif
2400 	cur_si->si_next_list = spp->sp_next_list;
2401 	cur_si->si_extmatch = ref_extmatch(next_match_extmatch);
2402 	if (spp->sp_type == SPTYPE_START && !(spp->sp_flags & HL_ONELINE))
2403 	{
2404 	    // Try to find the end pattern in the current line
2405 	    update_si_end(cur_si, (int)(next_match_m_endpos.col), TRUE);
2406 	    check_keepend();
2407 	}
2408 	else
2409 	{
2410 	    cur_si->si_m_endpos = next_match_m_endpos;
2411 	    cur_si->si_h_endpos = next_match_h_endpos;
2412 	    cur_si->si_ends = TRUE;
2413 	    cur_si->si_flags |= next_match_flags;
2414 	    cur_si->si_eoe_pos = next_match_eoe_pos;
2415 	    cur_si->si_end_idx = next_match_end_idx;
2416 	}
2417 	if (keepend_level < 0 && (cur_si->si_flags & HL_KEEPEND))
2418 	    keepend_level = current_state.ga_len - 1;
2419 	check_keepend();
2420 	update_si_attr(current_state.ga_len - 1);
2421 
2422 #ifdef FEAT_CONCEAL
2423 	save_flags = cur_si->si_flags & (HL_CONCEAL | HL_CONCEALENDS);
2424 #endif
2425 	/*
2426 	 * If the start pattern has another highlight group, push another item
2427 	 * on the stack for the start pattern.
2428 	 */
2429 	if (	   spp->sp_type == SPTYPE_START
2430 		&& spp->sp_syn_match_id != 0
2431 		&& push_current_state(next_match_idx) == OK)
2432 	{
2433 	    cur_si = &CUR_STATE(current_state.ga_len - 1);
2434 	    cur_si->si_h_startpos = next_match_h_startpos;
2435 	    cur_si->si_m_startcol = current_col;
2436 	    cur_si->si_m_lnum = current_lnum;
2437 	    cur_si->si_m_endpos = next_match_eos_pos;
2438 	    cur_si->si_h_endpos = next_match_eos_pos;
2439 	    cur_si->si_ends = TRUE;
2440 	    cur_si->si_end_idx = 0;
2441 	    cur_si->si_flags = HL_MATCH;
2442 #ifdef FEAT_CONCEAL
2443 	    cur_si->si_seqnr = next_seqnr++;
2444 	    cur_si->si_flags |= save_flags;
2445 	    if (cur_si->si_flags & HL_CONCEALENDS)
2446 		cur_si->si_flags |= HL_CONCEAL;
2447 #endif
2448 	    cur_si->si_next_list = NULL;
2449 	    check_keepend();
2450 	    update_si_attr(current_state.ga_len - 1);
2451 	}
2452     }
2453 
2454     next_match_idx = -1;	// try other match next time
2455 
2456     return cur_si;
2457 }
2458 
2459 /*
2460  * Check for end of current state (and the states before it).
2461  */
2462     static void
2463 check_state_ends(void)
2464 {
2465     stateitem_T	*cur_si;
2466     int		had_extend;
2467 
2468     cur_si = &CUR_STATE(current_state.ga_len - 1);
2469     for (;;)
2470     {
2471 	if (cur_si->si_ends
2472 		&& (cur_si->si_m_endpos.lnum < current_lnum
2473 		    || (cur_si->si_m_endpos.lnum == current_lnum
2474 			&& cur_si->si_m_endpos.col <= current_col)))
2475 	{
2476 	    /*
2477 	     * If there is an end pattern group ID, highlight the end pattern
2478 	     * now.  No need to pop the current item from the stack.
2479 	     * Only do this if the end pattern continues beyond the current
2480 	     * position.
2481 	     */
2482 	    if (cur_si->si_end_idx
2483 		    && (cur_si->si_eoe_pos.lnum > current_lnum
2484 			|| (cur_si->si_eoe_pos.lnum == current_lnum
2485 			    && cur_si->si_eoe_pos.col > current_col)))
2486 	    {
2487 		cur_si->si_idx = cur_si->si_end_idx;
2488 		cur_si->si_end_idx = 0;
2489 		cur_si->si_m_endpos = cur_si->si_eoe_pos;
2490 		cur_si->si_h_endpos = cur_si->si_eoe_pos;
2491 		cur_si->si_flags |= HL_MATCH;
2492 #ifdef FEAT_CONCEAL
2493 		cur_si->si_seqnr = next_seqnr++;
2494 		if (cur_si->si_flags & HL_CONCEALENDS)
2495 		    cur_si->si_flags |= HL_CONCEAL;
2496 #endif
2497 		update_si_attr(current_state.ga_len - 1);
2498 
2499 		// nextgroup= should not match in the end pattern
2500 		current_next_list = NULL;
2501 
2502 		// what matches next may be different now, clear it
2503 		next_match_idx = 0;
2504 		next_match_col = MAXCOL;
2505 		break;
2506 	    }
2507 	    else
2508 	    {
2509 		// handle next_list, unless at end of line and no "skipnl" or
2510 		// "skipempty"
2511 		current_next_list = cur_si->si_next_list;
2512 		current_next_flags = cur_si->si_flags;
2513 		if (!(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY))
2514 			&& syn_getcurline()[current_col] == NUL)
2515 		    current_next_list = NULL;
2516 
2517 		// When the ended item has "extend", another item with
2518 		// "keepend" now needs to check for its end.
2519 		 had_extend = (cur_si->si_flags & HL_EXTEND);
2520 
2521 		pop_current_state();
2522 
2523 		if (current_state.ga_len == 0)
2524 		    break;
2525 
2526 		if (had_extend && keepend_level >= 0)
2527 		{
2528 		    syn_update_ends(FALSE);
2529 		    if (current_state.ga_len == 0)
2530 			break;
2531 		}
2532 
2533 		cur_si = &CUR_STATE(current_state.ga_len - 1);
2534 
2535 		/*
2536 		 * Only for a region the search for the end continues after
2537 		 * the end of the contained item.  If the contained match
2538 		 * included the end-of-line, break here, the region continues.
2539 		 * Don't do this when:
2540 		 * - "keepend" is used for the contained item
2541 		 * - not at the end of the line (could be end="x$"me=e-1).
2542 		 * - "excludenl" is used (HL_HAS_EOL won't be set)
2543 		 */
2544 		if (cur_si->si_idx >= 0
2545 			&& SYN_ITEMS(syn_block)[cur_si->si_idx].sp_type
2546 							       == SPTYPE_START
2547 			&& !(cur_si->si_flags & (HL_MATCH | HL_KEEPEND)))
2548 		{
2549 		    update_si_end(cur_si, (int)current_col, TRUE);
2550 		    check_keepend();
2551 		    if ((current_next_flags & HL_HAS_EOL)
2552 			    && keepend_level < 0
2553 			    && syn_getcurline()[current_col] == NUL)
2554 			break;
2555 		}
2556 	    }
2557 	}
2558 	else
2559 	    break;
2560     }
2561 }
2562 
2563 /*
2564  * Update an entry in the current_state stack for a match or region.  This
2565  * fills in si_attr, si_next_list and si_cont_list.
2566  */
2567     static void
2568 update_si_attr(int idx)
2569 {
2570     stateitem_T	*sip = &CUR_STATE(idx);
2571     synpat_T	*spp;
2572 
2573     // This should not happen...
2574     if (sip->si_idx < 0)
2575 	return;
2576 
2577     spp = &(SYN_ITEMS(syn_block)[sip->si_idx]);
2578     if (sip->si_flags & HL_MATCH)
2579 	sip->si_id = spp->sp_syn_match_id;
2580     else
2581 	sip->si_id = spp->sp_syn.id;
2582     sip->si_attr = syn_id2attr(sip->si_id);
2583     sip->si_trans_id = sip->si_id;
2584     if (sip->si_flags & HL_MATCH)
2585 	sip->si_cont_list = NULL;
2586     else
2587 	sip->si_cont_list = spp->sp_cont_list;
2588 
2589     /*
2590      * For transparent items, take attr from outer item.
2591      * Also take cont_list, if there is none.
2592      * Don't do this for the matchgroup of a start or end pattern.
2593      */
2594     if ((spp->sp_flags & HL_TRANSP) && !(sip->si_flags & HL_MATCH))
2595     {
2596 	if (idx == 0)
2597 	{
2598 	    sip->si_attr = 0;
2599 	    sip->si_trans_id = 0;
2600 	    if (sip->si_cont_list == NULL)
2601 		sip->si_cont_list = ID_LIST_ALL;
2602 	}
2603 	else
2604 	{
2605 	    sip->si_attr = CUR_STATE(idx - 1).si_attr;
2606 	    sip->si_trans_id = CUR_STATE(idx - 1).si_trans_id;
2607 	    sip->si_h_startpos = CUR_STATE(idx - 1).si_h_startpos;
2608 	    sip->si_h_endpos = CUR_STATE(idx - 1).si_h_endpos;
2609 	    if (sip->si_cont_list == NULL)
2610 	    {
2611 		sip->si_flags |= HL_TRANS_CONT;
2612 		sip->si_cont_list = CUR_STATE(idx - 1).si_cont_list;
2613 	    }
2614 	}
2615     }
2616 }
2617 
2618 /*
2619  * Check the current stack for patterns with "keepend" flag.
2620  * Propagate the match-end to contained items, until a "skipend" item is found.
2621  */
2622     static void
2623 check_keepend(void)
2624 {
2625     int		i;
2626     lpos_T	maxpos;
2627     lpos_T	maxpos_h;
2628     stateitem_T	*sip;
2629 
2630     /*
2631      * This check can consume a lot of time; only do it from the level where
2632      * there really is a keepend.
2633      */
2634     if (keepend_level < 0)
2635 	return;
2636 
2637     /*
2638      * Find the last index of an "extend" item.  "keepend" items before that
2639      * won't do anything.  If there is no "extend" item "i" will be
2640      * "keepend_level" and all "keepend" items will work normally.
2641      */
2642     for (i = current_state.ga_len - 1; i > keepend_level; --i)
2643 	if (CUR_STATE(i).si_flags & HL_EXTEND)
2644 	    break;
2645 
2646     maxpos.lnum = 0;
2647     maxpos.col = 0;
2648     maxpos_h.lnum = 0;
2649     maxpos_h.col = 0;
2650     for ( ; i < current_state.ga_len; ++i)
2651     {
2652 	sip = &CUR_STATE(i);
2653 	if (maxpos.lnum != 0)
2654 	{
2655 	    limit_pos_zero(&sip->si_m_endpos, &maxpos);
2656 	    limit_pos_zero(&sip->si_h_endpos, &maxpos_h);
2657 	    limit_pos_zero(&sip->si_eoe_pos, &maxpos);
2658 	    sip->si_ends = TRUE;
2659 	}
2660 	if (sip->si_ends && (sip->si_flags & HL_KEEPEND))
2661 	{
2662 	    if (maxpos.lnum == 0
2663 		    || maxpos.lnum > sip->si_m_endpos.lnum
2664 		    || (maxpos.lnum == sip->si_m_endpos.lnum
2665 			&& maxpos.col > sip->si_m_endpos.col))
2666 		maxpos = sip->si_m_endpos;
2667 	    if (maxpos_h.lnum == 0
2668 		    || maxpos_h.lnum > sip->si_h_endpos.lnum
2669 		    || (maxpos_h.lnum == sip->si_h_endpos.lnum
2670 			&& maxpos_h.col > sip->si_h_endpos.col))
2671 		maxpos_h = sip->si_h_endpos;
2672 	}
2673     }
2674 }
2675 
2676 /*
2677  * Update an entry in the current_state stack for a start-skip-end pattern.
2678  * This finds the end of the current item, if it's in the current line.
2679  *
2680  * Return the flags for the matched END.
2681  */
2682     static void
2683 update_si_end(
2684     stateitem_T	*sip,
2685     int		startcol,   // where to start searching for the end
2686     int		force)	    // when TRUE overrule a previous end
2687 {
2688     lpos_T	startpos;
2689     lpos_T	endpos;
2690     lpos_T	hl_endpos;
2691     lpos_T	end_endpos;
2692     int		end_idx;
2693 
2694     // return quickly for a keyword
2695     if (sip->si_idx < 0)
2696 	return;
2697 
2698     // Don't update when it's already done.  Can be a match of an end pattern
2699     // that started in a previous line.  Watch out: can also be a "keepend"
2700     // from a containing item.
2701     if (!force && sip->si_m_endpos.lnum >= current_lnum)
2702 	return;
2703 
2704     /*
2705      * We need to find the end of the region.  It may continue in the next
2706      * line.
2707      */
2708     end_idx = 0;
2709     startpos.lnum = current_lnum;
2710     startpos.col = startcol;
2711     find_endpos(sip->si_idx, &startpos, &endpos, &hl_endpos,
2712 		   &(sip->si_flags), &end_endpos, &end_idx, sip->si_extmatch);
2713 
2714     if (endpos.lnum == 0)
2715     {
2716 	// No end pattern matched.
2717 	if (SYN_ITEMS(syn_block)[sip->si_idx].sp_flags & HL_ONELINE)
2718 	{
2719 	    // a "oneline" never continues in the next line
2720 	    sip->si_ends = TRUE;
2721 	    sip->si_m_endpos.lnum = current_lnum;
2722 	    sip->si_m_endpos.col = (colnr_T)STRLEN(syn_getcurline());
2723 	}
2724 	else
2725 	{
2726 	    // continues in the next line
2727 	    sip->si_ends = FALSE;
2728 	    sip->si_m_endpos.lnum = 0;
2729 	}
2730 	sip->si_h_endpos = sip->si_m_endpos;
2731     }
2732     else
2733     {
2734 	// match within this line
2735 	sip->si_m_endpos = endpos;
2736 	sip->si_h_endpos = hl_endpos;
2737 	sip->si_eoe_pos = end_endpos;
2738 	sip->si_ends = TRUE;
2739 	sip->si_end_idx = end_idx;
2740     }
2741 }
2742 
2743 /*
2744  * Add a new state to the current state stack.
2745  * It is cleared and the index set to "idx".
2746  * Return FAIL if it's not possible (out of memory).
2747  */
2748     static int
2749 push_current_state(int idx)
2750 {
2751     if (ga_grow(&current_state, 1) == FAIL)
2752 	return FAIL;
2753     vim_memset(&CUR_STATE(current_state.ga_len), 0, sizeof(stateitem_T));
2754     CUR_STATE(current_state.ga_len).si_idx = idx;
2755     ++current_state.ga_len;
2756     return OK;
2757 }
2758 
2759 /*
2760  * Remove a state from the current_state stack.
2761  */
2762     static void
2763 pop_current_state(void)
2764 {
2765     if (current_state.ga_len)
2766     {
2767 	unref_extmatch(CUR_STATE(current_state.ga_len - 1).si_extmatch);
2768 	--current_state.ga_len;
2769     }
2770     // after the end of a pattern, try matching a keyword or pattern
2771     next_match_idx = -1;
2772 
2773     // if first state with "keepend" is popped, reset keepend_level
2774     if (keepend_level >= current_state.ga_len)
2775 	keepend_level = -1;
2776 }
2777 
2778 /*
2779  * Find the end of a start/skip/end syntax region after "startpos".
2780  * Only checks one line.
2781  * Also handles a match item that continued from a previous line.
2782  * If not found, the syntax item continues in the next line.  m_endpos->lnum
2783  * will be 0.
2784  * If found, the end of the region and the end of the highlighting is
2785  * computed.
2786  */
2787     static void
2788 find_endpos(
2789     int		idx,		// index of the pattern
2790     lpos_T	*startpos,	// where to start looking for an END match
2791     lpos_T	*m_endpos,	// return: end of match
2792     lpos_T	*hl_endpos,	// return: end of highlighting
2793     long	*flagsp,	// return: flags of matching END
2794     lpos_T	*end_endpos,	// return: end of end pattern match
2795     int		*end_idx,	// return: group ID for end pat. match, or 0
2796     reg_extmatch_T *start_ext)	// submatches from the start pattern
2797 {
2798     colnr_T	matchcol;
2799     synpat_T	*spp, *spp_skip;
2800     int		start_idx;
2801     int		best_idx;
2802     regmmatch_T	regmatch;
2803     regmmatch_T	best_regmatch;	    // startpos/endpos of best match
2804     lpos_T	pos;
2805     char_u	*line;
2806     int		had_match = FALSE;
2807     char_u	buf_chartab[32];  // chartab array for syn option iskyeyword
2808 
2809     // just in case we are invoked for a keyword
2810     if (idx < 0)
2811 	return;
2812 
2813     /*
2814      * Check for being called with a START pattern.
2815      * Can happen with a match that continues to the next line, because it
2816      * contained a region.
2817      */
2818     spp = &(SYN_ITEMS(syn_block)[idx]);
2819     if (spp->sp_type != SPTYPE_START)
2820     {
2821 	*hl_endpos = *startpos;
2822 	return;
2823     }
2824 
2825     /*
2826      * Find the SKIP or first END pattern after the last START pattern.
2827      */
2828     for (;;)
2829     {
2830 	spp = &(SYN_ITEMS(syn_block)[idx]);
2831 	if (spp->sp_type != SPTYPE_START)
2832 	    break;
2833 	++idx;
2834     }
2835 
2836     /*
2837      *	Lookup the SKIP pattern (if present)
2838      */
2839     if (spp->sp_type == SPTYPE_SKIP)
2840     {
2841 	spp_skip = spp;
2842 	++idx;
2843     }
2844     else
2845 	spp_skip = NULL;
2846 
2847     // Setup external matches for syn_regexec().
2848     unref_extmatch(re_extmatch_in);
2849     re_extmatch_in = ref_extmatch(start_ext);
2850 
2851     matchcol = startpos->col;	// start looking for a match at sstart
2852     start_idx = idx;		// remember the first END pattern.
2853     best_regmatch.startpos[0].col = 0;		// avoid compiler warning
2854 
2855     // use syntax iskeyword option
2856     save_chartab(buf_chartab);
2857 
2858     for (;;)
2859     {
2860 	/*
2861 	 * Find end pattern that matches first after "matchcol".
2862 	 */
2863 	best_idx = -1;
2864 	for (idx = start_idx; idx < syn_block->b_syn_patterns.ga_len; ++idx)
2865 	{
2866 	    int lc_col = matchcol;
2867 	    int r;
2868 
2869 	    spp = &(SYN_ITEMS(syn_block)[idx]);
2870 	    if (spp->sp_type != SPTYPE_END)	// past last END pattern
2871 		break;
2872 	    lc_col -= spp->sp_offsets[SPO_LC_OFF];
2873 	    if (lc_col < 0)
2874 		lc_col = 0;
2875 
2876 	    regmatch.rmm_ic = spp->sp_ic;
2877 	    regmatch.regprog = spp->sp_prog;
2878 	    r = syn_regexec(&regmatch, startpos->lnum, lc_col,
2879 						  IF_SYN_TIME(&spp->sp_time));
2880 	    spp->sp_prog = regmatch.regprog;
2881 	    if (r)
2882 	    {
2883 		if (best_idx == -1 || regmatch.startpos[0].col
2884 					      < best_regmatch.startpos[0].col)
2885 		{
2886 		    best_idx = idx;
2887 		    best_regmatch.startpos[0] = regmatch.startpos[0];
2888 		    best_regmatch.endpos[0] = regmatch.endpos[0];
2889 		}
2890 	    }
2891 	}
2892 
2893 	/*
2894 	 * If all end patterns have been tried, and there is no match, the
2895 	 * item continues until end-of-line.
2896 	 */
2897 	if (best_idx == -1)
2898 	    break;
2899 
2900 	/*
2901 	 * If the skip pattern matches before the end pattern,
2902 	 * continue searching after the skip pattern.
2903 	 */
2904 	if (spp_skip != NULL)
2905 	{
2906 	    int lc_col = matchcol - spp_skip->sp_offsets[SPO_LC_OFF];
2907 	    int r;
2908 
2909 	    if (lc_col < 0)
2910 		lc_col = 0;
2911 	    regmatch.rmm_ic = spp_skip->sp_ic;
2912 	    regmatch.regprog = spp_skip->sp_prog;
2913 	    r = syn_regexec(&regmatch, startpos->lnum, lc_col,
2914 					      IF_SYN_TIME(&spp_skip->sp_time));
2915 	    spp_skip->sp_prog = regmatch.regprog;
2916 	    if (r && regmatch.startpos[0].col
2917 					     <= best_regmatch.startpos[0].col)
2918 	    {
2919 		int line_len;
2920 
2921 		// Add offset to skip pattern match
2922 		syn_add_end_off(&pos, &regmatch, spp_skip, SPO_ME_OFF, 1);
2923 
2924 		// If the skip pattern goes on to the next line, there is no
2925 		// match with an end pattern in this line.
2926 		if (pos.lnum > startpos->lnum)
2927 		    break;
2928 
2929 		line = ml_get_buf(syn_buf, startpos->lnum, FALSE);
2930 		line_len = (int)STRLEN(line);
2931 
2932 		// take care of an empty match or negative offset
2933 		if (pos.col <= matchcol)
2934 		    ++matchcol;
2935 		else if (pos.col <= regmatch.endpos[0].col)
2936 		    matchcol = pos.col;
2937 		else
2938 		    // Be careful not to jump over the NUL at the end-of-line
2939 		    for (matchcol = regmatch.endpos[0].col;
2940 			    matchcol < line_len && matchcol < pos.col;
2941 								   ++matchcol)
2942 			;
2943 
2944 		// if the skip pattern includes end-of-line, break here
2945 		if (matchcol >= line_len)
2946 		    break;
2947 
2948 		continue;	    // start with first end pattern again
2949 	    }
2950 	}
2951 
2952 	/*
2953 	 * Match from start pattern to end pattern.
2954 	 * Correct for match and highlight offset of end pattern.
2955 	 */
2956 	spp = &(SYN_ITEMS(syn_block)[best_idx]);
2957 	syn_add_end_off(m_endpos, &best_regmatch, spp, SPO_ME_OFF, 1);
2958 	// can't end before the start
2959 	if (m_endpos->lnum == startpos->lnum && m_endpos->col < startpos->col)
2960 	    m_endpos->col = startpos->col;
2961 
2962 	syn_add_end_off(end_endpos, &best_regmatch, spp, SPO_HE_OFF, 1);
2963 	// can't end before the start
2964 	if (end_endpos->lnum == startpos->lnum
2965 					   && end_endpos->col < startpos->col)
2966 	    end_endpos->col = startpos->col;
2967 	// can't end after the match
2968 	limit_pos(end_endpos, m_endpos);
2969 
2970 	/*
2971 	 * If the end group is highlighted differently, adjust the pointers.
2972 	 */
2973 	if (spp->sp_syn_match_id != spp->sp_syn.id && spp->sp_syn_match_id != 0)
2974 	{
2975 	    *end_idx = best_idx;
2976 	    if (spp->sp_off_flags & (1 << (SPO_RE_OFF + SPO_COUNT)))
2977 	    {
2978 		hl_endpos->lnum = best_regmatch.endpos[0].lnum;
2979 		hl_endpos->col = best_regmatch.endpos[0].col;
2980 	    }
2981 	    else
2982 	    {
2983 		hl_endpos->lnum = best_regmatch.startpos[0].lnum;
2984 		hl_endpos->col = best_regmatch.startpos[0].col;
2985 	    }
2986 	    hl_endpos->col += spp->sp_offsets[SPO_RE_OFF];
2987 
2988 	    // can't end before the start
2989 	    if (hl_endpos->lnum == startpos->lnum
2990 					    && hl_endpos->col < startpos->col)
2991 		hl_endpos->col = startpos->col;
2992 	    limit_pos(hl_endpos, m_endpos);
2993 
2994 	    // now the match ends where the highlighting ends, it is turned
2995 	    // into the matchgroup for the end
2996 	    *m_endpos = *hl_endpos;
2997 	}
2998 	else
2999 	{
3000 	    *end_idx = 0;
3001 	    *hl_endpos = *end_endpos;
3002 	}
3003 
3004 	*flagsp = spp->sp_flags;
3005 
3006 	had_match = TRUE;
3007 	break;
3008     }
3009 
3010     // no match for an END pattern in this line
3011     if (!had_match)
3012 	m_endpos->lnum = 0;
3013 
3014     restore_chartab(buf_chartab);
3015 
3016     // Remove external matches.
3017     unref_extmatch(re_extmatch_in);
3018     re_extmatch_in = NULL;
3019 }
3020 
3021 /*
3022  * Limit "pos" not to be after "limit".
3023  */
3024     static void
3025 limit_pos(lpos_T *pos, lpos_T *limit)
3026 {
3027     if (pos->lnum > limit->lnum)
3028 	*pos = *limit;
3029     else if (pos->lnum == limit->lnum && pos->col > limit->col)
3030 	pos->col = limit->col;
3031 }
3032 
3033 /*
3034  * Limit "pos" not to be after "limit", unless pos->lnum is zero.
3035  */
3036     static void
3037 limit_pos_zero(
3038     lpos_T	*pos,
3039     lpos_T	*limit)
3040 {
3041     if (pos->lnum == 0)
3042 	*pos = *limit;
3043     else
3044 	limit_pos(pos, limit);
3045 }
3046 
3047 /*
3048  * Add offset to matched text for end of match or highlight.
3049  */
3050     static void
3051 syn_add_end_off(
3052     lpos_T	*result,	// returned position
3053     regmmatch_T	*regmatch,	// start/end of match
3054     synpat_T	*spp,		// matched pattern
3055     int		idx,		// index of offset
3056     int		extra)		// extra chars for offset to start
3057 {
3058     int		col;
3059     int		off;
3060     char_u	*base;
3061     char_u	*p;
3062 
3063     if (spp->sp_off_flags & (1 << idx))
3064     {
3065 	result->lnum = regmatch->startpos[0].lnum;
3066 	col = regmatch->startpos[0].col;
3067 	off = spp->sp_offsets[idx] + extra;
3068     }
3069     else
3070     {
3071 	result->lnum = regmatch->endpos[0].lnum;
3072 	col = regmatch->endpos[0].col;
3073 	off = spp->sp_offsets[idx];
3074     }
3075     // Don't go past the end of the line.  Matters for "rs=e+2" when there
3076     // is a matchgroup. Watch out for match with last NL in the buffer.
3077     if (result->lnum > syn_buf->b_ml.ml_line_count)
3078 	col = 0;
3079     else if (off != 0)
3080     {
3081 	base = ml_get_buf(syn_buf, result->lnum, FALSE);
3082 	p = base + col;
3083 	if (off > 0)
3084 	{
3085 	    while (off-- > 0 && *p != NUL)
3086 		MB_PTR_ADV(p);
3087 	}
3088 	else if (off < 0)
3089 	{
3090 	    while (off++ < 0 && base < p)
3091 		MB_PTR_BACK(base, p);
3092 	}
3093 	col = (int)(p - base);
3094     }
3095     result->col = col;
3096 }
3097 
3098 /*
3099  * Add offset to matched text for start of match or highlight.
3100  * Avoid resulting column to become negative.
3101  */
3102     static void
3103 syn_add_start_off(
3104     lpos_T	*result,	// returned position
3105     regmmatch_T	*regmatch,	// start/end of match
3106     synpat_T	*spp,
3107     int		idx,
3108     int		extra)	    // extra chars for offset to end
3109 {
3110     int		col;
3111     int		off;
3112     char_u	*base;
3113     char_u	*p;
3114 
3115     if (spp->sp_off_flags & (1 << (idx + SPO_COUNT)))
3116     {
3117 	result->lnum = regmatch->endpos[0].lnum;
3118 	col = regmatch->endpos[0].col;
3119 	off = spp->sp_offsets[idx] + extra;
3120     }
3121     else
3122     {
3123 	result->lnum = regmatch->startpos[0].lnum;
3124 	col = regmatch->startpos[0].col;
3125 	off = spp->sp_offsets[idx];
3126     }
3127     if (result->lnum > syn_buf->b_ml.ml_line_count)
3128     {
3129 	// a "\n" at the end of the pattern may take us below the last line
3130 	result->lnum = syn_buf->b_ml.ml_line_count;
3131 	col = (int)STRLEN(ml_get_buf(syn_buf, result->lnum, FALSE));
3132     }
3133     if (off != 0)
3134     {
3135 	base = ml_get_buf(syn_buf, result->lnum, FALSE);
3136 	p = base + col;
3137 	if (off > 0)
3138 	{
3139 	    while (off-- && *p != NUL)
3140 		MB_PTR_ADV(p);
3141 	}
3142 	else if (off < 0)
3143 	{
3144 	    while (off++ && base < p)
3145 		MB_PTR_BACK(base, p);
3146 	}
3147 	col = (int)(p - base);
3148     }
3149     result->col = col;
3150 }
3151 
3152 /*
3153  * Get current line in syntax buffer.
3154  */
3155     static char_u *
3156 syn_getcurline(void)
3157 {
3158     return ml_get_buf(syn_buf, current_lnum, FALSE);
3159 }
3160 
3161 /*
3162  * Call vim_regexec() to find a match with "rmp" in "syn_buf".
3163  * Returns TRUE when there is a match.
3164  */
3165     static int
3166 syn_regexec(
3167     regmmatch_T	*rmp,
3168     linenr_T	lnum,
3169     colnr_T	col,
3170     syn_time_T  *st UNUSED)
3171 {
3172     int r;
3173 #ifdef FEAT_RELTIME
3174     int timed_out = FALSE;
3175 #endif
3176 #ifdef FEAT_PROFILE
3177     proftime_T	pt;
3178 
3179     if (syn_time_on)
3180 	profile_start(&pt);
3181 #endif
3182 
3183     if (rmp->regprog == NULL)
3184 	// This can happen if a previous call to vim_regexec_multi() tried to
3185 	// use the NFA engine, which resulted in NFA_TOO_EXPENSIVE, and
3186 	// compiling the pattern with the other engine fails.
3187 	return FALSE;
3188 
3189     rmp->rmm_maxcol = syn_buf->b_p_smc;
3190     r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col,
3191 #ifdef FEAT_RELTIME
3192 	    syn_tm, &timed_out
3193 #else
3194 	    NULL, NULL
3195 #endif
3196 	    );
3197 
3198 #ifdef FEAT_PROFILE
3199     if (syn_time_on)
3200     {
3201 	profile_end(&pt);
3202 	profile_add(&st->total, &pt);
3203 	if (profile_cmp(&pt, &st->slowest) < 0)
3204 	    st->slowest = pt;
3205 	++st->count;
3206 	if (r > 0)
3207 	    ++st->match;
3208     }
3209 #endif
3210 #ifdef FEAT_RELTIME
3211     if (timed_out && !syn_win->w_s->b_syn_slow)
3212     {
3213 	syn_win->w_s->b_syn_slow = TRUE;
3214 	msg(_("'redrawtime' exceeded, syntax highlighting disabled"));
3215     }
3216 #endif
3217 
3218     if (r > 0)
3219     {
3220 	rmp->startpos[0].lnum += lnum;
3221 	rmp->endpos[0].lnum += lnum;
3222 	return TRUE;
3223     }
3224     return FALSE;
3225 }
3226 
3227 /*
3228  * Check one position in a line for a matching keyword.
3229  * The caller must check if a keyword can start at startcol.
3230  * Return its ID if found, 0 otherwise.
3231  */
3232     static int
3233 check_keyword_id(
3234     char_u	*line,
3235     int		startcol,	// position in line to check for keyword
3236     int		*endcolp,	// return: character after found keyword
3237     long	*flagsp,	// return: flags of matching keyword
3238     short	**next_listp,	// return: next_list of matching keyword
3239     stateitem_T	*cur_si,	// item at the top of the stack
3240     int		*ccharp UNUSED)	// conceal substitution char
3241 {
3242     keyentry_T	*kp;
3243     char_u	*kwp;
3244     int		round;
3245     int		kwlen;
3246     char_u	keyword[MAXKEYWLEN + 1]; // assume max. keyword len is 80
3247     hashtab_T	*ht;
3248     hashitem_T	*hi;
3249 
3250     // Find first character after the keyword.  First character was already
3251     // checked.
3252     kwp = line + startcol;
3253     kwlen = 0;
3254     do
3255     {
3256 	if (has_mbyte)
3257 	    kwlen += (*mb_ptr2len)(kwp + kwlen);
3258 	else
3259 	    ++kwlen;
3260     }
3261     while (vim_iswordp_buf(kwp + kwlen, syn_buf));
3262 
3263     if (kwlen > MAXKEYWLEN)
3264 	return 0;
3265 
3266     /*
3267      * Must make a copy of the keyword, so we can add a NUL and make it
3268      * lowercase.
3269      */
3270     vim_strncpy(keyword, kwp, kwlen);
3271 
3272     /*
3273      * Try twice:
3274      * 1. matching case
3275      * 2. ignoring case
3276      */
3277     for (round = 1; round <= 2; ++round)
3278     {
3279 	ht = round == 1 ? &syn_block->b_keywtab : &syn_block->b_keywtab_ic;
3280 	if (ht->ht_used == 0)
3281 	    continue;
3282 	if (round == 2)	// ignore case
3283 	    (void)str_foldcase(kwp, kwlen, keyword, MAXKEYWLEN + 1);
3284 
3285 	/*
3286 	 * Find keywords that match.  There can be several with different
3287 	 * attributes.
3288 	 * When current_next_list is non-zero accept only that group, otherwise:
3289 	 *  Accept a not-contained keyword at toplevel.
3290 	 *  Accept a keyword at other levels only if it is in the contains list.
3291 	 */
3292 	hi = hash_find(ht, keyword);
3293 	if (!HASHITEM_EMPTY(hi))
3294 	    for (kp = HI2KE(hi); kp != NULL; kp = kp->ke_next)
3295 	    {
3296 		if (current_next_list != 0
3297 			? in_id_list(NULL, current_next_list, &kp->k_syn, 0)
3298 			: (cur_si == NULL
3299 			    ? !(kp->flags & HL_CONTAINED)
3300 			    : in_id_list(cur_si, cur_si->si_cont_list,
3301 				      &kp->k_syn, kp->flags & HL_CONTAINED)))
3302 		{
3303 		    *endcolp = startcol + kwlen;
3304 		    *flagsp = kp->flags;
3305 		    *next_listp = kp->next_list;
3306 #ifdef FEAT_CONCEAL
3307 		    *ccharp = kp->k_char;
3308 #endif
3309 		    return kp->k_syn.id;
3310 		}
3311 	    }
3312     }
3313     return 0;
3314 }
3315 
3316 /*
3317  * Handle ":syntax conceal" command.
3318  */
3319     static void
3320 syn_cmd_conceal(exarg_T *eap UNUSED, int syncing UNUSED)
3321 {
3322 #ifdef FEAT_CONCEAL
3323     char_u	*arg = eap->arg;
3324     char_u	*next;
3325 
3326     eap->nextcmd = find_nextcmd(arg);
3327     if (eap->skip)
3328 	return;
3329 
3330     next = skiptowhite(arg);
3331     if (*arg == NUL)
3332     {
3333 	if (curwin->w_s->b_syn_conceal)
3334 	    msg(_("syntax conceal on"));
3335 	else
3336 	    msg(_("syntax conceal off"));
3337     }
3338     else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2)
3339 	curwin->w_s->b_syn_conceal = TRUE;
3340     else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3)
3341 	curwin->w_s->b_syn_conceal = FALSE;
3342     else
3343 	semsg(_("E390: Illegal argument: %s"), arg);
3344 #endif
3345 }
3346 
3347 /*
3348  * Handle ":syntax case" command.
3349  */
3350     static void
3351 syn_cmd_case(exarg_T *eap, int syncing UNUSED)
3352 {
3353     char_u	*arg = eap->arg;
3354     char_u	*next;
3355 
3356     eap->nextcmd = find_nextcmd(arg);
3357     if (eap->skip)
3358 	return;
3359 
3360     next = skiptowhite(arg);
3361     if (*arg == NUL)
3362     {
3363 	if (curwin->w_s->b_syn_ic)
3364 	    msg(_("syntax case ignore"));
3365 	else
3366 	    msg(_("syntax case match"));
3367     }
3368     else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5)
3369 	curwin->w_s->b_syn_ic = FALSE;
3370     else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6)
3371 	curwin->w_s->b_syn_ic = TRUE;
3372     else
3373 	semsg(_("E390: Illegal argument: %s"), arg);
3374 }
3375 
3376 /*
3377  * Handle ":syntax spell" command.
3378  */
3379     static void
3380 syn_cmd_spell(exarg_T *eap, int syncing UNUSED)
3381 {
3382     char_u	*arg = eap->arg;
3383     char_u	*next;
3384 
3385     eap->nextcmd = find_nextcmd(arg);
3386     if (eap->skip)
3387 	return;
3388 
3389     next = skiptowhite(arg);
3390     if (*arg == NUL)
3391     {
3392 	if (curwin->w_s->b_syn_spell == SYNSPL_TOP)
3393 	    msg(_("syntax spell toplevel"));
3394 	else if (curwin->w_s->b_syn_spell == SYNSPL_NOTOP)
3395 	    msg(_("syntax spell notoplevel"));
3396 	else
3397 	    msg(_("syntax spell default"));
3398     }
3399     else if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8)
3400 	curwin->w_s->b_syn_spell = SYNSPL_TOP;
3401     else if (STRNICMP(arg, "notoplevel", 10) == 0 && next - arg == 10)
3402 	curwin->w_s->b_syn_spell = SYNSPL_NOTOP;
3403     else if (STRNICMP(arg, "default", 7) == 0 && next - arg == 7)
3404 	curwin->w_s->b_syn_spell = SYNSPL_DEFAULT;
3405     else
3406     {
3407 	semsg(_("E390: Illegal argument: %s"), arg);
3408 	return;
3409     }
3410 
3411     // assume spell checking changed, force a redraw
3412     redraw_win_later(curwin, NOT_VALID);
3413 }
3414 
3415 /*
3416  * Handle ":syntax iskeyword" command.
3417  */
3418     static void
3419 syn_cmd_iskeyword(exarg_T *eap, int syncing UNUSED)
3420 {
3421     char_u	*arg = eap->arg;
3422     char_u	save_chartab[32];
3423     char_u	*save_isk;
3424 
3425     if (eap->skip)
3426 	return;
3427 
3428     arg = skipwhite(arg);
3429     if (*arg == NUL)
3430     {
3431 	msg_puts("\n");
3432 	if (curwin->w_s->b_syn_isk != empty_option)
3433 	{
3434 	    msg_puts(_("syntax iskeyword "));
3435 	    msg_outtrans(curwin->w_s->b_syn_isk);
3436 	}
3437 	else
3438 	    msg_outtrans((char_u *)_("syntax iskeyword not set"));
3439     }
3440     else
3441     {
3442 	if (STRNICMP(arg, "clear", 5) == 0)
3443 	{
3444 	    mch_memmove(curwin->w_s->b_syn_chartab, curbuf->b_chartab,
3445 								  (size_t)32);
3446 	    clear_string_option(&curwin->w_s->b_syn_isk);
3447 	}
3448 	else
3449 	{
3450 	    mch_memmove(save_chartab, curbuf->b_chartab, (size_t)32);
3451 	    save_isk = curbuf->b_p_isk;
3452 	    curbuf->b_p_isk = vim_strsave(arg);
3453 
3454 	    buf_init_chartab(curbuf, FALSE);
3455 	    mch_memmove(curwin->w_s->b_syn_chartab, curbuf->b_chartab,
3456 								  (size_t)32);
3457 	    mch_memmove(curbuf->b_chartab, save_chartab, (size_t)32);
3458 	    clear_string_option(&curwin->w_s->b_syn_isk);
3459 	    curwin->w_s->b_syn_isk = curbuf->b_p_isk;
3460 	    curbuf->b_p_isk = save_isk;
3461 	}
3462     }
3463     redraw_win_later(curwin, NOT_VALID);
3464 }
3465 
3466 /*
3467  * Clear all syntax info for one buffer.
3468  */
3469     void
3470 syntax_clear(synblock_T *block)
3471 {
3472     int i;
3473 
3474     block->b_syn_error = FALSE;	    // clear previous error
3475 #ifdef FEAT_RELTIME
3476     block->b_syn_slow = FALSE;	    // clear previous timeout
3477 #endif
3478     block->b_syn_ic = FALSE;	    // Use case, by default
3479     block->b_syn_spell = SYNSPL_DEFAULT; // default spell checking
3480     block->b_syn_containedin = FALSE;
3481 #ifdef FEAT_CONCEAL
3482     block->b_syn_conceal = FALSE;
3483 #endif
3484 
3485     // free the keywords
3486     clear_keywtab(&block->b_keywtab);
3487     clear_keywtab(&block->b_keywtab_ic);
3488 
3489     // free the syntax patterns
3490     for (i = block->b_syn_patterns.ga_len; --i >= 0; )
3491 	syn_clear_pattern(block, i);
3492     ga_clear(&block->b_syn_patterns);
3493 
3494     // free the syntax clusters
3495     for (i = block->b_syn_clusters.ga_len; --i >= 0; )
3496 	syn_clear_cluster(block, i);
3497     ga_clear(&block->b_syn_clusters);
3498     block->b_spell_cluster_id = 0;
3499     block->b_nospell_cluster_id = 0;
3500 
3501     block->b_syn_sync_flags = 0;
3502     block->b_syn_sync_minlines = 0;
3503     block->b_syn_sync_maxlines = 0;
3504     block->b_syn_sync_linebreaks = 0;
3505 
3506     vim_regfree(block->b_syn_linecont_prog);
3507     block->b_syn_linecont_prog = NULL;
3508     VIM_CLEAR(block->b_syn_linecont_pat);
3509 #ifdef FEAT_FOLDING
3510     block->b_syn_folditems = 0;
3511 #endif
3512     clear_string_option(&block->b_syn_isk);
3513 
3514     // free the stored states
3515     syn_stack_free_all(block);
3516     invalidate_current_state();
3517 
3518     // Reset the counter for ":syn include"
3519     running_syn_inc_tag = 0;
3520 }
3521 
3522 /*
3523  * Get rid of ownsyntax for window "wp".
3524  */
3525     void
3526 reset_synblock(win_T *wp)
3527 {
3528     if (wp->w_s != &wp->w_buffer->b_s)
3529     {
3530 	syntax_clear(wp->w_s);
3531 	vim_free(wp->w_s);
3532 	wp->w_s = &wp->w_buffer->b_s;
3533     }
3534 }
3535 
3536 /*
3537  * Clear syncing info for one buffer.
3538  */
3539     static void
3540 syntax_sync_clear(void)
3541 {
3542     int i;
3543 
3544     // free the syntax patterns
3545     for (i = curwin->w_s->b_syn_patterns.ga_len; --i >= 0; )
3546 	if (SYN_ITEMS(curwin->w_s)[i].sp_syncing)
3547 	    syn_remove_pattern(curwin->w_s, i);
3548 
3549     curwin->w_s->b_syn_sync_flags = 0;
3550     curwin->w_s->b_syn_sync_minlines = 0;
3551     curwin->w_s->b_syn_sync_maxlines = 0;
3552     curwin->w_s->b_syn_sync_linebreaks = 0;
3553 
3554     vim_regfree(curwin->w_s->b_syn_linecont_prog);
3555     curwin->w_s->b_syn_linecont_prog = NULL;
3556     VIM_CLEAR(curwin->w_s->b_syn_linecont_pat);
3557     clear_string_option(&curwin->w_s->b_syn_isk);
3558 
3559     syn_stack_free_all(curwin->w_s);	// Need to recompute all syntax.
3560 }
3561 
3562 /*
3563  * Remove one pattern from the buffer's pattern list.
3564  */
3565     static void
3566 syn_remove_pattern(
3567     synblock_T	*block,
3568     int		idx)
3569 {
3570     synpat_T	*spp;
3571 
3572     spp = &(SYN_ITEMS(block)[idx]);
3573 #ifdef FEAT_FOLDING
3574     if (spp->sp_flags & HL_FOLD)
3575 	--block->b_syn_folditems;
3576 #endif
3577     syn_clear_pattern(block, idx);
3578     mch_memmove(spp, spp + 1,
3579 		   sizeof(synpat_T) * (block->b_syn_patterns.ga_len - idx - 1));
3580     --block->b_syn_patterns.ga_len;
3581 }
3582 
3583 /*
3584  * Clear and free one syntax pattern.  When clearing all, must be called from
3585  * last to first!
3586  */
3587     static void
3588 syn_clear_pattern(synblock_T *block, int i)
3589 {
3590     vim_free(SYN_ITEMS(block)[i].sp_pattern);
3591     vim_regfree(SYN_ITEMS(block)[i].sp_prog);
3592     // Only free sp_cont_list and sp_next_list of first start pattern
3593     if (i == 0 || SYN_ITEMS(block)[i - 1].sp_type != SPTYPE_START)
3594     {
3595 	vim_free(SYN_ITEMS(block)[i].sp_cont_list);
3596 	vim_free(SYN_ITEMS(block)[i].sp_next_list);
3597 	vim_free(SYN_ITEMS(block)[i].sp_syn.cont_in_list);
3598     }
3599 }
3600 
3601 /*
3602  * Clear and free one syntax cluster.
3603  */
3604     static void
3605 syn_clear_cluster(synblock_T *block, int i)
3606 {
3607     vim_free(SYN_CLSTR(block)[i].scl_name);
3608     vim_free(SYN_CLSTR(block)[i].scl_name_u);
3609     vim_free(SYN_CLSTR(block)[i].scl_list);
3610 }
3611 
3612 /*
3613  * Handle ":syntax clear" command.
3614  */
3615     static void
3616 syn_cmd_clear(exarg_T *eap, int syncing)
3617 {
3618     char_u	*arg = eap->arg;
3619     char_u	*arg_end;
3620     int		id;
3621 
3622     eap->nextcmd = find_nextcmd(arg);
3623     if (eap->skip)
3624 	return;
3625 
3626     /*
3627      * We have to disable this within ":syn include @group filename",
3628      * because otherwise @group would get deleted.
3629      * Only required for Vim 5.x syntax files, 6.0 ones don't contain ":syn
3630      * clear".
3631      */
3632     if (curwin->w_s->b_syn_topgrp != 0)
3633 	return;
3634 
3635     if (ends_excmd(*arg))
3636     {
3637 	/*
3638 	 * No argument: Clear all syntax items.
3639 	 */
3640 	if (syncing)
3641 	    syntax_sync_clear();
3642 	else
3643 	{
3644 	    syntax_clear(curwin->w_s);
3645 	    if (curwin->w_s == &curwin->w_buffer->b_s)
3646 		do_unlet((char_u *)"b:current_syntax", TRUE);
3647 	    do_unlet((char_u *)"w:current_syntax", TRUE);
3648 	}
3649     }
3650     else
3651     {
3652 	/*
3653 	 * Clear the group IDs that are in the argument.
3654 	 */
3655 	while (!ends_excmd(*arg))
3656 	{
3657 	    arg_end = skiptowhite(arg);
3658 	    if (*arg == '@')
3659 	    {
3660 		id = syn_scl_namen2id(arg + 1, (int)(arg_end - arg - 1));
3661 		if (id == 0)
3662 		{
3663 		    semsg(_("E391: No such syntax cluster: %s"), arg);
3664 		    break;
3665 		}
3666 		else
3667 		{
3668 		    /*
3669 		     * We can't physically delete a cluster without changing
3670 		     * the IDs of other clusters, so we do the next best thing
3671 		     * and make it empty.
3672 		     */
3673 		    short scl_id = id - SYNID_CLUSTER;
3674 
3675 		    VIM_CLEAR(SYN_CLSTR(curwin->w_s)[scl_id].scl_list);
3676 		}
3677 	    }
3678 	    else
3679 	    {
3680 		id = syn_namen2id(arg, (int)(arg_end - arg));
3681 		if (id == 0)
3682 		{
3683 		    semsg(_(e_nogroup), arg);
3684 		    break;
3685 		}
3686 		else
3687 		    syn_clear_one(id, syncing);
3688 	    }
3689 	    arg = skipwhite(arg_end);
3690 	}
3691     }
3692     redraw_curbuf_later(SOME_VALID);
3693     syn_stack_free_all(curwin->w_s);		// Need to recompute all syntax.
3694 }
3695 
3696 /*
3697  * Clear one syntax group for the current buffer.
3698  */
3699     static void
3700 syn_clear_one(int id, int syncing)
3701 {
3702     synpat_T	*spp;
3703     int		idx;
3704 
3705     // Clear keywords only when not ":syn sync clear group-name"
3706     if (!syncing)
3707     {
3708 	(void)syn_clear_keyword(id, &curwin->w_s->b_keywtab);
3709 	(void)syn_clear_keyword(id, &curwin->w_s->b_keywtab_ic);
3710     }
3711 
3712     // clear the patterns for "id"
3713     for (idx = curwin->w_s->b_syn_patterns.ga_len; --idx >= 0; )
3714     {
3715 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
3716 	if (spp->sp_syn.id != id || spp->sp_syncing != syncing)
3717 	    continue;
3718 	syn_remove_pattern(curwin->w_s, idx);
3719     }
3720 }
3721 
3722 /*
3723  * Handle ":syntax on" command.
3724  */
3725     static void
3726 syn_cmd_on(exarg_T *eap, int syncing UNUSED)
3727 {
3728     syn_cmd_onoff(eap, "syntax");
3729 }
3730 
3731 /*
3732  * Handle ":syntax enable" command.
3733  */
3734     static void
3735 syn_cmd_enable(exarg_T *eap, int syncing UNUSED)
3736 {
3737     set_internal_string_var((char_u *)"syntax_cmd", (char_u *)"enable");
3738     syn_cmd_onoff(eap, "syntax");
3739     do_unlet((char_u *)"g:syntax_cmd", TRUE);
3740 }
3741 
3742 /*
3743  * Handle ":syntax reset" command.
3744  * It actually resets highlighting, not syntax.
3745  */
3746     static void
3747 syn_cmd_reset(exarg_T *eap, int syncing UNUSED)
3748 {
3749     eap->nextcmd = check_nextcmd(eap->arg);
3750     if (!eap->skip)
3751     {
3752 	set_internal_string_var((char_u *)"syntax_cmd", (char_u *)"reset");
3753 	do_cmdline_cmd((char_u *)"runtime! syntax/syncolor.vim");
3754 	do_unlet((char_u *)"g:syntax_cmd", TRUE);
3755     }
3756 }
3757 
3758 /*
3759  * Handle ":syntax manual" command.
3760  */
3761     static void
3762 syn_cmd_manual(exarg_T *eap, int syncing UNUSED)
3763 {
3764     syn_cmd_onoff(eap, "manual");
3765 }
3766 
3767 /*
3768  * Handle ":syntax off" command.
3769  */
3770     static void
3771 syn_cmd_off(exarg_T *eap, int syncing UNUSED)
3772 {
3773     syn_cmd_onoff(eap, "nosyntax");
3774 }
3775 
3776     static void
3777 syn_cmd_onoff(exarg_T *eap, char *name)
3778 {
3779     char_u	buf[100];
3780 
3781     eap->nextcmd = check_nextcmd(eap->arg);
3782     if (!eap->skip)
3783     {
3784 	STRCPY(buf, "so ");
3785 	vim_snprintf((char *)buf + 3, sizeof(buf) - 3, SYNTAX_FNAME, name);
3786 	do_cmdline_cmd(buf);
3787     }
3788 }
3789 
3790 /*
3791  * Handle ":syntax [list]" command: list current syntax words.
3792  */
3793     static void
3794 syn_cmd_list(
3795     exarg_T	*eap,
3796     int		syncing)	    // when TRUE: list syncing items
3797 {
3798     char_u	*arg = eap->arg;
3799     int		id;
3800     char_u	*arg_end;
3801 
3802     eap->nextcmd = find_nextcmd(arg);
3803     if (eap->skip)
3804 	return;
3805 
3806     if (!syntax_present(curwin))
3807     {
3808 	msg(_(msg_no_items));
3809 	return;
3810     }
3811 
3812     if (syncing)
3813     {
3814 	if (curwin->w_s->b_syn_sync_flags & SF_CCOMMENT)
3815 	{
3816 	    msg_puts(_("syncing on C-style comments"));
3817 	    syn_lines_msg();
3818 	    syn_match_msg();
3819 	    return;
3820 	}
3821 	else if (!(curwin->w_s->b_syn_sync_flags & SF_MATCH))
3822 	{
3823 	    if (curwin->w_s->b_syn_sync_minlines == 0)
3824 		msg_puts(_("no syncing"));
3825 	    else
3826 	    {
3827 		msg_puts(_("syncing starts "));
3828 		msg_outnum(curwin->w_s->b_syn_sync_minlines);
3829 		msg_puts(_(" lines before top line"));
3830 		syn_match_msg();
3831 	    }
3832 	    return;
3833 	}
3834 	msg_puts_title(_("\n--- Syntax sync items ---"));
3835 	if (curwin->w_s->b_syn_sync_minlines > 0
3836 		|| curwin->w_s->b_syn_sync_maxlines > 0
3837 		|| curwin->w_s->b_syn_sync_linebreaks > 0)
3838 	{
3839 	    msg_puts(_("\nsyncing on items"));
3840 	    syn_lines_msg();
3841 	    syn_match_msg();
3842 	}
3843     }
3844     else
3845 	msg_puts_title(_("\n--- Syntax items ---"));
3846     if (ends_excmd(*arg))
3847     {
3848 	/*
3849 	 * No argument: List all group IDs and all syntax clusters.
3850 	 */
3851 	for (id = 1; id <= highlight_num_groups() && !got_int; ++id)
3852 	    syn_list_one(id, syncing, FALSE);
3853 	for (id = 0; id < curwin->w_s->b_syn_clusters.ga_len && !got_int; ++id)
3854 	    syn_list_cluster(id);
3855     }
3856     else
3857     {
3858 	/*
3859 	 * List the group IDs and syntax clusters that are in the argument.
3860 	 */
3861 	while (!ends_excmd(*arg) && !got_int)
3862 	{
3863 	    arg_end = skiptowhite(arg);
3864 	    if (*arg == '@')
3865 	    {
3866 		id = syn_scl_namen2id(arg + 1, (int)(arg_end - arg - 1));
3867 		if (id == 0)
3868 		    semsg(_("E392: No such syntax cluster: %s"), arg);
3869 		else
3870 		    syn_list_cluster(id - SYNID_CLUSTER);
3871 	    }
3872 	    else
3873 	    {
3874 		id = syn_namen2id(arg, (int)(arg_end - arg));
3875 		if (id == 0)
3876 		    semsg(_(e_nogroup), arg);
3877 		else
3878 		    syn_list_one(id, syncing, TRUE);
3879 	    }
3880 	    arg = skipwhite(arg_end);
3881 	}
3882     }
3883     eap->nextcmd = check_nextcmd(arg);
3884 }
3885 
3886     static void
3887 syn_lines_msg(void)
3888 {
3889     if (curwin->w_s->b_syn_sync_maxlines > 0
3890 				      || curwin->w_s->b_syn_sync_minlines > 0)
3891     {
3892 	msg_puts("; ");
3893 	if (curwin->w_s->b_syn_sync_minlines > 0)
3894 	{
3895 	    msg_puts(_("minimal "));
3896 	    msg_outnum(curwin->w_s->b_syn_sync_minlines);
3897 	    if (curwin->w_s->b_syn_sync_maxlines)
3898 		msg_puts(", ");
3899 	}
3900 	if (curwin->w_s->b_syn_sync_maxlines > 0)
3901 	{
3902 	    msg_puts(_("maximal "));
3903 	    msg_outnum(curwin->w_s->b_syn_sync_maxlines);
3904 	}
3905 	msg_puts(_(" lines before top line"));
3906     }
3907 }
3908 
3909     static void
3910 syn_match_msg(void)
3911 {
3912     if (curwin->w_s->b_syn_sync_linebreaks > 0)
3913     {
3914 	msg_puts(_("; match "));
3915 	msg_outnum(curwin->w_s->b_syn_sync_linebreaks);
3916 	msg_puts(_(" line breaks"));
3917     }
3918 }
3919 
3920 static int  last_matchgroup;
3921 
3922 struct name_list
3923 {
3924     int		flag;
3925     char	*name;
3926 };
3927 
3928 static void syn_list_flags(struct name_list *nl, int flags, int attr);
3929 
3930 /*
3931  * List one syntax item, for ":syntax" or "syntax list syntax_name".
3932  */
3933     static void
3934 syn_list_one(
3935     int		id,
3936     int		syncing,	    // when TRUE: list syncing items
3937     int		link_only)	    // when TRUE; list link-only too
3938 {
3939     int		attr;
3940     int		idx;
3941     int		did_header = FALSE;
3942     synpat_T	*spp;
3943     static struct name_list namelist1[] =
3944 		{
3945 		    {HL_DISPLAY, "display"},
3946 		    {HL_CONTAINED, "contained"},
3947 		    {HL_ONELINE, "oneline"},
3948 		    {HL_KEEPEND, "keepend"},
3949 		    {HL_EXTEND, "extend"},
3950 		    {HL_EXCLUDENL, "excludenl"},
3951 		    {HL_TRANSP, "transparent"},
3952 		    {HL_FOLD, "fold"},
3953 #ifdef FEAT_CONCEAL
3954 		    {HL_CONCEAL, "conceal"},
3955 		    {HL_CONCEALENDS, "concealends"},
3956 #endif
3957 		    {0, NULL}
3958 		};
3959     static struct name_list namelist2[] =
3960 		{
3961 		    {HL_SKIPWHITE, "skipwhite"},
3962 		    {HL_SKIPNL, "skipnl"},
3963 		    {HL_SKIPEMPTY, "skipempty"},
3964 		    {0, NULL}
3965 		};
3966 
3967     attr = HL_ATTR(HLF_D);		// highlight like directories
3968 
3969     // list the keywords for "id"
3970     if (!syncing)
3971     {
3972 	did_header = syn_list_keywords(id, &curwin->w_s->b_keywtab, FALSE, attr);
3973 	did_header = syn_list_keywords(id, &curwin->w_s->b_keywtab_ic,
3974 							    did_header, attr);
3975     }
3976 
3977     // list the patterns for "id"
3978     for (idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len && !got_int; ++idx)
3979     {
3980 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
3981 	if (spp->sp_syn.id != id || spp->sp_syncing != syncing)
3982 	    continue;
3983 
3984 	(void)syn_list_header(did_header, 999, id);
3985 	did_header = TRUE;
3986 	last_matchgroup = 0;
3987 	if (spp->sp_type == SPTYPE_MATCH)
3988 	{
3989 	    put_pattern("match", ' ', spp, attr);
3990 	    msg_putchar(' ');
3991 	}
3992 	else if (spp->sp_type == SPTYPE_START)
3993 	{
3994 	    while (SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_START)
3995 		put_pattern("start", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr);
3996 	    if (SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_SKIP)
3997 		put_pattern("skip", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr);
3998 	    while (idx < curwin->w_s->b_syn_patterns.ga_len
3999 			  && SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_END)
4000 		put_pattern("end", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr);
4001 	    --idx;
4002 	    msg_putchar(' ');
4003 	}
4004 	syn_list_flags(namelist1, spp->sp_flags, attr);
4005 
4006 	if (spp->sp_cont_list != NULL)
4007 	    put_id_list((char_u *)"contains", spp->sp_cont_list, attr);
4008 
4009 	if (spp->sp_syn.cont_in_list != NULL)
4010 	    put_id_list((char_u *)"containedin",
4011 					      spp->sp_syn.cont_in_list, attr);
4012 
4013 	if (spp->sp_next_list != NULL)
4014 	{
4015 	    put_id_list((char_u *)"nextgroup", spp->sp_next_list, attr);
4016 	    syn_list_flags(namelist2, spp->sp_flags, attr);
4017 	}
4018 	if (spp->sp_flags & (HL_SYNC_HERE|HL_SYNC_THERE))
4019 	{
4020 	    if (spp->sp_flags & HL_SYNC_HERE)
4021 		msg_puts_attr("grouphere", attr);
4022 	    else
4023 		msg_puts_attr("groupthere", attr);
4024 	    msg_putchar(' ');
4025 	    if (spp->sp_sync_idx >= 0)
4026 		msg_outtrans(highlight_group_name(SYN_ITEMS(curwin->w_s)
4027 				   [spp->sp_sync_idx].sp_syn.id - 1));
4028 	    else
4029 		msg_puts("NONE");
4030 	    msg_putchar(' ');
4031 	}
4032     }
4033 
4034     // list the link, if there is one
4035     if (highlight_link_id(id - 1) && (did_header || link_only) && !got_int)
4036     {
4037 	(void)syn_list_header(did_header, 999, id);
4038 	msg_puts_attr("links to", attr);
4039 	msg_putchar(' ');
4040 	msg_outtrans(highlight_group_name(highlight_link_id(id - 1) - 1));
4041     }
4042 }
4043 
4044     static void
4045 syn_list_flags(struct name_list *nlist, int flags, int attr)
4046 {
4047     int		i;
4048 
4049     for (i = 0; nlist[i].flag != 0; ++i)
4050 	if (flags & nlist[i].flag)
4051 	{
4052 	    msg_puts_attr(nlist[i].name, attr);
4053 	    msg_putchar(' ');
4054 	}
4055 }
4056 
4057 /*
4058  * List one syntax cluster, for ":syntax" or "syntax list syntax_name".
4059  */
4060     static void
4061 syn_list_cluster(int id)
4062 {
4063     int	    endcol = 15;
4064 
4065     // slight hack:  roughly duplicate the guts of syn_list_header()
4066     msg_putchar('\n');
4067     msg_outtrans(SYN_CLSTR(curwin->w_s)[id].scl_name);
4068 
4069     if (msg_col >= endcol)	// output at least one space
4070 	endcol = msg_col + 1;
4071     if (Columns <= endcol)	// avoid hang for tiny window
4072 	endcol = Columns - 1;
4073 
4074     msg_advance(endcol);
4075     if (SYN_CLSTR(curwin->w_s)[id].scl_list != NULL)
4076     {
4077 	put_id_list((char_u *)"cluster", SYN_CLSTR(curwin->w_s)[id].scl_list,
4078 		    HL_ATTR(HLF_D));
4079     }
4080     else
4081     {
4082 	msg_puts_attr("cluster", HL_ATTR(HLF_D));
4083 	msg_puts("=NONE");
4084     }
4085 }
4086 
4087     static void
4088 put_id_list(char_u *name, short *list, int attr)
4089 {
4090     short		*p;
4091 
4092     msg_puts_attr((char *)name, attr);
4093     msg_putchar('=');
4094     for (p = list; *p; ++p)
4095     {
4096 	if (*p >= SYNID_ALLBUT && *p < SYNID_TOP)
4097 	{
4098 	    if (p[1])
4099 		msg_puts("ALLBUT");
4100 	    else
4101 		msg_puts("ALL");
4102 	}
4103 	else if (*p >= SYNID_TOP && *p < SYNID_CONTAINED)
4104 	{
4105 	    msg_puts("TOP");
4106 	}
4107 	else if (*p >= SYNID_CONTAINED && *p < SYNID_CLUSTER)
4108 	{
4109 	    msg_puts("CONTAINED");
4110 	}
4111 	else if (*p >= SYNID_CLUSTER)
4112 	{
4113 	    short scl_id = *p - SYNID_CLUSTER;
4114 
4115 	    msg_putchar('@');
4116 	    msg_outtrans(SYN_CLSTR(curwin->w_s)[scl_id].scl_name);
4117 	}
4118 	else
4119 	    msg_outtrans(highlight_group_name(*p - 1));
4120 	if (p[1])
4121 	    msg_putchar(',');
4122     }
4123     msg_putchar(' ');
4124 }
4125 
4126     static void
4127 put_pattern(
4128     char	*s,
4129     int		c,
4130     synpat_T	*spp,
4131     int		attr)
4132 {
4133     long	n;
4134     int		mask;
4135     int		first;
4136     static char	*sepchars = "/+=-#@\"|'^&";
4137     int		i;
4138 
4139     // May have to write "matchgroup=group"
4140     if (last_matchgroup != spp->sp_syn_match_id)
4141     {
4142 	last_matchgroup = spp->sp_syn_match_id;
4143 	msg_puts_attr("matchgroup", attr);
4144 	msg_putchar('=');
4145 	if (last_matchgroup == 0)
4146 	    msg_outtrans((char_u *)"NONE");
4147 	else
4148 	    msg_outtrans(highlight_group_name(last_matchgroup - 1));
4149 	msg_putchar(' ');
4150     }
4151 
4152     // output the name of the pattern and an '=' or ' '
4153     msg_puts_attr(s, attr);
4154     msg_putchar(c);
4155 
4156     // output the pattern, in between a char that is not in the pattern
4157     for (i = 0; vim_strchr(spp->sp_pattern, sepchars[i]) != NULL; )
4158 	if (sepchars[++i] == NUL)
4159 	{
4160 	    i = 0;	// no good char found, just use the first one
4161 	    break;
4162 	}
4163     msg_putchar(sepchars[i]);
4164     msg_outtrans(spp->sp_pattern);
4165     msg_putchar(sepchars[i]);
4166 
4167     // output any pattern options
4168     first = TRUE;
4169     for (i = 0; i < SPO_COUNT; ++i)
4170     {
4171 	mask = (1 << i);
4172 	if (spp->sp_off_flags & (mask + (mask << SPO_COUNT)))
4173 	{
4174 	    if (!first)
4175 		msg_putchar(',');	// separate with commas
4176 	    msg_puts(spo_name_tab[i]);
4177 	    n = spp->sp_offsets[i];
4178 	    if (i != SPO_LC_OFF)
4179 	    {
4180 		if (spp->sp_off_flags & mask)
4181 		    msg_putchar('s');
4182 		else
4183 		    msg_putchar('e');
4184 		if (n > 0)
4185 		    msg_putchar('+');
4186 	    }
4187 	    if (n || i == SPO_LC_OFF)
4188 		msg_outnum(n);
4189 	    first = FALSE;
4190 	}
4191     }
4192     msg_putchar(' ');
4193 }
4194 
4195 /*
4196  * List or clear the keywords for one syntax group.
4197  * Return TRUE if the header has been printed.
4198  */
4199     static int
4200 syn_list_keywords(
4201     int		id,
4202     hashtab_T	*ht,
4203     int		did_header,		// header has already been printed
4204     int		attr)
4205 {
4206     int		outlen;
4207     hashitem_T	*hi;
4208     keyentry_T	*kp;
4209     int		todo;
4210     int		prev_contained = 0;
4211     short	*prev_next_list = NULL;
4212     short	*prev_cont_in_list = NULL;
4213     int		prev_skipnl = 0;
4214     int		prev_skipwhite = 0;
4215     int		prev_skipempty = 0;
4216 
4217     /*
4218      * Unfortunately, this list of keywords is not sorted on alphabet but on
4219      * hash value...
4220      */
4221     todo = (int)ht->ht_used;
4222     for (hi = ht->ht_array; todo > 0 && !got_int; ++hi)
4223     {
4224 	if (!HASHITEM_EMPTY(hi))
4225 	{
4226 	    --todo;
4227 	    for (kp = HI2KE(hi); kp != NULL && !got_int; kp = kp->ke_next)
4228 	    {
4229 		if (kp->k_syn.id == id)
4230 		{
4231 		    if (prev_contained != (kp->flags & HL_CONTAINED)
4232 			    || prev_skipnl != (kp->flags & HL_SKIPNL)
4233 			    || prev_skipwhite != (kp->flags & HL_SKIPWHITE)
4234 			    || prev_skipempty != (kp->flags & HL_SKIPEMPTY)
4235 			    || prev_cont_in_list != kp->k_syn.cont_in_list
4236 			    || prev_next_list != kp->next_list)
4237 			outlen = 9999;
4238 		    else
4239 			outlen = (int)STRLEN(kp->keyword);
4240 		    // output "contained" and "nextgroup" on each line
4241 		    if (syn_list_header(did_header, outlen, id))
4242 		    {
4243 			prev_contained = 0;
4244 			prev_next_list = NULL;
4245 			prev_cont_in_list = NULL;
4246 			prev_skipnl = 0;
4247 			prev_skipwhite = 0;
4248 			prev_skipempty = 0;
4249 		    }
4250 		    did_header = TRUE;
4251 		    if (prev_contained != (kp->flags & HL_CONTAINED))
4252 		    {
4253 			msg_puts_attr("contained", attr);
4254 			msg_putchar(' ');
4255 			prev_contained = (kp->flags & HL_CONTAINED);
4256 		    }
4257 		    if (kp->k_syn.cont_in_list != prev_cont_in_list)
4258 		    {
4259 			put_id_list((char_u *)"containedin",
4260 						kp->k_syn.cont_in_list, attr);
4261 			msg_putchar(' ');
4262 			prev_cont_in_list = kp->k_syn.cont_in_list;
4263 		    }
4264 		    if (kp->next_list != prev_next_list)
4265 		    {
4266 			put_id_list((char_u *)"nextgroup", kp->next_list, attr);
4267 			msg_putchar(' ');
4268 			prev_next_list = kp->next_list;
4269 			if (kp->flags & HL_SKIPNL)
4270 			{
4271 			    msg_puts_attr("skipnl", attr);
4272 			    msg_putchar(' ');
4273 			    prev_skipnl = (kp->flags & HL_SKIPNL);
4274 			}
4275 			if (kp->flags & HL_SKIPWHITE)
4276 			{
4277 			    msg_puts_attr("skipwhite", attr);
4278 			    msg_putchar(' ');
4279 			    prev_skipwhite = (kp->flags & HL_SKIPWHITE);
4280 			}
4281 			if (kp->flags & HL_SKIPEMPTY)
4282 			{
4283 			    msg_puts_attr("skipempty", attr);
4284 			    msg_putchar(' ');
4285 			    prev_skipempty = (kp->flags & HL_SKIPEMPTY);
4286 			}
4287 		    }
4288 		    msg_outtrans(kp->keyword);
4289 		}
4290 	    }
4291 	}
4292     }
4293 
4294     return did_header;
4295 }
4296 
4297     static void
4298 syn_clear_keyword(int id, hashtab_T *ht)
4299 {
4300     hashitem_T	*hi;
4301     keyentry_T	*kp;
4302     keyentry_T	*kp_prev;
4303     keyentry_T	*kp_next;
4304     int		todo;
4305 
4306     hash_lock(ht);
4307     todo = (int)ht->ht_used;
4308     for (hi = ht->ht_array; todo > 0; ++hi)
4309     {
4310 	if (!HASHITEM_EMPTY(hi))
4311 	{
4312 	    --todo;
4313 	    kp_prev = NULL;
4314 	    for (kp = HI2KE(hi); kp != NULL; )
4315 	    {
4316 		if (kp->k_syn.id == id)
4317 		{
4318 		    kp_next = kp->ke_next;
4319 		    if (kp_prev == NULL)
4320 		    {
4321 			if (kp_next == NULL)
4322 			    hash_remove(ht, hi);
4323 			else
4324 			    hi->hi_key = KE2HIKEY(kp_next);
4325 		    }
4326 		    else
4327 			kp_prev->ke_next = kp_next;
4328 		    vim_free(kp->next_list);
4329 		    vim_free(kp->k_syn.cont_in_list);
4330 		    vim_free(kp);
4331 		    kp = kp_next;
4332 		}
4333 		else
4334 		{
4335 		    kp_prev = kp;
4336 		    kp = kp->ke_next;
4337 		}
4338 	    }
4339 	}
4340     }
4341     hash_unlock(ht);
4342 }
4343 
4344 /*
4345  * Clear a whole keyword table.
4346  */
4347     static void
4348 clear_keywtab(hashtab_T *ht)
4349 {
4350     hashitem_T	*hi;
4351     int		todo;
4352     keyentry_T	*kp;
4353     keyentry_T	*kp_next;
4354 
4355     todo = (int)ht->ht_used;
4356     for (hi = ht->ht_array; todo > 0; ++hi)
4357     {
4358 	if (!HASHITEM_EMPTY(hi))
4359 	{
4360 	    --todo;
4361 	    for (kp = HI2KE(hi); kp != NULL; kp = kp_next)
4362 	    {
4363 		kp_next = kp->ke_next;
4364 		vim_free(kp->next_list);
4365 		vim_free(kp->k_syn.cont_in_list);
4366 		vim_free(kp);
4367 	    }
4368 	}
4369     }
4370     hash_clear(ht);
4371     hash_init(ht);
4372 }
4373 
4374 /*
4375  * Add a keyword to the list of keywords.
4376  */
4377     static void
4378 add_keyword(
4379     char_u	*name,	    // name of keyword
4380     int		id,	    // group ID for this keyword
4381     int		flags,	    // flags for this keyword
4382     short	*cont_in_list, // containedin for this keyword
4383     short	*next_list, // nextgroup for this keyword
4384     int		conceal_char)
4385 {
4386     keyentry_T	*kp;
4387     hashtab_T	*ht;
4388     hashitem_T	*hi;
4389     char_u	*name_ic;
4390     long_u	hash;
4391     char_u	name_folded[MAXKEYWLEN + 1];
4392 
4393     if (curwin->w_s->b_syn_ic)
4394 	name_ic = str_foldcase(name, (int)STRLEN(name),
4395 						 name_folded, MAXKEYWLEN + 1);
4396     else
4397 	name_ic = name;
4398     kp = alloc(offsetof(keyentry_T, keyword) + STRLEN(name_ic) + 1);
4399     if (kp == NULL)
4400 	return;
4401     STRCPY(kp->keyword, name_ic);
4402     kp->k_syn.id = id;
4403     kp->k_syn.inc_tag = current_syn_inc_tag;
4404     kp->flags = flags;
4405     kp->k_char = conceal_char;
4406     kp->k_syn.cont_in_list = copy_id_list(cont_in_list);
4407     if (cont_in_list != NULL)
4408 	curwin->w_s->b_syn_containedin = TRUE;
4409     kp->next_list = copy_id_list(next_list);
4410 
4411     if (curwin->w_s->b_syn_ic)
4412 	ht = &curwin->w_s->b_keywtab_ic;
4413     else
4414 	ht = &curwin->w_s->b_keywtab;
4415 
4416     hash = hash_hash(kp->keyword);
4417     hi = hash_lookup(ht, kp->keyword, hash);
4418     if (HASHITEM_EMPTY(hi))
4419     {
4420 	// new keyword, add to hashtable
4421 	kp->ke_next = NULL;
4422 	hash_add_item(ht, hi, kp->keyword, hash);
4423     }
4424     else
4425     {
4426 	// keyword already exists, prepend to list
4427 	kp->ke_next = HI2KE(hi);
4428 	hi->hi_key = KE2HIKEY(kp);
4429     }
4430 }
4431 
4432 /*
4433  * Get the start and end of the group name argument.
4434  * Return a pointer to the first argument.
4435  * Return NULL if the end of the command was found instead of further args.
4436  */
4437     static char_u *
4438 get_group_name(
4439     char_u	*arg,		// start of the argument
4440     char_u	**name_end)	// pointer to end of the name
4441 {
4442     char_u	*rest;
4443 
4444     *name_end = skiptowhite(arg);
4445     rest = skipwhite(*name_end);
4446 
4447     /*
4448      * Check if there are enough arguments.  The first argument may be a
4449      * pattern, where '|' is allowed, so only check for NUL.
4450      */
4451     if (ends_excmd(*arg) || *rest == NUL)
4452 	return NULL;
4453     return rest;
4454 }
4455 
4456 /*
4457  * Check for syntax command option arguments.
4458  * This can be called at any place in the list of arguments, and just picks
4459  * out the arguments that are known.  Can be called several times in a row to
4460  * collect all options in between other arguments.
4461  * Return a pointer to the next argument (which isn't an option).
4462  * Return NULL for any error;
4463  */
4464     static char_u *
4465 get_syn_options(
4466     char_u	    *arg,		// next argument to be checked
4467     syn_opt_arg_T   *opt,		// various things
4468     int		    *conceal_char UNUSED,
4469     int		    skip)		// TRUE if skipping over command
4470 {
4471     char_u	*gname_start, *gname;
4472     int		syn_id;
4473     int		len;
4474     char	*p;
4475     int		i;
4476     int		fidx;
4477     static struct flag
4478     {
4479 	char	*name;
4480 	int	argtype;
4481 	int	flags;
4482     } flagtab[] = { {"cCoOnNtTaAiInNeEdD",	0,	HL_CONTAINED},
4483 		    {"oOnNeElLiInNeE",		0,	HL_ONELINE},
4484 		    {"kKeEeEpPeEnNdD",		0,	HL_KEEPEND},
4485 		    {"eExXtTeEnNdD",		0,	HL_EXTEND},
4486 		    {"eExXcClLuUdDeEnNlL",	0,	HL_EXCLUDENL},
4487 		    {"tTrRaAnNsSpPaArReEnNtT",	0,	HL_TRANSP},
4488 		    {"sSkKiIpPnNlL",		0,	HL_SKIPNL},
4489 		    {"sSkKiIpPwWhHiItTeE",	0,	HL_SKIPWHITE},
4490 		    {"sSkKiIpPeEmMpPtTyY",	0,	HL_SKIPEMPTY},
4491 		    {"gGrRoOuUpPhHeErReE",	0,	HL_SYNC_HERE},
4492 		    {"gGrRoOuUpPtThHeErReE",	0,	HL_SYNC_THERE},
4493 		    {"dDiIsSpPlLaAyY",		0,	HL_DISPLAY},
4494 		    {"fFoOlLdD",		0,	HL_FOLD},
4495 		    {"cCoOnNcCeEaAlL",		0,	HL_CONCEAL},
4496 		    {"cCoOnNcCeEaAlLeEnNdDsS",	0,	HL_CONCEALENDS},
4497 		    {"cCcChHaArR",		11,	0},
4498 		    {"cCoOnNtTaAiInNsS",	1,	0},
4499 		    {"cCoOnNtTaAiInNeEdDiInN",	2,	0},
4500 		    {"nNeExXtTgGrRoOuUpP",	3,	0},
4501 		};
4502     static char *first_letters = "cCoOkKeEtTsSgGdDfFnN";
4503 
4504     if (arg == NULL)		// already detected error
4505 	return NULL;
4506 
4507 #ifdef FEAT_CONCEAL
4508     if (curwin->w_s->b_syn_conceal)
4509 	opt->flags |= HL_CONCEAL;
4510 #endif
4511 
4512     for (;;)
4513     {
4514 	/*
4515 	 * This is used very often when a large number of keywords is defined.
4516 	 * Need to skip quickly when no option name is found.
4517 	 * Also avoid tolower(), it's slow.
4518 	 */
4519 	if (strchr(first_letters, *arg) == NULL)
4520 	    break;
4521 
4522 	for (fidx = sizeof(flagtab) / sizeof(struct flag); --fidx >= 0; )
4523 	{
4524 	    p = flagtab[fidx].name;
4525 	    for (i = 0, len = 0; p[i] != NUL; i += 2, ++len)
4526 		if (arg[len] != p[i] && arg[len] != p[i + 1])
4527 		    break;
4528 	    if (p[i] == NUL && (VIM_ISWHITE(arg[len])
4529 				    || (flagtab[fidx].argtype > 0
4530 					 ? arg[len] == '='
4531 					 : ends_excmd(arg[len]))))
4532 	    {
4533 		if (opt->keyword
4534 			&& (flagtab[fidx].flags == HL_DISPLAY
4535 			    || flagtab[fidx].flags == HL_FOLD
4536 			    || flagtab[fidx].flags == HL_EXTEND))
4537 		    // treat "display", "fold" and "extend" as a keyword
4538 		    fidx = -1;
4539 		break;
4540 	    }
4541 	}
4542 	if (fidx < 0)	    // no match found
4543 	    break;
4544 
4545 	if (flagtab[fidx].argtype == 1)
4546 	{
4547 	    if (!opt->has_cont_list)
4548 	    {
4549 		emsg(_("E395: contains argument not accepted here"));
4550 		return NULL;
4551 	    }
4552 	    if (get_id_list(&arg, 8, &opt->cont_list, skip) == FAIL)
4553 		return NULL;
4554 	}
4555 	else if (flagtab[fidx].argtype == 2)
4556 	{
4557 	    if (get_id_list(&arg, 11, &opt->cont_in_list, skip) == FAIL)
4558 		return NULL;
4559 	}
4560 	else if (flagtab[fidx].argtype == 3)
4561 	{
4562 	    if (get_id_list(&arg, 9, &opt->next_list, skip) == FAIL)
4563 		return NULL;
4564 	}
4565 	else if (flagtab[fidx].argtype == 11 && arg[5] == '=')
4566 	{
4567 	    // cchar=?
4568 	    if (has_mbyte)
4569 	    {
4570 #ifdef FEAT_CONCEAL
4571 		*conceal_char = mb_ptr2char(arg + 6);
4572 #endif
4573 		arg += mb_ptr2len(arg + 6) - 1;
4574 	    }
4575 	    else
4576 	    {
4577 #ifdef FEAT_CONCEAL
4578 		*conceal_char = arg[6];
4579 #else
4580 		;
4581 #endif
4582 	    }
4583 #ifdef FEAT_CONCEAL
4584 	    if (!vim_isprintc_strict(*conceal_char))
4585 	    {
4586 		emsg(_("E844: invalid cchar value"));
4587 		return NULL;
4588 	    }
4589 #endif
4590 	    arg = skipwhite(arg + 7);
4591 	}
4592 	else
4593 	{
4594 	    opt->flags |= flagtab[fidx].flags;
4595 	    arg = skipwhite(arg + len);
4596 
4597 	    if (flagtab[fidx].flags == HL_SYNC_HERE
4598 		    || flagtab[fidx].flags == HL_SYNC_THERE)
4599 	    {
4600 		if (opt->sync_idx == NULL)
4601 		{
4602 		    emsg(_("E393: group[t]here not accepted here"));
4603 		    return NULL;
4604 		}
4605 		gname_start = arg;
4606 		arg = skiptowhite(arg);
4607 		if (gname_start == arg)
4608 		    return NULL;
4609 		gname = vim_strnsave(gname_start, (int)(arg - gname_start));
4610 		if (gname == NULL)
4611 		    return NULL;
4612 		if (STRCMP(gname, "NONE") == 0)
4613 		    *opt->sync_idx = NONE_IDX;
4614 		else
4615 		{
4616 		    syn_id = syn_name2id(gname);
4617 		    for (i = curwin->w_s->b_syn_patterns.ga_len; --i >= 0; )
4618 			if (SYN_ITEMS(curwin->w_s)[i].sp_syn.id == syn_id
4619 			      && SYN_ITEMS(curwin->w_s)[i].sp_type == SPTYPE_START)
4620 			{
4621 			    *opt->sync_idx = i;
4622 			    break;
4623 			}
4624 		    if (i < 0)
4625 		    {
4626 			semsg(_("E394: Didn't find region item for %s"), gname);
4627 			vim_free(gname);
4628 			return NULL;
4629 		    }
4630 		}
4631 
4632 		vim_free(gname);
4633 		arg = skipwhite(arg);
4634 	    }
4635 #ifdef FEAT_FOLDING
4636 	    else if (flagtab[fidx].flags == HL_FOLD
4637 						&& foldmethodIsSyntax(curwin))
4638 		// Need to update folds later.
4639 		foldUpdateAll(curwin);
4640 #endif
4641 	}
4642     }
4643 
4644     return arg;
4645 }
4646 
4647 /*
4648  * Adjustments to syntax item when declared in a ":syn include"'d file.
4649  * Set the contained flag, and if the item is not already contained, add it
4650  * to the specified top-level group, if any.
4651  */
4652     static void
4653 syn_incl_toplevel(int id, int *flagsp)
4654 {
4655     if ((*flagsp & HL_CONTAINED) || curwin->w_s->b_syn_topgrp == 0)
4656 	return;
4657     *flagsp |= HL_CONTAINED;
4658     if (curwin->w_s->b_syn_topgrp >= SYNID_CLUSTER)
4659     {
4660 	// We have to alloc this, because syn_combine_list() will free it.
4661 	short	    *grp_list = ALLOC_MULT(short, 2);
4662 	int	    tlg_id = curwin->w_s->b_syn_topgrp - SYNID_CLUSTER;
4663 
4664 	if (grp_list != NULL)
4665 	{
4666 	    grp_list[0] = id;
4667 	    grp_list[1] = 0;
4668 	    syn_combine_list(&SYN_CLSTR(curwin->w_s)[tlg_id].scl_list,
4669 						       &grp_list, CLUSTER_ADD);
4670 	}
4671     }
4672 }
4673 
4674 /*
4675  * Handle ":syntax include [@{group-name}] filename" command.
4676  */
4677     static void
4678 syn_cmd_include(exarg_T *eap, int syncing UNUSED)
4679 {
4680     char_u	*arg = eap->arg;
4681     int		sgl_id = 1;
4682     char_u	*group_name_end;
4683     char_u	*rest;
4684     char	*errormsg = NULL;
4685     int		prev_toplvl_grp;
4686     int		prev_syn_inc_tag;
4687     int		source = FALSE;
4688 
4689     eap->nextcmd = find_nextcmd(arg);
4690     if (eap->skip)
4691 	return;
4692 
4693     if (arg[0] == '@')
4694     {
4695 	++arg;
4696 	rest = get_group_name(arg, &group_name_end);
4697 	if (rest == NULL)
4698 	{
4699 	    emsg(_("E397: Filename required"));
4700 	    return;
4701 	}
4702 	sgl_id = syn_check_cluster(arg, (int)(group_name_end - arg));
4703 	if (sgl_id == 0)
4704 	    return;
4705 	// separate_nextcmd() and expand_filename() depend on this
4706 	eap->arg = rest;
4707     }
4708 
4709     /*
4710      * Everything that's left, up to the next command, should be the
4711      * filename to include.
4712      */
4713     eap->argt |= (EX_XFILE | EX_NOSPC);
4714     separate_nextcmd(eap);
4715     if (*eap->arg == '<' || *eap->arg == '$' || mch_isFullName(eap->arg))
4716     {
4717 	// For an absolute path, "$VIM/..." or "<sfile>.." we ":source" the
4718 	// file.  Need to expand the file name first.  In other cases
4719 	// ":runtime!" is used.
4720 	source = TRUE;
4721 	if (expand_filename(eap, syn_cmdlinep, &errormsg) == FAIL)
4722 	{
4723 	    if (errormsg != NULL)
4724 		emsg(errormsg);
4725 	    return;
4726 	}
4727     }
4728 
4729     /*
4730      * Save and restore the existing top-level grouplist id and ":syn
4731      * include" tag around the actual inclusion.
4732      */
4733     if (running_syn_inc_tag >= MAX_SYN_INC_TAG)
4734     {
4735 	emsg(_("E847: Too many syntax includes"));
4736 	return;
4737     }
4738     prev_syn_inc_tag = current_syn_inc_tag;
4739     current_syn_inc_tag = ++running_syn_inc_tag;
4740     prev_toplvl_grp = curwin->w_s->b_syn_topgrp;
4741     curwin->w_s->b_syn_topgrp = sgl_id;
4742     if (source ? do_source(eap->arg, FALSE, DOSO_NONE, NULL) == FAIL
4743 				: source_runtime(eap->arg, DIP_ALL) == FAIL)
4744 	semsg(_(e_notopen), eap->arg);
4745     curwin->w_s->b_syn_topgrp = prev_toplvl_grp;
4746     current_syn_inc_tag = prev_syn_inc_tag;
4747 }
4748 
4749 /*
4750  * Handle ":syntax keyword {group-name} [{option}] keyword .." command.
4751  */
4752     static void
4753 syn_cmd_keyword(exarg_T *eap, int syncing UNUSED)
4754 {
4755     char_u	*arg = eap->arg;
4756     char_u	*group_name_end;
4757     int		syn_id;
4758     char_u	*rest;
4759     char_u	*keyword_copy = NULL;
4760     char_u	*p;
4761     char_u	*kw;
4762     syn_opt_arg_T syn_opt_arg;
4763     int		cnt;
4764     int		conceal_char = NUL;
4765 
4766     rest = get_group_name(arg, &group_name_end);
4767 
4768     if (rest != NULL)
4769     {
4770 	if (eap->skip)
4771 	    syn_id = -1;
4772 	else
4773 	    syn_id = syn_check_group(arg, (int)(group_name_end - arg));
4774 	if (syn_id != 0)
4775 	    // allocate a buffer, for removing backslashes in the keyword
4776 	    keyword_copy = alloc(STRLEN(rest) + 1);
4777 	if (keyword_copy != NULL)
4778 	{
4779 	    syn_opt_arg.flags = 0;
4780 	    syn_opt_arg.keyword = TRUE;
4781 	    syn_opt_arg.sync_idx = NULL;
4782 	    syn_opt_arg.has_cont_list = FALSE;
4783 	    syn_opt_arg.cont_in_list = NULL;
4784 	    syn_opt_arg.next_list = NULL;
4785 
4786 	    /*
4787 	     * The options given apply to ALL keywords, so all options must be
4788 	     * found before keywords can be created.
4789 	     * 1: collect the options and copy the keywords to keyword_copy.
4790 	     */
4791 	    cnt = 0;
4792 	    p = keyword_copy;
4793 	    for ( ; rest != NULL && !ends_excmd(*rest); rest = skipwhite(rest))
4794 	    {
4795 		rest = get_syn_options(rest, &syn_opt_arg, &conceal_char,
4796 								    eap->skip);
4797 		if (rest == NULL || ends_excmd(*rest))
4798 		    break;
4799 		// Copy the keyword, removing backslashes, and add a NUL.
4800 		while (*rest != NUL && !VIM_ISWHITE(*rest))
4801 		{
4802 		    if (*rest == '\\' && rest[1] != NUL)
4803 			++rest;
4804 		    *p++ = *rest++;
4805 		}
4806 		*p++ = NUL;
4807 		++cnt;
4808 	    }
4809 
4810 	    if (!eap->skip)
4811 	    {
4812 		// Adjust flags for use of ":syn include".
4813 		syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
4814 
4815 		/*
4816 		 * 2: Add an entry for each keyword.
4817 		 */
4818 		for (kw = keyword_copy; --cnt >= 0; kw += STRLEN(kw) + 1)
4819 		{
4820 		    for (p = vim_strchr(kw, '['); ; )
4821 		    {
4822 			if (p != NULL)
4823 			    *p = NUL;
4824 			add_keyword(kw, syn_id, syn_opt_arg.flags,
4825 				syn_opt_arg.cont_in_list,
4826 					 syn_opt_arg.next_list, conceal_char);
4827 			if (p == NULL)
4828 			    break;
4829 			if (p[1] == NUL)
4830 			{
4831 			    semsg(_("E789: Missing ']': %s"), kw);
4832 			    goto error;
4833 			}
4834 			if (p[1] == ']')
4835 			{
4836 			    if (p[2] != NUL)
4837 			    {
4838 				semsg(_("E890: trailing char after ']': %s]%s"),
4839 								kw, &p[2]);
4840 				goto error;
4841 			    }
4842 			    kw = p + 1;		// skip over the "]"
4843 			    break;
4844 			}
4845 			if (has_mbyte)
4846 			{
4847 			    int l = (*mb_ptr2len)(p + 1);
4848 
4849 			    mch_memmove(p, p + 1, l);
4850 			    p += l;
4851 			}
4852 			else
4853 			{
4854 			    p[0] = p[1];
4855 			    ++p;
4856 			}
4857 		    }
4858 		}
4859 	    }
4860 error:
4861 	    vim_free(keyword_copy);
4862 	    vim_free(syn_opt_arg.cont_in_list);
4863 	    vim_free(syn_opt_arg.next_list);
4864 	}
4865     }
4866 
4867     if (rest != NULL)
4868 	eap->nextcmd = check_nextcmd(rest);
4869     else
4870 	semsg(_(e_invarg2), arg);
4871 
4872     redraw_curbuf_later(SOME_VALID);
4873     syn_stack_free_all(curwin->w_s);		// Need to recompute all syntax.
4874 }
4875 
4876 /*
4877  * Handle ":syntax match {name} [{options}] {pattern} [{options}]".
4878  *
4879  * Also ":syntax sync match {name} [[grouphere | groupthere] {group-name}] .."
4880  */
4881     static void
4882 syn_cmd_match(
4883     exarg_T	*eap,
4884     int		syncing)	    // TRUE for ":syntax sync match .. "
4885 {
4886     char_u	*arg = eap->arg;
4887     char_u	*group_name_end;
4888     char_u	*rest;
4889     synpat_T	item;		// the item found in the line
4890     int		syn_id;
4891     int		idx;
4892     syn_opt_arg_T syn_opt_arg;
4893     int		sync_idx = 0;
4894     int		conceal_char = NUL;
4895 
4896     // Isolate the group name, check for validity
4897     rest = get_group_name(arg, &group_name_end);
4898 
4899     // Get options before the pattern
4900     syn_opt_arg.flags = 0;
4901     syn_opt_arg.keyword = FALSE;
4902     syn_opt_arg.sync_idx = syncing ? &sync_idx : NULL;
4903     syn_opt_arg.has_cont_list = TRUE;
4904     syn_opt_arg.cont_list = NULL;
4905     syn_opt_arg.cont_in_list = NULL;
4906     syn_opt_arg.next_list = NULL;
4907     rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
4908 
4909     // get the pattern.
4910     init_syn_patterns();
4911     vim_memset(&item, 0, sizeof(item));
4912     rest = get_syn_pattern(rest, &item);
4913     if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL))
4914 	syn_opt_arg.flags |= HL_HAS_EOL;
4915 
4916     // Get options after the pattern
4917     rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
4918 
4919     if (rest != NULL)		// all arguments are valid
4920     {
4921 	/*
4922 	 * Check for trailing command and illegal trailing arguments.
4923 	 */
4924 	eap->nextcmd = check_nextcmd(rest);
4925 	if (!ends_excmd(*rest) || eap->skip)
4926 	    rest = NULL;
4927 	else if (ga_grow(&curwin->w_s->b_syn_patterns, 1) != FAIL
4928 		&& (syn_id = syn_check_group(arg,
4929 					   (int)(group_name_end - arg))) != 0)
4930 	{
4931 	    syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
4932 	    /*
4933 	     * Store the pattern in the syn_items list
4934 	     */
4935 	    idx = curwin->w_s->b_syn_patterns.ga_len;
4936 	    SYN_ITEMS(curwin->w_s)[idx] = item;
4937 	    SYN_ITEMS(curwin->w_s)[idx].sp_syncing = syncing;
4938 	    SYN_ITEMS(curwin->w_s)[idx].sp_type = SPTYPE_MATCH;
4939 	    SYN_ITEMS(curwin->w_s)[idx].sp_syn.id = syn_id;
4940 	    SYN_ITEMS(curwin->w_s)[idx].sp_syn.inc_tag = current_syn_inc_tag;
4941 	    SYN_ITEMS(curwin->w_s)[idx].sp_flags = syn_opt_arg.flags;
4942 	    SYN_ITEMS(curwin->w_s)[idx].sp_sync_idx = sync_idx;
4943 	    SYN_ITEMS(curwin->w_s)[idx].sp_cont_list = syn_opt_arg.cont_list;
4944 	    SYN_ITEMS(curwin->w_s)[idx].sp_syn.cont_in_list =
4945 						     syn_opt_arg.cont_in_list;
4946 #ifdef FEAT_CONCEAL
4947 	    SYN_ITEMS(curwin->w_s)[idx].sp_cchar = conceal_char;
4948 #endif
4949 	    if (syn_opt_arg.cont_in_list != NULL)
4950 		curwin->w_s->b_syn_containedin = TRUE;
4951 	    SYN_ITEMS(curwin->w_s)[idx].sp_next_list = syn_opt_arg.next_list;
4952 	    ++curwin->w_s->b_syn_patterns.ga_len;
4953 
4954 	    // remember that we found a match for syncing on
4955 	    if (syn_opt_arg.flags & (HL_SYNC_HERE|HL_SYNC_THERE))
4956 		curwin->w_s->b_syn_sync_flags |= SF_MATCH;
4957 #ifdef FEAT_FOLDING
4958 	    if (syn_opt_arg.flags & HL_FOLD)
4959 		++curwin->w_s->b_syn_folditems;
4960 #endif
4961 
4962 	    redraw_curbuf_later(SOME_VALID);
4963 	    syn_stack_free_all(curwin->w_s);	// Need to recompute all syntax.
4964 	    return;	// don't free the progs and patterns now
4965 	}
4966     }
4967 
4968     /*
4969      * Something failed, free the allocated memory.
4970      */
4971     vim_regfree(item.sp_prog);
4972     vim_free(item.sp_pattern);
4973     vim_free(syn_opt_arg.cont_list);
4974     vim_free(syn_opt_arg.cont_in_list);
4975     vim_free(syn_opt_arg.next_list);
4976 
4977     if (rest == NULL)
4978 	semsg(_(e_invarg2), arg);
4979 }
4980 
4981 /*
4982  * Handle ":syntax region {group-name} [matchgroup={group-name}]
4983  *		start {start} .. [skip {skip}] end {end} .. [{options}]".
4984  */
4985     static void
4986 syn_cmd_region(
4987     exarg_T	*eap,
4988     int		syncing)	    // TRUE for ":syntax sync region .."
4989 {
4990     char_u		*arg = eap->arg;
4991     char_u		*group_name_end;
4992     char_u		*rest;			// next arg, NULL on error
4993     char_u		*key_end;
4994     char_u		*key = NULL;
4995     char_u		*p;
4996     int			item;
4997 #define ITEM_START	    0
4998 #define ITEM_SKIP	    1
4999 #define ITEM_END	    2
5000 #define ITEM_MATCHGROUP	    3
5001     struct pat_ptr
5002     {
5003 	synpat_T	*pp_synp;		// pointer to syn_pattern
5004 	int		pp_matchgroup_id;	// matchgroup ID
5005 	struct pat_ptr	*pp_next;		// pointer to next pat_ptr
5006     }			*(pat_ptrs[3]);
5007 					// patterns found in the line
5008     struct pat_ptr	*ppp;
5009     struct pat_ptr	*ppp_next;
5010     int			pat_count = 0;		// nr of syn_patterns found
5011     int			syn_id;
5012     int			matchgroup_id = 0;
5013     int			not_enough = FALSE;	// not enough arguments
5014     int			illegal = FALSE;	// illegal arguments
5015     int			success = FALSE;
5016     int			idx;
5017     syn_opt_arg_T	syn_opt_arg;
5018     int			conceal_char = NUL;
5019 
5020     // Isolate the group name, check for validity
5021     rest = get_group_name(arg, &group_name_end);
5022 
5023     pat_ptrs[0] = NULL;
5024     pat_ptrs[1] = NULL;
5025     pat_ptrs[2] = NULL;
5026 
5027     init_syn_patterns();
5028 
5029     syn_opt_arg.flags = 0;
5030     syn_opt_arg.keyword = FALSE;
5031     syn_opt_arg.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 
5037     /*
5038      * get the options, patterns and matchgroup.
5039      */
5040     while (rest != NULL && !ends_excmd(*rest))
5041     {
5042 	// Check for option arguments
5043 	rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
5044 	if (rest == NULL || ends_excmd(*rest))
5045 	    break;
5046 
5047 	// must be a pattern or matchgroup then
5048 	key_end = rest;
5049 	while (*key_end && !VIM_ISWHITE(*key_end) && *key_end != '=')
5050 	    ++key_end;
5051 	vim_free(key);
5052 	key = vim_strnsave_up(rest, (int)(key_end - rest));
5053 	if (key == NULL)			// out of memory
5054 	{
5055 	    rest = NULL;
5056 	    break;
5057 	}
5058 	if (STRCMP(key, "MATCHGROUP") == 0)
5059 	    item = ITEM_MATCHGROUP;
5060 	else if (STRCMP(key, "START") == 0)
5061 	    item = ITEM_START;
5062 	else if (STRCMP(key, "END") == 0)
5063 	    item = ITEM_END;
5064 	else if (STRCMP(key, "SKIP") == 0)
5065 	{
5066 	    if (pat_ptrs[ITEM_SKIP] != NULL)	// one skip pattern allowed
5067 	    {
5068 		illegal = TRUE;
5069 		break;
5070 	    }
5071 	    item = ITEM_SKIP;
5072 	}
5073 	else
5074 	    break;
5075 	rest = skipwhite(key_end);
5076 	if (*rest != '=')
5077 	{
5078 	    rest = NULL;
5079 	    semsg(_("E398: Missing '=': %s"), arg);
5080 	    break;
5081 	}
5082 	rest = skipwhite(rest + 1);
5083 	if (*rest == NUL)
5084 	{
5085 	    not_enough = TRUE;
5086 	    break;
5087 	}
5088 
5089 	if (item == ITEM_MATCHGROUP)
5090 	{
5091 	    p = skiptowhite(rest);
5092 	    if ((p - rest == 4 && STRNCMP(rest, "NONE", 4) == 0) || eap->skip)
5093 		matchgroup_id = 0;
5094 	    else
5095 	    {
5096 		matchgroup_id = syn_check_group(rest, (int)(p - rest));
5097 		if (matchgroup_id == 0)
5098 		{
5099 		    illegal = TRUE;
5100 		    break;
5101 		}
5102 	    }
5103 	    rest = skipwhite(p);
5104 	}
5105 	else
5106 	{
5107 	    /*
5108 	     * Allocate room for a syn_pattern, and link it in the list of
5109 	     * syn_patterns for this item, at the start (because the list is
5110 	     * used from end to start).
5111 	     */
5112 	    ppp = ALLOC_ONE(struct pat_ptr);
5113 	    if (ppp == NULL)
5114 	    {
5115 		rest = NULL;
5116 		break;
5117 	    }
5118 	    ppp->pp_next = pat_ptrs[item];
5119 	    pat_ptrs[item] = ppp;
5120 	    ppp->pp_synp = ALLOC_CLEAR_ONE(synpat_T);
5121 	    if (ppp->pp_synp == NULL)
5122 	    {
5123 		rest = NULL;
5124 		break;
5125 	    }
5126 
5127 	    /*
5128 	     * Get the syntax pattern and the following offset(s).
5129 	     */
5130 	    // Enable the appropriate \z specials.
5131 	    if (item == ITEM_START)
5132 		reg_do_extmatch = REX_SET;
5133 	    else if (item == ITEM_SKIP || item == ITEM_END)
5134 		reg_do_extmatch = REX_USE;
5135 	    rest = get_syn_pattern(rest, ppp->pp_synp);
5136 	    reg_do_extmatch = 0;
5137 	    if (item == ITEM_END && vim_regcomp_had_eol()
5138 				       && !(syn_opt_arg.flags & HL_EXCLUDENL))
5139 		ppp->pp_synp->sp_flags |= HL_HAS_EOL;
5140 	    ppp->pp_matchgroup_id = matchgroup_id;
5141 	    ++pat_count;
5142 	}
5143     }
5144     vim_free(key);
5145     if (illegal || not_enough)
5146 	rest = NULL;
5147 
5148     /*
5149      * Must have a "start" and "end" pattern.
5150      */
5151     if (rest != NULL && (pat_ptrs[ITEM_START] == NULL ||
5152 						  pat_ptrs[ITEM_END] == NULL))
5153     {
5154 	not_enough = TRUE;
5155 	rest = NULL;
5156     }
5157 
5158     if (rest != NULL)
5159     {
5160 	/*
5161 	 * Check for trailing garbage or command.
5162 	 * If OK, add the item.
5163 	 */
5164 	eap->nextcmd = check_nextcmd(rest);
5165 	if (!ends_excmd(*rest) || eap->skip)
5166 	    rest = NULL;
5167 	else if (ga_grow(&(curwin->w_s->b_syn_patterns), pat_count) != FAIL
5168 		&& (syn_id = syn_check_group(arg,
5169 					   (int)(group_name_end - arg))) != 0)
5170 	{
5171 	    syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
5172 	    /*
5173 	     * Store the start/skip/end in the syn_items list
5174 	     */
5175 	    idx = curwin->w_s->b_syn_patterns.ga_len;
5176 	    for (item = ITEM_START; item <= ITEM_END; ++item)
5177 	    {
5178 		for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp->pp_next)
5179 		{
5180 		    SYN_ITEMS(curwin->w_s)[idx] = *(ppp->pp_synp);
5181 		    SYN_ITEMS(curwin->w_s)[idx].sp_syncing = syncing;
5182 		    SYN_ITEMS(curwin->w_s)[idx].sp_type =
5183 			    (item == ITEM_START) ? SPTYPE_START :
5184 			    (item == ITEM_SKIP) ? SPTYPE_SKIP : SPTYPE_END;
5185 		    SYN_ITEMS(curwin->w_s)[idx].sp_flags |= syn_opt_arg.flags;
5186 		    SYN_ITEMS(curwin->w_s)[idx].sp_syn.id = syn_id;
5187 		    SYN_ITEMS(curwin->w_s)[idx].sp_syn.inc_tag =
5188 							  current_syn_inc_tag;
5189 		    SYN_ITEMS(curwin->w_s)[idx].sp_syn_match_id =
5190 							ppp->pp_matchgroup_id;
5191 #ifdef FEAT_CONCEAL
5192 		    SYN_ITEMS(curwin->w_s)[idx].sp_cchar = conceal_char;
5193 #endif
5194 		    if (item == ITEM_START)
5195 		    {
5196 			SYN_ITEMS(curwin->w_s)[idx].sp_cont_list =
5197 							syn_opt_arg.cont_list;
5198 			SYN_ITEMS(curwin->w_s)[idx].sp_syn.cont_in_list =
5199 						     syn_opt_arg.cont_in_list;
5200 			if (syn_opt_arg.cont_in_list != NULL)
5201 			    curwin->w_s->b_syn_containedin = TRUE;
5202 			SYN_ITEMS(curwin->w_s)[idx].sp_next_list =
5203 							syn_opt_arg.next_list;
5204 		    }
5205 		    ++curwin->w_s->b_syn_patterns.ga_len;
5206 		    ++idx;
5207 #ifdef FEAT_FOLDING
5208 		    if (syn_opt_arg.flags & HL_FOLD)
5209 			++curwin->w_s->b_syn_folditems;
5210 #endif
5211 		}
5212 	    }
5213 
5214 	    redraw_curbuf_later(SOME_VALID);
5215 	    syn_stack_free_all(curwin->w_s);	// Need to recompute all syntax.
5216 	    success = TRUE;	    // don't free the progs and patterns now
5217 	}
5218     }
5219 
5220     /*
5221      * Free the allocated memory.
5222      */
5223     for (item = ITEM_START; item <= ITEM_END; ++item)
5224 	for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp_next)
5225 	{
5226 	    if (!success && ppp->pp_synp != NULL)
5227 	    {
5228 		vim_regfree(ppp->pp_synp->sp_prog);
5229 		vim_free(ppp->pp_synp->sp_pattern);
5230 	    }
5231 	    vim_free(ppp->pp_synp);
5232 	    ppp_next = ppp->pp_next;
5233 	    vim_free(ppp);
5234 	}
5235 
5236     if (!success)
5237     {
5238 	vim_free(syn_opt_arg.cont_list);
5239 	vim_free(syn_opt_arg.cont_in_list);
5240 	vim_free(syn_opt_arg.next_list);
5241 	if (not_enough)
5242 	    semsg(_("E399: Not enough arguments: syntax region %s"), arg);
5243 	else if (illegal || rest == NULL)
5244 	    semsg(_(e_invarg2), arg);
5245     }
5246 }
5247 
5248 /*
5249  * A simple syntax group ID comparison function suitable for use in qsort()
5250  */
5251     static int
5252 syn_compare_stub(const void *v1, const void *v2)
5253 {
5254     const short	*s1 = v1;
5255     const short	*s2 = v2;
5256 
5257     return (*s1 > *s2 ? 1 : *s1 < *s2 ? -1 : 0);
5258 }
5259 
5260 /*
5261  * Combines lists of syntax clusters.
5262  * *clstr1 and *clstr2 must both be allocated memory; they will be consumed.
5263  */
5264     static void
5265 syn_combine_list(short **clstr1, short **clstr2, int list_op)
5266 {
5267     int		count1 = 0;
5268     int		count2 = 0;
5269     short	*g1;
5270     short	*g2;
5271     short	*clstr = NULL;
5272     int		count;
5273     int		round;
5274 
5275     /*
5276      * Handle degenerate cases.
5277      */
5278     if (*clstr2 == NULL)
5279 	return;
5280     if (*clstr1 == NULL || list_op == CLUSTER_REPLACE)
5281     {
5282 	if (list_op == CLUSTER_REPLACE)
5283 	    vim_free(*clstr1);
5284 	if (list_op == CLUSTER_REPLACE || list_op == CLUSTER_ADD)
5285 	    *clstr1 = *clstr2;
5286 	else
5287 	    vim_free(*clstr2);
5288 	return;
5289     }
5290 
5291     for (g1 = *clstr1; *g1; g1++)
5292 	++count1;
5293     for (g2 = *clstr2; *g2; g2++)
5294 	++count2;
5295 
5296     /*
5297      * For speed purposes, sort both lists.
5298      */
5299     qsort(*clstr1, (size_t)count1, sizeof(short), syn_compare_stub);
5300     qsort(*clstr2, (size_t)count2, sizeof(short), syn_compare_stub);
5301 
5302     /*
5303      * We proceed in two passes; in round 1, we count the elements to place
5304      * in the new list, and in round 2, we allocate and populate the new
5305      * list.  For speed, we use a mergesort-like method, adding the smaller
5306      * of the current elements in each list to the new list.
5307      */
5308     for (round = 1; round <= 2; round++)
5309     {
5310 	g1 = *clstr1;
5311 	g2 = *clstr2;
5312 	count = 0;
5313 
5314 	/*
5315 	 * First, loop through the lists until one of them is empty.
5316 	 */
5317 	while (*g1 && *g2)
5318 	{
5319 	    /*
5320 	     * We always want to add from the first list.
5321 	     */
5322 	    if (*g1 < *g2)
5323 	    {
5324 		if (round == 2)
5325 		    clstr[count] = *g1;
5326 		count++;
5327 		g1++;
5328 		continue;
5329 	    }
5330 	    /*
5331 	     * We only want to add from the second list if we're adding the
5332 	     * lists.
5333 	     */
5334 	    if (list_op == CLUSTER_ADD)
5335 	    {
5336 		if (round == 2)
5337 		    clstr[count] = *g2;
5338 		count++;
5339 	    }
5340 	    if (*g1 == *g2)
5341 		g1++;
5342 	    g2++;
5343 	}
5344 
5345 	/*
5346 	 * Now add the leftovers from whichever list didn't get finished
5347 	 * first.  As before, we only want to add from the second list if
5348 	 * we're adding the lists.
5349 	 */
5350 	for (; *g1; g1++, count++)
5351 	    if (round == 2)
5352 		clstr[count] = *g1;
5353 	if (list_op == CLUSTER_ADD)
5354 	    for (; *g2; g2++, count++)
5355 		if (round == 2)
5356 		    clstr[count] = *g2;
5357 
5358 	if (round == 1)
5359 	{
5360 	    /*
5361 	     * If the group ended up empty, we don't need to allocate any
5362 	     * space for it.
5363 	     */
5364 	    if (count == 0)
5365 	    {
5366 		clstr = NULL;
5367 		break;
5368 	    }
5369 	    clstr = ALLOC_MULT(short, count + 1);
5370 	    if (clstr == NULL)
5371 		break;
5372 	    clstr[count] = 0;
5373 	}
5374     }
5375 
5376     /*
5377      * Finally, put the new list in place.
5378      */
5379     vim_free(*clstr1);
5380     vim_free(*clstr2);
5381     *clstr1 = clstr;
5382 }
5383 
5384 /*
5385  * Lookup a syntax cluster name and return its ID.
5386  * If it is not found, 0 is returned.
5387  */
5388     static int
5389 syn_scl_name2id(char_u *name)
5390 {
5391     int		i;
5392     char_u	*name_u;
5393 
5394     // Avoid using stricmp() too much, it's slow on some systems
5395     name_u = vim_strsave_up(name);
5396     if (name_u == NULL)
5397 	return 0;
5398     for (i = curwin->w_s->b_syn_clusters.ga_len; --i >= 0; )
5399 	if (SYN_CLSTR(curwin->w_s)[i].scl_name_u != NULL
5400 		&& STRCMP(name_u, SYN_CLSTR(curwin->w_s)[i].scl_name_u) == 0)
5401 	    break;
5402     vim_free(name_u);
5403     return (i < 0 ? 0 : i + SYNID_CLUSTER);
5404 }
5405 
5406 /*
5407  * Like syn_scl_name2id(), but take a pointer + length argument.
5408  */
5409     static int
5410 syn_scl_namen2id(char_u *linep, int len)
5411 {
5412     char_u  *name;
5413     int	    id = 0;
5414 
5415     name = vim_strnsave(linep, len);
5416     if (name != NULL)
5417     {
5418 	id = syn_scl_name2id(name);
5419 	vim_free(name);
5420     }
5421     return id;
5422 }
5423 
5424 /*
5425  * Find syntax cluster name in the table and return its ID.
5426  * The argument is a pointer to the name and the length of the name.
5427  * If it doesn't exist yet, a new entry is created.
5428  * Return 0 for failure.
5429  */
5430     static int
5431 syn_check_cluster(char_u *pp, int len)
5432 {
5433     int		id;
5434     char_u	*name;
5435 
5436     name = vim_strnsave(pp, len);
5437     if (name == NULL)
5438 	return 0;
5439 
5440     id = syn_scl_name2id(name);
5441     if (id == 0)			// doesn't exist yet
5442 	id = syn_add_cluster(name);
5443     else
5444 	vim_free(name);
5445     return id;
5446 }
5447 
5448 /*
5449  * Add new syntax cluster and return its ID.
5450  * "name" must be an allocated string, it will be consumed.
5451  * Return 0 for failure.
5452  */
5453     static int
5454 syn_add_cluster(char_u *name)
5455 {
5456     int		len;
5457 
5458     /*
5459      * First call for this growarray: init growing array.
5460      */
5461     if (curwin->w_s->b_syn_clusters.ga_data == NULL)
5462     {
5463 	curwin->w_s->b_syn_clusters.ga_itemsize = sizeof(syn_cluster_T);
5464 	curwin->w_s->b_syn_clusters.ga_growsize = 10;
5465     }
5466 
5467     len = curwin->w_s->b_syn_clusters.ga_len;
5468     if (len >= MAX_CLUSTER_ID)
5469     {
5470 	emsg(_("E848: Too many syntax clusters"));
5471 	vim_free(name);
5472 	return 0;
5473     }
5474 
5475     /*
5476      * Make room for at least one other cluster entry.
5477      */
5478     if (ga_grow(&curwin->w_s->b_syn_clusters, 1) == FAIL)
5479     {
5480 	vim_free(name);
5481 	return 0;
5482     }
5483 
5484     vim_memset(&(SYN_CLSTR(curwin->w_s)[len]), 0, sizeof(syn_cluster_T));
5485     SYN_CLSTR(curwin->w_s)[len].scl_name = name;
5486     SYN_CLSTR(curwin->w_s)[len].scl_name_u = vim_strsave_up(name);
5487     SYN_CLSTR(curwin->w_s)[len].scl_list = NULL;
5488     ++curwin->w_s->b_syn_clusters.ga_len;
5489 
5490     if (STRICMP(name, "Spell") == 0)
5491 	curwin->w_s->b_spell_cluster_id = len + SYNID_CLUSTER;
5492     if (STRICMP(name, "NoSpell") == 0)
5493 	curwin->w_s->b_nospell_cluster_id = len + SYNID_CLUSTER;
5494 
5495     return len + SYNID_CLUSTER;
5496 }
5497 
5498 /*
5499  * Handle ":syntax cluster {cluster-name} [contains={groupname},..]
5500  *		[add={groupname},..] [remove={groupname},..]".
5501  */
5502     static void
5503 syn_cmd_cluster(exarg_T *eap, int syncing UNUSED)
5504 {
5505     char_u	*arg = eap->arg;
5506     char_u	*group_name_end;
5507     char_u	*rest;
5508     int		scl_id;
5509     short	*clstr_list;
5510     int		got_clstr = FALSE;
5511     int		opt_len;
5512     int		list_op;
5513 
5514     eap->nextcmd = find_nextcmd(arg);
5515     if (eap->skip)
5516 	return;
5517 
5518     rest = get_group_name(arg, &group_name_end);
5519 
5520     if (rest != NULL)
5521     {
5522 	scl_id = syn_check_cluster(arg, (int)(group_name_end - arg));
5523 	if (scl_id == 0)
5524 	    return;
5525 	scl_id -= SYNID_CLUSTER;
5526 
5527 	for (;;)
5528 	{
5529 	    if (STRNICMP(rest, "add", 3) == 0
5530 		    && (VIM_ISWHITE(rest[3]) || rest[3] == '='))
5531 	    {
5532 		opt_len = 3;
5533 		list_op = CLUSTER_ADD;
5534 	    }
5535 	    else if (STRNICMP(rest, "remove", 6) == 0
5536 		    && (VIM_ISWHITE(rest[6]) || rest[6] == '='))
5537 	    {
5538 		opt_len = 6;
5539 		list_op = CLUSTER_SUBTRACT;
5540 	    }
5541 	    else if (STRNICMP(rest, "contains", 8) == 0
5542 			&& (VIM_ISWHITE(rest[8]) || rest[8] == '='))
5543 	    {
5544 		opt_len = 8;
5545 		list_op = CLUSTER_REPLACE;
5546 	    }
5547 	    else
5548 		break;
5549 
5550 	    clstr_list = NULL;
5551 	    if (get_id_list(&rest, opt_len, &clstr_list, eap->skip) == FAIL)
5552 	    {
5553 		semsg(_(e_invarg2), rest);
5554 		break;
5555 	    }
5556 	    if (scl_id >= 0)
5557 		syn_combine_list(&SYN_CLSTR(curwin->w_s)[scl_id].scl_list,
5558 			     &clstr_list, list_op);
5559 	    else
5560 		vim_free(clstr_list);
5561 	    got_clstr = TRUE;
5562 	}
5563 
5564 	if (got_clstr)
5565 	{
5566 	    redraw_curbuf_later(SOME_VALID);
5567 	    syn_stack_free_all(curwin->w_s);	// Need to recompute all.
5568 	}
5569     }
5570 
5571     if (!got_clstr)
5572 	emsg(_("E400: No cluster specified"));
5573     if (rest == NULL || !ends_excmd(*rest))
5574 	semsg(_(e_invarg2), arg);
5575 }
5576 
5577 /*
5578  * On first call for current buffer: Init growing array.
5579  */
5580     static void
5581 init_syn_patterns(void)
5582 {
5583     curwin->w_s->b_syn_patterns.ga_itemsize = sizeof(synpat_T);
5584     curwin->w_s->b_syn_patterns.ga_growsize = 10;
5585 }
5586 
5587 /*
5588  * Get one pattern for a ":syntax match" or ":syntax region" command.
5589  * Stores the pattern and program in a synpat_T.
5590  * Returns a pointer to the next argument, or NULL in case of an error.
5591  */
5592     static char_u *
5593 get_syn_pattern(char_u *arg, synpat_T *ci)
5594 {
5595     char_u	*end;
5596     int		*p;
5597     int		idx;
5598     char_u	*cpo_save;
5599 
5600     // need at least three chars
5601     if (arg == NULL || arg[0] == NUL || arg[1] == NUL || arg[2] == NUL)
5602 	return NULL;
5603 
5604     end = skip_regexp(arg + 1, *arg, TRUE);
5605     if (*end != *arg)			    // end delimiter not found
5606     {
5607 	semsg(_("E401: Pattern delimiter not found: %s"), arg);
5608 	return NULL;
5609     }
5610     // store the pattern and compiled regexp program
5611     if ((ci->sp_pattern = vim_strnsave(arg + 1, (int)(end - arg - 1))) == NULL)
5612 	return NULL;
5613 
5614     // Make 'cpoptions' empty, to avoid the 'l' flag
5615     cpo_save = p_cpo;
5616     p_cpo = (char_u *)"";
5617     ci->sp_prog = vim_regcomp(ci->sp_pattern, RE_MAGIC);
5618     p_cpo = cpo_save;
5619 
5620     if (ci->sp_prog == NULL)
5621 	return NULL;
5622     ci->sp_ic = curwin->w_s->b_syn_ic;
5623 #ifdef FEAT_PROFILE
5624     syn_clear_time(&ci->sp_time);
5625 #endif
5626 
5627     /*
5628      * Check for a match, highlight or region offset.
5629      */
5630     ++end;
5631     do
5632     {
5633 	for (idx = SPO_COUNT; --idx >= 0; )
5634 	    if (STRNCMP(end, spo_name_tab[idx], 3) == 0)
5635 		break;
5636 	if (idx >= 0)
5637 	{
5638 	    p = &(ci->sp_offsets[idx]);
5639 	    if (idx != SPO_LC_OFF)
5640 		switch (end[3])
5641 		{
5642 		    case 's':   break;
5643 		    case 'b':   break;
5644 		    case 'e':   idx += SPO_COUNT; break;
5645 		    default:    idx = -1; break;
5646 		}
5647 	    if (idx >= 0)
5648 	    {
5649 		ci->sp_off_flags |= (1 << idx);
5650 		if (idx == SPO_LC_OFF)	    // lc=99
5651 		{
5652 		    end += 3;
5653 		    *p = getdigits(&end);
5654 
5655 		    // "lc=" offset automatically sets "ms=" offset
5656 		    if (!(ci->sp_off_flags & (1 << SPO_MS_OFF)))
5657 		    {
5658 			ci->sp_off_flags |= (1 << SPO_MS_OFF);
5659 			ci->sp_offsets[SPO_MS_OFF] = *p;
5660 		    }
5661 		}
5662 		else			    // yy=x+99
5663 		{
5664 		    end += 4;
5665 		    if (*end == '+')
5666 		    {
5667 			++end;
5668 			*p = getdigits(&end);		// positive offset
5669 		    }
5670 		    else if (*end == '-')
5671 		    {
5672 			++end;
5673 			*p = -getdigits(&end);		// negative offset
5674 		    }
5675 		}
5676 		if (*end != ',')
5677 		    break;
5678 		++end;
5679 	    }
5680 	}
5681     } while (idx >= 0);
5682 
5683     if (!ends_excmd(*end) && !VIM_ISWHITE(*end))
5684     {
5685 	semsg(_("E402: Garbage after pattern: %s"), arg);
5686 	return NULL;
5687     }
5688     return skipwhite(end);
5689 }
5690 
5691 /*
5692  * Handle ":syntax sync .." command.
5693  */
5694     static void
5695 syn_cmd_sync(exarg_T *eap, int syncing UNUSED)
5696 {
5697     char_u	*arg_start = eap->arg;
5698     char_u	*arg_end;
5699     char_u	*key = NULL;
5700     char_u	*next_arg;
5701     int		illegal = FALSE;
5702     int		finished = FALSE;
5703     long	n;
5704     char_u	*cpo_save;
5705 
5706     if (ends_excmd(*arg_start))
5707     {
5708 	syn_cmd_list(eap, TRUE);
5709 	return;
5710     }
5711 
5712     while (!ends_excmd(*arg_start))
5713     {
5714 	arg_end = skiptowhite(arg_start);
5715 	next_arg = skipwhite(arg_end);
5716 	vim_free(key);
5717 	key = vim_strnsave_up(arg_start, (int)(arg_end - arg_start));
5718 	if (STRCMP(key, "CCOMMENT") == 0)
5719 	{
5720 	    if (!eap->skip)
5721 		curwin->w_s->b_syn_sync_flags |= SF_CCOMMENT;
5722 	    if (!ends_excmd(*next_arg))
5723 	    {
5724 		arg_end = skiptowhite(next_arg);
5725 		if (!eap->skip)
5726 		    curwin->w_s->b_syn_sync_id = syn_check_group(next_arg,
5727 						   (int)(arg_end - next_arg));
5728 		next_arg = skipwhite(arg_end);
5729 	    }
5730 	    else if (!eap->skip)
5731 		curwin->w_s->b_syn_sync_id = syn_name2id((char_u *)"Comment");
5732 	}
5733 	else if (  STRNCMP(key, "LINES", 5) == 0
5734 		|| STRNCMP(key, "MINLINES", 8) == 0
5735 		|| STRNCMP(key, "MAXLINES", 8) == 0
5736 		|| STRNCMP(key, "LINEBREAKS", 10) == 0)
5737 	{
5738 	    if (key[4] == 'S')
5739 		arg_end = key + 6;
5740 	    else if (key[0] == 'L')
5741 		arg_end = key + 11;
5742 	    else
5743 		arg_end = key + 9;
5744 	    if (arg_end[-1] != '=' || !VIM_ISDIGIT(*arg_end))
5745 	    {
5746 		illegal = TRUE;
5747 		break;
5748 	    }
5749 	    n = getdigits(&arg_end);
5750 	    if (!eap->skip)
5751 	    {
5752 		if (key[4] == 'B')
5753 		    curwin->w_s->b_syn_sync_linebreaks = n;
5754 		else if (key[1] == 'A')
5755 		    curwin->w_s->b_syn_sync_maxlines = n;
5756 		else
5757 		    curwin->w_s->b_syn_sync_minlines = n;
5758 	    }
5759 	}
5760 	else if (STRCMP(key, "FROMSTART") == 0)
5761 	{
5762 	    if (!eap->skip)
5763 	    {
5764 		curwin->w_s->b_syn_sync_minlines = MAXLNUM;
5765 		curwin->w_s->b_syn_sync_maxlines = 0;
5766 	    }
5767 	}
5768 	else if (STRCMP(key, "LINECONT") == 0)
5769 	{
5770 	    if (*next_arg == NUL)	   // missing pattern
5771 	    {
5772 		illegal = TRUE;
5773 		break;
5774 	    }
5775 	    if (curwin->w_s->b_syn_linecont_pat != NULL)
5776 	    {
5777 		emsg(_("E403: syntax sync: line continuations pattern specified twice"));
5778 		finished = TRUE;
5779 		break;
5780 	    }
5781 	    arg_end = skip_regexp(next_arg + 1, *next_arg, TRUE);
5782 	    if (*arg_end != *next_arg)	    // end delimiter not found
5783 	    {
5784 		illegal = TRUE;
5785 		break;
5786 	    }
5787 
5788 	    if (!eap->skip)
5789 	    {
5790 		// store the pattern and compiled regexp program
5791 		if ((curwin->w_s->b_syn_linecont_pat = vim_strnsave(next_arg + 1,
5792 				      (int)(arg_end - next_arg - 1))) == NULL)
5793 		{
5794 		    finished = TRUE;
5795 		    break;
5796 		}
5797 		curwin->w_s->b_syn_linecont_ic = curwin->w_s->b_syn_ic;
5798 
5799 		// Make 'cpoptions' empty, to avoid the 'l' flag
5800 		cpo_save = p_cpo;
5801 		p_cpo = (char_u *)"";
5802 		curwin->w_s->b_syn_linecont_prog =
5803 		       vim_regcomp(curwin->w_s->b_syn_linecont_pat, RE_MAGIC);
5804 		p_cpo = cpo_save;
5805 #ifdef FEAT_PROFILE
5806 		syn_clear_time(&curwin->w_s->b_syn_linecont_time);
5807 #endif
5808 
5809 		if (curwin->w_s->b_syn_linecont_prog == NULL)
5810 		{
5811 		    VIM_CLEAR(curwin->w_s->b_syn_linecont_pat);
5812 		    finished = TRUE;
5813 		    break;
5814 		}
5815 	    }
5816 	    next_arg = skipwhite(arg_end + 1);
5817 	}
5818 	else
5819 	{
5820 	    eap->arg = next_arg;
5821 	    if (STRCMP(key, "MATCH") == 0)
5822 		syn_cmd_match(eap, TRUE);
5823 	    else if (STRCMP(key, "REGION") == 0)
5824 		syn_cmd_region(eap, TRUE);
5825 	    else if (STRCMP(key, "CLEAR") == 0)
5826 		syn_cmd_clear(eap, TRUE);
5827 	    else
5828 		illegal = TRUE;
5829 	    finished = TRUE;
5830 	    break;
5831 	}
5832 	arg_start = next_arg;
5833     }
5834     vim_free(key);
5835     if (illegal)
5836 	semsg(_("E404: Illegal arguments: %s"), arg_start);
5837     else if (!finished)
5838     {
5839 	eap->nextcmd = check_nextcmd(arg_start);
5840 	redraw_curbuf_later(SOME_VALID);
5841 	syn_stack_free_all(curwin->w_s);	// Need to recompute all syntax.
5842     }
5843 }
5844 
5845 /*
5846  * Convert a line of highlight group names into a list of group ID numbers.
5847  * "arg" should point to the "contains" or "nextgroup" keyword.
5848  * "arg" is advanced to after the last group name.
5849  * Careful: the argument is modified (NULs added).
5850  * returns FAIL for some error, OK for success.
5851  */
5852     static int
5853 get_id_list(
5854     char_u	**arg,
5855     int		keylen,		// length of keyword
5856     short	**list,		// where to store the resulting list, if not
5857 				// NULL, the list is silently skipped!
5858     int		skip)
5859 {
5860     char_u	*p = NULL;
5861     char_u	*end;
5862     int		round;
5863     int		count;
5864     int		total_count = 0;
5865     short	*retval = NULL;
5866     char_u	*name;
5867     regmatch_T	regmatch;
5868     int		id;
5869     int		i;
5870     int		failed = FALSE;
5871 
5872     /*
5873      * We parse the list twice:
5874      * round == 1: count the number of items, allocate the array.
5875      * round == 2: fill the array with the items.
5876      * In round 1 new groups may be added, causing the number of items to
5877      * grow when a regexp is used.  In that case round 1 is done once again.
5878      */
5879     for (round = 1; round <= 2; ++round)
5880     {
5881 	/*
5882 	 * skip "contains"
5883 	 */
5884 	p = skipwhite(*arg + keylen);
5885 	if (*p != '=')
5886 	{
5887 	    semsg(_("E405: Missing equal sign: %s"), *arg);
5888 	    break;
5889 	}
5890 	p = skipwhite(p + 1);
5891 	if (ends_excmd(*p))
5892 	{
5893 	    semsg(_("E406: Empty argument: %s"), *arg);
5894 	    break;
5895 	}
5896 
5897 	/*
5898 	 * parse the arguments after "contains"
5899 	 */
5900 	count = 0;
5901 	while (!ends_excmd(*p))
5902 	{
5903 	    for (end = p; *end && !VIM_ISWHITE(*end) && *end != ','; ++end)
5904 		;
5905 	    name = alloc(end - p + 3);	    // leave room for "^$"
5906 	    if (name == NULL)
5907 	    {
5908 		failed = TRUE;
5909 		break;
5910 	    }
5911 	    vim_strncpy(name + 1, p, end - p);
5912 	    if (       STRCMP(name + 1, "ALLBUT") == 0
5913 		    || STRCMP(name + 1, "ALL") == 0
5914 		    || STRCMP(name + 1, "TOP") == 0
5915 		    || STRCMP(name + 1, "CONTAINED") == 0)
5916 	    {
5917 		if (TOUPPER_ASC(**arg) != 'C')
5918 		{
5919 		    semsg(_("E407: %s not allowed here"), name + 1);
5920 		    failed = TRUE;
5921 		    vim_free(name);
5922 		    break;
5923 		}
5924 		if (count != 0)
5925 		{
5926 		    semsg(_("E408: %s must be first in contains list"),
5927 								     name + 1);
5928 		    failed = TRUE;
5929 		    vim_free(name);
5930 		    break;
5931 		}
5932 		if (name[1] == 'A')
5933 		    id = SYNID_ALLBUT;
5934 		else if (name[1] == 'T')
5935 		    id = SYNID_TOP;
5936 		else
5937 		    id = SYNID_CONTAINED;
5938 		id += current_syn_inc_tag;
5939 	    }
5940 	    else if (name[1] == '@')
5941 	    {
5942 		if (skip)
5943 		    id = -1;
5944 		else
5945 		    id = syn_check_cluster(name + 2, (int)(end - p - 1));
5946 	    }
5947 	    else
5948 	    {
5949 		/*
5950 		 * Handle full group name.
5951 		 */
5952 		if (vim_strpbrk(name + 1, (char_u *)"\\.*^$~[") == NULL)
5953 		    id = syn_check_group(name + 1, (int)(end - p));
5954 		else
5955 		{
5956 		    /*
5957 		     * Handle match of regexp with group names.
5958 		     */
5959 		    *name = '^';
5960 		    STRCAT(name, "$");
5961 		    regmatch.regprog = vim_regcomp(name, RE_MAGIC);
5962 		    if (regmatch.regprog == NULL)
5963 		    {
5964 			failed = TRUE;
5965 			vim_free(name);
5966 			break;
5967 		    }
5968 
5969 		    regmatch.rm_ic = TRUE;
5970 		    id = 0;
5971 		    for (i = highlight_num_groups(); --i >= 0; )
5972 		    {
5973 			if (vim_regexec(&regmatch, highlight_group_name(i),
5974 								  (colnr_T)0))
5975 			{
5976 			    if (round == 2)
5977 			    {
5978 				// Got more items than expected; can happen
5979 				// when adding items that match:
5980 				// "contains=a.*b,axb".
5981 				// Go back to first round
5982 				if (count >= total_count)
5983 				{
5984 				    vim_free(retval);
5985 				    round = 1;
5986 				}
5987 				else
5988 				    retval[count] = i + 1;
5989 			    }
5990 			    ++count;
5991 			    id = -1;	    // remember that we found one
5992 			}
5993 		    }
5994 		    vim_regfree(regmatch.regprog);
5995 		}
5996 	    }
5997 	    vim_free(name);
5998 	    if (id == 0)
5999 	    {
6000 		semsg(_("E409: Unknown group name: %s"), p);
6001 		failed = TRUE;
6002 		break;
6003 	    }
6004 	    if (id > 0)
6005 	    {
6006 		if (round == 2)
6007 		{
6008 		    // Got more items than expected, go back to first round
6009 		    if (count >= total_count)
6010 		    {
6011 			vim_free(retval);
6012 			round = 1;
6013 		    }
6014 		    else
6015 			retval[count] = id;
6016 		}
6017 		++count;
6018 	    }
6019 	    p = skipwhite(end);
6020 	    if (*p != ',')
6021 		break;
6022 	    p = skipwhite(p + 1);	// skip comma in between arguments
6023 	}
6024 	if (failed)
6025 	    break;
6026 	if (round == 1)
6027 	{
6028 	    retval = ALLOC_MULT(short, count + 1);
6029 	    if (retval == NULL)
6030 		break;
6031 	    retval[count] = 0;	    // zero means end of the list
6032 	    total_count = count;
6033 	}
6034     }
6035 
6036     *arg = p;
6037     if (failed || retval == NULL)
6038     {
6039 	vim_free(retval);
6040 	return FAIL;
6041     }
6042 
6043     if (*list == NULL)
6044 	*list = retval;
6045     else
6046 	vim_free(retval);	// list already found, don't overwrite it
6047 
6048     return OK;
6049 }
6050 
6051 /*
6052  * Make a copy of an ID list.
6053  */
6054     static short *
6055 copy_id_list(short *list)
6056 {
6057     int	    len;
6058     int	    count;
6059     short   *retval;
6060 
6061     if (list == NULL)
6062 	return NULL;
6063 
6064     for (count = 0; list[count]; ++count)
6065 	;
6066     len = (count + 1) * sizeof(short);
6067     retval = alloc(len);
6068     if (retval != NULL)
6069 	mch_memmove(retval, list, (size_t)len);
6070 
6071     return retval;
6072 }
6073 
6074 /*
6075  * Check if syntax group "ssp" is in the ID list "list" of "cur_si".
6076  * "cur_si" can be NULL if not checking the "containedin" list.
6077  * Used to check if a syntax item is in the "contains" or "nextgroup" list of
6078  * the current item.
6079  * This function is called very often, keep it fast!!
6080  */
6081     static int
6082 in_id_list(
6083     stateitem_T	*cur_si,	// current item or NULL
6084     short	*list,		// id list
6085     struct sp_syn *ssp,		// group id and ":syn include" tag of group
6086     int		contained)	// group id is contained
6087 {
6088     int		retval;
6089     short	*scl_list;
6090     short	item;
6091     short	id = ssp->id;
6092     static int	depth = 0;
6093     int		r;
6094 
6095     // If ssp has a "containedin" list and "cur_si" is in it, return TRUE.
6096     if (cur_si != NULL && ssp->cont_in_list != NULL
6097 					    && !(cur_si->si_flags & HL_MATCH))
6098     {
6099 	// Ignore transparent items without a contains argument.  Double check
6100 	// that we don't go back past the first one.
6101 	while ((cur_si->si_flags & HL_TRANS_CONT)
6102 		&& cur_si > (stateitem_T *)(current_state.ga_data))
6103 	    --cur_si;
6104 	// cur_si->si_idx is -1 for keywords, these never contain anything.
6105 	if (cur_si->si_idx >= 0 && in_id_list(NULL, ssp->cont_in_list,
6106 		&(SYN_ITEMS(syn_block)[cur_si->si_idx].sp_syn),
6107 		  SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags & HL_CONTAINED))
6108 	    return TRUE;
6109     }
6110 
6111     if (list == NULL)
6112 	return FALSE;
6113 
6114     /*
6115      * If list is ID_LIST_ALL, we are in a transparent item that isn't
6116      * inside anything.  Only allow not-contained groups.
6117      */
6118     if (list == ID_LIST_ALL)
6119 	return !contained;
6120 
6121     /*
6122      * If the first item is "ALLBUT", return TRUE if "id" is NOT in the
6123      * contains list.  We also require that "id" is at the same ":syn include"
6124      * level as the list.
6125      */
6126     item = *list;
6127     if (item >= SYNID_ALLBUT && item < SYNID_CLUSTER)
6128     {
6129 	if (item < SYNID_TOP)
6130 	{
6131 	    // ALL or ALLBUT: accept all groups in the same file
6132 	    if (item - SYNID_ALLBUT != ssp->inc_tag)
6133 		return FALSE;
6134 	}
6135 	else if (item < SYNID_CONTAINED)
6136 	{
6137 	    // TOP: accept all not-contained groups in the same file
6138 	    if (item - SYNID_TOP != ssp->inc_tag || contained)
6139 		return FALSE;
6140 	}
6141 	else
6142 	{
6143 	    // CONTAINED: accept all contained groups in the same file
6144 	    if (item - SYNID_CONTAINED != ssp->inc_tag || !contained)
6145 		return FALSE;
6146 	}
6147 	item = *++list;
6148 	retval = FALSE;
6149     }
6150     else
6151 	retval = TRUE;
6152 
6153     /*
6154      * Return "retval" if id is in the contains list.
6155      */
6156     while (item != 0)
6157     {
6158 	if (item == id)
6159 	    return retval;
6160 	if (item >= SYNID_CLUSTER)
6161 	{
6162 	    scl_list = SYN_CLSTR(syn_block)[item - SYNID_CLUSTER].scl_list;
6163 	    // restrict recursiveness to 30 to avoid an endless loop for a
6164 	    // cluster that includes itself (indirectly)
6165 	    if (scl_list != NULL && depth < 30)
6166 	    {
6167 		++depth;
6168 		r = in_id_list(NULL, scl_list, ssp, contained);
6169 		--depth;
6170 		if (r)
6171 		    return retval;
6172 	    }
6173 	}
6174 	item = *++list;
6175     }
6176     return !retval;
6177 }
6178 
6179 struct subcommand
6180 {
6181     char    *name;			// subcommand name
6182     void    (*func)(exarg_T *, int);	// function to call
6183 };
6184 
6185 static struct subcommand subcommands[] =
6186 {
6187     {"case",		syn_cmd_case},
6188     {"clear",		syn_cmd_clear},
6189     {"cluster",		syn_cmd_cluster},
6190     {"conceal",		syn_cmd_conceal},
6191     {"enable",		syn_cmd_enable},
6192     {"include",		syn_cmd_include},
6193     {"iskeyword",	syn_cmd_iskeyword},
6194     {"keyword",		syn_cmd_keyword},
6195     {"list",		syn_cmd_list},
6196     {"manual",		syn_cmd_manual},
6197     {"match",		syn_cmd_match},
6198     {"on",		syn_cmd_on},
6199     {"off",		syn_cmd_off},
6200     {"region",		syn_cmd_region},
6201     {"reset",		syn_cmd_reset},
6202     {"spell",		syn_cmd_spell},
6203     {"sync",		syn_cmd_sync},
6204     {"",		syn_cmd_list},
6205     {NULL, NULL}
6206 };
6207 
6208 /*
6209  * ":syntax".
6210  * This searches the subcommands[] table for the subcommand name, and calls a
6211  * syntax_subcommand() function to do the rest.
6212  */
6213     void
6214 ex_syntax(exarg_T *eap)
6215 {
6216     char_u	*arg = eap->arg;
6217     char_u	*subcmd_end;
6218     char_u	*subcmd_name;
6219     int		i;
6220 
6221     syn_cmdlinep = eap->cmdlinep;
6222 
6223     // isolate subcommand name
6224     for (subcmd_end = arg; ASCII_ISALPHA(*subcmd_end); ++subcmd_end)
6225 	;
6226     subcmd_name = vim_strnsave(arg, (int)(subcmd_end - arg));
6227     if (subcmd_name != NULL)
6228     {
6229 	if (eap->skip)		// skip error messages for all subcommands
6230 	    ++emsg_skip;
6231 	for (i = 0; ; ++i)
6232 	{
6233 	    if (subcommands[i].name == NULL)
6234 	    {
6235 		semsg(_("E410: Invalid :syntax subcommand: %s"), subcmd_name);
6236 		break;
6237 	    }
6238 	    if (STRCMP(subcmd_name, (char_u *)subcommands[i].name) == 0)
6239 	    {
6240 		eap->arg = skipwhite(subcmd_end);
6241 		(subcommands[i].func)(eap, FALSE);
6242 		break;
6243 	    }
6244 	}
6245 	vim_free(subcmd_name);
6246 	if (eap->skip)
6247 	    --emsg_skip;
6248     }
6249 }
6250 
6251     void
6252 ex_ownsyntax(exarg_T *eap)
6253 {
6254     char_u	*old_value;
6255     char_u	*new_value;
6256 
6257     if (curwin->w_s == &curwin->w_buffer->b_s)
6258     {
6259 	curwin->w_s = ALLOC_ONE(synblock_T);
6260 	memset(curwin->w_s, 0, sizeof(synblock_T));
6261 	hash_init(&curwin->w_s->b_keywtab);
6262 	hash_init(&curwin->w_s->b_keywtab_ic);
6263 #ifdef FEAT_SPELL
6264 	// TODO: keep the spell checking as it was.
6265 	curwin->w_p_spell = FALSE;	// No spell checking
6266 	clear_string_option(&curwin->w_s->b_p_spc);
6267 	clear_string_option(&curwin->w_s->b_p_spf);
6268 	clear_string_option(&curwin->w_s->b_p_spl);
6269 #endif
6270 	clear_string_option(&curwin->w_s->b_syn_isk);
6271     }
6272 
6273     // save value of b:current_syntax
6274     old_value = get_var_value((char_u *)"b:current_syntax");
6275     if (old_value != NULL)
6276 	old_value = vim_strsave(old_value);
6277 
6278     // Apply the "syntax" autocommand event, this finds and loads the syntax
6279     // file.
6280     apply_autocmds(EVENT_SYNTAX, eap->arg, curbuf->b_fname, TRUE, curbuf);
6281 
6282     // move value of b:current_syntax to w:current_syntax
6283     new_value = get_var_value((char_u *)"b:current_syntax");
6284     if (new_value != NULL)
6285 	set_internal_string_var((char_u *)"w:current_syntax", new_value);
6286 
6287     // restore value of b:current_syntax
6288     if (old_value == NULL)
6289 	do_unlet((char_u *)"b:current_syntax", TRUE);
6290     else
6291     {
6292 	set_internal_string_var((char_u *)"b:current_syntax", old_value);
6293 	vim_free(old_value);
6294     }
6295 }
6296 
6297     int
6298 syntax_present(win_T *win)
6299 {
6300     return (win->w_s->b_syn_patterns.ga_len != 0
6301 	    || win->w_s->b_syn_clusters.ga_len != 0
6302 	    || win->w_s->b_keywtab.ht_used > 0
6303 	    || win->w_s->b_keywtab_ic.ht_used > 0);
6304 }
6305 
6306 
6307 static enum
6308 {
6309     EXP_SUBCMD,	    // expand ":syn" sub-commands
6310     EXP_CASE,	    // expand ":syn case" arguments
6311     EXP_SPELL,	    // expand ":syn spell" arguments
6312     EXP_SYNC	    // expand ":syn sync" arguments
6313 } expand_what;
6314 
6315 /*
6316  * Reset include_link, include_default, include_none to 0.
6317  * Called when we are done expanding.
6318  */
6319     void
6320 reset_expand_highlight(void)
6321 {
6322     include_link = include_default = include_none = 0;
6323 }
6324 
6325 /*
6326  * Handle command line completion for :match and :echohl command: Add "None"
6327  * as highlight group.
6328  */
6329     void
6330 set_context_in_echohl_cmd(expand_T *xp, char_u *arg)
6331 {
6332     xp->xp_context = EXPAND_HIGHLIGHT;
6333     xp->xp_pattern = arg;
6334     include_none = 1;
6335 }
6336 
6337 /*
6338  * Handle command line completion for :syntax command.
6339  */
6340     void
6341 set_context_in_syntax_cmd(expand_T *xp, char_u *arg)
6342 {
6343     char_u	*p;
6344 
6345     // Default: expand subcommands
6346     xp->xp_context = EXPAND_SYNTAX;
6347     expand_what = EXP_SUBCMD;
6348     xp->xp_pattern = arg;
6349     include_link = 0;
6350     include_default = 0;
6351 
6352     // (part of) subcommand already typed
6353     if (*arg != NUL)
6354     {
6355 	p = skiptowhite(arg);
6356 	if (*p != NUL)		    // past first word
6357 	{
6358 	    xp->xp_pattern = skipwhite(p);
6359 	    if (*skiptowhite(xp->xp_pattern) != NUL)
6360 		xp->xp_context = EXPAND_NOTHING;
6361 	    else if (STRNICMP(arg, "case", p - arg) == 0)
6362 		expand_what = EXP_CASE;
6363 	    else if (STRNICMP(arg, "spell", p - arg) == 0)
6364 		expand_what = EXP_SPELL;
6365 	    else if (STRNICMP(arg, "sync", p - arg) == 0)
6366 		expand_what = EXP_SYNC;
6367 	    else if (  STRNICMP(arg, "keyword", p - arg) == 0
6368 		    || STRNICMP(arg, "region", p - arg) == 0
6369 		    || STRNICMP(arg, "match", p - arg) == 0
6370 		    || STRNICMP(arg, "list", p - arg) == 0)
6371 		xp->xp_context = EXPAND_HIGHLIGHT;
6372 	    else
6373 		xp->xp_context = EXPAND_NOTHING;
6374 	}
6375     }
6376 }
6377 
6378 /*
6379  * Function given to ExpandGeneric() to obtain the list syntax names for
6380  * expansion.
6381  */
6382     char_u *
6383 get_syntax_name(expand_T *xp UNUSED, int idx)
6384 {
6385     switch (expand_what)
6386     {
6387 	case EXP_SUBCMD:
6388 	    return (char_u *)subcommands[idx].name;
6389 	case EXP_CASE:
6390 	{
6391 	    static char *case_args[] = {"match", "ignore", NULL};
6392 	    return (char_u *)case_args[idx];
6393 	}
6394 	case EXP_SPELL:
6395 	{
6396 	    static char *spell_args[] =
6397 		{"toplevel", "notoplevel", "default", NULL};
6398 	    return (char_u *)spell_args[idx];
6399 	}
6400 	case EXP_SYNC:
6401 	{
6402 	    static char *sync_args[] =
6403 		{"ccomment", "clear", "fromstart",
6404 		 "linebreaks=", "linecont", "lines=", "match",
6405 		 "maxlines=", "minlines=", "region", NULL};
6406 	    return (char_u *)sync_args[idx];
6407 	}
6408     }
6409     return NULL;
6410 }
6411 
6412 
6413 /*
6414  * Function called for expression evaluation: get syntax ID at file position.
6415  */
6416     int
6417 syn_get_id(
6418     win_T	*wp,
6419     long	lnum,
6420     colnr_T	col,
6421     int		trans,	     // remove transparency
6422     int		*spellp,     // return: can do spell checking
6423     int		keep_state)  // keep state of char at "col"
6424 {
6425     // When the position is not after the current position and in the same
6426     // line of the same buffer, need to restart parsing.
6427     if (wp->w_buffer != syn_buf
6428 	    || lnum != current_lnum
6429 	    || col < current_col)
6430 	syntax_start(wp, lnum);
6431     else if (wp->w_buffer == syn_buf
6432 	    && lnum == current_lnum
6433 	    && col > current_col)
6434 	// next_match may not be correct when moving around, e.g. with the
6435 	// "skip" expression in searchpair()
6436 	next_match_idx = -1;
6437 
6438     (void)get_syntax_attr(col, spellp, keep_state);
6439 
6440     return (trans ? current_trans_id : current_id);
6441 }
6442 
6443 #if defined(FEAT_CONCEAL) || defined(PROTO)
6444 /*
6445  * Get extra information about the syntax item.  Must be called right after
6446  * get_syntax_attr().
6447  * Stores the current item sequence nr in "*seqnrp".
6448  * Returns the current flags.
6449  */
6450     int
6451 get_syntax_info(int *seqnrp)
6452 {
6453     *seqnrp = current_seqnr;
6454     return current_flags;
6455 }
6456 
6457 /*
6458  * Return conceal substitution character
6459  */
6460     int
6461 syn_get_sub_char(void)
6462 {
6463     return current_sub_char;
6464 }
6465 #endif
6466 
6467 #if defined(FEAT_EVAL) || defined(PROTO)
6468 /*
6469  * Return the syntax ID at position "i" in the current stack.
6470  * The caller must have called syn_get_id() before to fill the stack.
6471  * Returns -1 when "i" is out of range.
6472  */
6473     int
6474 syn_get_stack_item(int i)
6475 {
6476     if (i >= current_state.ga_len)
6477     {
6478 	// Need to invalidate the state, because we didn't properly finish it
6479 	// for the last character, "keep_state" was TRUE.
6480 	invalidate_current_state();
6481 	current_col = MAXCOL;
6482 	return -1;
6483     }
6484     return CUR_STATE(i).si_id;
6485 }
6486 #endif
6487 
6488 #if defined(FEAT_FOLDING) || defined(PROTO)
6489 /*
6490  * Function called to get folding level for line "lnum" in window "wp".
6491  */
6492     int
6493 syn_get_foldlevel(win_T *wp, long lnum)
6494 {
6495     int		level = 0;
6496     int		i;
6497 
6498     // Return quickly when there are no fold items at all.
6499     if (wp->w_s->b_syn_folditems != 0
6500 	    && !wp->w_s->b_syn_error
6501 # ifdef SYN_TIME_LIMIT
6502 	    && !wp->w_s->b_syn_slow
6503 # endif
6504 	    )
6505     {
6506 	syntax_start(wp, lnum);
6507 
6508 	for (i = 0; i < current_state.ga_len; ++i)
6509 	    if (CUR_STATE(i).si_flags & HL_FOLD)
6510 		++level;
6511     }
6512     if (level > wp->w_p_fdn)
6513     {
6514 	level = wp->w_p_fdn;
6515 	if (level < 0)
6516 	    level = 0;
6517     }
6518     return level;
6519 }
6520 #endif
6521 
6522 #if defined(FEAT_PROFILE) || defined(PROTO)
6523 /*
6524  * ":syntime".
6525  */
6526     void
6527 ex_syntime(exarg_T *eap)
6528 {
6529     if (STRCMP(eap->arg, "on") == 0)
6530 	syn_time_on = TRUE;
6531     else if (STRCMP(eap->arg, "off") == 0)
6532 	syn_time_on = FALSE;
6533     else if (STRCMP(eap->arg, "clear") == 0)
6534 	syntime_clear();
6535     else if (STRCMP(eap->arg, "report") == 0)
6536 	syntime_report();
6537     else
6538 	semsg(_(e_invarg2), eap->arg);
6539 }
6540 
6541     static void
6542 syn_clear_time(syn_time_T *st)
6543 {
6544     profile_zero(&st->total);
6545     profile_zero(&st->slowest);
6546     st->count = 0;
6547     st->match = 0;
6548 }
6549 
6550 /*
6551  * Clear the syntax timing for the current buffer.
6552  */
6553     static void
6554 syntime_clear(void)
6555 {
6556     int		idx;
6557     synpat_T	*spp;
6558 
6559     if (!syntax_present(curwin))
6560     {
6561 	msg(_(msg_no_items));
6562 	return;
6563     }
6564     for (idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx)
6565     {
6566 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
6567 	syn_clear_time(&spp->sp_time);
6568     }
6569 }
6570 
6571 /*
6572  * Function given to ExpandGeneric() to obtain the possible arguments of the
6573  * ":syntime {on,off,clear,report}" command.
6574  */
6575     char_u *
6576 get_syntime_arg(expand_T *xp UNUSED, int idx)
6577 {
6578     switch (idx)
6579     {
6580 	case 0: return (char_u *)"on";
6581 	case 1: return (char_u *)"off";
6582 	case 2: return (char_u *)"clear";
6583 	case 3: return (char_u *)"report";
6584     }
6585     return NULL;
6586 }
6587 
6588 typedef struct
6589 {
6590     proftime_T	total;
6591     int		count;
6592     int		match;
6593     proftime_T	slowest;
6594     proftime_T	average;
6595     int		id;
6596     char_u	*pattern;
6597 } time_entry_T;
6598 
6599     static int
6600 syn_compare_syntime(const void *v1, const void *v2)
6601 {
6602     const time_entry_T	*s1 = v1;
6603     const time_entry_T	*s2 = v2;
6604 
6605     return profile_cmp(&s1->total, &s2->total);
6606 }
6607 
6608 /*
6609  * Clear the syntax timing for the current buffer.
6610  */
6611     static void
6612 syntime_report(void)
6613 {
6614     int		idx;
6615     synpat_T	*spp;
6616 # ifdef FEAT_FLOAT
6617     proftime_T	tm;
6618 # endif
6619     int		len;
6620     proftime_T	total_total;
6621     int		total_count = 0;
6622     garray_T    ga;
6623     time_entry_T *p;
6624 
6625     if (!syntax_present(curwin))
6626     {
6627 	msg(_(msg_no_items));
6628 	return;
6629     }
6630 
6631     ga_init2(&ga, sizeof(time_entry_T), 50);
6632     profile_zero(&total_total);
6633     for (idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx)
6634     {
6635 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
6636 	if (spp->sp_time.count > 0)
6637 	{
6638 	    (void)ga_grow(&ga, 1);
6639 	    p = ((time_entry_T *)ga.ga_data) + ga.ga_len;
6640 	    p->total = spp->sp_time.total;
6641 	    profile_add(&total_total, &spp->sp_time.total);
6642 	    p->count = spp->sp_time.count;
6643 	    p->match = spp->sp_time.match;
6644 	    total_count += spp->sp_time.count;
6645 	    p->slowest = spp->sp_time.slowest;
6646 # ifdef FEAT_FLOAT
6647 	    profile_divide(&spp->sp_time.total, spp->sp_time.count, &tm);
6648 	    p->average = tm;
6649 # endif
6650 	    p->id = spp->sp_syn.id;
6651 	    p->pattern = spp->sp_pattern;
6652 	    ++ga.ga_len;
6653 	}
6654     }
6655 
6656     // Sort on total time. Skip if there are no items to avoid passing NULL
6657     // pointer to qsort().
6658     if (ga.ga_len > 1)
6659 	qsort(ga.ga_data, (size_t)ga.ga_len, sizeof(time_entry_T),
6660 							 syn_compare_syntime);
6661 
6662     msg_puts_title(_("  TOTAL      COUNT  MATCH   SLOWEST     AVERAGE   NAME               PATTERN"));
6663     msg_puts("\n");
6664     for (idx = 0; idx < ga.ga_len && !got_int; ++idx)
6665     {
6666 	p = ((time_entry_T *)ga.ga_data) + idx;
6667 
6668 	msg_puts(profile_msg(&p->total));
6669 	msg_puts(" "); // make sure there is always a separating space
6670 	msg_advance(13);
6671 	msg_outnum(p->count);
6672 	msg_puts(" ");
6673 	msg_advance(20);
6674 	msg_outnum(p->match);
6675 	msg_puts(" ");
6676 	msg_advance(26);
6677 	msg_puts(profile_msg(&p->slowest));
6678 	msg_puts(" ");
6679 	msg_advance(38);
6680 # ifdef FEAT_FLOAT
6681 	msg_puts(profile_msg(&p->average));
6682 	msg_puts(" ");
6683 # endif
6684 	msg_advance(50);
6685 	msg_outtrans(highlight_group_name(p->id - 1));
6686 	msg_puts(" ");
6687 
6688 	msg_advance(69);
6689 	if (Columns < 80)
6690 	    len = 20; // will wrap anyway
6691 	else
6692 	    len = Columns - 70;
6693 	if (len > (int)STRLEN(p->pattern))
6694 	    len = (int)STRLEN(p->pattern);
6695 	msg_outtrans_len(p->pattern, len);
6696 	msg_puts("\n");
6697     }
6698     ga_clear(&ga);
6699     if (!got_int)
6700     {
6701 	msg_puts("\n");
6702 	msg_puts(profile_msg(&total_total));
6703 	msg_advance(13);
6704 	msg_outnum(total_count);
6705 	msg_puts("\n");
6706     }
6707 }
6708 #endif
6709 
6710 #endif // FEAT_SYN_HL
6711