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