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