xref: /vim-8.2.3635/src/syntax.c (revision ceb56ddb)
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 		msg_puts(_("syncing starts "));
3873 		msg_outnum(curwin->w_s->b_syn_sync_minlines);
3874 		msg_puts(_(" lines before top line"));
3875 		syn_match_msg();
3876 	    }
3877 	    return;
3878 	}
3879 	msg_puts_title(_("\n--- Syntax sync items ---"));
3880 	if (curwin->w_s->b_syn_sync_minlines > 0
3881 		|| curwin->w_s->b_syn_sync_maxlines > 0
3882 		|| curwin->w_s->b_syn_sync_linebreaks > 0)
3883 	{
3884 	    msg_puts(_("\nsyncing on items"));
3885 	    syn_lines_msg();
3886 	    syn_match_msg();
3887 	}
3888     }
3889     else
3890 	msg_puts_title(_("\n--- Syntax items ---"));
3891     if (ends_excmd2(eap->cmd, arg))
3892     {
3893 	/*
3894 	 * No argument: List all group IDs and all syntax clusters.
3895 	 */
3896 	for (id = 1; id <= highlight_num_groups() && !got_int; ++id)
3897 	    syn_list_one(id, syncing, FALSE);
3898 	for (id = 0; id < curwin->w_s->b_syn_clusters.ga_len && !got_int; ++id)
3899 	    syn_list_cluster(id);
3900     }
3901     else
3902     {
3903 	/*
3904 	 * List the group IDs and syntax clusters that are in the argument.
3905 	 */
3906 	while (!ends_excmd2(eap->cmd, arg) && !got_int)
3907 	{
3908 	    arg_end = skiptowhite(arg);
3909 	    if (*arg == '@')
3910 	    {
3911 		id = syn_scl_namen2id(arg + 1, (int)(arg_end - arg - 1));
3912 		if (id == 0)
3913 		    semsg(_("E392: No such syntax cluster: %s"), arg);
3914 		else
3915 		    syn_list_cluster(id - SYNID_CLUSTER);
3916 	    }
3917 	    else
3918 	    {
3919 		id = syn_namen2id(arg, (int)(arg_end - arg));
3920 		if (id == 0)
3921 		    semsg(_(e_nogroup), arg);
3922 		else
3923 		    syn_list_one(id, syncing, TRUE);
3924 	    }
3925 	    arg = skipwhite(arg_end);
3926 	}
3927     }
3928     eap->nextcmd = check_nextcmd(arg);
3929 }
3930 
3931     static void
3932 syn_lines_msg(void)
3933 {
3934     if (curwin->w_s->b_syn_sync_maxlines > 0
3935 				      || curwin->w_s->b_syn_sync_minlines > 0)
3936     {
3937 	msg_puts("; ");
3938 	if (curwin->w_s->b_syn_sync_minlines > 0)
3939 	{
3940 	    msg_puts(_("minimal "));
3941 	    msg_outnum(curwin->w_s->b_syn_sync_minlines);
3942 	    if (curwin->w_s->b_syn_sync_maxlines)
3943 		msg_puts(", ");
3944 	}
3945 	if (curwin->w_s->b_syn_sync_maxlines > 0)
3946 	{
3947 	    msg_puts(_("maximal "));
3948 	    msg_outnum(curwin->w_s->b_syn_sync_maxlines);
3949 	}
3950 	msg_puts(_(" lines before top line"));
3951     }
3952 }
3953 
3954     static void
3955 syn_match_msg(void)
3956 {
3957     if (curwin->w_s->b_syn_sync_linebreaks > 0)
3958     {
3959 	msg_puts(_("; match "));
3960 	msg_outnum(curwin->w_s->b_syn_sync_linebreaks);
3961 	msg_puts(_(" line breaks"));
3962     }
3963 }
3964 
3965 static int  last_matchgroup;
3966 
3967 struct name_list
3968 {
3969     int		flag;
3970     char	*name;
3971 };
3972 
3973 static void syn_list_flags(struct name_list *nl, int flags, int attr);
3974 
3975 /*
3976  * List one syntax item, for ":syntax" or "syntax list syntax_name".
3977  */
3978     static void
3979 syn_list_one(
3980     int		id,
3981     int		syncing,	    // when TRUE: list syncing items
3982     int		link_only)	    // when TRUE; list link-only too
3983 {
3984     int		attr;
3985     int		idx;
3986     int		did_header = FALSE;
3987     synpat_T	*spp;
3988     static struct name_list namelist1[] =
3989 		{
3990 		    {HL_DISPLAY, "display"},
3991 		    {HL_CONTAINED, "contained"},
3992 		    {HL_ONELINE, "oneline"},
3993 		    {HL_KEEPEND, "keepend"},
3994 		    {HL_EXTEND, "extend"},
3995 		    {HL_EXCLUDENL, "excludenl"},
3996 		    {HL_TRANSP, "transparent"},
3997 		    {HL_FOLD, "fold"},
3998 #ifdef FEAT_CONCEAL
3999 		    {HL_CONCEAL, "conceal"},
4000 		    {HL_CONCEALENDS, "concealends"},
4001 #endif
4002 		    {0, NULL}
4003 		};
4004     static struct name_list namelist2[] =
4005 		{
4006 		    {HL_SKIPWHITE, "skipwhite"},
4007 		    {HL_SKIPNL, "skipnl"},
4008 		    {HL_SKIPEMPTY, "skipempty"},
4009 		    {0, NULL}
4010 		};
4011 
4012     attr = HL_ATTR(HLF_D);		// highlight like directories
4013 
4014     // list the keywords for "id"
4015     if (!syncing)
4016     {
4017 	did_header = syn_list_keywords(id, &curwin->w_s->b_keywtab, FALSE, attr);
4018 	did_header = syn_list_keywords(id, &curwin->w_s->b_keywtab_ic,
4019 							    did_header, attr);
4020     }
4021 
4022     // list the patterns for "id"
4023     for (idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len && !got_int; ++idx)
4024     {
4025 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
4026 	if (spp->sp_syn.id != id || spp->sp_syncing != syncing)
4027 	    continue;
4028 
4029 	(void)syn_list_header(did_header, 999, id);
4030 	did_header = TRUE;
4031 	last_matchgroup = 0;
4032 	if (spp->sp_type == SPTYPE_MATCH)
4033 	{
4034 	    put_pattern("match", ' ', spp, attr);
4035 	    msg_putchar(' ');
4036 	}
4037 	else if (spp->sp_type == SPTYPE_START)
4038 	{
4039 	    while (SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_START)
4040 		put_pattern("start", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr);
4041 	    if (SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_SKIP)
4042 		put_pattern("skip", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr);
4043 	    while (idx < curwin->w_s->b_syn_patterns.ga_len
4044 			  && SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_END)
4045 		put_pattern("end", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr);
4046 	    --idx;
4047 	    msg_putchar(' ');
4048 	}
4049 	syn_list_flags(namelist1, spp->sp_flags, attr);
4050 
4051 	if (spp->sp_cont_list != NULL)
4052 	    put_id_list((char_u *)"contains", spp->sp_cont_list, attr);
4053 
4054 	if (spp->sp_syn.cont_in_list != NULL)
4055 	    put_id_list((char_u *)"containedin",
4056 					      spp->sp_syn.cont_in_list, attr);
4057 
4058 	if (spp->sp_next_list != NULL)
4059 	{
4060 	    put_id_list((char_u *)"nextgroup", spp->sp_next_list, attr);
4061 	    syn_list_flags(namelist2, spp->sp_flags, attr);
4062 	}
4063 	if (spp->sp_flags & (HL_SYNC_HERE|HL_SYNC_THERE))
4064 	{
4065 	    if (spp->sp_flags & HL_SYNC_HERE)
4066 		msg_puts_attr("grouphere", attr);
4067 	    else
4068 		msg_puts_attr("groupthere", attr);
4069 	    msg_putchar(' ');
4070 	    if (spp->sp_sync_idx >= 0)
4071 		msg_outtrans(highlight_group_name(SYN_ITEMS(curwin->w_s)
4072 				   [spp->sp_sync_idx].sp_syn.id - 1));
4073 	    else
4074 		msg_puts("NONE");
4075 	    msg_putchar(' ');
4076 	}
4077     }
4078 
4079     // list the link, if there is one
4080     if (highlight_link_id(id - 1) && (did_header || link_only) && !got_int)
4081     {
4082 	(void)syn_list_header(did_header, 999, id);
4083 	msg_puts_attr("links to", attr);
4084 	msg_putchar(' ');
4085 	msg_outtrans(highlight_group_name(highlight_link_id(id - 1) - 1));
4086     }
4087 }
4088 
4089     static void
4090 syn_list_flags(struct name_list *nlist, int flags, int attr)
4091 {
4092     int		i;
4093 
4094     for (i = 0; nlist[i].flag != 0; ++i)
4095 	if (flags & nlist[i].flag)
4096 	{
4097 	    msg_puts_attr(nlist[i].name, attr);
4098 	    msg_putchar(' ');
4099 	}
4100 }
4101 
4102 /*
4103  * List one syntax cluster, for ":syntax" or "syntax list syntax_name".
4104  */
4105     static void
4106 syn_list_cluster(int id)
4107 {
4108     int	    endcol = 15;
4109 
4110     // slight hack:  roughly duplicate the guts of syn_list_header()
4111     msg_putchar('\n');
4112     msg_outtrans(SYN_CLSTR(curwin->w_s)[id].scl_name);
4113 
4114     if (msg_col >= endcol)	// output at least one space
4115 	endcol = msg_col + 1;
4116     if (Columns <= endcol)	// avoid hang for tiny window
4117 	endcol = Columns - 1;
4118 
4119     msg_advance(endcol);
4120     if (SYN_CLSTR(curwin->w_s)[id].scl_list != NULL)
4121     {
4122 	put_id_list((char_u *)"cluster", SYN_CLSTR(curwin->w_s)[id].scl_list,
4123 		    HL_ATTR(HLF_D));
4124     }
4125     else
4126     {
4127 	msg_puts_attr("cluster", HL_ATTR(HLF_D));
4128 	msg_puts("=NONE");
4129     }
4130 }
4131 
4132     static void
4133 put_id_list(char_u *name, short *list, int attr)
4134 {
4135     short		*p;
4136 
4137     msg_puts_attr((char *)name, attr);
4138     msg_putchar('=');
4139     for (p = list; *p; ++p)
4140     {
4141 	if (*p >= SYNID_ALLBUT && *p < SYNID_TOP)
4142 	{
4143 	    if (p[1])
4144 		msg_puts("ALLBUT");
4145 	    else
4146 		msg_puts("ALL");
4147 	}
4148 	else if (*p >= SYNID_TOP && *p < SYNID_CONTAINED)
4149 	{
4150 	    msg_puts("TOP");
4151 	}
4152 	else if (*p >= SYNID_CONTAINED && *p < SYNID_CLUSTER)
4153 	{
4154 	    msg_puts("CONTAINED");
4155 	}
4156 	else if (*p >= SYNID_CLUSTER)
4157 	{
4158 	    short scl_id = *p - SYNID_CLUSTER;
4159 
4160 	    msg_putchar('@');
4161 	    msg_outtrans(SYN_CLSTR(curwin->w_s)[scl_id].scl_name);
4162 	}
4163 	else
4164 	    msg_outtrans(highlight_group_name(*p - 1));
4165 	if (p[1])
4166 	    msg_putchar(',');
4167     }
4168     msg_putchar(' ');
4169 }
4170 
4171     static void
4172 put_pattern(
4173     char	*s,
4174     int		c,
4175     synpat_T	*spp,
4176     int		attr)
4177 {
4178     long	n;
4179     int		mask;
4180     int		first;
4181     static char	*sepchars = "/+=-#@\"|'^&";
4182     int		i;
4183 
4184     // May have to write "matchgroup=group"
4185     if (last_matchgroup != spp->sp_syn_match_id)
4186     {
4187 	last_matchgroup = spp->sp_syn_match_id;
4188 	msg_puts_attr("matchgroup", attr);
4189 	msg_putchar('=');
4190 	if (last_matchgroup == 0)
4191 	    msg_outtrans((char_u *)"NONE");
4192 	else
4193 	    msg_outtrans(highlight_group_name(last_matchgroup - 1));
4194 	msg_putchar(' ');
4195     }
4196 
4197     // output the name of the pattern and an '=' or ' '
4198     msg_puts_attr(s, attr);
4199     msg_putchar(c);
4200 
4201     // output the pattern, in between a char that is not in the pattern
4202     for (i = 0; vim_strchr(spp->sp_pattern, sepchars[i]) != NULL; )
4203 	if (sepchars[++i] == NUL)
4204 	{
4205 	    i = 0;	// no good char found, just use the first one
4206 	    break;
4207 	}
4208     msg_putchar(sepchars[i]);
4209     msg_outtrans(spp->sp_pattern);
4210     msg_putchar(sepchars[i]);
4211 
4212     // output any pattern options
4213     first = TRUE;
4214     for (i = 0; i < SPO_COUNT; ++i)
4215     {
4216 	mask = (1 << i);
4217 	if (spp->sp_off_flags & (mask + (mask << SPO_COUNT)))
4218 	{
4219 	    if (!first)
4220 		msg_putchar(',');	// separate with commas
4221 	    msg_puts(spo_name_tab[i]);
4222 	    n = spp->sp_offsets[i];
4223 	    if (i != SPO_LC_OFF)
4224 	    {
4225 		if (spp->sp_off_flags & mask)
4226 		    msg_putchar('s');
4227 		else
4228 		    msg_putchar('e');
4229 		if (n > 0)
4230 		    msg_putchar('+');
4231 	    }
4232 	    if (n || i == SPO_LC_OFF)
4233 		msg_outnum(n);
4234 	    first = FALSE;
4235 	}
4236     }
4237     msg_putchar(' ');
4238 }
4239 
4240 /*
4241  * List or clear the keywords for one syntax group.
4242  * Return TRUE if the header has been printed.
4243  */
4244     static int
4245 syn_list_keywords(
4246     int		id,
4247     hashtab_T	*ht,
4248     int		did_header,		// header has already been printed
4249     int		attr)
4250 {
4251     int		outlen;
4252     hashitem_T	*hi;
4253     keyentry_T	*kp;
4254     int		todo;
4255     int		prev_contained = 0;
4256     short	*prev_next_list = NULL;
4257     short	*prev_cont_in_list = NULL;
4258     int		prev_skipnl = 0;
4259     int		prev_skipwhite = 0;
4260     int		prev_skipempty = 0;
4261 
4262     /*
4263      * Unfortunately, this list of keywords is not sorted on alphabet but on
4264      * hash value...
4265      */
4266     todo = (int)ht->ht_used;
4267     for (hi = ht->ht_array; todo > 0 && !got_int; ++hi)
4268     {
4269 	if (!HASHITEM_EMPTY(hi))
4270 	{
4271 	    --todo;
4272 	    for (kp = HI2KE(hi); kp != NULL && !got_int; kp = kp->ke_next)
4273 	    {
4274 		if (kp->k_syn.id == id)
4275 		{
4276 		    if (prev_contained != (kp->flags & HL_CONTAINED)
4277 			    || prev_skipnl != (kp->flags & HL_SKIPNL)
4278 			    || prev_skipwhite != (kp->flags & HL_SKIPWHITE)
4279 			    || prev_skipempty != (kp->flags & HL_SKIPEMPTY)
4280 			    || prev_cont_in_list != kp->k_syn.cont_in_list
4281 			    || prev_next_list != kp->next_list)
4282 			outlen = 9999;
4283 		    else
4284 			outlen = (int)STRLEN(kp->keyword);
4285 		    // output "contained" and "nextgroup" on each line
4286 		    if (syn_list_header(did_header, outlen, id))
4287 		    {
4288 			prev_contained = 0;
4289 			prev_next_list = NULL;
4290 			prev_cont_in_list = NULL;
4291 			prev_skipnl = 0;
4292 			prev_skipwhite = 0;
4293 			prev_skipempty = 0;
4294 		    }
4295 		    did_header = TRUE;
4296 		    if (prev_contained != (kp->flags & HL_CONTAINED))
4297 		    {
4298 			msg_puts_attr("contained", attr);
4299 			msg_putchar(' ');
4300 			prev_contained = (kp->flags & HL_CONTAINED);
4301 		    }
4302 		    if (kp->k_syn.cont_in_list != prev_cont_in_list)
4303 		    {
4304 			put_id_list((char_u *)"containedin",
4305 						kp->k_syn.cont_in_list, attr);
4306 			msg_putchar(' ');
4307 			prev_cont_in_list = kp->k_syn.cont_in_list;
4308 		    }
4309 		    if (kp->next_list != prev_next_list)
4310 		    {
4311 			put_id_list((char_u *)"nextgroup", kp->next_list, attr);
4312 			msg_putchar(' ');
4313 			prev_next_list = kp->next_list;
4314 			if (kp->flags & HL_SKIPNL)
4315 			{
4316 			    msg_puts_attr("skipnl", attr);
4317 			    msg_putchar(' ');
4318 			    prev_skipnl = (kp->flags & HL_SKIPNL);
4319 			}
4320 			if (kp->flags & HL_SKIPWHITE)
4321 			{
4322 			    msg_puts_attr("skipwhite", attr);
4323 			    msg_putchar(' ');
4324 			    prev_skipwhite = (kp->flags & HL_SKIPWHITE);
4325 			}
4326 			if (kp->flags & HL_SKIPEMPTY)
4327 			{
4328 			    msg_puts_attr("skipempty", attr);
4329 			    msg_putchar(' ');
4330 			    prev_skipempty = (kp->flags & HL_SKIPEMPTY);
4331 			}
4332 		    }
4333 		    msg_outtrans(kp->keyword);
4334 		}
4335 	    }
4336 	}
4337     }
4338 
4339     return did_header;
4340 }
4341 
4342     static void
4343 syn_clear_keyword(int id, hashtab_T *ht)
4344 {
4345     hashitem_T	*hi;
4346     keyentry_T	*kp;
4347     keyentry_T	*kp_prev;
4348     keyentry_T	*kp_next;
4349     int		todo;
4350 
4351     hash_lock(ht);
4352     todo = (int)ht->ht_used;
4353     for (hi = ht->ht_array; todo > 0; ++hi)
4354     {
4355 	if (!HASHITEM_EMPTY(hi))
4356 	{
4357 	    --todo;
4358 	    kp_prev = NULL;
4359 	    for (kp = HI2KE(hi); kp != NULL; )
4360 	    {
4361 		if (kp->k_syn.id == id)
4362 		{
4363 		    kp_next = kp->ke_next;
4364 		    if (kp_prev == NULL)
4365 		    {
4366 			if (kp_next == NULL)
4367 			    hash_remove(ht, hi);
4368 			else
4369 			    hi->hi_key = KE2HIKEY(kp_next);
4370 		    }
4371 		    else
4372 			kp_prev->ke_next = kp_next;
4373 		    vim_free(kp->next_list);
4374 		    vim_free(kp->k_syn.cont_in_list);
4375 		    vim_free(kp);
4376 		    kp = kp_next;
4377 		}
4378 		else
4379 		{
4380 		    kp_prev = kp;
4381 		    kp = kp->ke_next;
4382 		}
4383 	    }
4384 	}
4385     }
4386     hash_unlock(ht);
4387 }
4388 
4389 /*
4390  * Clear a whole keyword table.
4391  */
4392     static void
4393 clear_keywtab(hashtab_T *ht)
4394 {
4395     hashitem_T	*hi;
4396     int		todo;
4397     keyentry_T	*kp;
4398     keyentry_T	*kp_next;
4399 
4400     todo = (int)ht->ht_used;
4401     for (hi = ht->ht_array; todo > 0; ++hi)
4402     {
4403 	if (!HASHITEM_EMPTY(hi))
4404 	{
4405 	    --todo;
4406 	    for (kp = HI2KE(hi); kp != NULL; kp = kp_next)
4407 	    {
4408 		kp_next = kp->ke_next;
4409 		vim_free(kp->next_list);
4410 		vim_free(kp->k_syn.cont_in_list);
4411 		vim_free(kp);
4412 	    }
4413 	}
4414     }
4415     hash_clear(ht);
4416     hash_init(ht);
4417 }
4418 
4419 /*
4420  * Add a keyword to the list of keywords.
4421  */
4422     static void
4423 add_keyword(
4424     char_u	*name,	    // name of keyword
4425     int		id,	    // group ID for this keyword
4426     int		flags,	    // flags for this keyword
4427     short	*cont_in_list, // containedin for this keyword
4428     short	*next_list, // nextgroup for this keyword
4429     int		conceal_char)
4430 {
4431     keyentry_T	*kp;
4432     hashtab_T	*ht;
4433     hashitem_T	*hi;
4434     char_u	*name_ic;
4435     long_u	hash;
4436     char_u	name_folded[MAXKEYWLEN + 1];
4437 
4438     if (curwin->w_s->b_syn_ic)
4439 	name_ic = str_foldcase(name, (int)STRLEN(name),
4440 						 name_folded, MAXKEYWLEN + 1);
4441     else
4442 	name_ic = name;
4443     kp = alloc(offsetof(keyentry_T, keyword) + STRLEN(name_ic) + 1);
4444     if (kp == NULL)
4445 	return;
4446     STRCPY(kp->keyword, name_ic);
4447     kp->k_syn.id = id;
4448     kp->k_syn.inc_tag = current_syn_inc_tag;
4449     kp->flags = flags;
4450     kp->k_char = conceal_char;
4451     kp->k_syn.cont_in_list = copy_id_list(cont_in_list);
4452     if (cont_in_list != NULL)
4453 	curwin->w_s->b_syn_containedin = TRUE;
4454     kp->next_list = copy_id_list(next_list);
4455 
4456     if (curwin->w_s->b_syn_ic)
4457 	ht = &curwin->w_s->b_keywtab_ic;
4458     else
4459 	ht = &curwin->w_s->b_keywtab;
4460 
4461     hash = hash_hash(kp->keyword);
4462     hi = hash_lookup(ht, kp->keyword, hash);
4463     if (HASHITEM_EMPTY(hi))
4464     {
4465 	// new keyword, add to hashtable
4466 	kp->ke_next = NULL;
4467 	hash_add_item(ht, hi, kp->keyword, hash);
4468     }
4469     else
4470     {
4471 	// keyword already exists, prepend to list
4472 	kp->ke_next = HI2KE(hi);
4473 	hi->hi_key = KE2HIKEY(kp);
4474     }
4475 }
4476 
4477 /*
4478  * Get the start and end of the group name argument.
4479  * Return a pointer to the first argument.
4480  * Return NULL if the end of the command was found instead of further args.
4481  */
4482     static char_u *
4483 get_group_name(
4484     char_u	*arg,		// start of the argument
4485     char_u	**name_end)	// pointer to end of the name
4486 {
4487     char_u	*rest;
4488 
4489     *name_end = skiptowhite(arg);
4490     rest = skipwhite(*name_end);
4491 
4492     /*
4493      * Check if there are enough arguments.  The first argument may be a
4494      * pattern, where '|' is allowed, so only check for NUL.
4495      */
4496     if (ends_excmd(*arg) || *rest == NUL)
4497 	return NULL;
4498     return rest;
4499 }
4500 
4501 /*
4502  * Check for syntax command option arguments.
4503  * This can be called at any place in the list of arguments, and just picks
4504  * out the arguments that are known.  Can be called several times in a row to
4505  * collect all options in between other arguments.
4506  * Return a pointer to the next argument (which isn't an option).
4507  * Return NULL for any error;
4508  */
4509     static char_u *
4510 get_syn_options(
4511     char_u	    *start,		// next argument to be checked
4512     syn_opt_arg_T   *opt,		// various things
4513     int		    *conceal_char UNUSED,
4514     int		    skip)		// TRUE if skipping over command
4515 {
4516     char_u	*arg = start;
4517     char_u	*gname_start, *gname;
4518     int		syn_id;
4519     int		len;
4520     char	*p;
4521     int		i;
4522     int		fidx;
4523     static struct flag
4524     {
4525 	char	*name;
4526 	int	argtype;
4527 	int	flags;
4528     } flagtab[] = { {"cCoOnNtTaAiInNeEdD",	0,	HL_CONTAINED},
4529 		    {"oOnNeElLiInNeE",		0,	HL_ONELINE},
4530 		    {"kKeEeEpPeEnNdD",		0,	HL_KEEPEND},
4531 		    {"eExXtTeEnNdD",		0,	HL_EXTEND},
4532 		    {"eExXcClLuUdDeEnNlL",	0,	HL_EXCLUDENL},
4533 		    {"tTrRaAnNsSpPaArReEnNtT",	0,	HL_TRANSP},
4534 		    {"sSkKiIpPnNlL",		0,	HL_SKIPNL},
4535 		    {"sSkKiIpPwWhHiItTeE",	0,	HL_SKIPWHITE},
4536 		    {"sSkKiIpPeEmMpPtTyY",	0,	HL_SKIPEMPTY},
4537 		    {"gGrRoOuUpPhHeErReE",	0,	HL_SYNC_HERE},
4538 		    {"gGrRoOuUpPtThHeErReE",	0,	HL_SYNC_THERE},
4539 		    {"dDiIsSpPlLaAyY",		0,	HL_DISPLAY},
4540 		    {"fFoOlLdD",		0,	HL_FOLD},
4541 		    {"cCoOnNcCeEaAlL",		0,	HL_CONCEAL},
4542 		    {"cCoOnNcCeEaAlLeEnNdDsS",	0,	HL_CONCEALENDS},
4543 		    {"cCcChHaArR",		11,	0},
4544 		    {"cCoOnNtTaAiInNsS",	1,	0},
4545 		    {"cCoOnNtTaAiInNeEdDiInN",	2,	0},
4546 		    {"nNeExXtTgGrRoOuUpP",	3,	0},
4547 		};
4548     static char *first_letters = "cCoOkKeEtTsSgGdDfFnN";
4549 
4550     if (arg == NULL)		// already detected error
4551 	return NULL;
4552 
4553 #ifdef FEAT_CONCEAL
4554     if (curwin->w_s->b_syn_conceal)
4555 	opt->flags |= HL_CONCEAL;
4556 #endif
4557 
4558     for (;;)
4559     {
4560 	/*
4561 	 * This is used very often when a large number of keywords is defined.
4562 	 * Need to skip quickly when no option name is found.
4563 	 * Also avoid tolower(), it's slow.
4564 	 */
4565 	if (strchr(first_letters, *arg) == NULL)
4566 	    break;
4567 
4568 	for (fidx = sizeof(flagtab) / sizeof(struct flag); --fidx >= 0; )
4569 	{
4570 	    p = flagtab[fidx].name;
4571 	    for (i = 0, len = 0; p[i] != NUL; i += 2, ++len)
4572 		if (arg[len] != p[i] && arg[len] != p[i + 1])
4573 		    break;
4574 	    if (p[i] == NUL && (VIM_ISWHITE(arg[len])
4575 				    || (flagtab[fidx].argtype > 0
4576 					 ? arg[len] == '='
4577 					 : ends_excmd2(start, arg + len))))
4578 	    {
4579 		if (opt->keyword
4580 			&& (flagtab[fidx].flags == HL_DISPLAY
4581 			    || flagtab[fidx].flags == HL_FOLD
4582 			    || flagtab[fidx].flags == HL_EXTEND))
4583 		    // treat "display", "fold" and "extend" as a keyword
4584 		    fidx = -1;
4585 		break;
4586 	    }
4587 	}
4588 	if (fidx < 0)	    // no match found
4589 	    break;
4590 
4591 	if (flagtab[fidx].argtype == 1)
4592 	{
4593 	    if (!opt->has_cont_list)
4594 	    {
4595 		emsg(_("E395: contains argument not accepted here"));
4596 		return NULL;
4597 	    }
4598 	    if (get_id_list(&arg, 8, &opt->cont_list, skip) == FAIL)
4599 		return NULL;
4600 	}
4601 	else if (flagtab[fidx].argtype == 2)
4602 	{
4603 	    if (get_id_list(&arg, 11, &opt->cont_in_list, skip) == FAIL)
4604 		return NULL;
4605 	}
4606 	else if (flagtab[fidx].argtype == 3)
4607 	{
4608 	    if (get_id_list(&arg, 9, &opt->next_list, skip) == FAIL)
4609 		return NULL;
4610 	}
4611 	else if (flagtab[fidx].argtype == 11 && arg[5] == '=')
4612 	{
4613 	    // cchar=?
4614 	    if (has_mbyte)
4615 	    {
4616 #ifdef FEAT_CONCEAL
4617 		*conceal_char = mb_ptr2char(arg + 6);
4618 #endif
4619 		arg += mb_ptr2len(arg + 6) - 1;
4620 	    }
4621 	    else
4622 	    {
4623 #ifdef FEAT_CONCEAL
4624 		*conceal_char = arg[6];
4625 #else
4626 		;
4627 #endif
4628 	    }
4629 #ifdef FEAT_CONCEAL
4630 	    if (!vim_isprintc_strict(*conceal_char))
4631 	    {
4632 		emsg(_("E844: invalid cchar value"));
4633 		return NULL;
4634 	    }
4635 #endif
4636 	    arg = skipwhite(arg + 7);
4637 	}
4638 	else
4639 	{
4640 	    opt->flags |= flagtab[fidx].flags;
4641 	    arg = skipwhite(arg + len);
4642 
4643 	    if (flagtab[fidx].flags == HL_SYNC_HERE
4644 		    || flagtab[fidx].flags == HL_SYNC_THERE)
4645 	    {
4646 		if (opt->sync_idx == NULL)
4647 		{
4648 		    emsg(_("E393: group[t]here not accepted here"));
4649 		    return NULL;
4650 		}
4651 		gname_start = arg;
4652 		arg = skiptowhite(arg);
4653 		if (gname_start == arg)
4654 		    return NULL;
4655 		gname = vim_strnsave(gname_start, arg - gname_start);
4656 		if (gname == NULL)
4657 		    return NULL;
4658 		if (STRCMP(gname, "NONE") == 0)
4659 		    *opt->sync_idx = NONE_IDX;
4660 		else
4661 		{
4662 		    syn_id = syn_name2id(gname);
4663 		    for (i = curwin->w_s->b_syn_patterns.ga_len; --i >= 0; )
4664 			if (SYN_ITEMS(curwin->w_s)[i].sp_syn.id == syn_id
4665 			      && SYN_ITEMS(curwin->w_s)[i].sp_type
4666 							       == SPTYPE_START)
4667 			{
4668 			    *opt->sync_idx = i;
4669 			    break;
4670 			}
4671 		    if (i < 0)
4672 		    {
4673 			semsg(_("E394: Didn't find region item for %s"), gname);
4674 			vim_free(gname);
4675 			return NULL;
4676 		    }
4677 		}
4678 
4679 		vim_free(gname);
4680 		arg = skipwhite(arg);
4681 	    }
4682 #ifdef FEAT_FOLDING
4683 	    else if (flagtab[fidx].flags == HL_FOLD
4684 						&& foldmethodIsSyntax(curwin))
4685 		// Need to update folds later.
4686 		foldUpdateAll(curwin);
4687 #endif
4688 	}
4689     }
4690 
4691     return arg;
4692 }
4693 
4694 /*
4695  * Adjustments to syntax item when declared in a ":syn include"'d file.
4696  * Set the contained flag, and if the item is not already contained, add it
4697  * to the specified top-level group, if any.
4698  */
4699     static void
4700 syn_incl_toplevel(int id, int *flagsp)
4701 {
4702     if ((*flagsp & HL_CONTAINED) || curwin->w_s->b_syn_topgrp == 0)
4703 	return;
4704     *flagsp |= HL_CONTAINED;
4705     if (curwin->w_s->b_syn_topgrp >= SYNID_CLUSTER)
4706     {
4707 	// We have to alloc this, because syn_combine_list() will free it.
4708 	short	    *grp_list = ALLOC_MULT(short, 2);
4709 	int	    tlg_id = curwin->w_s->b_syn_topgrp - SYNID_CLUSTER;
4710 
4711 	if (grp_list != NULL)
4712 	{
4713 	    grp_list[0] = id;
4714 	    grp_list[1] = 0;
4715 	    syn_combine_list(&SYN_CLSTR(curwin->w_s)[tlg_id].scl_list,
4716 						       &grp_list, CLUSTER_ADD);
4717 	}
4718     }
4719 }
4720 
4721 /*
4722  * Handle ":syntax include [@{group-name}] filename" command.
4723  */
4724     static void
4725 syn_cmd_include(exarg_T *eap, int syncing UNUSED)
4726 {
4727     char_u	*arg = eap->arg;
4728     int		sgl_id = 1;
4729     char_u	*group_name_end;
4730     char_u	*rest;
4731     char	*errormsg = NULL;
4732     int		prev_toplvl_grp;
4733     int		prev_syn_inc_tag;
4734     int		source = FALSE;
4735 
4736     eap->nextcmd = find_nextcmd(arg);
4737     if (eap->skip)
4738 	return;
4739 
4740     if (arg[0] == '@')
4741     {
4742 	++arg;
4743 	rest = get_group_name(arg, &group_name_end);
4744 	if (rest == NULL)
4745 	{
4746 	    emsg(_("E397: Filename required"));
4747 	    return;
4748 	}
4749 	sgl_id = syn_check_cluster(arg, (int)(group_name_end - arg));
4750 	if (sgl_id == 0)
4751 	    return;
4752 	// separate_nextcmd() and expand_filename() depend on this
4753 	eap->arg = rest;
4754     }
4755 
4756     /*
4757      * Everything that's left, up to the next command, should be the
4758      * filename to include.
4759      */
4760     eap->argt |= (EX_XFILE | EX_NOSPC);
4761     separate_nextcmd(eap);
4762     if (*eap->arg == '<' || *eap->arg == '$' || mch_isFullName(eap->arg))
4763     {
4764 	// For an absolute path, "$VIM/..." or "<sfile>.." we ":source" the
4765 	// file.  Need to expand the file name first.  In other cases
4766 	// ":runtime!" is used.
4767 	source = TRUE;
4768 	if (expand_filename(eap, syn_cmdlinep, &errormsg) == FAIL)
4769 	{
4770 	    if (errormsg != NULL)
4771 		emsg(errormsg);
4772 	    return;
4773 	}
4774     }
4775 
4776     /*
4777      * Save and restore the existing top-level grouplist id and ":syn
4778      * include" tag around the actual inclusion.
4779      */
4780     if (running_syn_inc_tag >= MAX_SYN_INC_TAG)
4781     {
4782 	emsg(_("E847: Too many syntax includes"));
4783 	return;
4784     }
4785     prev_syn_inc_tag = current_syn_inc_tag;
4786     current_syn_inc_tag = ++running_syn_inc_tag;
4787     prev_toplvl_grp = curwin->w_s->b_syn_topgrp;
4788     curwin->w_s->b_syn_topgrp = sgl_id;
4789     if (source ? do_source(eap->arg, FALSE, DOSO_NONE, NULL) == FAIL
4790 				: source_runtime(eap->arg, DIP_ALL) == FAIL)
4791 	semsg(_(e_notopen), eap->arg);
4792     curwin->w_s->b_syn_topgrp = prev_toplvl_grp;
4793     current_syn_inc_tag = prev_syn_inc_tag;
4794 }
4795 
4796 /*
4797  * Handle ":syntax keyword {group-name} [{option}] keyword .." command.
4798  */
4799     static void
4800 syn_cmd_keyword(exarg_T *eap, int syncing UNUSED)
4801 {
4802     char_u	*arg = eap->arg;
4803     char_u	*group_name_end;
4804     int		syn_id;
4805     char_u	*rest;
4806     char_u	*keyword_copy = NULL;
4807     char_u	*p;
4808     char_u	*kw;
4809     syn_opt_arg_T syn_opt_arg;
4810     int		cnt;
4811     int		conceal_char = NUL;
4812 
4813     rest = get_group_name(arg, &group_name_end);
4814 
4815     if (rest != NULL)
4816     {
4817 	if (eap->skip)
4818 	    syn_id = -1;
4819 	else
4820 	    syn_id = syn_check_group(arg, (int)(group_name_end - arg));
4821 	if (syn_id != 0)
4822 	    // allocate a buffer, for removing backslashes in the keyword
4823 	    keyword_copy = alloc(STRLEN(rest) + 1);
4824 	if (keyword_copy != NULL)
4825 	{
4826 	    syn_opt_arg.flags = 0;
4827 	    syn_opt_arg.keyword = TRUE;
4828 	    syn_opt_arg.sync_idx = NULL;
4829 	    syn_opt_arg.has_cont_list = FALSE;
4830 	    syn_opt_arg.cont_in_list = NULL;
4831 	    syn_opt_arg.next_list = NULL;
4832 
4833 	    /*
4834 	     * The options given apply to ALL keywords, so all options must be
4835 	     * found before keywords can be created.
4836 	     * 1: collect the options and copy the keywords to keyword_copy.
4837 	     */
4838 	    cnt = 0;
4839 	    p = keyword_copy;
4840 	    for ( ; rest != NULL && !ends_excmd2(eap->arg, rest);
4841 							rest = skipwhite(rest))
4842 	    {
4843 		rest = get_syn_options(rest, &syn_opt_arg, &conceal_char,
4844 								    eap->skip);
4845 		if (rest == NULL || ends_excmd2(eap->arg, rest))
4846 		    break;
4847 		// Copy the keyword, removing backslashes, and add a NUL.
4848 		while (*rest != NUL && !VIM_ISWHITE(*rest))
4849 		{
4850 		    if (*rest == '\\' && rest[1] != NUL)
4851 			++rest;
4852 		    *p++ = *rest++;
4853 		}
4854 		*p++ = NUL;
4855 		++cnt;
4856 	    }
4857 
4858 	    if (!eap->skip)
4859 	    {
4860 		// Adjust flags for use of ":syn include".
4861 		syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
4862 
4863 		/*
4864 		 * 2: Add an entry for each keyword.
4865 		 */
4866 		for (kw = keyword_copy; --cnt >= 0; kw += STRLEN(kw) + 1)
4867 		{
4868 		    for (p = vim_strchr(kw, '['); ; )
4869 		    {
4870 			if (p != NULL)
4871 			    *p = NUL;
4872 			add_keyword(kw, syn_id, syn_opt_arg.flags,
4873 				syn_opt_arg.cont_in_list,
4874 					 syn_opt_arg.next_list, conceal_char);
4875 			if (p == NULL)
4876 			    break;
4877 			if (p[1] == NUL)
4878 			{
4879 			    semsg(_("E789: Missing ']': %s"), kw);
4880 			    goto error;
4881 			}
4882 			if (p[1] == ']')
4883 			{
4884 			    if (p[2] != NUL)
4885 			    {
4886 				semsg(_("E890: trailing char after ']': %s]%s"),
4887 								kw, &p[2]);
4888 				goto error;
4889 			    }
4890 			    kw = p + 1;		// skip over the "]"
4891 			    break;
4892 			}
4893 			if (has_mbyte)
4894 			{
4895 			    int l = (*mb_ptr2len)(p + 1);
4896 
4897 			    mch_memmove(p, p + 1, l);
4898 			    p += l;
4899 			}
4900 			else
4901 			{
4902 			    p[0] = p[1];
4903 			    ++p;
4904 			}
4905 		    }
4906 		}
4907 	    }
4908 error:
4909 	    vim_free(keyword_copy);
4910 	    vim_free(syn_opt_arg.cont_in_list);
4911 	    vim_free(syn_opt_arg.next_list);
4912 	}
4913     }
4914 
4915     if (rest != NULL)
4916 	eap->nextcmd = check_nextcmd(rest);
4917     else
4918 	semsg(_(e_invarg2), arg);
4919 
4920     redraw_curbuf_later(SOME_VALID);
4921     syn_stack_free_all(curwin->w_s);		// Need to recompute all syntax.
4922 }
4923 
4924 /*
4925  * Handle ":syntax match {name} [{options}] {pattern} [{options}]".
4926  *
4927  * Also ":syntax sync match {name} [[grouphere | groupthere] {group-name}] .."
4928  */
4929     static void
4930 syn_cmd_match(
4931     exarg_T	*eap,
4932     int		syncing)	    // TRUE for ":syntax sync match .. "
4933 {
4934     char_u	*arg = eap->arg;
4935     char_u	*group_name_end;
4936     char_u	*rest;
4937     synpat_T	item;		// the item found in the line
4938     int		syn_id;
4939     int		idx;
4940     syn_opt_arg_T syn_opt_arg;
4941     int		sync_idx = 0;
4942     int		conceal_char = NUL;
4943     int		orig_called_emsg = called_emsg;
4944 
4945     // Isolate the group name, check for validity
4946     rest = get_group_name(arg, &group_name_end);
4947 
4948     // Get options before the pattern
4949     syn_opt_arg.flags = 0;
4950     syn_opt_arg.keyword = FALSE;
4951     syn_opt_arg.sync_idx = syncing ? &sync_idx : NULL;
4952     syn_opt_arg.has_cont_list = TRUE;
4953     syn_opt_arg.cont_list = NULL;
4954     syn_opt_arg.cont_in_list = NULL;
4955     syn_opt_arg.next_list = NULL;
4956     rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
4957 
4958     // get the pattern.
4959     init_syn_patterns();
4960     CLEAR_FIELD(item);
4961     rest = get_syn_pattern(rest, &item);
4962     if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL))
4963 	syn_opt_arg.flags |= HL_HAS_EOL;
4964 
4965     // Get options after the pattern
4966     rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
4967 
4968     if (rest != NULL)		// all arguments are valid
4969     {
4970 	/*
4971 	 * Check for trailing command and illegal trailing arguments.
4972 	 */
4973 	eap->nextcmd = check_nextcmd(rest);
4974 	if (!ends_excmd2(eap->cmd, rest) || eap->skip)
4975 	    rest = NULL;
4976 	else if (ga_grow(&curwin->w_s->b_syn_patterns, 1) != FAIL
4977 		&& (syn_id = syn_check_group(arg,
4978 					   (int)(group_name_end - arg))) != 0)
4979 	{
4980 	    syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
4981 	    /*
4982 	     * Store the pattern in the syn_items list
4983 	     */
4984 	    idx = curwin->w_s->b_syn_patterns.ga_len;
4985 	    SYN_ITEMS(curwin->w_s)[idx] = item;
4986 	    SYN_ITEMS(curwin->w_s)[idx].sp_syncing = syncing;
4987 	    SYN_ITEMS(curwin->w_s)[idx].sp_type = SPTYPE_MATCH;
4988 	    SYN_ITEMS(curwin->w_s)[idx].sp_syn.id = syn_id;
4989 	    SYN_ITEMS(curwin->w_s)[idx].sp_syn.inc_tag = current_syn_inc_tag;
4990 	    SYN_ITEMS(curwin->w_s)[idx].sp_flags = syn_opt_arg.flags;
4991 	    SYN_ITEMS(curwin->w_s)[idx].sp_sync_idx = sync_idx;
4992 	    SYN_ITEMS(curwin->w_s)[idx].sp_cont_list = syn_opt_arg.cont_list;
4993 	    SYN_ITEMS(curwin->w_s)[idx].sp_syn.cont_in_list =
4994 						     syn_opt_arg.cont_in_list;
4995 #ifdef FEAT_CONCEAL
4996 	    SYN_ITEMS(curwin->w_s)[idx].sp_cchar = conceal_char;
4997 #endif
4998 	    if (syn_opt_arg.cont_in_list != NULL)
4999 		curwin->w_s->b_syn_containedin = TRUE;
5000 	    SYN_ITEMS(curwin->w_s)[idx].sp_next_list = syn_opt_arg.next_list;
5001 	    ++curwin->w_s->b_syn_patterns.ga_len;
5002 
5003 	    // remember that we found a match for syncing on
5004 	    if (syn_opt_arg.flags & (HL_SYNC_HERE|HL_SYNC_THERE))
5005 		curwin->w_s->b_syn_sync_flags |= SF_MATCH;
5006 #ifdef FEAT_FOLDING
5007 	    if (syn_opt_arg.flags & HL_FOLD)
5008 		++curwin->w_s->b_syn_folditems;
5009 #endif
5010 
5011 	    redraw_curbuf_later(SOME_VALID);
5012 	    syn_stack_free_all(curwin->w_s);	// Need to recompute all syntax.
5013 	    return;	// don't free the progs and patterns now
5014 	}
5015     }
5016 
5017     /*
5018      * Something failed, free the allocated memory.
5019      */
5020     vim_regfree(item.sp_prog);
5021     vim_free(item.sp_pattern);
5022     vim_free(syn_opt_arg.cont_list);
5023     vim_free(syn_opt_arg.cont_in_list);
5024     vim_free(syn_opt_arg.next_list);
5025 
5026     if (rest == NULL && called_emsg == orig_called_emsg)
5027 	semsg(_(e_invarg2), arg);
5028 }
5029 
5030 /*
5031  * Handle ":syntax region {group-name} [matchgroup={group-name}]
5032  *		start {start} .. [skip {skip}] end {end} .. [{options}]".
5033  */
5034     static void
5035 syn_cmd_region(
5036     exarg_T	*eap,
5037     int		syncing)	    // TRUE for ":syntax sync region .."
5038 {
5039     char_u		*arg = eap->arg;
5040     char_u		*group_name_end;
5041     char_u		*rest;			// next arg, NULL on error
5042     char_u		*key_end;
5043     char_u		*key = NULL;
5044     char_u		*p;
5045     int			item;
5046 #define ITEM_START	    0
5047 #define ITEM_SKIP	    1
5048 #define ITEM_END	    2
5049 #define ITEM_MATCHGROUP	    3
5050     struct pat_ptr
5051     {
5052 	synpat_T	*pp_synp;		// pointer to syn_pattern
5053 	int		pp_matchgroup_id;	// matchgroup ID
5054 	struct pat_ptr	*pp_next;		// pointer to next pat_ptr
5055     }			*(pat_ptrs[3]);
5056 					// patterns found in the line
5057     struct pat_ptr	*ppp;
5058     struct pat_ptr	*ppp_next;
5059     int			pat_count = 0;		// nr of syn_patterns found
5060     int			syn_id;
5061     int			matchgroup_id = 0;
5062     int			not_enough = FALSE;	// not enough arguments
5063     int			illegal = FALSE;	// illegal arguments
5064     int			success = FALSE;
5065     int			idx;
5066     syn_opt_arg_T	syn_opt_arg;
5067     int			conceal_char = NUL;
5068 
5069     // Isolate the group name, check for validity
5070     rest = get_group_name(arg, &group_name_end);
5071 
5072     pat_ptrs[0] = NULL;
5073     pat_ptrs[1] = NULL;
5074     pat_ptrs[2] = NULL;
5075 
5076     init_syn_patterns();
5077 
5078     syn_opt_arg.flags = 0;
5079     syn_opt_arg.keyword = FALSE;
5080     syn_opt_arg.sync_idx = NULL;
5081     syn_opt_arg.has_cont_list = TRUE;
5082     syn_opt_arg.cont_list = NULL;
5083     syn_opt_arg.cont_in_list = NULL;
5084     syn_opt_arg.next_list = NULL;
5085 
5086     /*
5087      * get the options, patterns and matchgroup.
5088      */
5089     while (rest != NULL && !ends_excmd2(eap->cmd, rest))
5090     {
5091 	// Check for option arguments
5092 	rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
5093 	if (rest == NULL || ends_excmd2(eap->cmd, rest))
5094 	    break;
5095 
5096 	// must be a pattern or matchgroup then
5097 	key_end = rest;
5098 	while (*key_end && !VIM_ISWHITE(*key_end) && *key_end != '=')
5099 	    ++key_end;
5100 	vim_free(key);
5101 	key = vim_strnsave_up(rest, key_end - rest);
5102 	if (key == NULL)			// out of memory
5103 	{
5104 	    rest = NULL;
5105 	    break;
5106 	}
5107 	if (STRCMP(key, "MATCHGROUP") == 0)
5108 	    item = ITEM_MATCHGROUP;
5109 	else if (STRCMP(key, "START") == 0)
5110 	    item = ITEM_START;
5111 	else if (STRCMP(key, "END") == 0)
5112 	    item = ITEM_END;
5113 	else if (STRCMP(key, "SKIP") == 0)
5114 	{
5115 	    if (pat_ptrs[ITEM_SKIP] != NULL)	// one skip pattern allowed
5116 	    {
5117 		illegal = TRUE;
5118 		break;
5119 	    }
5120 	    item = ITEM_SKIP;
5121 	}
5122 	else
5123 	    break;
5124 	rest = skipwhite(key_end);
5125 	if (*rest != '=')
5126 	{
5127 	    rest = NULL;
5128 	    semsg(_("E398: Missing '=': %s"), arg);
5129 	    break;
5130 	}
5131 	rest = skipwhite(rest + 1);
5132 	if (*rest == NUL)
5133 	{
5134 	    not_enough = TRUE;
5135 	    break;
5136 	}
5137 
5138 	if (item == ITEM_MATCHGROUP)
5139 	{
5140 	    p = skiptowhite(rest);
5141 	    if ((p - rest == 4 && STRNCMP(rest, "NONE", 4) == 0) || eap->skip)
5142 		matchgroup_id = 0;
5143 	    else
5144 	    {
5145 		matchgroup_id = syn_check_group(rest, (int)(p - rest));
5146 		if (matchgroup_id == 0)
5147 		{
5148 		    illegal = TRUE;
5149 		    break;
5150 		}
5151 	    }
5152 	    rest = skipwhite(p);
5153 	}
5154 	else
5155 	{
5156 	    /*
5157 	     * Allocate room for a syn_pattern, and link it in the list of
5158 	     * syn_patterns for this item, at the start (because the list is
5159 	     * used from end to start).
5160 	     */
5161 	    ppp = ALLOC_ONE(struct pat_ptr);
5162 	    if (ppp == NULL)
5163 	    {
5164 		rest = NULL;
5165 		break;
5166 	    }
5167 	    ppp->pp_next = pat_ptrs[item];
5168 	    pat_ptrs[item] = ppp;
5169 	    ppp->pp_synp = ALLOC_CLEAR_ONE(synpat_T);
5170 	    if (ppp->pp_synp == NULL)
5171 	    {
5172 		rest = NULL;
5173 		break;
5174 	    }
5175 
5176 	    /*
5177 	     * Get the syntax pattern and the following offset(s).
5178 	     */
5179 	    // Enable the appropriate \z specials.
5180 	    if (item == ITEM_START)
5181 		reg_do_extmatch = REX_SET;
5182 	    else if (item == ITEM_SKIP || item == ITEM_END)
5183 		reg_do_extmatch = REX_USE;
5184 	    rest = get_syn_pattern(rest, ppp->pp_synp);
5185 	    reg_do_extmatch = 0;
5186 	    if (item == ITEM_END && vim_regcomp_had_eol()
5187 				       && !(syn_opt_arg.flags & HL_EXCLUDENL))
5188 		ppp->pp_synp->sp_flags |= HL_HAS_EOL;
5189 	    ppp->pp_matchgroup_id = matchgroup_id;
5190 	    ++pat_count;
5191 	}
5192     }
5193     vim_free(key);
5194     if (illegal || not_enough)
5195 	rest = NULL;
5196 
5197     /*
5198      * Must have a "start" and "end" pattern.
5199      */
5200     if (rest != NULL && (pat_ptrs[ITEM_START] == NULL ||
5201 						  pat_ptrs[ITEM_END] == NULL))
5202     {
5203 	not_enough = TRUE;
5204 	rest = NULL;
5205     }
5206 
5207     if (rest != NULL)
5208     {
5209 	/*
5210 	 * Check for trailing garbage or command.
5211 	 * If OK, add the item.
5212 	 */
5213 	eap->nextcmd = check_nextcmd(rest);
5214 	if (!ends_excmd(*rest) || eap->skip)
5215 	    rest = NULL;
5216 	else if (ga_grow(&(curwin->w_s->b_syn_patterns), pat_count) != FAIL
5217 		&& (syn_id = syn_check_group(arg,
5218 					   (int)(group_name_end - arg))) != 0)
5219 	{
5220 	    syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
5221 	    /*
5222 	     * Store the start/skip/end in the syn_items list
5223 	     */
5224 	    idx = curwin->w_s->b_syn_patterns.ga_len;
5225 	    for (item = ITEM_START; item <= ITEM_END; ++item)
5226 	    {
5227 		for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp->pp_next)
5228 		{
5229 		    SYN_ITEMS(curwin->w_s)[idx] = *(ppp->pp_synp);
5230 		    SYN_ITEMS(curwin->w_s)[idx].sp_syncing = syncing;
5231 		    SYN_ITEMS(curwin->w_s)[idx].sp_type =
5232 			    (item == ITEM_START) ? SPTYPE_START :
5233 			    (item == ITEM_SKIP) ? SPTYPE_SKIP : SPTYPE_END;
5234 		    SYN_ITEMS(curwin->w_s)[idx].sp_flags |= syn_opt_arg.flags;
5235 		    SYN_ITEMS(curwin->w_s)[idx].sp_syn.id = syn_id;
5236 		    SYN_ITEMS(curwin->w_s)[idx].sp_syn.inc_tag =
5237 							  current_syn_inc_tag;
5238 		    SYN_ITEMS(curwin->w_s)[idx].sp_syn_match_id =
5239 							ppp->pp_matchgroup_id;
5240 #ifdef FEAT_CONCEAL
5241 		    SYN_ITEMS(curwin->w_s)[idx].sp_cchar = conceal_char;
5242 #endif
5243 		    if (item == ITEM_START)
5244 		    {
5245 			SYN_ITEMS(curwin->w_s)[idx].sp_cont_list =
5246 							syn_opt_arg.cont_list;
5247 			SYN_ITEMS(curwin->w_s)[idx].sp_syn.cont_in_list =
5248 						     syn_opt_arg.cont_in_list;
5249 			if (syn_opt_arg.cont_in_list != NULL)
5250 			    curwin->w_s->b_syn_containedin = TRUE;
5251 			SYN_ITEMS(curwin->w_s)[idx].sp_next_list =
5252 							syn_opt_arg.next_list;
5253 		    }
5254 		    ++curwin->w_s->b_syn_patterns.ga_len;
5255 		    ++idx;
5256 #ifdef FEAT_FOLDING
5257 		    if (syn_opt_arg.flags & HL_FOLD)
5258 			++curwin->w_s->b_syn_folditems;
5259 #endif
5260 		}
5261 	    }
5262 
5263 	    redraw_curbuf_later(SOME_VALID);
5264 	    syn_stack_free_all(curwin->w_s);	// Need to recompute all syntax.
5265 	    success = TRUE;	    // don't free the progs and patterns now
5266 	}
5267     }
5268 
5269     /*
5270      * Free the allocated memory.
5271      */
5272     for (item = ITEM_START; item <= ITEM_END; ++item)
5273 	for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp_next)
5274 	{
5275 	    if (!success && ppp->pp_synp != NULL)
5276 	    {
5277 		vim_regfree(ppp->pp_synp->sp_prog);
5278 		vim_free(ppp->pp_synp->sp_pattern);
5279 	    }
5280 	    vim_free(ppp->pp_synp);
5281 	    ppp_next = ppp->pp_next;
5282 	    vim_free(ppp);
5283 	}
5284 
5285     if (!success)
5286     {
5287 	vim_free(syn_opt_arg.cont_list);
5288 	vim_free(syn_opt_arg.cont_in_list);
5289 	vim_free(syn_opt_arg.next_list);
5290 	if (not_enough)
5291 	    semsg(_("E399: Not enough arguments: syntax region %s"), arg);
5292 	else if (illegal || rest == NULL)
5293 	    semsg(_(e_invarg2), arg);
5294     }
5295 }
5296 
5297 /*
5298  * A simple syntax group ID comparison function suitable for use in qsort()
5299  */
5300     static int
5301 syn_compare_stub(const void *v1, const void *v2)
5302 {
5303     const short	*s1 = v1;
5304     const short	*s2 = v2;
5305 
5306     return (*s1 > *s2 ? 1 : *s1 < *s2 ? -1 : 0);
5307 }
5308 
5309 /*
5310  * Combines lists of syntax clusters.
5311  * *clstr1 and *clstr2 must both be allocated memory; they will be consumed.
5312  */
5313     static void
5314 syn_combine_list(short **clstr1, short **clstr2, int list_op)
5315 {
5316     int		count1 = 0;
5317     int		count2 = 0;
5318     short	*g1;
5319     short	*g2;
5320     short	*clstr = NULL;
5321     int		count;
5322     int		round;
5323 
5324     /*
5325      * Handle degenerate cases.
5326      */
5327     if (*clstr2 == NULL)
5328 	return;
5329     if (*clstr1 == NULL || list_op == CLUSTER_REPLACE)
5330     {
5331 	if (list_op == CLUSTER_REPLACE)
5332 	    vim_free(*clstr1);
5333 	if (list_op == CLUSTER_REPLACE || list_op == CLUSTER_ADD)
5334 	    *clstr1 = *clstr2;
5335 	else
5336 	    vim_free(*clstr2);
5337 	return;
5338     }
5339 
5340     for (g1 = *clstr1; *g1; g1++)
5341 	++count1;
5342     for (g2 = *clstr2; *g2; g2++)
5343 	++count2;
5344 
5345     /*
5346      * For speed purposes, sort both lists.
5347      */
5348     qsort(*clstr1, (size_t)count1, sizeof(short), syn_compare_stub);
5349     qsort(*clstr2, (size_t)count2, sizeof(short), syn_compare_stub);
5350 
5351     /*
5352      * We proceed in two passes; in round 1, we count the elements to place
5353      * in the new list, and in round 2, we allocate and populate the new
5354      * list.  For speed, we use a mergesort-like method, adding the smaller
5355      * of the current elements in each list to the new list.
5356      */
5357     for (round = 1; round <= 2; round++)
5358     {
5359 	g1 = *clstr1;
5360 	g2 = *clstr2;
5361 	count = 0;
5362 
5363 	/*
5364 	 * First, loop through the lists until one of them is empty.
5365 	 */
5366 	while (*g1 && *g2)
5367 	{
5368 	    /*
5369 	     * We always want to add from the first list.
5370 	     */
5371 	    if (*g1 < *g2)
5372 	    {
5373 		if (round == 2)
5374 		    clstr[count] = *g1;
5375 		count++;
5376 		g1++;
5377 		continue;
5378 	    }
5379 	    /*
5380 	     * We only want to add from the second list if we're adding the
5381 	     * lists.
5382 	     */
5383 	    if (list_op == CLUSTER_ADD)
5384 	    {
5385 		if (round == 2)
5386 		    clstr[count] = *g2;
5387 		count++;
5388 	    }
5389 	    if (*g1 == *g2)
5390 		g1++;
5391 	    g2++;
5392 	}
5393 
5394 	/*
5395 	 * Now add the leftovers from whichever list didn't get finished
5396 	 * first.  As before, we only want to add from the second list if
5397 	 * we're adding the lists.
5398 	 */
5399 	for (; *g1; g1++, count++)
5400 	    if (round == 2)
5401 		clstr[count] = *g1;
5402 	if (list_op == CLUSTER_ADD)
5403 	    for (; *g2; g2++, count++)
5404 		if (round == 2)
5405 		    clstr[count] = *g2;
5406 
5407 	if (round == 1)
5408 	{
5409 	    /*
5410 	     * If the group ended up empty, we don't need to allocate any
5411 	     * space for it.
5412 	     */
5413 	    if (count == 0)
5414 	    {
5415 		clstr = NULL;
5416 		break;
5417 	    }
5418 	    clstr = ALLOC_MULT(short, count + 1);
5419 	    if (clstr == NULL)
5420 		break;
5421 	    clstr[count] = 0;
5422 	}
5423     }
5424 
5425     /*
5426      * Finally, put the new list in place.
5427      */
5428     vim_free(*clstr1);
5429     vim_free(*clstr2);
5430     *clstr1 = clstr;
5431 }
5432 
5433 /*
5434  * Lookup a syntax cluster name and return its ID.
5435  * If it is not found, 0 is returned.
5436  */
5437     static int
5438 syn_scl_name2id(char_u *name)
5439 {
5440     int		i;
5441     char_u	*name_u;
5442 
5443     // Avoid using stricmp() too much, it's slow on some systems
5444     name_u = vim_strsave_up(name);
5445     if (name_u == NULL)
5446 	return 0;
5447     for (i = curwin->w_s->b_syn_clusters.ga_len; --i >= 0; )
5448 	if (SYN_CLSTR(curwin->w_s)[i].scl_name_u != NULL
5449 		&& STRCMP(name_u, SYN_CLSTR(curwin->w_s)[i].scl_name_u) == 0)
5450 	    break;
5451     vim_free(name_u);
5452     return (i < 0 ? 0 : i + SYNID_CLUSTER);
5453 }
5454 
5455 /*
5456  * Like syn_scl_name2id(), but take a pointer + length argument.
5457  */
5458     static int
5459 syn_scl_namen2id(char_u *linep, int len)
5460 {
5461     char_u  *name;
5462     int	    id = 0;
5463 
5464     name = vim_strnsave(linep, len);
5465     if (name != NULL)
5466     {
5467 	id = syn_scl_name2id(name);
5468 	vim_free(name);
5469     }
5470     return id;
5471 }
5472 
5473 /*
5474  * Find syntax cluster name in the table and return its ID.
5475  * The argument is a pointer to the name and the length of the name.
5476  * If it doesn't exist yet, a new entry is created.
5477  * Return 0 for failure.
5478  */
5479     static int
5480 syn_check_cluster(char_u *pp, int len)
5481 {
5482     int		id;
5483     char_u	*name;
5484 
5485     name = vim_strnsave(pp, len);
5486     if (name == NULL)
5487 	return 0;
5488 
5489     id = syn_scl_name2id(name);
5490     if (id == 0)			// doesn't exist yet
5491 	id = syn_add_cluster(name);
5492     else
5493 	vim_free(name);
5494     return id;
5495 }
5496 
5497 /*
5498  * Add new syntax cluster and return its ID.
5499  * "name" must be an allocated string, it will be consumed.
5500  * Return 0 for failure.
5501  */
5502     static int
5503 syn_add_cluster(char_u *name)
5504 {
5505     int		len;
5506 
5507     /*
5508      * First call for this growarray: init growing array.
5509      */
5510     if (curwin->w_s->b_syn_clusters.ga_data == NULL)
5511     {
5512 	curwin->w_s->b_syn_clusters.ga_itemsize = sizeof(syn_cluster_T);
5513 	curwin->w_s->b_syn_clusters.ga_growsize = 10;
5514     }
5515 
5516     len = curwin->w_s->b_syn_clusters.ga_len;
5517     if (len >= MAX_CLUSTER_ID)
5518     {
5519 	emsg(_("E848: Too many syntax clusters"));
5520 	vim_free(name);
5521 	return 0;
5522     }
5523 
5524     /*
5525      * Make room for at least one other cluster entry.
5526      */
5527     if (ga_grow(&curwin->w_s->b_syn_clusters, 1) == FAIL)
5528     {
5529 	vim_free(name);
5530 	return 0;
5531     }
5532 
5533     CLEAR_POINTER(&(SYN_CLSTR(curwin->w_s)[len]));
5534     SYN_CLSTR(curwin->w_s)[len].scl_name = name;
5535     SYN_CLSTR(curwin->w_s)[len].scl_name_u = vim_strsave_up(name);
5536     SYN_CLSTR(curwin->w_s)[len].scl_list = NULL;
5537     ++curwin->w_s->b_syn_clusters.ga_len;
5538 
5539     if (STRICMP(name, "Spell") == 0)
5540 	curwin->w_s->b_spell_cluster_id = len + SYNID_CLUSTER;
5541     if (STRICMP(name, "NoSpell") == 0)
5542 	curwin->w_s->b_nospell_cluster_id = len + SYNID_CLUSTER;
5543 
5544     return len + SYNID_CLUSTER;
5545 }
5546 
5547 /*
5548  * Handle ":syntax cluster {cluster-name} [contains={groupname},..]
5549  *		[add={groupname},..] [remove={groupname},..]".
5550  */
5551     static void
5552 syn_cmd_cluster(exarg_T *eap, int syncing UNUSED)
5553 {
5554     char_u	*arg = eap->arg;
5555     char_u	*group_name_end;
5556     char_u	*rest;
5557     int		scl_id;
5558     short	*clstr_list;
5559     int		got_clstr = FALSE;
5560     int		opt_len;
5561     int		list_op;
5562 
5563     eap->nextcmd = find_nextcmd(arg);
5564     if (eap->skip)
5565 	return;
5566 
5567     rest = get_group_name(arg, &group_name_end);
5568 
5569     if (rest != NULL)
5570     {
5571 	scl_id = syn_check_cluster(arg, (int)(group_name_end - arg));
5572 	if (scl_id == 0)
5573 	    return;
5574 	scl_id -= SYNID_CLUSTER;
5575 
5576 	for (;;)
5577 	{
5578 	    if (STRNICMP(rest, "add", 3) == 0
5579 		    && (VIM_ISWHITE(rest[3]) || rest[3] == '='))
5580 	    {
5581 		opt_len = 3;
5582 		list_op = CLUSTER_ADD;
5583 	    }
5584 	    else if (STRNICMP(rest, "remove", 6) == 0
5585 		    && (VIM_ISWHITE(rest[6]) || rest[6] == '='))
5586 	    {
5587 		opt_len = 6;
5588 		list_op = CLUSTER_SUBTRACT;
5589 	    }
5590 	    else if (STRNICMP(rest, "contains", 8) == 0
5591 			&& (VIM_ISWHITE(rest[8]) || rest[8] == '='))
5592 	    {
5593 		opt_len = 8;
5594 		list_op = CLUSTER_REPLACE;
5595 	    }
5596 	    else
5597 		break;
5598 
5599 	    clstr_list = NULL;
5600 	    if (get_id_list(&rest, opt_len, &clstr_list, eap->skip) == FAIL)
5601 	    {
5602 		semsg(_(e_invarg2), rest);
5603 		break;
5604 	    }
5605 	    if (scl_id >= 0)
5606 		syn_combine_list(&SYN_CLSTR(curwin->w_s)[scl_id].scl_list,
5607 			     &clstr_list, list_op);
5608 	    else
5609 		vim_free(clstr_list);
5610 	    got_clstr = TRUE;
5611 	}
5612 
5613 	if (got_clstr)
5614 	{
5615 	    redraw_curbuf_later(SOME_VALID);
5616 	    syn_stack_free_all(curwin->w_s);	// Need to recompute all.
5617 	}
5618     }
5619 
5620     if (!got_clstr)
5621 	emsg(_("E400: No cluster specified"));
5622     if (rest == NULL || !ends_excmd2(eap->cmd, rest))
5623 	semsg(_(e_invarg2), arg);
5624 }
5625 
5626 /*
5627  * On first call for current buffer: Init growing array.
5628  */
5629     static void
5630 init_syn_patterns(void)
5631 {
5632     curwin->w_s->b_syn_patterns.ga_itemsize = sizeof(synpat_T);
5633     curwin->w_s->b_syn_patterns.ga_growsize = 10;
5634 }
5635 
5636 /*
5637  * Get one pattern for a ":syntax match" or ":syntax region" command.
5638  * Stores the pattern and program in a synpat_T.
5639  * Returns a pointer to the next argument, or NULL in case of an error.
5640  */
5641     static char_u *
5642 get_syn_pattern(char_u *arg, synpat_T *ci)
5643 {
5644     char_u	*end;
5645     int		*p;
5646     int		idx;
5647     char_u	*cpo_save;
5648 
5649     // need at least three chars
5650     if (arg == NULL || arg[0] == NUL || arg[1] == NUL || arg[2] == NUL)
5651 	return NULL;
5652 
5653     end = skip_regexp(arg + 1, *arg, TRUE);
5654     if (*end != *arg)			    // end delimiter not found
5655     {
5656 	semsg(_("E401: Pattern delimiter not found: %s"), arg);
5657 	return NULL;
5658     }
5659     // store the pattern and compiled regexp program
5660     if ((ci->sp_pattern = vim_strnsave(arg + 1, end - arg - 1)) == NULL)
5661 	return NULL;
5662 
5663     // Make 'cpoptions' empty, to avoid the 'l' flag
5664     cpo_save = p_cpo;
5665     p_cpo = (char_u *)"";
5666     ci->sp_prog = vim_regcomp(ci->sp_pattern, RE_MAGIC);
5667     p_cpo = cpo_save;
5668 
5669     if (ci->sp_prog == NULL)
5670 	return NULL;
5671     ci->sp_ic = curwin->w_s->b_syn_ic;
5672 #ifdef FEAT_PROFILE
5673     syn_clear_time(&ci->sp_time);
5674 #endif
5675 
5676     /*
5677      * Check for a match, highlight or region offset.
5678      */
5679     ++end;
5680     do
5681     {
5682 	for (idx = SPO_COUNT; --idx >= 0; )
5683 	    if (STRNCMP(end, spo_name_tab[idx], 3) == 0)
5684 		break;
5685 	if (idx >= 0)
5686 	{
5687 	    p = &(ci->sp_offsets[idx]);
5688 	    if (idx != SPO_LC_OFF)
5689 		switch (end[3])
5690 		{
5691 		    case 's':   break;
5692 		    case 'b':   break;
5693 		    case 'e':   idx += SPO_COUNT; break;
5694 		    default:    idx = -1; break;
5695 		}
5696 	    if (idx >= 0)
5697 	    {
5698 		ci->sp_off_flags |= (1 << idx);
5699 		if (idx == SPO_LC_OFF)	    // lc=99
5700 		{
5701 		    end += 3;
5702 		    *p = getdigits(&end);
5703 
5704 		    // "lc=" offset automatically sets "ms=" offset
5705 		    if (!(ci->sp_off_flags & (1 << SPO_MS_OFF)))
5706 		    {
5707 			ci->sp_off_flags |= (1 << SPO_MS_OFF);
5708 			ci->sp_offsets[SPO_MS_OFF] = *p;
5709 		    }
5710 		}
5711 		else			    // yy=x+99
5712 		{
5713 		    end += 4;
5714 		    if (*end == '+')
5715 		    {
5716 			++end;
5717 			*p = getdigits(&end);		// positive offset
5718 		    }
5719 		    else if (*end == '-')
5720 		    {
5721 			++end;
5722 			*p = -getdigits(&end);		// negative offset
5723 		    }
5724 		}
5725 		if (*end != ',')
5726 		    break;
5727 		++end;
5728 	    }
5729 	}
5730     } while (idx >= 0);
5731 
5732     if (!ends_excmd2(arg, end) && !VIM_ISWHITE(*end))
5733     {
5734 	semsg(_("E402: Garbage after pattern: %s"), arg);
5735 	return NULL;
5736     }
5737     return skipwhite(end);
5738 }
5739 
5740 /*
5741  * Handle ":syntax sync .." command.
5742  */
5743     static void
5744 syn_cmd_sync(exarg_T *eap, int syncing UNUSED)
5745 {
5746     char_u	*arg_start = eap->arg;
5747     char_u	*arg_end;
5748     char_u	*key = NULL;
5749     char_u	*next_arg;
5750     int		illegal = FALSE;
5751     int		finished = FALSE;
5752     long	n;
5753     char_u	*cpo_save;
5754 
5755     if (ends_excmd2(eap->cmd, arg_start))
5756     {
5757 	syn_cmd_list(eap, TRUE);
5758 	return;
5759     }
5760 
5761     while (!ends_excmd2(eap->cmd, arg_start))
5762     {
5763 	arg_end = skiptowhite(arg_start);
5764 	next_arg = skipwhite(arg_end);
5765 	vim_free(key);
5766 	key = vim_strnsave_up(arg_start, arg_end - arg_start);
5767 	if (key == NULL)
5768 	    break;
5769 	if (STRCMP(key, "CCOMMENT") == 0)
5770 	{
5771 	    if (!eap->skip)
5772 		curwin->w_s->b_syn_sync_flags |= SF_CCOMMENT;
5773 	    if (!ends_excmd2(eap->cmd, next_arg))
5774 	    {
5775 		arg_end = skiptowhite(next_arg);
5776 		if (!eap->skip)
5777 		    curwin->w_s->b_syn_sync_id = syn_check_group(next_arg,
5778 						   (int)(arg_end - next_arg));
5779 		next_arg = skipwhite(arg_end);
5780 	    }
5781 	    else if (!eap->skip)
5782 		curwin->w_s->b_syn_sync_id = syn_name2id((char_u *)"Comment");
5783 	}
5784 	else if (  STRNCMP(key, "LINES", 5) == 0
5785 		|| STRNCMP(key, "MINLINES", 8) == 0
5786 		|| STRNCMP(key, "MAXLINES", 8) == 0
5787 		|| STRNCMP(key, "LINEBREAKS", 10) == 0)
5788 	{
5789 	    if (key[4] == 'S')
5790 		arg_end = key + 6;
5791 	    else if (key[0] == 'L')
5792 		arg_end = key + 11;
5793 	    else
5794 		arg_end = key + 9;
5795 	    if (arg_end[-1] != '=' || !VIM_ISDIGIT(*arg_end))
5796 	    {
5797 		illegal = TRUE;
5798 		break;
5799 	    }
5800 	    n = getdigits(&arg_end);
5801 	    if (!eap->skip)
5802 	    {
5803 		if (key[4] == 'B')
5804 		    curwin->w_s->b_syn_sync_linebreaks = n;
5805 		else if (key[1] == 'A')
5806 		    curwin->w_s->b_syn_sync_maxlines = n;
5807 		else
5808 		    curwin->w_s->b_syn_sync_minlines = n;
5809 	    }
5810 	}
5811 	else if (STRCMP(key, "FROMSTART") == 0)
5812 	{
5813 	    if (!eap->skip)
5814 	    {
5815 		curwin->w_s->b_syn_sync_minlines = MAXLNUM;
5816 		curwin->w_s->b_syn_sync_maxlines = 0;
5817 	    }
5818 	}
5819 	else if (STRCMP(key, "LINECONT") == 0)
5820 	{
5821 	    if (*next_arg == NUL)	   // missing pattern
5822 	    {
5823 		illegal = TRUE;
5824 		break;
5825 	    }
5826 	    if (curwin->w_s->b_syn_linecont_pat != NULL)
5827 	    {
5828 		emsg(_("E403: syntax sync: line continuations pattern specified twice"));
5829 		finished = TRUE;
5830 		break;
5831 	    }
5832 	    arg_end = skip_regexp(next_arg + 1, *next_arg, TRUE);
5833 	    if (*arg_end != *next_arg)	    // end delimiter not found
5834 	    {
5835 		illegal = TRUE;
5836 		break;
5837 	    }
5838 
5839 	    if (!eap->skip)
5840 	    {
5841 		// store the pattern and compiled regexp program
5842 		if ((curwin->w_s->b_syn_linecont_pat =
5843 			    vim_strnsave(next_arg + 1,
5844 				      arg_end - next_arg - 1)) == NULL)
5845 		{
5846 		    finished = TRUE;
5847 		    break;
5848 		}
5849 		curwin->w_s->b_syn_linecont_ic = curwin->w_s->b_syn_ic;
5850 
5851 		// Make 'cpoptions' empty, to avoid the 'l' flag
5852 		cpo_save = p_cpo;
5853 		p_cpo = (char_u *)"";
5854 		curwin->w_s->b_syn_linecont_prog =
5855 		       vim_regcomp(curwin->w_s->b_syn_linecont_pat, RE_MAGIC);
5856 		p_cpo = cpo_save;
5857 #ifdef FEAT_PROFILE
5858 		syn_clear_time(&curwin->w_s->b_syn_linecont_time);
5859 #endif
5860 
5861 		if (curwin->w_s->b_syn_linecont_prog == NULL)
5862 		{
5863 		    VIM_CLEAR(curwin->w_s->b_syn_linecont_pat);
5864 		    finished = TRUE;
5865 		    break;
5866 		}
5867 	    }
5868 	    next_arg = skipwhite(arg_end + 1);
5869 	}
5870 	else
5871 	{
5872 	    eap->arg = next_arg;
5873 	    if (STRCMP(key, "MATCH") == 0)
5874 		syn_cmd_match(eap, TRUE);
5875 	    else if (STRCMP(key, "REGION") == 0)
5876 		syn_cmd_region(eap, TRUE);
5877 	    else if (STRCMP(key, "CLEAR") == 0)
5878 		syn_cmd_clear(eap, TRUE);
5879 	    else
5880 		illegal = TRUE;
5881 	    finished = TRUE;
5882 	    break;
5883 	}
5884 	arg_start = next_arg;
5885     }
5886     vim_free(key);
5887     if (illegal)
5888 	semsg(_("E404: Illegal arguments: %s"), arg_start);
5889     else if (!finished)
5890     {
5891 	eap->nextcmd = check_nextcmd(arg_start);
5892 	redraw_curbuf_later(SOME_VALID);
5893 	syn_stack_free_all(curwin->w_s);	// Need to recompute all syntax.
5894     }
5895 }
5896 
5897 /*
5898  * Convert a line of highlight group names into a list of group ID numbers.
5899  * "arg" should point to the "contains" or "nextgroup" keyword.
5900  * "arg" is advanced to after the last group name.
5901  * Careful: the argument is modified (NULs added).
5902  * returns FAIL for some error, OK for success.
5903  */
5904     static int
5905 get_id_list(
5906     char_u	**arg,
5907     int		keylen,		// length of keyword
5908     short	**list,		// where to store the resulting list, if not
5909 				// NULL, the list is silently skipped!
5910     int		skip)
5911 {
5912     char_u	*p = NULL;
5913     char_u	*end;
5914     int		round;
5915     int		count;
5916     int		total_count = 0;
5917     short	*retval = NULL;
5918     char_u	*name;
5919     regmatch_T	regmatch;
5920     int		id;
5921     int		i;
5922     int		failed = FALSE;
5923 
5924     /*
5925      * We parse the list twice:
5926      * round == 1: count the number of items, allocate the array.
5927      * round == 2: fill the array with the items.
5928      * In round 1 new groups may be added, causing the number of items to
5929      * grow when a regexp is used.  In that case round 1 is done once again.
5930      */
5931     for (round = 1; round <= 2; ++round)
5932     {
5933 	/*
5934 	 * skip "contains"
5935 	 */
5936 	p = skipwhite(*arg + keylen);
5937 	if (*p != '=')
5938 	{
5939 	    semsg(_("E405: Missing equal sign: %s"), *arg);
5940 	    break;
5941 	}
5942 	p = skipwhite(p + 1);
5943 	if (ends_excmd2(*arg, p))
5944 	{
5945 	    semsg(_("E406: Empty argument: %s"), *arg);
5946 	    break;
5947 	}
5948 
5949 	/*
5950 	 * parse the arguments after "contains"
5951 	 */
5952 	count = 0;
5953 	while (!ends_excmd2(*arg, p))
5954 	{
5955 	    for (end = p; *end && !VIM_ISWHITE(*end) && *end != ','; ++end)
5956 		;
5957 	    name = alloc(end - p + 3);	    // leave room for "^$"
5958 	    if (name == NULL)
5959 	    {
5960 		failed = TRUE;
5961 		break;
5962 	    }
5963 	    vim_strncpy(name + 1, p, end - p);
5964 	    if (       STRCMP(name + 1, "ALLBUT") == 0
5965 		    || STRCMP(name + 1, "ALL") == 0
5966 		    || STRCMP(name + 1, "TOP") == 0
5967 		    || STRCMP(name + 1, "CONTAINED") == 0)
5968 	    {
5969 		if (TOUPPER_ASC(**arg) != 'C')
5970 		{
5971 		    semsg(_("E407: %s not allowed here"), name + 1);
5972 		    failed = TRUE;
5973 		    vim_free(name);
5974 		    break;
5975 		}
5976 		if (count != 0)
5977 		{
5978 		    semsg(_("E408: %s must be first in contains list"),
5979 								     name + 1);
5980 		    failed = TRUE;
5981 		    vim_free(name);
5982 		    break;
5983 		}
5984 		if (name[1] == 'A')
5985 		    id = SYNID_ALLBUT;
5986 		else if (name[1] == 'T')
5987 		    id = SYNID_TOP;
5988 		else
5989 		    id = SYNID_CONTAINED;
5990 		id += current_syn_inc_tag;
5991 	    }
5992 	    else if (name[1] == '@')
5993 	    {
5994 		if (skip)
5995 		    id = -1;
5996 		else
5997 		    id = syn_check_cluster(name + 2, (int)(end - p - 1));
5998 	    }
5999 	    else
6000 	    {
6001 		/*
6002 		 * Handle full group name.
6003 		 */
6004 		if (vim_strpbrk(name + 1, (char_u *)"\\.*^$~[") == NULL)
6005 		    id = syn_check_group(name + 1, (int)(end - p));
6006 		else
6007 		{
6008 		    /*
6009 		     * Handle match of regexp with group names.
6010 		     */
6011 		    *name = '^';
6012 		    STRCAT(name, "$");
6013 		    regmatch.regprog = vim_regcomp(name, RE_MAGIC);
6014 		    if (regmatch.regprog == NULL)
6015 		    {
6016 			failed = TRUE;
6017 			vim_free(name);
6018 			break;
6019 		    }
6020 
6021 		    regmatch.rm_ic = TRUE;
6022 		    id = 0;
6023 		    for (i = highlight_num_groups(); --i >= 0; )
6024 		    {
6025 			if (vim_regexec(&regmatch, highlight_group_name(i),
6026 								  (colnr_T)0))
6027 			{
6028 			    if (round == 2)
6029 			    {
6030 				// Got more items than expected; can happen
6031 				// when adding items that match:
6032 				// "contains=a.*b,axb".
6033 				// Go back to first round
6034 				if (count >= total_count)
6035 				{
6036 				    vim_free(retval);
6037 				    round = 1;
6038 				}
6039 				else
6040 				    retval[count] = i + 1;
6041 			    }
6042 			    ++count;
6043 			    id = -1;	    // remember that we found one
6044 			}
6045 		    }
6046 		    vim_regfree(regmatch.regprog);
6047 		}
6048 	    }
6049 	    vim_free(name);
6050 	    if (id == 0)
6051 	    {
6052 		semsg(_("E409: Unknown group name: %s"), p);
6053 		failed = TRUE;
6054 		break;
6055 	    }
6056 	    if (id > 0)
6057 	    {
6058 		if (round == 2)
6059 		{
6060 		    // Got more items than expected, go back to first round
6061 		    if (count >= total_count)
6062 		    {
6063 			vim_free(retval);
6064 			round = 1;
6065 		    }
6066 		    else
6067 			retval[count] = id;
6068 		}
6069 		++count;
6070 	    }
6071 	    p = skipwhite(end);
6072 	    if (*p != ',')
6073 		break;
6074 	    p = skipwhite(p + 1);	// skip comma in between arguments
6075 	}
6076 	if (failed)
6077 	    break;
6078 	if (round == 1)
6079 	{
6080 	    retval = ALLOC_MULT(short, count + 1);
6081 	    if (retval == NULL)
6082 		break;
6083 	    retval[count] = 0;	    // zero means end of the list
6084 	    total_count = count;
6085 	}
6086     }
6087 
6088     *arg = p;
6089     if (failed || retval == NULL)
6090     {
6091 	vim_free(retval);
6092 	return FAIL;
6093     }
6094 
6095     if (*list == NULL)
6096 	*list = retval;
6097     else
6098 	vim_free(retval);	// list already found, don't overwrite it
6099 
6100     return OK;
6101 }
6102 
6103 /*
6104  * Make a copy of an ID list.
6105  */
6106     static short *
6107 copy_id_list(short *list)
6108 {
6109     int	    len;
6110     int	    count;
6111     short   *retval;
6112 
6113     if (list == NULL)
6114 	return NULL;
6115 
6116     for (count = 0; list[count]; ++count)
6117 	;
6118     len = (count + 1) * sizeof(short);
6119     retval = alloc(len);
6120     if (retval != NULL)
6121 	mch_memmove(retval, list, (size_t)len);
6122 
6123     return retval;
6124 }
6125 
6126 /*
6127  * Check if syntax group "ssp" is in the ID list "list" of "cur_si".
6128  * "cur_si" can be NULL if not checking the "containedin" list.
6129  * Used to check if a syntax item is in the "contains" or "nextgroup" list of
6130  * the current item.
6131  * This function is called very often, keep it fast!!
6132  */
6133     static int
6134 in_id_list(
6135     stateitem_T	*cur_si,	// current item or NULL
6136     short	*list,		// id list
6137     struct sp_syn *ssp,		// group id and ":syn include" tag of group
6138     int		contained)	// group id is contained
6139 {
6140     int		retval;
6141     short	*scl_list;
6142     short	item;
6143     short	id = ssp->id;
6144     static int	depth = 0;
6145     int		r;
6146 
6147     // If ssp has a "containedin" list and "cur_si" is in it, return TRUE.
6148     if (cur_si != NULL && ssp->cont_in_list != NULL
6149 					    && !(cur_si->si_flags & HL_MATCH))
6150     {
6151 	// Ignore transparent items without a contains argument.  Double check
6152 	// that we don't go back past the first one.
6153 	while ((cur_si->si_flags & HL_TRANS_CONT)
6154 		&& cur_si > (stateitem_T *)(current_state.ga_data))
6155 	    --cur_si;
6156 	// cur_si->si_idx is -1 for keywords, these never contain anything.
6157 	if (cur_si->si_idx >= 0 && in_id_list(NULL, ssp->cont_in_list,
6158 		&(SYN_ITEMS(syn_block)[cur_si->si_idx].sp_syn),
6159 		  SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags & HL_CONTAINED))
6160 	    return TRUE;
6161     }
6162 
6163     if (list == NULL)
6164 	return FALSE;
6165 
6166     /*
6167      * If list is ID_LIST_ALL, we are in a transparent item that isn't
6168      * inside anything.  Only allow not-contained groups.
6169      */
6170     if (list == ID_LIST_ALL)
6171 	return !contained;
6172 
6173     /*
6174      * If the first item is "ALLBUT", return TRUE if "id" is NOT in the
6175      * contains list.  We also require that "id" is at the same ":syn include"
6176      * level as the list.
6177      */
6178     item = *list;
6179     if (item >= SYNID_ALLBUT && item < SYNID_CLUSTER)
6180     {
6181 	if (item < SYNID_TOP)
6182 	{
6183 	    // ALL or ALLBUT: accept all groups in the same file
6184 	    if (item - SYNID_ALLBUT != ssp->inc_tag)
6185 		return FALSE;
6186 	}
6187 	else if (item < SYNID_CONTAINED)
6188 	{
6189 	    // TOP: accept all not-contained groups in the same file
6190 	    if (item - SYNID_TOP != ssp->inc_tag || contained)
6191 		return FALSE;
6192 	}
6193 	else
6194 	{
6195 	    // CONTAINED: accept all contained groups in the same file
6196 	    if (item - SYNID_CONTAINED != ssp->inc_tag || !contained)
6197 		return FALSE;
6198 	}
6199 	item = *++list;
6200 	retval = FALSE;
6201     }
6202     else
6203 	retval = TRUE;
6204 
6205     /*
6206      * Return "retval" if id is in the contains list.
6207      */
6208     while (item != 0)
6209     {
6210 	if (item == id)
6211 	    return retval;
6212 	if (item >= SYNID_CLUSTER)
6213 	{
6214 	    scl_list = SYN_CLSTR(syn_block)[item - SYNID_CLUSTER].scl_list;
6215 	    // restrict recursiveness to 30 to avoid an endless loop for a
6216 	    // cluster that includes itself (indirectly)
6217 	    if (scl_list != NULL && depth < 30)
6218 	    {
6219 		++depth;
6220 		r = in_id_list(NULL, scl_list, ssp, contained);
6221 		--depth;
6222 		if (r)
6223 		    return retval;
6224 	    }
6225 	}
6226 	item = *++list;
6227     }
6228     return !retval;
6229 }
6230 
6231 struct subcommand
6232 {
6233     char    *name;			// subcommand name
6234     void    (*func)(exarg_T *, int);	// function to call
6235 };
6236 
6237 static struct subcommand subcommands[] =
6238 {
6239     {"case",		syn_cmd_case},
6240     {"clear",		syn_cmd_clear},
6241     {"cluster",		syn_cmd_cluster},
6242     {"conceal",		syn_cmd_conceal},
6243     {"enable",		syn_cmd_enable},
6244     {"foldlevel",	syn_cmd_foldlevel},
6245     {"include",		syn_cmd_include},
6246     {"iskeyword",	syn_cmd_iskeyword},
6247     {"keyword",		syn_cmd_keyword},
6248     {"list",		syn_cmd_list},
6249     {"manual",		syn_cmd_manual},
6250     {"match",		syn_cmd_match},
6251     {"on",		syn_cmd_on},
6252     {"off",		syn_cmd_off},
6253     {"region",		syn_cmd_region},
6254     {"reset",		syn_cmd_reset},
6255     {"spell",		syn_cmd_spell},
6256     {"sync",		syn_cmd_sync},
6257     {"",		syn_cmd_list},
6258     {NULL, NULL}
6259 };
6260 
6261 /*
6262  * ":syntax".
6263  * This searches the subcommands[] table for the subcommand name, and calls a
6264  * syntax_subcommand() function to do the rest.
6265  */
6266     void
6267 ex_syntax(exarg_T *eap)
6268 {
6269     char_u	*arg = eap->arg;
6270     char_u	*subcmd_end;
6271     char_u	*subcmd_name;
6272     int		i;
6273 
6274     syn_cmdlinep = eap->cmdlinep;
6275 
6276     // isolate subcommand name
6277     for (subcmd_end = arg; ASCII_ISALPHA(*subcmd_end); ++subcmd_end)
6278 	;
6279     subcmd_name = vim_strnsave(arg, subcmd_end - arg);
6280     if (subcmd_name != NULL)
6281     {
6282 	if (eap->skip)		// skip error messages for all subcommands
6283 	    ++emsg_skip;
6284 	for (i = 0; ; ++i)
6285 	{
6286 	    if (subcommands[i].name == NULL)
6287 	    {
6288 		semsg(_("E410: Invalid :syntax subcommand: %s"), subcmd_name);
6289 		break;
6290 	    }
6291 	    if (STRCMP(subcmd_name, (char_u *)subcommands[i].name) == 0)
6292 	    {
6293 		eap->arg = skipwhite(subcmd_end);
6294 		(subcommands[i].func)(eap, FALSE);
6295 		break;
6296 	    }
6297 	}
6298 	vim_free(subcmd_name);
6299 	if (eap->skip)
6300 	    --emsg_skip;
6301     }
6302 }
6303 
6304     void
6305 ex_ownsyntax(exarg_T *eap)
6306 {
6307     char_u	*old_value;
6308     char_u	*new_value;
6309 
6310     if (curwin->w_s == &curwin->w_buffer->b_s)
6311     {
6312 	curwin->w_s = ALLOC_ONE(synblock_T);
6313 	memset(curwin->w_s, 0, sizeof(synblock_T));
6314 	hash_init(&curwin->w_s->b_keywtab);
6315 	hash_init(&curwin->w_s->b_keywtab_ic);
6316 #ifdef FEAT_SPELL
6317 	// TODO: keep the spell checking as it was.
6318 	curwin->w_p_spell = FALSE;	// No spell checking
6319 	clear_string_option(&curwin->w_s->b_p_spc);
6320 	clear_string_option(&curwin->w_s->b_p_spf);
6321 	clear_string_option(&curwin->w_s->b_p_spl);
6322 #endif
6323 	clear_string_option(&curwin->w_s->b_syn_isk);
6324     }
6325 
6326     // save value of b:current_syntax
6327     old_value = get_var_value((char_u *)"b:current_syntax");
6328     if (old_value != NULL)
6329 	old_value = vim_strsave(old_value);
6330 
6331     // Apply the "syntax" autocommand event, this finds and loads the syntax
6332     // file.
6333     apply_autocmds(EVENT_SYNTAX, eap->arg, curbuf->b_fname, TRUE, curbuf);
6334 
6335     // move value of b:current_syntax to w:current_syntax
6336     new_value = get_var_value((char_u *)"b:current_syntax");
6337     if (new_value != NULL)
6338 	set_internal_string_var((char_u *)"w:current_syntax", new_value);
6339 
6340     // restore value of b:current_syntax
6341     if (old_value == NULL)
6342 	do_unlet((char_u *)"b:current_syntax", TRUE);
6343     else
6344     {
6345 	set_internal_string_var((char_u *)"b:current_syntax", old_value);
6346 	vim_free(old_value);
6347     }
6348 }
6349 
6350     int
6351 syntax_present(win_T *win)
6352 {
6353     return (win->w_s->b_syn_patterns.ga_len != 0
6354 	    || win->w_s->b_syn_clusters.ga_len != 0
6355 	    || win->w_s->b_keywtab.ht_used > 0
6356 	    || win->w_s->b_keywtab_ic.ht_used > 0);
6357 }
6358 
6359 
6360 static enum
6361 {
6362     EXP_SUBCMD,	    // expand ":syn" sub-commands
6363     EXP_CASE,	    // expand ":syn case" arguments
6364     EXP_SPELL,	    // expand ":syn spell" arguments
6365     EXP_SYNC	    // expand ":syn sync" arguments
6366 } expand_what;
6367 
6368 /*
6369  * Reset include_link, include_default, include_none to 0.
6370  * Called when we are done expanding.
6371  */
6372     void
6373 reset_expand_highlight(void)
6374 {
6375     include_link = include_default = include_none = 0;
6376 }
6377 
6378 /*
6379  * Handle command line completion for :match and :echohl command: Add "None"
6380  * as highlight group.
6381  */
6382     void
6383 set_context_in_echohl_cmd(expand_T *xp, char_u *arg)
6384 {
6385     xp->xp_context = EXPAND_HIGHLIGHT;
6386     xp->xp_pattern = arg;
6387     include_none = 1;
6388 }
6389 
6390 /*
6391  * Handle command line completion for :syntax command.
6392  */
6393     void
6394 set_context_in_syntax_cmd(expand_T *xp, char_u *arg)
6395 {
6396     char_u	*p;
6397 
6398     // Default: expand subcommands
6399     xp->xp_context = EXPAND_SYNTAX;
6400     expand_what = EXP_SUBCMD;
6401     xp->xp_pattern = arg;
6402     include_link = 0;
6403     include_default = 0;
6404 
6405     // (part of) subcommand already typed
6406     if (*arg != NUL)
6407     {
6408 	p = skiptowhite(arg);
6409 	if (*p != NUL)		    // past first word
6410 	{
6411 	    xp->xp_pattern = skipwhite(p);
6412 	    if (*skiptowhite(xp->xp_pattern) != NUL)
6413 		xp->xp_context = EXPAND_NOTHING;
6414 	    else if (STRNICMP(arg, "case", p - arg) == 0)
6415 		expand_what = EXP_CASE;
6416 	    else if (STRNICMP(arg, "spell", p - arg) == 0)
6417 		expand_what = EXP_SPELL;
6418 	    else if (STRNICMP(arg, "sync", p - arg) == 0)
6419 		expand_what = EXP_SYNC;
6420 	    else if (  STRNICMP(arg, "keyword", p - arg) == 0
6421 		    || STRNICMP(arg, "region", p - arg) == 0
6422 		    || STRNICMP(arg, "match", p - arg) == 0
6423 		    || STRNICMP(arg, "list", p - arg) == 0)
6424 		xp->xp_context = EXPAND_HIGHLIGHT;
6425 	    else
6426 		xp->xp_context = EXPAND_NOTHING;
6427 	}
6428     }
6429 }
6430 
6431 /*
6432  * Function given to ExpandGeneric() to obtain the list syntax names for
6433  * expansion.
6434  */
6435     char_u *
6436 get_syntax_name(expand_T *xp UNUSED, int idx)
6437 {
6438     switch (expand_what)
6439     {
6440 	case EXP_SUBCMD:
6441 	    return (char_u *)subcommands[idx].name;
6442 	case EXP_CASE:
6443 	{
6444 	    static char *case_args[] = {"match", "ignore", NULL};
6445 	    return (char_u *)case_args[idx];
6446 	}
6447 	case EXP_SPELL:
6448 	{
6449 	    static char *spell_args[] =
6450 		{"toplevel", "notoplevel", "default", NULL};
6451 	    return (char_u *)spell_args[idx];
6452 	}
6453 	case EXP_SYNC:
6454 	{
6455 	    static char *sync_args[] =
6456 		{"ccomment", "clear", "fromstart",
6457 		 "linebreaks=", "linecont", "lines=", "match",
6458 		 "maxlines=", "minlines=", "region", NULL};
6459 	    return (char_u *)sync_args[idx];
6460 	}
6461     }
6462     return NULL;
6463 }
6464 
6465 
6466 /*
6467  * Function called for expression evaluation: get syntax ID at file position.
6468  */
6469     int
6470 syn_get_id(
6471     win_T	*wp,
6472     long	lnum,
6473     colnr_T	col,
6474     int		trans,	     // remove transparency
6475     int		*spellp,     // return: can do spell checking
6476     int		keep_state)  // keep state of char at "col"
6477 {
6478     // When the position is not after the current position and in the same
6479     // line of the same buffer, need to restart parsing.
6480     if (wp->w_buffer != syn_buf
6481 	    || lnum != current_lnum
6482 	    || col < current_col)
6483 	syntax_start(wp, lnum);
6484     else if (wp->w_buffer == syn_buf
6485 	    && lnum == current_lnum
6486 	    && col > current_col)
6487 	// next_match may not be correct when moving around, e.g. with the
6488 	// "skip" expression in searchpair()
6489 	next_match_idx = -1;
6490 
6491     (void)get_syntax_attr(col, spellp, keep_state);
6492 
6493     return (trans ? current_trans_id : current_id);
6494 }
6495 
6496 #if defined(FEAT_CONCEAL) || defined(PROTO)
6497 /*
6498  * Get extra information about the syntax item.  Must be called right after
6499  * get_syntax_attr().
6500  * Stores the current item sequence nr in "*seqnrp".
6501  * Returns the current flags.
6502  */
6503     int
6504 get_syntax_info(int *seqnrp)
6505 {
6506     *seqnrp = current_seqnr;
6507     return current_flags;
6508 }
6509 
6510 /*
6511  * Return conceal substitution character
6512  */
6513     int
6514 syn_get_sub_char(void)
6515 {
6516     return current_sub_char;
6517 }
6518 #endif
6519 
6520 #if defined(FEAT_EVAL) || defined(PROTO)
6521 /*
6522  * Return the syntax ID at position "i" in the current stack.
6523  * The caller must have called syn_get_id() before to fill the stack.
6524  * Returns -1 when "i" is out of range.
6525  */
6526     int
6527 syn_get_stack_item(int i)
6528 {
6529     if (i >= current_state.ga_len)
6530     {
6531 	// Need to invalidate the state, because we didn't properly finish it
6532 	// for the last character, "keep_state" was TRUE.
6533 	invalidate_current_state();
6534 	current_col = MAXCOL;
6535 	return -1;
6536     }
6537     return CUR_STATE(i).si_id;
6538 }
6539 #endif
6540 
6541 #if defined(FEAT_FOLDING) || defined(PROTO)
6542     static int
6543 syn_cur_foldlevel(void)
6544 {
6545     int		level = 0;
6546     int		i;
6547 
6548     for (i = 0; i < current_state.ga_len; ++i)
6549 	if (CUR_STATE(i).si_flags & HL_FOLD)
6550 	    ++level;
6551     return level;
6552 }
6553 
6554 /*
6555  * Function called to get folding level for line "lnum" in window "wp".
6556  */
6557     int
6558 syn_get_foldlevel(win_T *wp, long lnum)
6559 {
6560     int		level = 0;
6561     int		low_level;
6562     int		cur_level;
6563 
6564     // Return quickly when there are no fold items at all.
6565     if (wp->w_s->b_syn_folditems != 0
6566 	    && !wp->w_s->b_syn_error
6567 # ifdef SYN_TIME_LIMIT
6568 	    && !wp->w_s->b_syn_slow
6569 # endif
6570 	    )
6571     {
6572 	syntax_start(wp, lnum);
6573 
6574 	// Start with the fold level at the start of the line.
6575 	level = syn_cur_foldlevel();
6576 
6577 	if (wp->w_s->b_syn_foldlevel == SYNFLD_MINIMUM)
6578 	{
6579 	    // Find the lowest fold level that is followed by a higher one.
6580 	    cur_level = level;
6581 	    low_level = cur_level;
6582 	    while (!current_finished)
6583 	    {
6584 		(void)syn_current_attr(FALSE, FALSE, NULL, FALSE);
6585 		cur_level = syn_cur_foldlevel();
6586 		if (cur_level < low_level)
6587 		    low_level = cur_level;
6588 		else if (cur_level > low_level)
6589 		    level = low_level;
6590 		++current_col;
6591 	    }
6592 	}
6593     }
6594     if (level > wp->w_p_fdn)
6595     {
6596 	level = wp->w_p_fdn;
6597 	if (level < 0)
6598 	    level = 0;
6599     }
6600     return level;
6601 }
6602 #endif
6603 
6604 #if defined(FEAT_PROFILE) || defined(PROTO)
6605 /*
6606  * ":syntime".
6607  */
6608     void
6609 ex_syntime(exarg_T *eap)
6610 {
6611     if (STRCMP(eap->arg, "on") == 0)
6612 	syn_time_on = TRUE;
6613     else if (STRCMP(eap->arg, "off") == 0)
6614 	syn_time_on = FALSE;
6615     else if (STRCMP(eap->arg, "clear") == 0)
6616 	syntime_clear();
6617     else if (STRCMP(eap->arg, "report") == 0)
6618 	syntime_report();
6619     else
6620 	semsg(_(e_invarg2), eap->arg);
6621 }
6622 
6623     static void
6624 syn_clear_time(syn_time_T *st)
6625 {
6626     profile_zero(&st->total);
6627     profile_zero(&st->slowest);
6628     st->count = 0;
6629     st->match = 0;
6630 }
6631 
6632 /*
6633  * Clear the syntax timing for the current buffer.
6634  */
6635     static void
6636 syntime_clear(void)
6637 {
6638     int		idx;
6639     synpat_T	*spp;
6640 
6641     if (!syntax_present(curwin))
6642     {
6643 	msg(_(msg_no_items));
6644 	return;
6645     }
6646     for (idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx)
6647     {
6648 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
6649 	syn_clear_time(&spp->sp_time);
6650     }
6651 }
6652 
6653 /*
6654  * Function given to ExpandGeneric() to obtain the possible arguments of the
6655  * ":syntime {on,off,clear,report}" command.
6656  */
6657     char_u *
6658 get_syntime_arg(expand_T *xp UNUSED, int idx)
6659 {
6660     switch (idx)
6661     {
6662 	case 0: return (char_u *)"on";
6663 	case 1: return (char_u *)"off";
6664 	case 2: return (char_u *)"clear";
6665 	case 3: return (char_u *)"report";
6666     }
6667     return NULL;
6668 }
6669 
6670 typedef struct
6671 {
6672     proftime_T	total;
6673     int		count;
6674     int		match;
6675     proftime_T	slowest;
6676     proftime_T	average;
6677     int		id;
6678     char_u	*pattern;
6679 } time_entry_T;
6680 
6681     static int
6682 syn_compare_syntime(const void *v1, const void *v2)
6683 {
6684     const time_entry_T	*s1 = v1;
6685     const time_entry_T	*s2 = v2;
6686 
6687     return profile_cmp(&s1->total, &s2->total);
6688 }
6689 
6690 /*
6691  * Clear the syntax timing for the current buffer.
6692  */
6693     static void
6694 syntime_report(void)
6695 {
6696     int		idx;
6697     synpat_T	*spp;
6698 # ifdef FEAT_FLOAT
6699     proftime_T	tm;
6700 # endif
6701     int		len;
6702     proftime_T	total_total;
6703     int		total_count = 0;
6704     garray_T    ga;
6705     time_entry_T *p;
6706 
6707     if (!syntax_present(curwin))
6708     {
6709 	msg(_(msg_no_items));
6710 	return;
6711     }
6712 
6713     ga_init2(&ga, sizeof(time_entry_T), 50);
6714     profile_zero(&total_total);
6715     for (idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx)
6716     {
6717 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
6718 	if (spp->sp_time.count > 0)
6719 	{
6720 	    (void)ga_grow(&ga, 1);
6721 	    p = ((time_entry_T *)ga.ga_data) + ga.ga_len;
6722 	    p->total = spp->sp_time.total;
6723 	    profile_add(&total_total, &spp->sp_time.total);
6724 	    p->count = spp->sp_time.count;
6725 	    p->match = spp->sp_time.match;
6726 	    total_count += spp->sp_time.count;
6727 	    p->slowest = spp->sp_time.slowest;
6728 # ifdef FEAT_FLOAT
6729 	    profile_divide(&spp->sp_time.total, spp->sp_time.count, &tm);
6730 	    p->average = tm;
6731 # endif
6732 	    p->id = spp->sp_syn.id;
6733 	    p->pattern = spp->sp_pattern;
6734 	    ++ga.ga_len;
6735 	}
6736     }
6737 
6738     // Sort on total time. Skip if there are no items to avoid passing NULL
6739     // pointer to qsort().
6740     if (ga.ga_len > 1)
6741 	qsort(ga.ga_data, (size_t)ga.ga_len, sizeof(time_entry_T),
6742 							 syn_compare_syntime);
6743 
6744     msg_puts_title(_("  TOTAL      COUNT  MATCH   SLOWEST     AVERAGE   NAME               PATTERN"));
6745     msg_puts("\n");
6746     for (idx = 0; idx < ga.ga_len && !got_int; ++idx)
6747     {
6748 	p = ((time_entry_T *)ga.ga_data) + idx;
6749 
6750 	msg_puts(profile_msg(&p->total));
6751 	msg_puts(" "); // make sure there is always a separating space
6752 	msg_advance(13);
6753 	msg_outnum(p->count);
6754 	msg_puts(" ");
6755 	msg_advance(20);
6756 	msg_outnum(p->match);
6757 	msg_puts(" ");
6758 	msg_advance(26);
6759 	msg_puts(profile_msg(&p->slowest));
6760 	msg_puts(" ");
6761 	msg_advance(38);
6762 # ifdef FEAT_FLOAT
6763 	msg_puts(profile_msg(&p->average));
6764 	msg_puts(" ");
6765 # endif
6766 	msg_advance(50);
6767 	msg_outtrans(highlight_group_name(p->id - 1));
6768 	msg_puts(" ");
6769 
6770 	msg_advance(69);
6771 	if (Columns < 80)
6772 	    len = 20; // will wrap anyway
6773 	else
6774 	    len = Columns - 70;
6775 	if (len > (int)STRLEN(p->pattern))
6776 	    len = (int)STRLEN(p->pattern);
6777 	msg_outtrans_len(p->pattern, len);
6778 	msg_puts("\n");
6779     }
6780     ga_clear(&ga);
6781     if (!got_int)
6782     {
6783 	msg_puts("\n");
6784 	msg_puts(profile_msg(&total_total));
6785 	msg_advance(13);
6786 	msg_outnum(total_count);
6787 	msg_puts("\n");
6788     }
6789 }
6790 #endif
6791 
6792 #endif // FEAT_SYN_HL
6793