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