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