xref: /vim-8.2.3635/src/syntax.c (revision 12ee7ff0)
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 /*
17  * Structure that stores information about a highlight group.
18  * The ID of a highlight group is also called group ID.  It is the index in
19  * the highlight_ga array PLUS ONE.
20  */
21 struct hl_group
22 {
23     char_u	*sg_name;	/* highlight group name */
24     char_u	*sg_name_u;	/* uppercase of sg_name */
25     int		sg_cleared;	/* "hi clear" was used */
26 /* for normal terminals */
27     int		sg_term;	/* "term=" highlighting attributes */
28     char_u	*sg_start;	/* terminal string for start highl */
29     char_u	*sg_stop;	/* terminal string for stop highl */
30     int		sg_term_attr;	/* Screen attr for term mode */
31 /* for color terminals */
32     int		sg_cterm;	/* "cterm=" highlighting attr */
33     int		sg_cterm_bold;	/* bold attr was set for light color */
34     int		sg_cterm_fg;	/* terminal fg color number + 1 */
35     int		sg_cterm_bg;	/* terminal bg color number + 1 */
36     int		sg_cterm_attr;	/* Screen attr for color term mode */
37 /* for when using the GUI */
38 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
39     guicolor_T	sg_gui_fg;	/* GUI foreground color handle */
40     guicolor_T	sg_gui_bg;	/* GUI background color handle */
41 #endif
42 #ifdef FEAT_GUI
43     guicolor_T	sg_gui_sp;	/* GUI special color handle */
44     GuiFont	sg_font;	/* GUI font handle */
45 #ifdef FEAT_XFONTSET
46     GuiFontset	sg_fontset;	/* GUI fontset handle */
47 #endif
48     char_u	*sg_font_name;  /* GUI font or fontset name */
49     int		sg_gui_attr;    /* Screen attr for GUI mode */
50 #endif
51 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
52 /* Store the sp color name for the GUI or synIDattr() */
53     int		sg_gui;		/* "gui=" highlighting attributes */
54     char_u	*sg_gui_fg_name;/* GUI foreground color name */
55     char_u	*sg_gui_bg_name;/* GUI background color name */
56     char_u	*sg_gui_sp_name;/* GUI special color name */
57 #endif
58     int		sg_link;	/* link to this highlight group ID */
59     int		sg_set;		/* combination of SG_* flags */
60 #ifdef FEAT_EVAL
61     sctx_T	sg_script_ctx;	/* script in which the group was last set */
62 #endif
63 };
64 
65 #define SG_TERM		1	/* term has been set */
66 #define SG_CTERM	2	/* cterm has been set */
67 #define SG_GUI		4	/* gui has been set */
68 #define SG_LINK		8	/* link has been set */
69 
70 static garray_T highlight_ga;	/* highlight groups for 'highlight' option */
71 
72 #define HL_TABLE() ((struct hl_group *)((highlight_ga.ga_data)))
73 
74 #define MAX_HL_ID       20000	/* maximum value for a highlight ID. */
75 
76 #ifdef FEAT_CMDL_COMPL
77 /* Flags to indicate an additional string for highlight name completion. */
78 static int include_none = 0;	/* when 1 include "None" */
79 static int include_default = 0;	/* when 1 include "default" */
80 static int include_link = 0;	/* when 2 include "link" and "clear" */
81 #endif
82 
83 /*
84  * The "term", "cterm" and "gui" arguments can be any combination of the
85  * following names, separated by commas (but no spaces!).
86  */
87 static char *(hl_name_table[]) =
88     {"bold", "standout", "underline", "undercurl",
89       "italic", "reverse", "inverse", "nocombine", "strikethrough", "NONE"};
90 static int hl_attr_table[] =
91     {HL_BOLD, HL_STANDOUT, HL_UNDERLINE, HL_UNDERCURL, HL_ITALIC, HL_INVERSE, HL_INVERSE, HL_NOCOMBINE, HL_STRIKETHROUGH, 0};
92 #define ATTR_COMBINE(attr_a, attr_b) ((((attr_b) & HL_NOCOMBINE) ? attr_b : (attr_a)) | (attr_b))
93 
94 static void syn_unadd_group(void);
95 static void set_hl_attr(int idx);
96 static void highlight_list_one(int id);
97 static int highlight_list_arg(int id, int didh, int type, int iarg, char_u *sarg, char *name);
98 static int syn_add_group(char_u *name);
99 static int syn_list_header(int did_header, int outlen, int id);
100 static int hl_has_settings(int idx, int check_link);
101 static void highlight_clear(int idx);
102 
103 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
104 static void gui_do_one_color(int idx, int do_menu, int do_tooltip);
105 #endif
106 #ifdef FEAT_GUI
107 static int  set_group_colors(char_u *name, guicolor_T *fgp, guicolor_T *bgp, int do_menu, int use_norm, int do_tooltip);
108 static void hl_do_font(int idx, char_u *arg, int do_normal, int do_menu, int do_tooltip, int free_font);
109 #endif
110 
111 /*
112  * An attribute number is the index in attr_table plus ATTR_OFF.
113  */
114 #define ATTR_OFF (HL_ALL + 1)
115 
116 #if defined(FEAT_SYN_HL) || defined(PROTO)
117 
118 #define SYN_NAMELEN	50		/* maximum length of a syntax name */
119 
120 /* different types of offsets that are possible */
121 #define SPO_MS_OFF	0	/* match  start offset */
122 #define SPO_ME_OFF	1	/* match  end	offset */
123 #define SPO_HS_OFF	2	/* highl. start offset */
124 #define SPO_HE_OFF	3	/* highl. end	offset */
125 #define SPO_RS_OFF	4	/* region start offset */
126 #define SPO_RE_OFF	5	/* region end	offset */
127 #define SPO_LC_OFF	6	/* leading context offset */
128 #define SPO_COUNT	7
129 
130 static char *(spo_name_tab[SPO_COUNT]) =
131 	    {"ms=", "me=", "hs=", "he=", "rs=", "re=", "lc="};
132 
133 /*
134  * The patterns that are being searched for are stored in a syn_pattern.
135  * A match item consists of one pattern.
136  * A start/end item consists of n start patterns and m end patterns.
137  * A start/skip/end item consists of n start patterns, one skip pattern and m
138  * end patterns.
139  * For the latter two, the patterns are always consecutive: start-skip-end.
140  *
141  * A character offset can be given for the matched text (_m_start and _m_end)
142  * and for the actually highlighted text (_h_start and _h_end).
143  *
144  * Note that ordering of members is optimized to reduce padding.
145  */
146 typedef struct syn_pattern
147 {
148     char	 sp_type;		/* see SPTYPE_ defines below */
149     char	 sp_syncing;		/* this item used for syncing */
150     short	 sp_syn_match_id;	/* highlight group ID of pattern */
151     short	 sp_off_flags;		/* see below */
152     int		 sp_offsets[SPO_COUNT];	/* offsets */
153     int		 sp_flags;		/* see HL_ defines below */
154 #ifdef FEAT_CONCEAL
155     int		 sp_cchar;		/* conceal substitute character */
156 #endif
157     int		 sp_ic;			/* ignore-case flag for sp_prog */
158     int		 sp_sync_idx;		/* sync item index (syncing only) */
159     int		 sp_line_id;		/* ID of last line where tried */
160     int		 sp_startcol;		/* next match in sp_line_id line */
161     short	*sp_cont_list;		/* cont. group IDs, if non-zero */
162     short	*sp_next_list;		/* next group IDs, if non-zero */
163     struct sp_syn sp_syn;		/* struct passed to in_id_list() */
164     char_u	*sp_pattern;		/* regexp to match, pattern */
165     regprog_T	*sp_prog;		/* regexp to match, program */
166 #ifdef FEAT_PROFILE
167     syn_time_T	 sp_time;
168 #endif
169 } synpat_T;
170 
171 /* The sp_off_flags are computed like this:
172  * offset from the start of the matched text: (1 << SPO_XX_OFF)
173  * offset from the end	 of the matched text: (1 << (SPO_XX_OFF + SPO_COUNT))
174  * When both are present, only one is used.
175  */
176 
177 #define SPTYPE_MATCH	1	/* match keyword with this group ID */
178 #define SPTYPE_START	2	/* match a regexp, start of item */
179 #define SPTYPE_END	3	/* match a regexp, end of item */
180 #define SPTYPE_SKIP	4	/* match a regexp, skip within item */
181 
182 
183 #define SYN_ITEMS(buf)	((synpat_T *)((buf)->b_syn_patterns.ga_data))
184 
185 #define NONE_IDX	-2	/* value of sp_sync_idx for "NONE" */
186 
187 /*
188  * Flags for b_syn_sync_flags:
189  */
190 #define SF_CCOMMENT	0x01	/* sync on a C-style comment */
191 #define SF_MATCH	0x02	/* sync by matching a pattern */
192 
193 #define SYN_STATE_P(ssp)    ((bufstate_T *)((ssp)->ga_data))
194 
195 #define MAXKEYWLEN	80	    /* maximum length of a keyword */
196 
197 /*
198  * The attributes of the syntax item that has been recognized.
199  */
200 static int current_attr = 0;	    /* attr of current syntax word */
201 #ifdef FEAT_EVAL
202 static int current_id = 0;	    /* ID of current char for syn_get_id() */
203 static int current_trans_id = 0;    /* idem, transparency removed */
204 #endif
205 #ifdef FEAT_CONCEAL
206 static int current_flags = 0;
207 static int current_seqnr = 0;
208 static int current_sub_char = 0;
209 #endif
210 
211 typedef struct syn_cluster_S
212 {
213     char_u	    *scl_name;	    /* syntax cluster name */
214     char_u	    *scl_name_u;    /* uppercase of scl_name */
215     short	    *scl_list;	    /* IDs in this syntax cluster */
216 } syn_cluster_T;
217 
218 /*
219  * Methods of combining two clusters
220  */
221 #define CLUSTER_REPLACE	    1	/* replace first list with second */
222 #define CLUSTER_ADD	    2	/* add second list to first */
223 #define CLUSTER_SUBTRACT    3	/* subtract second list from first */
224 
225 #define SYN_CLSTR(buf)	((syn_cluster_T *)((buf)->b_syn_clusters.ga_data))
226 
227 /*
228  * Syntax group IDs have different types:
229  *     0 - 19999  normal syntax groups
230  * 20000 - 20999  ALLBUT indicator (current_syn_inc_tag added)
231  * 21000 - 21999  TOP indicator (current_syn_inc_tag added)
232  * 22000 - 22999  CONTAINED indicator (current_syn_inc_tag added)
233  * 23000 - 32767  cluster IDs (subtract SYNID_CLUSTER for the cluster ID)
234  */
235 #define SYNID_ALLBUT	MAX_HL_ID   /* syntax group ID for contains=ALLBUT */
236 #define SYNID_TOP	21000	    /* syntax group ID for contains=TOP */
237 #define SYNID_CONTAINED	22000	    /* syntax group ID for contains=CONTAINED */
238 #define SYNID_CLUSTER	23000	    /* first syntax group ID for clusters */
239 
240 #define MAX_SYN_INC_TAG	999	    /* maximum before the above overflow */
241 #define MAX_CLUSTER_ID  (32767 - SYNID_CLUSTER)
242 
243 /*
244  * Annoying Hack(TM):  ":syn include" needs this pointer to pass to
245  * expand_filename().  Most of the other syntax commands don't need it, so
246  * instead of passing it to them, we stow it here.
247  */
248 static char_u **syn_cmdlinep;
249 
250 /*
251  * Another Annoying Hack(TM):  To prevent rules from other ":syn include"'d
252  * files from leaking into ALLBUT lists, we assign a unique ID to the
253  * rules in each ":syn include"'d file.
254  */
255 static int current_syn_inc_tag = 0;
256 static int running_syn_inc_tag = 0;
257 
258 /*
259  * In a hashtable item "hi_key" points to "keyword" in a keyentry.
260  * This avoids adding a pointer to the hashtable item.
261  * KE2HIKEY() converts a var pointer to a hashitem key pointer.
262  * HIKEY2KE() converts a hashitem key pointer to a var pointer.
263  * HI2KE() converts a hashitem pointer to a var pointer.
264  */
265 static keyentry_T dumkey;
266 #define KE2HIKEY(kp)  ((kp)->keyword)
267 #define HIKEY2KE(p)   ((keyentry_T *)((p) - (dumkey.keyword - (char_u *)&dumkey)))
268 #define HI2KE(hi)      HIKEY2KE((hi)->hi_key)
269 
270 /*
271  * To reduce the time spent in keepend(), remember at which level in the state
272  * stack the first item with "keepend" is present.  When "-1", there is no
273  * "keepend" on the stack.
274  */
275 static int keepend_level = -1;
276 
277 static char msg_no_items[] = N_("No Syntax items defined for this buffer");
278 
279 /*
280  * For the current state we need to remember more than just the idx.
281  * When si_m_endpos.lnum is 0, the items other than si_idx are unknown.
282  * (The end positions have the column number of the next char)
283  */
284 typedef struct state_item
285 {
286     int		si_idx;			/* index of syntax pattern or
287 					   KEYWORD_IDX */
288     int		si_id;			/* highlight group ID for keywords */
289     int		si_trans_id;		/* idem, transparency removed */
290     int		si_m_lnum;		/* lnum of the match */
291     int		si_m_startcol;		/* starting column of the match */
292     lpos_T	si_m_endpos;		/* just after end posn of the match */
293     lpos_T	si_h_startpos;		/* start position of the highlighting */
294     lpos_T	si_h_endpos;		/* end position of the highlighting */
295     lpos_T	si_eoe_pos;		/* end position of end pattern */
296     int		si_end_idx;		/* group ID for end pattern or zero */
297     int		si_ends;		/* if match ends before si_m_endpos */
298     int		si_attr;		/* attributes in this state */
299     long	si_flags;		/* HL_HAS_EOL flag in this state, and
300 					 * HL_SKIP* for si_next_list */
301 #ifdef FEAT_CONCEAL
302     int		si_seqnr;		/* sequence number */
303     int		si_cchar;		/* substitution character for conceal */
304 #endif
305     short	*si_cont_list;		/* list of contained groups */
306     short	*si_next_list;		/* nextgroup IDs after this item ends */
307     reg_extmatch_T *si_extmatch;	/* \z(...\) matches from start
308 					 * pattern */
309 } stateitem_T;
310 
311 #define KEYWORD_IDX	-1	    /* value of si_idx for keywords */
312 #define ID_LIST_ALL	(short *)-1 /* valid of si_cont_list for containing all
313 				       but contained groups */
314 
315 #ifdef FEAT_CONCEAL
316 static int next_seqnr = 1;		/* value to use for si_seqnr */
317 #endif
318 
319 /*
320  * Struct to reduce the number of arguments to get_syn_options(), it's used
321  * very often.
322  */
323 typedef struct
324 {
325     int		flags;		/* flags for contained and transparent */
326     int		keyword;	/* TRUE for ":syn keyword" */
327     int		*sync_idx;	/* syntax item for "grouphere" argument, NULL
328 				   if not allowed */
329     char	has_cont_list;	/* TRUE if "cont_list" can be used */
330     short	*cont_list;	/* group IDs for "contains" argument */
331     short	*cont_in_list;	/* group IDs for "containedin" argument */
332     short	*next_list;	/* group IDs for "nextgroup" argument */
333 } syn_opt_arg_T;
334 
335 /*
336  * The next possible match in the current line for any pattern is remembered,
337  * to avoid having to try for a match in each column.
338  * If next_match_idx == -1, not tried (in this line) yet.
339  * If next_match_col == MAXCOL, no match found in this line.
340  * (All end positions have the column of the char after the end)
341  */
342 static int next_match_col;		/* column for start of next match */
343 static lpos_T next_match_m_endpos;	/* position for end of next match */
344 static lpos_T next_match_h_startpos;  /* pos. for highl. start of next match */
345 static lpos_T next_match_h_endpos;	/* pos. for highl. end of next match */
346 static int next_match_idx;		/* index of matched item */
347 static long next_match_flags;		/* flags for next match */
348 static lpos_T next_match_eos_pos;	/* end of start pattn (start region) */
349 static lpos_T next_match_eoe_pos;	/* pos. for end of end pattern */
350 static int next_match_end_idx;		/* ID of group for end pattn or zero */
351 static reg_extmatch_T *next_match_extmatch = NULL;
352 
353 /*
354  * A state stack is an array of integers or stateitem_T, stored in a
355  * garray_T.  A state stack is invalid if its itemsize entry is zero.
356  */
357 #define INVALID_STATE(ssp)  ((ssp)->ga_itemsize == 0)
358 #define VALID_STATE(ssp)    ((ssp)->ga_itemsize != 0)
359 
360 /*
361  * The current state (within the line) of the recognition engine.
362  * When current_state.ga_itemsize is 0 the current state is invalid.
363  */
364 static win_T	*syn_win;		/* current window for highlighting */
365 static buf_T	*syn_buf;		/* current buffer for highlighting */
366 static synblock_T *syn_block;		/* current buffer for highlighting */
367 #ifdef FEAT_RELTIME
368 static proftime_T *syn_tm;		/* timeout limit */
369 #endif
370 static linenr_T current_lnum = 0;	/* lnum of current state */
371 static colnr_T	current_col = 0;	/* column of current state */
372 static int	current_state_stored = 0; /* TRUE if stored current state
373 					   * after setting current_finished */
374 static int	current_finished = 0;	/* current line has been finished */
375 static garray_T current_state		/* current stack of state_items */
376 		= {0, 0, 0, 0, NULL};
377 static short	*current_next_list = NULL; /* when non-zero, nextgroup list */
378 static int	current_next_flags = 0; /* flags for current_next_list */
379 static int	current_line_id = 0;	/* unique number for current line */
380 
381 #define CUR_STATE(idx)	((stateitem_T *)(current_state.ga_data))[idx]
382 
383 static void syn_sync(win_T *wp, linenr_T lnum, synstate_T *last_valid);
384 static int syn_match_linecont(linenr_T lnum);
385 static void syn_start_line(void);
386 static void syn_update_ends(int startofline);
387 static void syn_stack_alloc(void);
388 static int syn_stack_cleanup(void);
389 static void syn_stack_free_entry(synblock_T *block, synstate_T *p);
390 static synstate_T *syn_stack_find_entry(linenr_T lnum);
391 static synstate_T *store_current_state(void);
392 static void load_current_state(synstate_T *from);
393 static void invalidate_current_state(void);
394 static int syn_stack_equal(synstate_T *sp);
395 static void validate_current_state(void);
396 static int syn_finish_line(int syncing);
397 static int syn_current_attr(int syncing, int displaying, int *can_spell, int keep_state);
398 static int did_match_already(int idx, garray_T *gap);
399 static stateitem_T *push_next_match(stateitem_T *cur_si);
400 static void check_state_ends(void);
401 static void update_si_attr(int idx);
402 static void check_keepend(void);
403 static void update_si_end(stateitem_T *sip, int startcol, int force);
404 static short *copy_id_list(short *list);
405 static int in_id_list(stateitem_T *item, short *cont_list, struct sp_syn *ssp, int contained);
406 static int push_current_state(int idx);
407 static void pop_current_state(void);
408 #ifdef FEAT_PROFILE
409 static void syn_clear_time(syn_time_T *tt);
410 static void syntime_clear(void);
411 static void syntime_report(void);
412 static int syn_time_on = FALSE;
413 # define IF_SYN_TIME(p) (p)
414 #else
415 # define IF_SYN_TIME(p) NULL
416 typedef int syn_time_T;
417 #endif
418 
419 static void syn_stack_apply_changes_block(synblock_T *block, buf_T *buf);
420 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);
421 
422 static void limit_pos(lpos_T *pos, lpos_T *limit);
423 static void limit_pos_zero(lpos_T *pos, lpos_T *limit);
424 static void syn_add_end_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *spp, int idx, int extra);
425 static void syn_add_start_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *spp, int idx, int extra);
426 static char_u *syn_getcurline(void);
427 static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T *st);
428 static int check_keyword_id(char_u *line, int startcol, int *endcol, long *flags, short **next_list, stateitem_T *cur_si, int *ccharp);
429 static void syn_remove_pattern(synblock_T *block, int idx);
430 static void syn_clear_pattern(synblock_T *block, int i);
431 static void syn_clear_cluster(synblock_T *block, int i);
432 static void syn_clear_one(int id, int syncing);
433 static void syn_cmd_onoff(exarg_T *eap, char *name);
434 static void syn_lines_msg(void);
435 static void syn_match_msg(void);
436 static void syn_list_one(int id, int syncing, int link_only);
437 static void syn_list_cluster(int id);
438 static void put_id_list(char_u *name, short *list, int attr);
439 static void put_pattern(char *s, int c, synpat_T *spp, int attr);
440 static int syn_list_keywords(int id, hashtab_T *ht, int did_header, int attr);
441 static void syn_clear_keyword(int id, hashtab_T *ht);
442 static void clear_keywtab(hashtab_T *ht);
443 static int syn_scl_namen2id(char_u *linep, int len);
444 static int syn_check_cluster(char_u *pp, int len);
445 static int syn_add_cluster(char_u *name);
446 static void init_syn_patterns(void);
447 static char_u *get_syn_pattern(char_u *arg, synpat_T *ci);
448 static int get_id_list(char_u **arg, int keylen, short **list, int skip);
449 static void syn_combine_list(short **clstr1, short **clstr2, int list_op);
450 
451 #if defined(FEAT_RELTIME) || defined(PROTO)
452 /*
453  * Set the timeout used for syntax highlighting.
454  * Use NULL to reset, no timeout.
455  */
456     void
457 syn_set_timeout(proftime_T *tm)
458 {
459     syn_tm = tm;
460 }
461 #endif
462 
463 /*
464  * Start the syntax recognition for a line.  This function is normally called
465  * from the screen updating, once for each displayed line.
466  * The buffer is remembered in syn_buf, because get_syntax_attr() doesn't get
467  * it.	Careful: curbuf and curwin are likely to point to another buffer and
468  * window.
469  */
470     void
471 syntax_start(win_T *wp, linenr_T lnum)
472 {
473     synstate_T	*p;
474     synstate_T	*last_valid = NULL;
475     synstate_T	*last_min_valid = NULL;
476     synstate_T	*sp, *prev = NULL;
477     linenr_T	parsed_lnum;
478     linenr_T	first_stored;
479     int		dist;
480     static varnumber_T changedtick = 0;	/* remember the last change ID */
481 
482 #ifdef FEAT_CONCEAL
483     current_sub_char = NUL;
484 #endif
485 
486     /*
487      * After switching buffers, invalidate current_state.
488      * Also do this when a change was made, the current state may be invalid
489      * then.
490      */
491     if (syn_block != wp->w_s
492 	    || syn_buf != wp->w_buffer
493 	    || changedtick != CHANGEDTICK(syn_buf))
494     {
495 	invalidate_current_state();
496 	syn_buf = wp->w_buffer;
497 	syn_block = wp->w_s;
498     }
499     changedtick = CHANGEDTICK(syn_buf);
500     syn_win = wp;
501 
502     /*
503      * Allocate syntax stack when needed.
504      */
505     syn_stack_alloc();
506     if (syn_block->b_sst_array == NULL)
507 	return;		/* out of memory */
508     syn_block->b_sst_lasttick = display_tick;
509 
510     /*
511      * If the state of the end of the previous line is useful, store it.
512      */
513     if (VALID_STATE(&current_state)
514 	    && current_lnum < lnum
515 	    && current_lnum < syn_buf->b_ml.ml_line_count)
516     {
517 	(void)syn_finish_line(FALSE);
518 	if (!current_state_stored)
519 	{
520 	    ++current_lnum;
521 	    (void)store_current_state();
522 	}
523 
524 	/*
525 	 * If the current_lnum is now the same as "lnum", keep the current
526 	 * state (this happens very often!).  Otherwise invalidate
527 	 * current_state and figure it out below.
528 	 */
529 	if (current_lnum != lnum)
530 	    invalidate_current_state();
531     }
532     else
533 	invalidate_current_state();
534 
535     /*
536      * Try to synchronize from a saved state in b_sst_array[].
537      * Only do this if lnum is not before and not to far beyond a saved state.
538      */
539     if (INVALID_STATE(&current_state) && syn_block->b_sst_array != NULL)
540     {
541 	/* Find last valid saved state before start_lnum. */
542 	for (p = syn_block->b_sst_first; p != NULL; p = p->sst_next)
543 	{
544 	    if (p->sst_lnum > lnum)
545 		break;
546 	    if (p->sst_lnum <= lnum && p->sst_change_lnum == 0)
547 	    {
548 		last_valid = p;
549 		if (p->sst_lnum >= lnum - syn_block->b_syn_sync_minlines)
550 		    last_min_valid = p;
551 	    }
552 	}
553 	if (last_min_valid != NULL)
554 	    load_current_state(last_min_valid);
555     }
556 
557     /*
558      * If "lnum" is before or far beyond a line with a saved state, need to
559      * re-synchronize.
560      */
561     if (INVALID_STATE(&current_state))
562     {
563 	syn_sync(wp, lnum, last_valid);
564 	if (current_lnum == 1)
565 	    /* First line is always valid, no matter "minlines". */
566 	    first_stored = 1;
567 	else
568 	    /* Need to parse "minlines" lines before state can be considered
569 	     * valid to store. */
570 	    first_stored = current_lnum + syn_block->b_syn_sync_minlines;
571     }
572     else
573 	first_stored = current_lnum;
574 
575     /*
576      * Advance from the sync point or saved state until the current line.
577      * Save some entries for syncing with later on.
578      */
579     if (syn_block->b_sst_len <= Rows)
580 	dist = 999999;
581     else
582 	dist = syn_buf->b_ml.ml_line_count / (syn_block->b_sst_len - Rows) + 1;
583     while (current_lnum < lnum)
584     {
585 	syn_start_line();
586 	(void)syn_finish_line(FALSE);
587 	++current_lnum;
588 
589 	/* If we parsed at least "minlines" lines or started at a valid
590 	 * state, the current state is considered valid. */
591 	if (current_lnum >= first_stored)
592 	{
593 	    /* Check if the saved state entry is for the current line and is
594 	     * equal to the current state.  If so, then validate all saved
595 	     * states that depended on a change before the parsed line. */
596 	    if (prev == NULL)
597 		prev = syn_stack_find_entry(current_lnum - 1);
598 	    if (prev == NULL)
599 		sp = syn_block->b_sst_first;
600 	    else
601 		sp = prev;
602 	    while (sp != NULL && sp->sst_lnum < current_lnum)
603 		sp = sp->sst_next;
604 	    if (sp != NULL
605 		    && sp->sst_lnum == current_lnum
606 		    && syn_stack_equal(sp))
607 	    {
608 		parsed_lnum = current_lnum;
609 		prev = sp;
610 		while (sp != NULL && sp->sst_change_lnum <= parsed_lnum)
611 		{
612 		    if (sp->sst_lnum <= lnum)
613 			/* valid state before desired line, use this one */
614 			prev = sp;
615 		    else if (sp->sst_change_lnum == 0)
616 			/* past saved states depending on change, break here. */
617 			break;
618 		    sp->sst_change_lnum = 0;
619 		    sp = sp->sst_next;
620 		}
621 		load_current_state(prev);
622 	    }
623 	    /* Store the state at this line when it's the first one, the line
624 	     * where we start parsing, or some distance from the previously
625 	     * saved state.  But only when parsed at least 'minlines'. */
626 	    else if (prev == NULL
627 			|| current_lnum == lnum
628 			|| current_lnum >= prev->sst_lnum + dist)
629 		prev = store_current_state();
630 	}
631 
632 	/* This can take a long time: break when CTRL-C pressed.  The current
633 	 * state will be wrong then. */
634 	line_breakcheck();
635 	if (got_int)
636 	{
637 	    current_lnum = lnum;
638 	    break;
639 	}
640     }
641 
642     syn_start_line();
643 }
644 
645 /*
646  * We cannot simply discard growarrays full of state_items or buf_states; we
647  * have to manually release their extmatch pointers first.
648  */
649     static void
650 clear_syn_state(synstate_T *p)
651 {
652     int		i;
653     garray_T	*gap;
654 
655     if (p->sst_stacksize > SST_FIX_STATES)
656     {
657 	gap = &(p->sst_union.sst_ga);
658 	for (i = 0; i < gap->ga_len; i++)
659 	    unref_extmatch(SYN_STATE_P(gap)[i].bs_extmatch);
660 	ga_clear(gap);
661     }
662     else
663     {
664 	for (i = 0; i < p->sst_stacksize; i++)
665 	    unref_extmatch(p->sst_union.sst_stack[i].bs_extmatch);
666     }
667 }
668 
669 /*
670  * Cleanup the current_state stack.
671  */
672     static void
673 clear_current_state(void)
674 {
675     int		i;
676     stateitem_T	*sip;
677 
678     sip = (stateitem_T *)(current_state.ga_data);
679     for (i = 0; i < current_state.ga_len; i++)
680 	unref_extmatch(sip[i].si_extmatch);
681     ga_clear(&current_state);
682 }
683 
684 /*
685  * Try to find a synchronisation point for line "lnum".
686  *
687  * This sets current_lnum and the current state.  One of three methods is
688  * used:
689  * 1. Search backwards for the end of a C-comment.
690  * 2. Search backwards for given sync patterns.
691  * 3. Simply start on a given number of lines above "lnum".
692  */
693     static void
694 syn_sync(
695     win_T	*wp,
696     linenr_T	start_lnum,
697     synstate_T	*last_valid)
698 {
699     buf_T	*curbuf_save;
700     win_T	*curwin_save;
701     pos_T	cursor_save;
702     int		idx;
703     linenr_T	lnum;
704     linenr_T	end_lnum;
705     linenr_T	break_lnum;
706     int		had_sync_point;
707     stateitem_T	*cur_si;
708     synpat_T	*spp;
709     char_u	*line;
710     int		found_flags = 0;
711     int		found_match_idx = 0;
712     linenr_T	found_current_lnum = 0;
713     int		found_current_col= 0;
714     lpos_T	found_m_endpos;
715     colnr_T	prev_current_col;
716 
717     /*
718      * Clear any current state that might be hanging around.
719      */
720     invalidate_current_state();
721 
722     /*
723      * Start at least "minlines" back.  Default starting point for parsing is
724      * there.
725      * Start further back, to avoid that scrolling backwards will result in
726      * resyncing for every line.  Now it resyncs only one out of N lines,
727      * where N is minlines * 1.5, or minlines * 2 if minlines is small.
728      * Watch out for overflow when minlines is MAXLNUM.
729      */
730     if (syn_block->b_syn_sync_minlines > start_lnum)
731 	start_lnum = 1;
732     else
733     {
734 	if (syn_block->b_syn_sync_minlines == 1)
735 	    lnum = 1;
736 	else if (syn_block->b_syn_sync_minlines < 10)
737 	    lnum = syn_block->b_syn_sync_minlines * 2;
738 	else
739 	    lnum = syn_block->b_syn_sync_minlines * 3 / 2;
740 	if (syn_block->b_syn_sync_maxlines != 0
741 				     && lnum > syn_block->b_syn_sync_maxlines)
742 	    lnum = syn_block->b_syn_sync_maxlines;
743 	if (lnum >= start_lnum)
744 	    start_lnum = 1;
745 	else
746 	    start_lnum -= lnum;
747     }
748     current_lnum = start_lnum;
749 
750     /*
751      * 1. Search backwards for the end of a C-style comment.
752      */
753     if (syn_block->b_syn_sync_flags & SF_CCOMMENT)
754     {
755 	/* Need to make syn_buf the current buffer for a moment, to be able to
756 	 * use find_start_comment(). */
757 	curwin_save = curwin;
758 	curwin = wp;
759 	curbuf_save = curbuf;
760 	curbuf = syn_buf;
761 
762 	/*
763 	 * Skip lines that end in a backslash.
764 	 */
765 	for ( ; start_lnum > 1; --start_lnum)
766 	{
767 	    line = ml_get(start_lnum - 1);
768 	    if (*line == NUL || *(line + STRLEN(line) - 1) != '\\')
769 		break;
770 	}
771 	current_lnum = start_lnum;
772 
773 	/* set cursor to start of search */
774 	cursor_save = wp->w_cursor;
775 	wp->w_cursor.lnum = start_lnum;
776 	wp->w_cursor.col = 0;
777 
778 	/*
779 	 * If the line is inside a comment, need to find the syntax item that
780 	 * defines the comment.
781 	 * Restrict the search for the end of a comment to b_syn_sync_maxlines.
782 	 */
783 	if (find_start_comment((int)syn_block->b_syn_sync_maxlines) != NULL)
784 	{
785 	    for (idx = syn_block->b_syn_patterns.ga_len; --idx >= 0; )
786 		if (SYN_ITEMS(syn_block)[idx].sp_syn.id
787 						   == syn_block->b_syn_sync_id
788 			&& SYN_ITEMS(syn_block)[idx].sp_type == SPTYPE_START)
789 		{
790 		    validate_current_state();
791 		    if (push_current_state(idx) == OK)
792 			update_si_attr(current_state.ga_len - 1);
793 		    break;
794 		}
795 	}
796 
797 	/* restore cursor and buffer */
798 	wp->w_cursor = cursor_save;
799 	curwin = curwin_save;
800 	curbuf = curbuf_save;
801     }
802 
803     /*
804      * 2. Search backwards for given sync patterns.
805      */
806     else if (syn_block->b_syn_sync_flags & SF_MATCH)
807     {
808 	if (syn_block->b_syn_sync_maxlines != 0
809 			       && start_lnum > syn_block->b_syn_sync_maxlines)
810 	    break_lnum = start_lnum - syn_block->b_syn_sync_maxlines;
811 	else
812 	    break_lnum = 0;
813 
814 	found_m_endpos.lnum = 0;
815 	found_m_endpos.col = 0;
816 	end_lnum = start_lnum;
817 	lnum = start_lnum;
818 	while (--lnum > break_lnum)
819 	{
820 	    /* This can take a long time: break when CTRL-C pressed. */
821 	    line_breakcheck();
822 	    if (got_int)
823 	    {
824 		invalidate_current_state();
825 		current_lnum = start_lnum;
826 		break;
827 	    }
828 
829 	    /* Check if we have run into a valid saved state stack now. */
830 	    if (last_valid != NULL && lnum == last_valid->sst_lnum)
831 	    {
832 		load_current_state(last_valid);
833 		break;
834 	    }
835 
836 	    /*
837 	     * Check if the previous line has the line-continuation pattern.
838 	     */
839 	    if (lnum > 1 && syn_match_linecont(lnum - 1))
840 		continue;
841 
842 	    /*
843 	     * Start with nothing on the state stack
844 	     */
845 	    validate_current_state();
846 
847 	    for (current_lnum = lnum; current_lnum < end_lnum; ++current_lnum)
848 	    {
849 		syn_start_line();
850 		for (;;)
851 		{
852 		    had_sync_point = syn_finish_line(TRUE);
853 		    /*
854 		     * When a sync point has been found, remember where, and
855 		     * continue to look for another one, further on in the line.
856 		     */
857 		    if (had_sync_point && current_state.ga_len)
858 		    {
859 			cur_si = &CUR_STATE(current_state.ga_len - 1);
860 			if (cur_si->si_m_endpos.lnum > start_lnum)
861 			{
862 			    /* ignore match that goes to after where started */
863 			    current_lnum = end_lnum;
864 			    break;
865 			}
866 			if (cur_si->si_idx < 0)
867 			{
868 			    /* Cannot happen? */
869 			    found_flags = 0;
870 			    found_match_idx = KEYWORD_IDX;
871 			}
872 			else
873 			{
874 			    spp = &(SYN_ITEMS(syn_block)[cur_si->si_idx]);
875 			    found_flags = spp->sp_flags;
876 			    found_match_idx = spp->sp_sync_idx;
877 			}
878 			found_current_lnum = current_lnum;
879 			found_current_col = current_col;
880 			found_m_endpos = cur_si->si_m_endpos;
881 			/*
882 			 * Continue after the match (be aware of a zero-length
883 			 * match).
884 			 */
885 			if (found_m_endpos.lnum > current_lnum)
886 			{
887 			    current_lnum = found_m_endpos.lnum;
888 			    current_col = found_m_endpos.col;
889 			    if (current_lnum >= end_lnum)
890 				break;
891 			}
892 			else if (found_m_endpos.col > current_col)
893 			    current_col = found_m_endpos.col;
894 			else
895 			    ++current_col;
896 
897 			/* syn_current_attr() will have skipped the check for
898 			 * an item that ends here, need to do that now.  Be
899 			 * careful not to go past the NUL. */
900 			prev_current_col = current_col;
901 			if (syn_getcurline()[current_col] != NUL)
902 			    ++current_col;
903 			check_state_ends();
904 			current_col = prev_current_col;
905 		    }
906 		    else
907 			break;
908 		}
909 	    }
910 
911 	    /*
912 	     * If a sync point was encountered, break here.
913 	     */
914 	    if (found_flags)
915 	    {
916 		/*
917 		 * Put the item that was specified by the sync point on the
918 		 * state stack.  If there was no item specified, make the
919 		 * state stack empty.
920 		 */
921 		clear_current_state();
922 		if (found_match_idx >= 0
923 			&& push_current_state(found_match_idx) == OK)
924 		    update_si_attr(current_state.ga_len - 1);
925 
926 		/*
927 		 * When using "grouphere", continue from the sync point
928 		 * match, until the end of the line.  Parsing starts at
929 		 * the next line.
930 		 * For "groupthere" the parsing starts at start_lnum.
931 		 */
932 		if (found_flags & HL_SYNC_HERE)
933 		{
934 		    if (current_state.ga_len)
935 		    {
936 			cur_si = &CUR_STATE(current_state.ga_len - 1);
937 			cur_si->si_h_startpos.lnum = found_current_lnum;
938 			cur_si->si_h_startpos.col = found_current_col;
939 			update_si_end(cur_si, (int)current_col, TRUE);
940 			check_keepend();
941 		    }
942 		    current_col = found_m_endpos.col;
943 		    current_lnum = found_m_endpos.lnum;
944 		    (void)syn_finish_line(FALSE);
945 		    ++current_lnum;
946 		}
947 		else
948 		    current_lnum = start_lnum;
949 
950 		break;
951 	    }
952 
953 	    end_lnum = lnum;
954 	    invalidate_current_state();
955 	}
956 
957 	/* Ran into start of the file or exceeded maximum number of lines */
958 	if (lnum <= break_lnum)
959 	{
960 	    invalidate_current_state();
961 	    current_lnum = break_lnum + 1;
962 	}
963     }
964 
965     validate_current_state();
966 }
967 
968     static void
969 save_chartab(char_u *chartab)
970 {
971     if (syn_block->b_syn_isk != empty_option)
972     {
973 	mch_memmove(chartab, syn_buf->b_chartab, (size_t)32);
974 	mch_memmove(syn_buf->b_chartab, syn_win->w_s->b_syn_chartab,
975 								  (size_t)32);
976     }
977 }
978 
979     static void
980 restore_chartab(char_u *chartab)
981 {
982     if (syn_win->w_s->b_syn_isk != empty_option)
983 	mch_memmove(syn_buf->b_chartab, chartab, (size_t)32);
984 }
985 
986 /*
987  * Return TRUE if the line-continuation pattern matches in line "lnum".
988  */
989     static int
990 syn_match_linecont(linenr_T lnum)
991 {
992     regmmatch_T regmatch;
993     int r;
994     char_u	buf_chartab[32];  /* chartab array for syn iskyeyword */
995 
996     if (syn_block->b_syn_linecont_prog != NULL)
997     {
998 	/* use syntax iskeyword option */
999 	save_chartab(buf_chartab);
1000 	regmatch.rmm_ic = syn_block->b_syn_linecont_ic;
1001 	regmatch.regprog = syn_block->b_syn_linecont_prog;
1002 	r = syn_regexec(&regmatch, lnum, (colnr_T)0,
1003 				IF_SYN_TIME(&syn_block->b_syn_linecont_time));
1004 	syn_block->b_syn_linecont_prog = regmatch.regprog;
1005 	restore_chartab(buf_chartab);
1006 	return r;
1007     }
1008     return FALSE;
1009 }
1010 
1011 /*
1012  * Prepare the current state for the start of a line.
1013  */
1014     static void
1015 syn_start_line(void)
1016 {
1017     current_finished = FALSE;
1018     current_col = 0;
1019 
1020     /*
1021      * Need to update the end of a start/skip/end that continues from the
1022      * previous line and regions that have "keepend".
1023      */
1024     if (current_state.ga_len > 0)
1025     {
1026 	syn_update_ends(TRUE);
1027 	check_state_ends();
1028     }
1029 
1030     next_match_idx = -1;
1031     ++current_line_id;
1032 #ifdef FEAT_CONCEAL
1033     next_seqnr = 1;
1034 #endif
1035 }
1036 
1037 /*
1038  * Check for items in the stack that need their end updated.
1039  * When "startofline" is TRUE the last item is always updated.
1040  * When "startofline" is FALSE the item with "keepend" is forcefully updated.
1041  */
1042     static void
1043 syn_update_ends(int startofline)
1044 {
1045     stateitem_T	*cur_si;
1046     int		i;
1047     int		seen_keepend;
1048 
1049     if (startofline)
1050     {
1051 	/* Check for a match carried over from a previous line with a
1052 	 * contained region.  The match ends as soon as the region ends. */
1053 	for (i = 0; i < current_state.ga_len; ++i)
1054 	{
1055 	    cur_si = &CUR_STATE(i);
1056 	    if (cur_si->si_idx >= 0
1057 		    && (SYN_ITEMS(syn_block)[cur_si->si_idx]).sp_type
1058 							       == SPTYPE_MATCH
1059 		    && cur_si->si_m_endpos.lnum < current_lnum)
1060 	    {
1061 		cur_si->si_flags |= HL_MATCHCONT;
1062 		cur_si->si_m_endpos.lnum = 0;
1063 		cur_si->si_m_endpos.col = 0;
1064 		cur_si->si_h_endpos = cur_si->si_m_endpos;
1065 		cur_si->si_ends = TRUE;
1066 	    }
1067 	}
1068     }
1069 
1070     /*
1071      * Need to update the end of a start/skip/end that continues from the
1072      * previous line.  And regions that have "keepend", because they may
1073      * influence contained items.  If we've just removed "extend"
1074      * (startofline == 0) then we should update ends of normal regions
1075      * contained inside "keepend" because "extend" could have extended
1076      * these "keepend" regions as well as contained normal regions.
1077      * Then check for items ending in column 0.
1078      */
1079     i = current_state.ga_len - 1;
1080     if (keepend_level >= 0)
1081 	for ( ; i > keepend_level; --i)
1082 	    if (CUR_STATE(i).si_flags & HL_EXTEND)
1083 		break;
1084 
1085     seen_keepend = FALSE;
1086     for ( ; i < current_state.ga_len; ++i)
1087     {
1088 	cur_si = &CUR_STATE(i);
1089 	if ((cur_si->si_flags & HL_KEEPEND)
1090 			    || (seen_keepend && !startofline)
1091 			    || (i == current_state.ga_len - 1 && startofline))
1092 	{
1093 	    cur_si->si_h_startpos.col = 0;	/* start highl. in col 0 */
1094 	    cur_si->si_h_startpos.lnum = current_lnum;
1095 
1096 	    if (!(cur_si->si_flags & HL_MATCHCONT))
1097 		update_si_end(cur_si, (int)current_col, !startofline);
1098 
1099 	    if (!startofline && (cur_si->si_flags & HL_KEEPEND))
1100 		seen_keepend = TRUE;
1101 	}
1102     }
1103     check_keepend();
1104 }
1105 
1106 /****************************************
1107  * Handling of the state stack cache.
1108  */
1109 
1110 /*
1111  * EXPLANATION OF THE SYNTAX STATE STACK CACHE
1112  *
1113  * To speed up syntax highlighting, the state stack for the start of some
1114  * lines is cached.  These entries can be used to start parsing at that point.
1115  *
1116  * The stack is kept in b_sst_array[] for each buffer.  There is a list of
1117  * valid entries.  b_sst_first points to the first one, then follow sst_next.
1118  * The entries are sorted on line number.  The first entry is often for line 2
1119  * (line 1 always starts with an empty stack).
1120  * There is also a list for free entries.  This construction is used to avoid
1121  * having to allocate and free memory blocks too often.
1122  *
1123  * When making changes to the buffer, this is logged in b_mod_*.  When calling
1124  * update_screen() to update the display, it will call
1125  * syn_stack_apply_changes() for each displayed buffer to adjust the cached
1126  * entries.  The entries which are inside the changed area are removed,
1127  * because they must be recomputed.  Entries below the changed have their line
1128  * number adjusted for deleted/inserted lines, and have their sst_change_lnum
1129  * set to indicate that a check must be made if the changed lines would change
1130  * the cached entry.
1131  *
1132  * When later displaying lines, an entry is stored for each line.  Displayed
1133  * lines are likely to be displayed again, in which case the state at the
1134  * start of the line is needed.
1135  * For not displayed lines, an entry is stored for every so many lines.  These
1136  * entries will be used e.g., when scrolling backwards.  The distance between
1137  * entries depends on the number of lines in the buffer.  For small buffers
1138  * the distance is fixed at SST_DIST, for large buffers there is a fixed
1139  * number of entries SST_MAX_ENTRIES, and the distance is computed.
1140  */
1141 
1142     static void
1143 syn_stack_free_block(synblock_T *block)
1144 {
1145     synstate_T	*p;
1146 
1147     if (block->b_sst_array != NULL)
1148     {
1149 	for (p = block->b_sst_first; p != NULL; p = p->sst_next)
1150 	    clear_syn_state(p);
1151 	VIM_CLEAR(block->b_sst_array);
1152 	block->b_sst_first = NULL;
1153 	block->b_sst_len = 0;
1154     }
1155 }
1156 /*
1157  * Free b_sst_array[] for buffer "buf".
1158  * Used when syntax items changed to force resyncing everywhere.
1159  */
1160     void
1161 syn_stack_free_all(synblock_T *block)
1162 {
1163 #ifdef FEAT_FOLDING
1164     win_T	*wp;
1165 #endif
1166 
1167     syn_stack_free_block(block);
1168 
1169 #ifdef FEAT_FOLDING
1170     /* When using "syntax" fold method, must update all folds. */
1171     FOR_ALL_WINDOWS(wp)
1172     {
1173 	if (wp->w_s == block && foldmethodIsSyntax(wp))
1174 	    foldUpdateAll(wp);
1175     }
1176 #endif
1177 }
1178 
1179 /*
1180  * Allocate the syntax state stack for syn_buf when needed.
1181  * If the number of entries in b_sst_array[] is much too big or a bit too
1182  * small, reallocate it.
1183  * Also used to allocate b_sst_array[] for the first time.
1184  */
1185     static void
1186 syn_stack_alloc(void)
1187 {
1188     long	len;
1189     synstate_T	*to, *from;
1190     synstate_T	*sstp;
1191 
1192     len = syn_buf->b_ml.ml_line_count / SST_DIST + Rows * 2;
1193     if (len < SST_MIN_ENTRIES)
1194 	len = SST_MIN_ENTRIES;
1195     else if (len > SST_MAX_ENTRIES)
1196 	len = SST_MAX_ENTRIES;
1197     if (syn_block->b_sst_len > len * 2 || syn_block->b_sst_len < len)
1198     {
1199 	/* Allocate 50% too much, to avoid reallocating too often. */
1200 	len = syn_buf->b_ml.ml_line_count;
1201 	len = (len + len / 2) / SST_DIST + Rows * 2;
1202 	if (len < SST_MIN_ENTRIES)
1203 	    len = SST_MIN_ENTRIES;
1204 	else if (len > SST_MAX_ENTRIES)
1205 	    len = SST_MAX_ENTRIES;
1206 
1207 	if (syn_block->b_sst_array != NULL)
1208 	{
1209 	    /* When shrinking the array, cleanup the existing stack.
1210 	     * Make sure that all valid entries fit in the new array. */
1211 	    while (syn_block->b_sst_len - syn_block->b_sst_freecount + 2 > len
1212 		    && syn_stack_cleanup())
1213 		;
1214 	    if (len < syn_block->b_sst_len - syn_block->b_sst_freecount + 2)
1215 		len = syn_block->b_sst_len - syn_block->b_sst_freecount + 2;
1216 	}
1217 
1218 	sstp = ALLOC_CLEAR_MULT(synstate_T, len);
1219 	if (sstp == NULL)	/* out of memory! */
1220 	    return;
1221 
1222 	to = sstp - 1;
1223 	if (syn_block->b_sst_array != NULL)
1224 	{
1225 	    /* Move the states from the old array to the new one. */
1226 	    for (from = syn_block->b_sst_first; from != NULL;
1227 							from = from->sst_next)
1228 	    {
1229 		++to;
1230 		*to = *from;
1231 		to->sst_next = to + 1;
1232 	    }
1233 	}
1234 	if (to != sstp - 1)
1235 	{
1236 	    to->sst_next = NULL;
1237 	    syn_block->b_sst_first = sstp;
1238 	    syn_block->b_sst_freecount = len - (int)(to - sstp) - 1;
1239 	}
1240 	else
1241 	{
1242 	    syn_block->b_sst_first = NULL;
1243 	    syn_block->b_sst_freecount = len;
1244 	}
1245 
1246 	/* Create the list of free entries. */
1247 	syn_block->b_sst_firstfree = to + 1;
1248 	while (++to < sstp + len)
1249 	    to->sst_next = to + 1;
1250 	(sstp + len - 1)->sst_next = NULL;
1251 
1252 	vim_free(syn_block->b_sst_array);
1253 	syn_block->b_sst_array = sstp;
1254 	syn_block->b_sst_len = len;
1255     }
1256 }
1257 
1258 /*
1259  * Check for changes in a buffer to affect stored syntax states.  Uses the
1260  * b_mod_* fields.
1261  * Called from update_screen(), before screen is being updated, once for each
1262  * displayed buffer.
1263  */
1264     void
1265 syn_stack_apply_changes(buf_T *buf)
1266 {
1267     win_T	*wp;
1268 
1269     syn_stack_apply_changes_block(&buf->b_s, buf);
1270 
1271     FOR_ALL_WINDOWS(wp)
1272     {
1273 	if ((wp->w_buffer == buf) && (wp->w_s != &buf->b_s))
1274 	    syn_stack_apply_changes_block(wp->w_s, buf);
1275     }
1276 }
1277 
1278     static void
1279 syn_stack_apply_changes_block(synblock_T *block, buf_T *buf)
1280 {
1281     synstate_T	*p, *prev, *np;
1282     linenr_T	n;
1283 
1284     prev = NULL;
1285     for (p = block->b_sst_first; p != NULL; )
1286     {
1287 	if (p->sst_lnum + block->b_syn_sync_linebreaks > buf->b_mod_top)
1288 	{
1289 	    n = p->sst_lnum + buf->b_mod_xlines;
1290 	    if (n <= buf->b_mod_bot)
1291 	    {
1292 		/* this state is inside the changed area, remove it */
1293 		np = p->sst_next;
1294 		if (prev == NULL)
1295 		    block->b_sst_first = np;
1296 		else
1297 		    prev->sst_next = np;
1298 		syn_stack_free_entry(block, p);
1299 		p = np;
1300 		continue;
1301 	    }
1302 	    /* This state is below the changed area.  Remember the line
1303 	     * that needs to be parsed before this entry can be made valid
1304 	     * again. */
1305 	    if (p->sst_change_lnum != 0 && p->sst_change_lnum > buf->b_mod_top)
1306 	    {
1307 		if (p->sst_change_lnum + buf->b_mod_xlines > buf->b_mod_top)
1308 		    p->sst_change_lnum += buf->b_mod_xlines;
1309 		else
1310 		    p->sst_change_lnum = buf->b_mod_top;
1311 	    }
1312 	    if (p->sst_change_lnum == 0
1313 		    || p->sst_change_lnum < buf->b_mod_bot)
1314 		p->sst_change_lnum = buf->b_mod_bot;
1315 
1316 	    p->sst_lnum = n;
1317 	}
1318 	prev = p;
1319 	p = p->sst_next;
1320     }
1321 }
1322 
1323 /*
1324  * Reduce the number of entries in the state stack for syn_buf.
1325  * Returns TRUE if at least one entry was freed.
1326  */
1327     static int
1328 syn_stack_cleanup(void)
1329 {
1330     synstate_T	*p, *prev;
1331     disptick_T	tick;
1332     int		above;
1333     int		dist;
1334     int		retval = FALSE;
1335 
1336     if (syn_block->b_sst_first == NULL)
1337 	return retval;
1338 
1339     /* Compute normal distance between non-displayed entries. */
1340     if (syn_block->b_sst_len <= Rows)
1341 	dist = 999999;
1342     else
1343 	dist = syn_buf->b_ml.ml_line_count / (syn_block->b_sst_len - Rows) + 1;
1344 
1345     /*
1346      * Go through the list to find the "tick" for the oldest entry that can
1347      * be removed.  Set "above" when the "tick" for the oldest entry is above
1348      * "b_sst_lasttick" (the display tick wraps around).
1349      */
1350     tick = syn_block->b_sst_lasttick;
1351     above = FALSE;
1352     prev = syn_block->b_sst_first;
1353     for (p = prev->sst_next; p != NULL; prev = p, p = p->sst_next)
1354     {
1355 	if (prev->sst_lnum + dist > p->sst_lnum)
1356 	{
1357 	    if (p->sst_tick > syn_block->b_sst_lasttick)
1358 	    {
1359 		if (!above || p->sst_tick < tick)
1360 		    tick = p->sst_tick;
1361 		above = TRUE;
1362 	    }
1363 	    else if (!above && p->sst_tick < tick)
1364 		tick = p->sst_tick;
1365 	}
1366     }
1367 
1368     /*
1369      * Go through the list to make the entries for the oldest tick at an
1370      * interval of several lines.
1371      */
1372     prev = syn_block->b_sst_first;
1373     for (p = prev->sst_next; p != NULL; prev = p, p = p->sst_next)
1374     {
1375 	if (p->sst_tick == tick && prev->sst_lnum + dist > p->sst_lnum)
1376 	{
1377 	    /* Move this entry from used list to free list */
1378 	    prev->sst_next = p->sst_next;
1379 	    syn_stack_free_entry(syn_block, p);
1380 	    p = prev;
1381 	    retval = TRUE;
1382 	}
1383     }
1384     return retval;
1385 }
1386 
1387 /*
1388  * Free the allocated memory for a syn_state item.
1389  * Move the entry into the free list.
1390  */
1391     static void
1392 syn_stack_free_entry(synblock_T *block, synstate_T *p)
1393 {
1394     clear_syn_state(p);
1395     p->sst_next = block->b_sst_firstfree;
1396     block->b_sst_firstfree = p;
1397     ++block->b_sst_freecount;
1398 }
1399 
1400 /*
1401  * Find an entry in the list of state stacks at or before "lnum".
1402  * Returns NULL when there is no entry or the first entry is after "lnum".
1403  */
1404     static synstate_T *
1405 syn_stack_find_entry(linenr_T lnum)
1406 {
1407     synstate_T	*p, *prev;
1408 
1409     prev = NULL;
1410     for (p = syn_block->b_sst_first; p != NULL; prev = p, p = p->sst_next)
1411     {
1412 	if (p->sst_lnum == lnum)
1413 	    return p;
1414 	if (p->sst_lnum > lnum)
1415 	    break;
1416     }
1417     return prev;
1418 }
1419 
1420 /*
1421  * Try saving the current state in b_sst_array[].
1422  * The current state must be valid for the start of the current_lnum line!
1423  */
1424     static synstate_T *
1425 store_current_state(void)
1426 {
1427     int		i;
1428     synstate_T	*p;
1429     bufstate_T	*bp;
1430     stateitem_T	*cur_si;
1431     synstate_T	*sp = syn_stack_find_entry(current_lnum);
1432 
1433     /*
1434      * If the current state contains a start or end pattern that continues
1435      * from the previous line, we can't use it.  Don't store it then.
1436      */
1437     for (i = current_state.ga_len - 1; i >= 0; --i)
1438     {
1439 	cur_si = &CUR_STATE(i);
1440 	if (cur_si->si_h_startpos.lnum >= current_lnum
1441 		|| cur_si->si_m_endpos.lnum >= current_lnum
1442 		|| cur_si->si_h_endpos.lnum >= current_lnum
1443 		|| (cur_si->si_end_idx
1444 		    && cur_si->si_eoe_pos.lnum >= current_lnum))
1445 	    break;
1446     }
1447     if (i >= 0)
1448     {
1449 	if (sp != NULL)
1450 	{
1451 	    /* find "sp" in the list and remove it */
1452 	    if (syn_block->b_sst_first == sp)
1453 		/* it's the first entry */
1454 		syn_block->b_sst_first = sp->sst_next;
1455 	    else
1456 	    {
1457 		/* find the entry just before this one to adjust sst_next */
1458 		for (p = syn_block->b_sst_first; p != NULL; p = p->sst_next)
1459 		    if (p->sst_next == sp)
1460 			break;
1461 		if (p != NULL)	/* just in case */
1462 		    p->sst_next = sp->sst_next;
1463 	    }
1464 	    syn_stack_free_entry(syn_block, sp);
1465 	    sp = NULL;
1466 	}
1467     }
1468     else if (sp == NULL || sp->sst_lnum != current_lnum)
1469     {
1470 	/*
1471 	 * Add a new entry
1472 	 */
1473 	/* If no free items, cleanup the array first. */
1474 	if (syn_block->b_sst_freecount == 0)
1475 	{
1476 	    (void)syn_stack_cleanup();
1477 	    /* "sp" may have been moved to the freelist now */
1478 	    sp = syn_stack_find_entry(current_lnum);
1479 	}
1480 	/* Still no free items?  Must be a strange problem... */
1481 	if (syn_block->b_sst_freecount == 0)
1482 	    sp = NULL;
1483 	else
1484 	{
1485 	    /* Take the first item from the free list and put it in the used
1486 	     * list, after *sp */
1487 	    p = syn_block->b_sst_firstfree;
1488 	    syn_block->b_sst_firstfree = p->sst_next;
1489 	    --syn_block->b_sst_freecount;
1490 	    if (sp == NULL)
1491 	    {
1492 		/* Insert in front of the list */
1493 		p->sst_next = syn_block->b_sst_first;
1494 		syn_block->b_sst_first = p;
1495 	    }
1496 	    else
1497 	    {
1498 		/* insert in list after *sp */
1499 		p->sst_next = sp->sst_next;
1500 		sp->sst_next = p;
1501 	    }
1502 	    sp = p;
1503 	    sp->sst_stacksize = 0;
1504 	    sp->sst_lnum = current_lnum;
1505 	}
1506     }
1507     if (sp != NULL)
1508     {
1509 	/* When overwriting an existing state stack, clear it first */
1510 	clear_syn_state(sp);
1511 	sp->sst_stacksize = current_state.ga_len;
1512 	if (current_state.ga_len > SST_FIX_STATES)
1513 	{
1514 	    /* Need to clear it, might be something remaining from when the
1515 	     * length was less than SST_FIX_STATES. */
1516 	    ga_init2(&sp->sst_union.sst_ga, (int)sizeof(bufstate_T), 1);
1517 	    if (ga_grow(&sp->sst_union.sst_ga, current_state.ga_len) == FAIL)
1518 		sp->sst_stacksize = 0;
1519 	    else
1520 		sp->sst_union.sst_ga.ga_len = current_state.ga_len;
1521 	    bp = SYN_STATE_P(&(sp->sst_union.sst_ga));
1522 	}
1523 	else
1524 	    bp = sp->sst_union.sst_stack;
1525 	for (i = 0; i < sp->sst_stacksize; ++i)
1526 	{
1527 	    bp[i].bs_idx = CUR_STATE(i).si_idx;
1528 	    bp[i].bs_flags = CUR_STATE(i).si_flags;
1529 #ifdef FEAT_CONCEAL
1530 	    bp[i].bs_seqnr = CUR_STATE(i).si_seqnr;
1531 	    bp[i].bs_cchar = CUR_STATE(i).si_cchar;
1532 #endif
1533 	    bp[i].bs_extmatch = ref_extmatch(CUR_STATE(i).si_extmatch);
1534 	}
1535 	sp->sst_next_flags = current_next_flags;
1536 	sp->sst_next_list = current_next_list;
1537 	sp->sst_tick = display_tick;
1538 	sp->sst_change_lnum = 0;
1539     }
1540     current_state_stored = TRUE;
1541     return sp;
1542 }
1543 
1544 /*
1545  * Copy a state stack from "from" in b_sst_array[] to current_state;
1546  */
1547     static void
1548 load_current_state(synstate_T *from)
1549 {
1550     int		i;
1551     bufstate_T	*bp;
1552 
1553     clear_current_state();
1554     validate_current_state();
1555     keepend_level = -1;
1556     if (from->sst_stacksize
1557 	    && ga_grow(&current_state, from->sst_stacksize) != FAIL)
1558     {
1559 	if (from->sst_stacksize > SST_FIX_STATES)
1560 	    bp = SYN_STATE_P(&(from->sst_union.sst_ga));
1561 	else
1562 	    bp = from->sst_union.sst_stack;
1563 	for (i = 0; i < from->sst_stacksize; ++i)
1564 	{
1565 	    CUR_STATE(i).si_idx = bp[i].bs_idx;
1566 	    CUR_STATE(i).si_flags = bp[i].bs_flags;
1567 #ifdef FEAT_CONCEAL
1568 	    CUR_STATE(i).si_seqnr = bp[i].bs_seqnr;
1569 	    CUR_STATE(i).si_cchar = bp[i].bs_cchar;
1570 #endif
1571 	    CUR_STATE(i).si_extmatch = ref_extmatch(bp[i].bs_extmatch);
1572 	    if (keepend_level < 0 && (CUR_STATE(i).si_flags & HL_KEEPEND))
1573 		keepend_level = i;
1574 	    CUR_STATE(i).si_ends = FALSE;
1575 	    CUR_STATE(i).si_m_lnum = 0;
1576 	    if (CUR_STATE(i).si_idx >= 0)
1577 		CUR_STATE(i).si_next_list =
1578 		     (SYN_ITEMS(syn_block)[CUR_STATE(i).si_idx]).sp_next_list;
1579 	    else
1580 		CUR_STATE(i).si_next_list = NULL;
1581 	    update_si_attr(i);
1582 	}
1583 	current_state.ga_len = from->sst_stacksize;
1584     }
1585     current_next_list = from->sst_next_list;
1586     current_next_flags = from->sst_next_flags;
1587     current_lnum = from->sst_lnum;
1588 }
1589 
1590 /*
1591  * Compare saved state stack "*sp" with the current state.
1592  * Return TRUE when they are equal.
1593  */
1594     static int
1595 syn_stack_equal(synstate_T *sp)
1596 {
1597     int		i, j;
1598     bufstate_T	*bp;
1599     reg_extmatch_T	*six, *bsx;
1600 
1601     /* First a quick check if the stacks have the same size end nextlist. */
1602     if (sp->sst_stacksize == current_state.ga_len
1603 	    && sp->sst_next_list == current_next_list)
1604     {
1605 	/* Need to compare all states on both stacks. */
1606 	if (sp->sst_stacksize > SST_FIX_STATES)
1607 	    bp = SYN_STATE_P(&(sp->sst_union.sst_ga));
1608 	else
1609 	    bp = sp->sst_union.sst_stack;
1610 
1611 	for (i = current_state.ga_len; --i >= 0; )
1612 	{
1613 	    /* If the item has another index the state is different. */
1614 	    if (bp[i].bs_idx != CUR_STATE(i).si_idx)
1615 		break;
1616 	    if (bp[i].bs_extmatch != CUR_STATE(i).si_extmatch)
1617 	    {
1618 		/* When the extmatch pointers are different, the strings in
1619 		 * them can still be the same.  Check if the extmatch
1620 		 * references are equal. */
1621 		bsx = bp[i].bs_extmatch;
1622 		six = CUR_STATE(i).si_extmatch;
1623 		/* If one of the extmatch pointers is NULL the states are
1624 		 * different. */
1625 		if (bsx == NULL || six == NULL)
1626 		    break;
1627 		for (j = 0; j < NSUBEXP; ++j)
1628 		{
1629 		    /* Check each referenced match string. They must all be
1630 		     * equal. */
1631 		    if (bsx->matches[j] != six->matches[j])
1632 		    {
1633 			/* If the pointer is different it can still be the
1634 			 * same text.  Compare the strings, ignore case when
1635 			 * the start item has the sp_ic flag set. */
1636 			if (bsx->matches[j] == NULL
1637 				|| six->matches[j] == NULL)
1638 			    break;
1639 			if ((SYN_ITEMS(syn_block)[CUR_STATE(i).si_idx]).sp_ic
1640 				? MB_STRICMP(bsx->matches[j],
1641 							 six->matches[j]) != 0
1642 				: STRCMP(bsx->matches[j], six->matches[j]) != 0)
1643 			    break;
1644 		    }
1645 		}
1646 		if (j != NSUBEXP)
1647 		    break;
1648 	    }
1649 	}
1650 	if (i < 0)
1651 	    return TRUE;
1652     }
1653     return FALSE;
1654 }
1655 
1656 /*
1657  * We stop parsing syntax above line "lnum".  If the stored state at or below
1658  * this line depended on a change before it, it now depends on the line below
1659  * the last parsed line.
1660  * The window looks like this:
1661  *	    line which changed
1662  *	    displayed line
1663  *	    displayed line
1664  * lnum ->  line below window
1665  */
1666     void
1667 syntax_end_parsing(linenr_T lnum)
1668 {
1669     synstate_T	*sp;
1670 
1671     sp = syn_stack_find_entry(lnum);
1672     if (sp != NULL && sp->sst_lnum < lnum)
1673 	sp = sp->sst_next;
1674 
1675     if (sp != NULL && sp->sst_change_lnum != 0)
1676 	sp->sst_change_lnum = lnum;
1677 }
1678 
1679 /*
1680  * End of handling of the state stack.
1681  ****************************************/
1682 
1683     static void
1684 invalidate_current_state(void)
1685 {
1686     clear_current_state();
1687     current_state.ga_itemsize = 0;	/* mark current_state invalid */
1688     current_next_list = NULL;
1689     keepend_level = -1;
1690 }
1691 
1692     static void
1693 validate_current_state(void)
1694 {
1695     current_state.ga_itemsize = sizeof(stateitem_T);
1696     current_state.ga_growsize = 3;
1697 }
1698 
1699 /*
1700  * Return TRUE if the syntax at start of lnum changed since last time.
1701  * This will only be called just after get_syntax_attr() for the previous
1702  * line, to check if the next line needs to be redrawn too.
1703  */
1704     int
1705 syntax_check_changed(linenr_T lnum)
1706 {
1707     int		retval = TRUE;
1708     synstate_T	*sp;
1709 
1710     /*
1711      * Check the state stack when:
1712      * - lnum is just below the previously syntaxed line.
1713      * - lnum is not before the lines with saved states.
1714      * - lnum is not past the lines with saved states.
1715      * - lnum is at or before the last changed line.
1716      */
1717     if (VALID_STATE(&current_state) && lnum == current_lnum + 1)
1718     {
1719 	sp = syn_stack_find_entry(lnum);
1720 	if (sp != NULL && sp->sst_lnum == lnum)
1721 	{
1722 	    /*
1723 	     * finish the previous line (needed when not all of the line was
1724 	     * drawn)
1725 	     */
1726 	    (void)syn_finish_line(FALSE);
1727 
1728 	    /*
1729 	     * Compare the current state with the previously saved state of
1730 	     * the line.
1731 	     */
1732 	    if (syn_stack_equal(sp))
1733 		retval = FALSE;
1734 
1735 	    /*
1736 	     * Store the current state in b_sst_array[] for later use.
1737 	     */
1738 	    ++current_lnum;
1739 	    (void)store_current_state();
1740 	}
1741     }
1742 
1743     return retval;
1744 }
1745 
1746 /*
1747  * Finish the current line.
1748  * This doesn't return any attributes, it only gets the state at the end of
1749  * the line.  It can start anywhere in the line, as long as the current state
1750  * is valid.
1751  */
1752     static int
1753 syn_finish_line(
1754     int	    syncing)		/* called for syncing */
1755 {
1756     stateitem_T	*cur_si;
1757     colnr_T	prev_current_col;
1758 
1759     while (!current_finished)
1760     {
1761 	(void)syn_current_attr(syncing, FALSE, NULL, FALSE);
1762 	/*
1763 	 * When syncing, and found some item, need to check the item.
1764 	 */
1765 	if (syncing && current_state.ga_len)
1766 	{
1767 	    /*
1768 	     * Check for match with sync item.
1769 	     */
1770 	    cur_si = &CUR_STATE(current_state.ga_len - 1);
1771 	    if (cur_si->si_idx >= 0
1772 		    && (SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags
1773 					  & (HL_SYNC_HERE|HL_SYNC_THERE)))
1774 		return TRUE;
1775 
1776 	    /* syn_current_attr() will have skipped the check for an item
1777 	     * that ends here, need to do that now.  Be careful not to go
1778 	     * past the NUL. */
1779 	    prev_current_col = current_col;
1780 	    if (syn_getcurline()[current_col] != NUL)
1781 		++current_col;
1782 	    check_state_ends();
1783 	    current_col = prev_current_col;
1784 	}
1785 	++current_col;
1786     }
1787     return FALSE;
1788 }
1789 
1790 /*
1791  * Return highlight attributes for next character.
1792  * Must first call syntax_start() once for the line.
1793  * "col" is normally 0 for the first use in a line, and increments by one each
1794  * time.  It's allowed to skip characters and to stop before the end of the
1795  * line.  But only a "col" after a previously used column is allowed.
1796  * When "can_spell" is not NULL set it to TRUE when spell-checking should be
1797  * done.
1798  */
1799     int
1800 get_syntax_attr(
1801     colnr_T	col,
1802     int		*can_spell,
1803     int		keep_state)	/* keep state of char at "col" */
1804 {
1805     int	    attr = 0;
1806 
1807     if (can_spell != NULL)
1808 	/* Default: Only do spelling when there is no @Spell cluster or when
1809 	 * ":syn spell toplevel" was used. */
1810 	*can_spell = syn_block->b_syn_spell == SYNSPL_DEFAULT
1811 		    ? (syn_block->b_spell_cluster_id == 0)
1812 		    : (syn_block->b_syn_spell == SYNSPL_TOP);
1813 
1814     /* check for out of memory situation */
1815     if (syn_block->b_sst_array == NULL)
1816 	return 0;
1817 
1818     /* After 'synmaxcol' the attribute is always zero. */
1819     if (syn_buf->b_p_smc > 0 && col >= (colnr_T)syn_buf->b_p_smc)
1820     {
1821 	clear_current_state();
1822 #ifdef FEAT_EVAL
1823 	current_id = 0;
1824 	current_trans_id = 0;
1825 #endif
1826 #ifdef FEAT_CONCEAL
1827 	current_flags = 0;
1828 	current_seqnr = 0;
1829 #endif
1830 	return 0;
1831     }
1832 
1833     /* Make sure current_state is valid */
1834     if (INVALID_STATE(&current_state))
1835 	validate_current_state();
1836 
1837     /*
1838      * Skip from the current column to "col", get the attributes for "col".
1839      */
1840     while (current_col <= col)
1841     {
1842 	attr = syn_current_attr(FALSE, TRUE, can_spell,
1843 				     current_col == col ? keep_state : FALSE);
1844 	++current_col;
1845     }
1846 
1847     return attr;
1848 }
1849 
1850 /*
1851  * Get syntax attributes for current_lnum, current_col.
1852  */
1853     static int
1854 syn_current_attr(
1855     int		syncing,		/* When 1: called for syncing */
1856     int		displaying,		/* result will be displayed */
1857     int		*can_spell,		/* return: do spell checking */
1858     int		keep_state)		/* keep syntax stack afterwards */
1859 {
1860     int		syn_id;
1861     lpos_T	endpos;		/* was: char_u *endp; */
1862     lpos_T	hl_startpos;	/* was: int hl_startcol; */
1863     lpos_T	hl_endpos;
1864     lpos_T	eos_pos;	/* end-of-start match (start region) */
1865     lpos_T	eoe_pos;	/* end-of-end pattern */
1866     int		end_idx;	/* group ID for end pattern */
1867     int		idx;
1868     synpat_T	*spp;
1869     stateitem_T	*cur_si, *sip = NULL;
1870     int		startcol;
1871     int		endcol;
1872     long	flags;
1873     int		cchar;
1874     short	*next_list;
1875     int		found_match;		    /* found usable match */
1876     static int	try_next_column = FALSE;    /* must try in next col */
1877     int		do_keywords;
1878     regmmatch_T	regmatch;
1879     lpos_T	pos;
1880     int		lc_col;
1881     reg_extmatch_T *cur_extmatch = NULL;
1882     char_u	buf_chartab[32];  /* chartab array for syn iskyeyword */
1883     char_u	*line;		/* current line.  NOTE: becomes invalid after
1884 				   looking for a pattern match! */
1885 
1886     /* variables for zero-width matches that have a "nextgroup" argument */
1887     int		keep_next_list;
1888     int		zero_width_next_list = FALSE;
1889     garray_T	zero_width_next_ga;
1890 
1891     /*
1892      * No character, no attributes!  Past end of line?
1893      * Do try matching with an empty line (could be the start of a region).
1894      */
1895     line = syn_getcurline();
1896     if (line[current_col] == NUL && current_col != 0)
1897     {
1898 	/*
1899 	 * If we found a match after the last column, use it.
1900 	 */
1901 	if (next_match_idx >= 0 && next_match_col >= (int)current_col
1902 						  && next_match_col != MAXCOL)
1903 	    (void)push_next_match(NULL);
1904 
1905 	current_finished = TRUE;
1906 	current_state_stored = FALSE;
1907 	return 0;
1908     }
1909 
1910     /* if the current or next character is NUL, we will finish the line now */
1911     if (line[current_col] == NUL || line[current_col + 1] == NUL)
1912     {
1913 	current_finished = TRUE;
1914 	current_state_stored = FALSE;
1915     }
1916 
1917     /*
1918      * When in the previous column there was a match but it could not be used
1919      * (empty match or already matched in this column) need to try again in
1920      * the next column.
1921      */
1922     if (try_next_column)
1923     {
1924 	next_match_idx = -1;
1925 	try_next_column = FALSE;
1926     }
1927 
1928     /* Only check for keywords when not syncing and there are some. */
1929     do_keywords = !syncing
1930 		    && (syn_block->b_keywtab.ht_used > 0
1931 			    || syn_block->b_keywtab_ic.ht_used > 0);
1932 
1933     /* Init the list of zero-width matches with a nextlist.  This is used to
1934      * avoid matching the same item in the same position twice. */
1935     ga_init2(&zero_width_next_ga, (int)sizeof(int), 10);
1936 
1937     /* use syntax iskeyword option */
1938     save_chartab(buf_chartab);
1939 
1940     /*
1941      * Repeat matching keywords and patterns, to find contained items at the
1942      * same column.  This stops when there are no extra matches at the current
1943      * column.
1944      */
1945     do
1946     {
1947 	found_match = FALSE;
1948 	keep_next_list = FALSE;
1949 	syn_id = 0;
1950 
1951 
1952 	/*
1953 	 * 1. Check for a current state.
1954 	 *    Only when there is no current state, or if the current state may
1955 	 *    contain other things, we need to check for keywords and patterns.
1956 	 *    Always need to check for contained items if some item has the
1957 	 *    "containedin" argument (takes extra time!).
1958 	 */
1959 	if (current_state.ga_len)
1960 	    cur_si = &CUR_STATE(current_state.ga_len - 1);
1961 	else
1962 	    cur_si = NULL;
1963 
1964 	if (syn_block->b_syn_containedin || cur_si == NULL
1965 					      || cur_si->si_cont_list != NULL)
1966 	{
1967 	    /*
1968 	     * 2. Check for keywords, if on a keyword char after a non-keyword
1969 	     *	  char.  Don't do this when syncing.
1970 	     */
1971 	    if (do_keywords)
1972 	    {
1973 	      line = syn_getcurline();
1974 	      if (vim_iswordp_buf(line + current_col, syn_buf)
1975 		      && (current_col == 0
1976 			  || !vim_iswordp_buf(line + current_col - 1
1977 			      - (has_mbyte
1978 				  ? (*mb_head_off)(line, line + current_col - 1)
1979 				  : 0) , syn_buf)))
1980 	      {
1981 		syn_id = check_keyword_id(line, (int)current_col,
1982 					 &endcol, &flags, &next_list, cur_si,
1983 					 &cchar);
1984 		if (syn_id != 0)
1985 		{
1986 		    if (push_current_state(KEYWORD_IDX) == OK)
1987 		    {
1988 			cur_si = &CUR_STATE(current_state.ga_len - 1);
1989 			cur_si->si_m_startcol = current_col;
1990 			cur_si->si_h_startpos.lnum = current_lnum;
1991 			cur_si->si_h_startpos.col = 0;	/* starts right away */
1992 			cur_si->si_m_endpos.lnum = current_lnum;
1993 			cur_si->si_m_endpos.col = endcol;
1994 			cur_si->si_h_endpos.lnum = current_lnum;
1995 			cur_si->si_h_endpos.col = endcol;
1996 			cur_si->si_ends = TRUE;
1997 			cur_si->si_end_idx = 0;
1998 			cur_si->si_flags = flags;
1999 #ifdef FEAT_CONCEAL
2000 			cur_si->si_seqnr = next_seqnr++;
2001 			cur_si->si_cchar = cchar;
2002 			if (current_state.ga_len > 1)
2003 			    cur_si->si_flags |=
2004 				  CUR_STATE(current_state.ga_len - 2).si_flags
2005 								 & HL_CONCEAL;
2006 #endif
2007 			cur_si->si_id = syn_id;
2008 			cur_si->si_trans_id = syn_id;
2009 			if (flags & HL_TRANSP)
2010 			{
2011 			    if (current_state.ga_len < 2)
2012 			    {
2013 				cur_si->si_attr = 0;
2014 				cur_si->si_trans_id = 0;
2015 			    }
2016 			    else
2017 			    {
2018 				cur_si->si_attr = CUR_STATE(
2019 					current_state.ga_len - 2).si_attr;
2020 				cur_si->si_trans_id = CUR_STATE(
2021 					current_state.ga_len - 2).si_trans_id;
2022 			    }
2023 			}
2024 			else
2025 			    cur_si->si_attr = syn_id2attr(syn_id);
2026 			cur_si->si_cont_list = NULL;
2027 			cur_si->si_next_list = next_list;
2028 			check_keepend();
2029 		    }
2030 		    else
2031 			vim_free(next_list);
2032 		}
2033 	      }
2034 	    }
2035 
2036 	    /*
2037 	     * 3. Check for patterns (only if no keyword found).
2038 	     */
2039 	    if (syn_id == 0 && syn_block->b_syn_patterns.ga_len)
2040 	    {
2041 		/*
2042 		 * If we didn't check for a match yet, or we are past it, check
2043 		 * for any match with a pattern.
2044 		 */
2045 		if (next_match_idx < 0 || next_match_col < (int)current_col)
2046 		{
2047 		    /*
2048 		     * Check all relevant patterns for a match at this
2049 		     * position.  This is complicated, because matching with a
2050 		     * pattern takes quite a bit of time, thus we want to
2051 		     * avoid doing it when it's not needed.
2052 		     */
2053 		    next_match_idx = 0;		/* no match in this line yet */
2054 		    next_match_col = MAXCOL;
2055 		    for (idx = syn_block->b_syn_patterns.ga_len; --idx >= 0; )
2056 		    {
2057 			spp = &(SYN_ITEMS(syn_block)[idx]);
2058 			if (	   spp->sp_syncing == syncing
2059 				&& (displaying || !(spp->sp_flags & HL_DISPLAY))
2060 				&& (spp->sp_type == SPTYPE_MATCH
2061 				    || spp->sp_type == SPTYPE_START)
2062 				&& (current_next_list != NULL
2063 				    ? in_id_list(NULL, current_next_list,
2064 							      &spp->sp_syn, 0)
2065 				    : (cur_si == NULL
2066 					? !(spp->sp_flags & HL_CONTAINED)
2067 					: in_id_list(cur_si,
2068 					    cur_si->si_cont_list, &spp->sp_syn,
2069 					    spp->sp_flags & HL_CONTAINED))))
2070 			{
2071 			    int r;
2072 
2073 			    /* If we already tried matching in this line, and
2074 			     * there isn't a match before next_match_col, skip
2075 			     * this item. */
2076 			    if (spp->sp_line_id == current_line_id
2077 				    && spp->sp_startcol >= next_match_col)
2078 				continue;
2079 			    spp->sp_line_id = current_line_id;
2080 
2081 			    lc_col = current_col - spp->sp_offsets[SPO_LC_OFF];
2082 			    if (lc_col < 0)
2083 				lc_col = 0;
2084 
2085 			    regmatch.rmm_ic = spp->sp_ic;
2086 			    regmatch.regprog = spp->sp_prog;
2087 			    r = syn_regexec(&regmatch,
2088 					     current_lnum,
2089 					     (colnr_T)lc_col,
2090 					     IF_SYN_TIME(&spp->sp_time));
2091 			    spp->sp_prog = regmatch.regprog;
2092 			    if (!r)
2093 			    {
2094 				/* no match in this line, try another one */
2095 				spp->sp_startcol = MAXCOL;
2096 				continue;
2097 			    }
2098 
2099 			    /*
2100 			     * Compute the first column of the match.
2101 			     */
2102 			    syn_add_start_off(&pos, &regmatch,
2103 							 spp, SPO_MS_OFF, -1);
2104 			    if (pos.lnum > current_lnum)
2105 			    {
2106 				/* must have used end of match in a next line,
2107 				 * we can't handle that */
2108 				spp->sp_startcol = MAXCOL;
2109 				continue;
2110 			    }
2111 			    startcol = pos.col;
2112 
2113 			    /* remember the next column where this pattern
2114 			     * matches in the current line */
2115 			    spp->sp_startcol = startcol;
2116 
2117 			    /*
2118 			     * If a previously found match starts at a lower
2119 			     * column number, don't use this one.
2120 			     */
2121 			    if (startcol >= next_match_col)
2122 				continue;
2123 
2124 			    /*
2125 			     * If we matched this pattern at this position
2126 			     * before, skip it.  Must retry in the next
2127 			     * column, because it may match from there.
2128 			     */
2129 			    if (did_match_already(idx, &zero_width_next_ga))
2130 			    {
2131 				try_next_column = TRUE;
2132 				continue;
2133 			    }
2134 
2135 			    endpos.lnum = regmatch.endpos[0].lnum;
2136 			    endpos.col = regmatch.endpos[0].col;
2137 
2138 			    /* Compute the highlight start. */
2139 			    syn_add_start_off(&hl_startpos, &regmatch,
2140 							 spp, SPO_HS_OFF, -1);
2141 
2142 			    /* Compute the region start. */
2143 			    /* Default is to use the end of the match. */
2144 			    syn_add_end_off(&eos_pos, &regmatch,
2145 							 spp, SPO_RS_OFF, 0);
2146 
2147 			    /*
2148 			     * Grab the external submatches before they get
2149 			     * overwritten.  Reference count doesn't change.
2150 			     */
2151 			    unref_extmatch(cur_extmatch);
2152 			    cur_extmatch = re_extmatch_out;
2153 			    re_extmatch_out = NULL;
2154 
2155 			    flags = 0;
2156 			    eoe_pos.lnum = 0;	/* avoid warning */
2157 			    eoe_pos.col = 0;
2158 			    end_idx = 0;
2159 			    hl_endpos.lnum = 0;
2160 
2161 			    /*
2162 			     * For a "oneline" the end must be found in the
2163 			     * same line too.  Search for it after the end of
2164 			     * the match with the start pattern.  Set the
2165 			     * resulting end positions at the same time.
2166 			     */
2167 			    if (spp->sp_type == SPTYPE_START
2168 					      && (spp->sp_flags & HL_ONELINE))
2169 			    {
2170 				lpos_T	startpos;
2171 
2172 				startpos = endpos;
2173 				find_endpos(idx, &startpos, &endpos, &hl_endpos,
2174 				    &flags, &eoe_pos, &end_idx, cur_extmatch);
2175 				if (endpos.lnum == 0)
2176 				    continue;	    /* not found */
2177 			    }
2178 
2179 			    /*
2180 			     * For a "match" the size must be > 0 after the
2181 			     * end offset needs has been added.  Except when
2182 			     * syncing.
2183 			     */
2184 			    else if (spp->sp_type == SPTYPE_MATCH)
2185 			    {
2186 				syn_add_end_off(&hl_endpos, &regmatch, spp,
2187 							       SPO_HE_OFF, 0);
2188 				syn_add_end_off(&endpos, &regmatch, spp,
2189 							       SPO_ME_OFF, 0);
2190 				if (endpos.lnum == current_lnum
2191 				      && (int)endpos.col + syncing < startcol)
2192 				{
2193 				    /*
2194 				     * If an empty string is matched, may need
2195 				     * to try matching again at next column.
2196 				     */
2197 				    if (regmatch.startpos[0].col
2198 						    == regmatch.endpos[0].col)
2199 					try_next_column = TRUE;
2200 				    continue;
2201 				}
2202 			    }
2203 
2204 			    /*
2205 			     * keep the best match so far in next_match_*
2206 			     */
2207 			    /* Highlighting must start after startpos and end
2208 			     * before endpos. */
2209 			    if (hl_startpos.lnum == current_lnum
2210 					   && (int)hl_startpos.col < startcol)
2211 				hl_startpos.col = startcol;
2212 			    limit_pos_zero(&hl_endpos, &endpos);
2213 
2214 			    next_match_idx = idx;
2215 			    next_match_col = startcol;
2216 			    next_match_m_endpos = endpos;
2217 			    next_match_h_endpos = hl_endpos;
2218 			    next_match_h_startpos = hl_startpos;
2219 			    next_match_flags = flags;
2220 			    next_match_eos_pos = eos_pos;
2221 			    next_match_eoe_pos = eoe_pos;
2222 			    next_match_end_idx = end_idx;
2223 			    unref_extmatch(next_match_extmatch);
2224 			    next_match_extmatch = cur_extmatch;
2225 			    cur_extmatch = NULL;
2226 			}
2227 		    }
2228 		}
2229 
2230 		/*
2231 		 * If we found a match at the current column, use it.
2232 		 */
2233 		if (next_match_idx >= 0 && next_match_col == (int)current_col)
2234 		{
2235 		    synpat_T	*lspp;
2236 
2237 		    /* When a zero-width item matched which has a nextgroup,
2238 		     * don't push the item but set nextgroup. */
2239 		    lspp = &(SYN_ITEMS(syn_block)[next_match_idx]);
2240 		    if (next_match_m_endpos.lnum == current_lnum
2241 			    && next_match_m_endpos.col == current_col
2242 			    && lspp->sp_next_list != NULL)
2243 		    {
2244 			current_next_list = lspp->sp_next_list;
2245 			current_next_flags = lspp->sp_flags;
2246 			keep_next_list = TRUE;
2247 			zero_width_next_list = TRUE;
2248 
2249 			/* Add the index to a list, so that we can check
2250 			 * later that we don't match it again (and cause an
2251 			 * endless loop). */
2252 			if (ga_grow(&zero_width_next_ga, 1) == OK)
2253 			{
2254 			    ((int *)(zero_width_next_ga.ga_data))
2255 				[zero_width_next_ga.ga_len++] = next_match_idx;
2256 			}
2257 			next_match_idx = -1;
2258 		    }
2259 		    else
2260 			cur_si = push_next_match(cur_si);
2261 		    found_match = TRUE;
2262 		}
2263 	    }
2264 	}
2265 
2266 	/*
2267 	 * Handle searching for nextgroup match.
2268 	 */
2269 	if (current_next_list != NULL && !keep_next_list)
2270 	{
2271 	    /*
2272 	     * If a nextgroup was not found, continue looking for one if:
2273 	     * - this is an empty line and the "skipempty" option was given
2274 	     * - we are on white space and the "skipwhite" option was given
2275 	     */
2276 	    if (!found_match)
2277 	    {
2278 		line = syn_getcurline();
2279 		if (((current_next_flags & HL_SKIPWHITE)
2280 			    && VIM_ISWHITE(line[current_col]))
2281 			|| ((current_next_flags & HL_SKIPEMPTY)
2282 			    && *line == NUL))
2283 		    break;
2284 	    }
2285 
2286 	    /*
2287 	     * If a nextgroup was found: Use it, and continue looking for
2288 	     * contained matches.
2289 	     * If a nextgroup was not found: Continue looking for a normal
2290 	     * match.
2291 	     * When did set current_next_list for a zero-width item and no
2292 	     * match was found don't loop (would get stuck).
2293 	     */
2294 	    current_next_list = NULL;
2295 	    next_match_idx = -1;
2296 	    if (!zero_width_next_list)
2297 		found_match = TRUE;
2298 	}
2299 
2300     } while (found_match);
2301 
2302     restore_chartab(buf_chartab);
2303 
2304     /*
2305      * Use attributes from the current state, if within its highlighting.
2306      * If not, use attributes from the current-but-one state, etc.
2307      */
2308     current_attr = 0;
2309 #ifdef FEAT_EVAL
2310     current_id = 0;
2311     current_trans_id = 0;
2312 #endif
2313 #ifdef FEAT_CONCEAL
2314     current_flags = 0;
2315     current_seqnr = 0;
2316 #endif
2317     if (cur_si != NULL)
2318     {
2319 #ifndef FEAT_EVAL
2320 	int	current_trans_id = 0;
2321 #endif
2322 	for (idx = current_state.ga_len - 1; idx >= 0; --idx)
2323 	{
2324 	    sip = &CUR_STATE(idx);
2325 	    if ((current_lnum > sip->si_h_startpos.lnum
2326 			|| (current_lnum == sip->si_h_startpos.lnum
2327 			    && current_col >= sip->si_h_startpos.col))
2328 		    && (sip->si_h_endpos.lnum == 0
2329 			|| current_lnum < sip->si_h_endpos.lnum
2330 			|| (current_lnum == sip->si_h_endpos.lnum
2331 			    && current_col < sip->si_h_endpos.col)))
2332 	    {
2333 		current_attr = sip->si_attr;
2334 #ifdef FEAT_EVAL
2335 		current_id = sip->si_id;
2336 #endif
2337 		current_trans_id = sip->si_trans_id;
2338 #ifdef FEAT_CONCEAL
2339 		current_flags = sip->si_flags;
2340 		current_seqnr = sip->si_seqnr;
2341 		current_sub_char = sip->si_cchar;
2342 #endif
2343 		break;
2344 	    }
2345 	}
2346 
2347 	if (can_spell != NULL)
2348 	{
2349 	    struct sp_syn   sps;
2350 
2351 	    /*
2352 	     * set "can_spell" to TRUE if spell checking is supposed to be
2353 	     * done in the current item.
2354 	     */
2355 	    if (syn_block->b_spell_cluster_id == 0)
2356 	    {
2357 		/* There is no @Spell cluster: Do spelling for items without
2358 		 * @NoSpell cluster. */
2359 		if (syn_block->b_nospell_cluster_id == 0
2360 						     || current_trans_id == 0)
2361 		    *can_spell = (syn_block->b_syn_spell != SYNSPL_NOTOP);
2362 		else
2363 		{
2364 		    sps.inc_tag = 0;
2365 		    sps.id = syn_block->b_nospell_cluster_id;
2366 		    sps.cont_in_list = NULL;
2367 		    *can_spell = !in_id_list(sip, sip->si_cont_list, &sps, 0);
2368 		}
2369 	    }
2370 	    else
2371 	    {
2372 		/* The @Spell cluster is defined: Do spelling in items with
2373 		 * the @Spell cluster.  But not when @NoSpell is also there.
2374 		 * At the toplevel only spell check when ":syn spell toplevel"
2375 		 * was used. */
2376 		if (current_trans_id == 0)
2377 		    *can_spell = (syn_block->b_syn_spell == SYNSPL_TOP);
2378 		else
2379 		{
2380 		    sps.inc_tag = 0;
2381 		    sps.id = syn_block->b_spell_cluster_id;
2382 		    sps.cont_in_list = NULL;
2383 		    *can_spell = in_id_list(sip, sip->si_cont_list, &sps, 0);
2384 
2385 		    if (syn_block->b_nospell_cluster_id != 0)
2386 		    {
2387 			sps.id = syn_block->b_nospell_cluster_id;
2388 			if (in_id_list(sip, sip->si_cont_list, &sps, 0))
2389 			    *can_spell = FALSE;
2390 		    }
2391 		}
2392 	    }
2393 	}
2394 
2395 
2396 	/*
2397 	 * Check for end of current state (and the states before it) at the
2398 	 * next column.  Don't do this for syncing, because we would miss a
2399 	 * single character match.
2400 	 * First check if the current state ends at the current column.  It
2401 	 * may be for an empty match and a containing item might end in the
2402 	 * current column.
2403 	 */
2404 	if (!syncing && !keep_state)
2405 	{
2406 	    check_state_ends();
2407 	    if (current_state.ga_len > 0
2408 				      && syn_getcurline()[current_col] != NUL)
2409 	    {
2410 		++current_col;
2411 		check_state_ends();
2412 		--current_col;
2413 	    }
2414 	}
2415     }
2416     else if (can_spell != NULL)
2417 	/* Default: Only do spelling when there is no @Spell cluster or when
2418 	 * ":syn spell toplevel" was used. */
2419 	*can_spell = syn_block->b_syn_spell == SYNSPL_DEFAULT
2420 		    ? (syn_block->b_spell_cluster_id == 0)
2421 		    : (syn_block->b_syn_spell == SYNSPL_TOP);
2422 
2423     /* nextgroup ends at end of line, unless "skipnl" or "skipempty" present */
2424     if (current_next_list != NULL
2425 	    && (line = syn_getcurline())[current_col] != NUL
2426 	    && line[current_col + 1] == NUL
2427 	    && !(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY)))
2428 	current_next_list = NULL;
2429 
2430     if (zero_width_next_ga.ga_len > 0)
2431 	ga_clear(&zero_width_next_ga);
2432 
2433     /* No longer need external matches.  But keep next_match_extmatch. */
2434     unref_extmatch(re_extmatch_out);
2435     re_extmatch_out = NULL;
2436     unref_extmatch(cur_extmatch);
2437 
2438     return current_attr;
2439 }
2440 
2441 
2442 /*
2443  * Check if we already matched pattern "idx" at the current column.
2444  */
2445     static int
2446 did_match_already(int idx, garray_T *gap)
2447 {
2448     int		i;
2449 
2450     for (i = current_state.ga_len; --i >= 0; )
2451 	if (CUR_STATE(i).si_m_startcol == (int)current_col
2452 		&& CUR_STATE(i).si_m_lnum == (int)current_lnum
2453 		&& CUR_STATE(i).si_idx == idx)
2454 	    return TRUE;
2455 
2456     /* Zero-width matches with a nextgroup argument are not put on the syntax
2457      * stack, and can only be matched once anyway. */
2458     for (i = gap->ga_len; --i >= 0; )
2459 	if (((int *)(gap->ga_data))[i] == idx)
2460 	    return TRUE;
2461 
2462     return FALSE;
2463 }
2464 
2465 /*
2466  * Push the next match onto the stack.
2467  */
2468     static stateitem_T *
2469 push_next_match(stateitem_T *cur_si)
2470 {
2471     synpat_T	*spp;
2472 #ifdef FEAT_CONCEAL
2473     int		 save_flags;
2474 #endif
2475 
2476     spp = &(SYN_ITEMS(syn_block)[next_match_idx]);
2477 
2478     /*
2479      * Push the item in current_state stack;
2480      */
2481     if (push_current_state(next_match_idx) == OK)
2482     {
2483 	/*
2484 	 * If it's a start-skip-end type that crosses lines, figure out how
2485 	 * much it continues in this line.  Otherwise just fill in the length.
2486 	 */
2487 	cur_si = &CUR_STATE(current_state.ga_len - 1);
2488 	cur_si->si_h_startpos = next_match_h_startpos;
2489 	cur_si->si_m_startcol = current_col;
2490 	cur_si->si_m_lnum = current_lnum;
2491 	cur_si->si_flags = spp->sp_flags;
2492 #ifdef FEAT_CONCEAL
2493 	cur_si->si_seqnr = next_seqnr++;
2494 	cur_si->si_cchar = spp->sp_cchar;
2495 	if (current_state.ga_len > 1)
2496 	    cur_si->si_flags |=
2497 		    CUR_STATE(current_state.ga_len - 2).si_flags & HL_CONCEAL;
2498 #endif
2499 	cur_si->si_next_list = spp->sp_next_list;
2500 	cur_si->si_extmatch = ref_extmatch(next_match_extmatch);
2501 	if (spp->sp_type == SPTYPE_START && !(spp->sp_flags & HL_ONELINE))
2502 	{
2503 	    /* Try to find the end pattern in the current line */
2504 	    update_si_end(cur_si, (int)(next_match_m_endpos.col), TRUE);
2505 	    check_keepend();
2506 	}
2507 	else
2508 	{
2509 	    cur_si->si_m_endpos = next_match_m_endpos;
2510 	    cur_si->si_h_endpos = next_match_h_endpos;
2511 	    cur_si->si_ends = TRUE;
2512 	    cur_si->si_flags |= next_match_flags;
2513 	    cur_si->si_eoe_pos = next_match_eoe_pos;
2514 	    cur_si->si_end_idx = next_match_end_idx;
2515 	}
2516 	if (keepend_level < 0 && (cur_si->si_flags & HL_KEEPEND))
2517 	    keepend_level = current_state.ga_len - 1;
2518 	check_keepend();
2519 	update_si_attr(current_state.ga_len - 1);
2520 
2521 #ifdef FEAT_CONCEAL
2522 	save_flags = cur_si->si_flags & (HL_CONCEAL | HL_CONCEALENDS);
2523 #endif
2524 	/*
2525 	 * If the start pattern has another highlight group, push another item
2526 	 * on the stack for the start pattern.
2527 	 */
2528 	if (	   spp->sp_type == SPTYPE_START
2529 		&& spp->sp_syn_match_id != 0
2530 		&& push_current_state(next_match_idx) == OK)
2531 	{
2532 	    cur_si = &CUR_STATE(current_state.ga_len - 1);
2533 	    cur_si->si_h_startpos = next_match_h_startpos;
2534 	    cur_si->si_m_startcol = current_col;
2535 	    cur_si->si_m_lnum = current_lnum;
2536 	    cur_si->si_m_endpos = next_match_eos_pos;
2537 	    cur_si->si_h_endpos = next_match_eos_pos;
2538 	    cur_si->si_ends = TRUE;
2539 	    cur_si->si_end_idx = 0;
2540 	    cur_si->si_flags = HL_MATCH;
2541 #ifdef FEAT_CONCEAL
2542 	    cur_si->si_seqnr = next_seqnr++;
2543 	    cur_si->si_flags |= save_flags;
2544 	    if (cur_si->si_flags & HL_CONCEALENDS)
2545 		cur_si->si_flags |= HL_CONCEAL;
2546 #endif
2547 	    cur_si->si_next_list = NULL;
2548 	    check_keepend();
2549 	    update_si_attr(current_state.ga_len - 1);
2550 	}
2551     }
2552 
2553     next_match_idx = -1;	/* try other match next time */
2554 
2555     return cur_si;
2556 }
2557 
2558 /*
2559  * Check for end of current state (and the states before it).
2560  */
2561     static void
2562 check_state_ends(void)
2563 {
2564     stateitem_T	*cur_si;
2565     int		had_extend;
2566 
2567     cur_si = &CUR_STATE(current_state.ga_len - 1);
2568     for (;;)
2569     {
2570 	if (cur_si->si_ends
2571 		&& (cur_si->si_m_endpos.lnum < current_lnum
2572 		    || (cur_si->si_m_endpos.lnum == current_lnum
2573 			&& cur_si->si_m_endpos.col <= current_col)))
2574 	{
2575 	    /*
2576 	     * If there is an end pattern group ID, highlight the end pattern
2577 	     * now.  No need to pop the current item from the stack.
2578 	     * Only do this if the end pattern continues beyond the current
2579 	     * position.
2580 	     */
2581 	    if (cur_si->si_end_idx
2582 		    && (cur_si->si_eoe_pos.lnum > current_lnum
2583 			|| (cur_si->si_eoe_pos.lnum == current_lnum
2584 			    && cur_si->si_eoe_pos.col > current_col)))
2585 	    {
2586 		cur_si->si_idx = cur_si->si_end_idx;
2587 		cur_si->si_end_idx = 0;
2588 		cur_si->si_m_endpos = cur_si->si_eoe_pos;
2589 		cur_si->si_h_endpos = cur_si->si_eoe_pos;
2590 		cur_si->si_flags |= HL_MATCH;
2591 #ifdef FEAT_CONCEAL
2592 		cur_si->si_seqnr = next_seqnr++;
2593 		if (cur_si->si_flags & HL_CONCEALENDS)
2594 		    cur_si->si_flags |= HL_CONCEAL;
2595 #endif
2596 		update_si_attr(current_state.ga_len - 1);
2597 
2598 		/* nextgroup= should not match in the end pattern */
2599 		current_next_list = NULL;
2600 
2601 		/* what matches next may be different now, clear it */
2602 		next_match_idx = 0;
2603 		next_match_col = MAXCOL;
2604 		break;
2605 	    }
2606 	    else
2607 	    {
2608 		/* handle next_list, unless at end of line and no "skipnl" or
2609 		 * "skipempty" */
2610 		current_next_list = cur_si->si_next_list;
2611 		current_next_flags = cur_si->si_flags;
2612 		if (!(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY))
2613 			&& syn_getcurline()[current_col] == NUL)
2614 		    current_next_list = NULL;
2615 
2616 		/* When the ended item has "extend", another item with
2617 		 * "keepend" now needs to check for its end. */
2618 		 had_extend = (cur_si->si_flags & HL_EXTEND);
2619 
2620 		pop_current_state();
2621 
2622 		if (current_state.ga_len == 0)
2623 		    break;
2624 
2625 		if (had_extend && keepend_level >= 0)
2626 		{
2627 		    syn_update_ends(FALSE);
2628 		    if (current_state.ga_len == 0)
2629 			break;
2630 		}
2631 
2632 		cur_si = &CUR_STATE(current_state.ga_len - 1);
2633 
2634 		/*
2635 		 * Only for a region the search for the end continues after
2636 		 * the end of the contained item.  If the contained match
2637 		 * included the end-of-line, break here, the region continues.
2638 		 * Don't do this when:
2639 		 * - "keepend" is used for the contained item
2640 		 * - not at the end of the line (could be end="x$"me=e-1).
2641 		 * - "excludenl" is used (HL_HAS_EOL won't be set)
2642 		 */
2643 		if (cur_si->si_idx >= 0
2644 			&& SYN_ITEMS(syn_block)[cur_si->si_idx].sp_type
2645 							       == SPTYPE_START
2646 			&& !(cur_si->si_flags & (HL_MATCH | HL_KEEPEND)))
2647 		{
2648 		    update_si_end(cur_si, (int)current_col, TRUE);
2649 		    check_keepend();
2650 		    if ((current_next_flags & HL_HAS_EOL)
2651 			    && keepend_level < 0
2652 			    && syn_getcurline()[current_col] == NUL)
2653 			break;
2654 		}
2655 	    }
2656 	}
2657 	else
2658 	    break;
2659     }
2660 }
2661 
2662 /*
2663  * Update an entry in the current_state stack for a match or region.  This
2664  * fills in si_attr, si_next_list and si_cont_list.
2665  */
2666     static void
2667 update_si_attr(int idx)
2668 {
2669     stateitem_T	*sip = &CUR_STATE(idx);
2670     synpat_T	*spp;
2671 
2672     /* This should not happen... */
2673     if (sip->si_idx < 0)
2674 	return;
2675 
2676     spp = &(SYN_ITEMS(syn_block)[sip->si_idx]);
2677     if (sip->si_flags & HL_MATCH)
2678 	sip->si_id = spp->sp_syn_match_id;
2679     else
2680 	sip->si_id = spp->sp_syn.id;
2681     sip->si_attr = syn_id2attr(sip->si_id);
2682     sip->si_trans_id = sip->si_id;
2683     if (sip->si_flags & HL_MATCH)
2684 	sip->si_cont_list = NULL;
2685     else
2686 	sip->si_cont_list = spp->sp_cont_list;
2687 
2688     /*
2689      * For transparent items, take attr from outer item.
2690      * Also take cont_list, if there is none.
2691      * Don't do this for the matchgroup of a start or end pattern.
2692      */
2693     if ((spp->sp_flags & HL_TRANSP) && !(sip->si_flags & HL_MATCH))
2694     {
2695 	if (idx == 0)
2696 	{
2697 	    sip->si_attr = 0;
2698 	    sip->si_trans_id = 0;
2699 	    if (sip->si_cont_list == NULL)
2700 		sip->si_cont_list = ID_LIST_ALL;
2701 	}
2702 	else
2703 	{
2704 	    sip->si_attr = CUR_STATE(idx - 1).si_attr;
2705 	    sip->si_trans_id = CUR_STATE(idx - 1).si_trans_id;
2706 	    sip->si_h_startpos = CUR_STATE(idx - 1).si_h_startpos;
2707 	    sip->si_h_endpos = CUR_STATE(idx - 1).si_h_endpos;
2708 	    if (sip->si_cont_list == NULL)
2709 	    {
2710 		sip->si_flags |= HL_TRANS_CONT;
2711 		sip->si_cont_list = CUR_STATE(idx - 1).si_cont_list;
2712 	    }
2713 	}
2714     }
2715 }
2716 
2717 /*
2718  * Check the current stack for patterns with "keepend" flag.
2719  * Propagate the match-end to contained items, until a "skipend" item is found.
2720  */
2721     static void
2722 check_keepend(void)
2723 {
2724     int		i;
2725     lpos_T	maxpos;
2726     lpos_T	maxpos_h;
2727     stateitem_T	*sip;
2728 
2729     /*
2730      * This check can consume a lot of time; only do it from the level where
2731      * there really is a keepend.
2732      */
2733     if (keepend_level < 0)
2734 	return;
2735 
2736     /*
2737      * Find the last index of an "extend" item.  "keepend" items before that
2738      * won't do anything.  If there is no "extend" item "i" will be
2739      * "keepend_level" and all "keepend" items will work normally.
2740      */
2741     for (i = current_state.ga_len - 1; i > keepend_level; --i)
2742 	if (CUR_STATE(i).si_flags & HL_EXTEND)
2743 	    break;
2744 
2745     maxpos.lnum = 0;
2746     maxpos.col = 0;
2747     maxpos_h.lnum = 0;
2748     maxpos_h.col = 0;
2749     for ( ; i < current_state.ga_len; ++i)
2750     {
2751 	sip = &CUR_STATE(i);
2752 	if (maxpos.lnum != 0)
2753 	{
2754 	    limit_pos_zero(&sip->si_m_endpos, &maxpos);
2755 	    limit_pos_zero(&sip->si_h_endpos, &maxpos_h);
2756 	    limit_pos_zero(&sip->si_eoe_pos, &maxpos);
2757 	    sip->si_ends = TRUE;
2758 	}
2759 	if (sip->si_ends && (sip->si_flags & HL_KEEPEND))
2760 	{
2761 	    if (maxpos.lnum == 0
2762 		    || maxpos.lnum > sip->si_m_endpos.lnum
2763 		    || (maxpos.lnum == sip->si_m_endpos.lnum
2764 			&& maxpos.col > sip->si_m_endpos.col))
2765 		maxpos = sip->si_m_endpos;
2766 	    if (maxpos_h.lnum == 0
2767 		    || maxpos_h.lnum > sip->si_h_endpos.lnum
2768 		    || (maxpos_h.lnum == sip->si_h_endpos.lnum
2769 			&& maxpos_h.col > sip->si_h_endpos.col))
2770 		maxpos_h = sip->si_h_endpos;
2771 	}
2772     }
2773 }
2774 
2775 /*
2776  * Update an entry in the current_state stack for a start-skip-end pattern.
2777  * This finds the end of the current item, if it's in the current line.
2778  *
2779  * Return the flags for the matched END.
2780  */
2781     static void
2782 update_si_end(
2783     stateitem_T	*sip,
2784     int		startcol,   /* where to start searching for the end */
2785     int		force)	    /* when TRUE overrule a previous end */
2786 {
2787     lpos_T	startpos;
2788     lpos_T	endpos;
2789     lpos_T	hl_endpos;
2790     lpos_T	end_endpos;
2791     int		end_idx;
2792 
2793     /* return quickly for a keyword */
2794     if (sip->si_idx < 0)
2795 	return;
2796 
2797     /* Don't update when it's already done.  Can be a match of an end pattern
2798      * that started in a previous line.  Watch out: can also be a "keepend"
2799      * from a containing item. */
2800     if (!force && sip->si_m_endpos.lnum >= current_lnum)
2801 	return;
2802 
2803     /*
2804      * We need to find the end of the region.  It may continue in the next
2805      * line.
2806      */
2807     end_idx = 0;
2808     startpos.lnum = current_lnum;
2809     startpos.col = startcol;
2810     find_endpos(sip->si_idx, &startpos, &endpos, &hl_endpos,
2811 		   &(sip->si_flags), &end_endpos, &end_idx, sip->si_extmatch);
2812 
2813     if (endpos.lnum == 0)
2814     {
2815 	/* No end pattern matched. */
2816 	if (SYN_ITEMS(syn_block)[sip->si_idx].sp_flags & HL_ONELINE)
2817 	{
2818 	    /* a "oneline" never continues in the next line */
2819 	    sip->si_ends = TRUE;
2820 	    sip->si_m_endpos.lnum = current_lnum;
2821 	    sip->si_m_endpos.col = (colnr_T)STRLEN(syn_getcurline());
2822 	}
2823 	else
2824 	{
2825 	    /* continues in the next line */
2826 	    sip->si_ends = FALSE;
2827 	    sip->si_m_endpos.lnum = 0;
2828 	}
2829 	sip->si_h_endpos = sip->si_m_endpos;
2830     }
2831     else
2832     {
2833 	/* match within this line */
2834 	sip->si_m_endpos = endpos;
2835 	sip->si_h_endpos = hl_endpos;
2836 	sip->si_eoe_pos = end_endpos;
2837 	sip->si_ends = TRUE;
2838 	sip->si_end_idx = end_idx;
2839     }
2840 }
2841 
2842 /*
2843  * Add a new state to the current state stack.
2844  * It is cleared and the index set to "idx".
2845  * Return FAIL if it's not possible (out of memory).
2846  */
2847     static int
2848 push_current_state(int idx)
2849 {
2850     if (ga_grow(&current_state, 1) == FAIL)
2851 	return FAIL;
2852     vim_memset(&CUR_STATE(current_state.ga_len), 0, sizeof(stateitem_T));
2853     CUR_STATE(current_state.ga_len).si_idx = idx;
2854     ++current_state.ga_len;
2855     return OK;
2856 }
2857 
2858 /*
2859  * Remove a state from the current_state stack.
2860  */
2861     static void
2862 pop_current_state(void)
2863 {
2864     if (current_state.ga_len)
2865     {
2866 	unref_extmatch(CUR_STATE(current_state.ga_len - 1).si_extmatch);
2867 	--current_state.ga_len;
2868     }
2869     /* after the end of a pattern, try matching a keyword or pattern */
2870     next_match_idx = -1;
2871 
2872     /* if first state with "keepend" is popped, reset keepend_level */
2873     if (keepend_level >= current_state.ga_len)
2874 	keepend_level = -1;
2875 }
2876 
2877 /*
2878  * Find the end of a start/skip/end syntax region after "startpos".
2879  * Only checks one line.
2880  * Also handles a match item that continued from a previous line.
2881  * If not found, the syntax item continues in the next line.  m_endpos->lnum
2882  * will be 0.
2883  * If found, the end of the region and the end of the highlighting is
2884  * computed.
2885  */
2886     static void
2887 find_endpos(
2888     int		idx,		/* index of the pattern */
2889     lpos_T	*startpos,	/* where to start looking for an END match */
2890     lpos_T	*m_endpos,	/* return: end of match */
2891     lpos_T	*hl_endpos,	/* return: end of highlighting */
2892     long	*flagsp,	/* return: flags of matching END */
2893     lpos_T	*end_endpos,	/* return: end of end pattern match */
2894     int		*end_idx,	/* return: group ID for end pat. match, or 0 */
2895     reg_extmatch_T *start_ext)	/* submatches from the start pattern */
2896 {
2897     colnr_T	matchcol;
2898     synpat_T	*spp, *spp_skip;
2899     int		start_idx;
2900     int		best_idx;
2901     regmmatch_T	regmatch;
2902     regmmatch_T	best_regmatch;	    /* startpos/endpos of best match */
2903     lpos_T	pos;
2904     char_u	*line;
2905     int		had_match = FALSE;
2906     char_u	buf_chartab[32];  /* chartab array for syn option iskyeyword */
2907 
2908     /* just in case we are invoked for a keyword */
2909     if (idx < 0)
2910 	return;
2911 
2912     /*
2913      * Check for being called with a START pattern.
2914      * Can happen with a match that continues to the next line, because it
2915      * contained a region.
2916      */
2917     spp = &(SYN_ITEMS(syn_block)[idx]);
2918     if (spp->sp_type != SPTYPE_START)
2919     {
2920 	*hl_endpos = *startpos;
2921 	return;
2922     }
2923 
2924     /*
2925      * Find the SKIP or first END pattern after the last START pattern.
2926      */
2927     for (;;)
2928     {
2929 	spp = &(SYN_ITEMS(syn_block)[idx]);
2930 	if (spp->sp_type != SPTYPE_START)
2931 	    break;
2932 	++idx;
2933     }
2934 
2935     /*
2936      *	Lookup the SKIP pattern (if present)
2937      */
2938     if (spp->sp_type == SPTYPE_SKIP)
2939     {
2940 	spp_skip = spp;
2941 	++idx;
2942     }
2943     else
2944 	spp_skip = NULL;
2945 
2946     /* Setup external matches for syn_regexec(). */
2947     unref_extmatch(re_extmatch_in);
2948     re_extmatch_in = ref_extmatch(start_ext);
2949 
2950     matchcol = startpos->col;	/* start looking for a match at sstart */
2951     start_idx = idx;		/* remember the first END pattern. */
2952     best_regmatch.startpos[0].col = 0;		/* avoid compiler warning */
2953 
2954     /* use syntax iskeyword option */
2955     save_chartab(buf_chartab);
2956 
2957     for (;;)
2958     {
2959 	/*
2960 	 * Find end pattern that matches first after "matchcol".
2961 	 */
2962 	best_idx = -1;
2963 	for (idx = start_idx; idx < syn_block->b_syn_patterns.ga_len; ++idx)
2964 	{
2965 	    int lc_col = matchcol;
2966 	    int r;
2967 
2968 	    spp = &(SYN_ITEMS(syn_block)[idx]);
2969 	    if (spp->sp_type != SPTYPE_END)	/* past last END pattern */
2970 		break;
2971 	    lc_col -= spp->sp_offsets[SPO_LC_OFF];
2972 	    if (lc_col < 0)
2973 		lc_col = 0;
2974 
2975 	    regmatch.rmm_ic = spp->sp_ic;
2976 	    regmatch.regprog = spp->sp_prog;
2977 	    r = syn_regexec(&regmatch, startpos->lnum, lc_col,
2978 						  IF_SYN_TIME(&spp->sp_time));
2979 	    spp->sp_prog = regmatch.regprog;
2980 	    if (r)
2981 	    {
2982 		if (best_idx == -1 || regmatch.startpos[0].col
2983 					      < best_regmatch.startpos[0].col)
2984 		{
2985 		    best_idx = idx;
2986 		    best_regmatch.startpos[0] = regmatch.startpos[0];
2987 		    best_regmatch.endpos[0] = regmatch.endpos[0];
2988 		}
2989 	    }
2990 	}
2991 
2992 	/*
2993 	 * If all end patterns have been tried, and there is no match, the
2994 	 * item continues until end-of-line.
2995 	 */
2996 	if (best_idx == -1)
2997 	    break;
2998 
2999 	/*
3000 	 * If the skip pattern matches before the end pattern,
3001 	 * continue searching after the skip pattern.
3002 	 */
3003 	if (spp_skip != NULL)
3004 	{
3005 	    int lc_col = matchcol - spp_skip->sp_offsets[SPO_LC_OFF];
3006 	    int r;
3007 
3008 	    if (lc_col < 0)
3009 		lc_col = 0;
3010 	    regmatch.rmm_ic = spp_skip->sp_ic;
3011 	    regmatch.regprog = spp_skip->sp_prog;
3012 	    r = syn_regexec(&regmatch, startpos->lnum, lc_col,
3013 					      IF_SYN_TIME(&spp_skip->sp_time));
3014 	    spp_skip->sp_prog = regmatch.regprog;
3015 	    if (r && regmatch.startpos[0].col
3016 					     <= best_regmatch.startpos[0].col)
3017 	    {
3018 		int line_len;
3019 
3020 		/* Add offset to skip pattern match */
3021 		syn_add_end_off(&pos, &regmatch, spp_skip, SPO_ME_OFF, 1);
3022 
3023 		/* If the skip pattern goes on to the next line, there is no
3024 		 * match with an end pattern in this line. */
3025 		if (pos.lnum > startpos->lnum)
3026 		    break;
3027 
3028 		line = ml_get_buf(syn_buf, startpos->lnum, FALSE);
3029 		line_len = (int)STRLEN(line);
3030 
3031 		/* take care of an empty match or negative offset */
3032 		if (pos.col <= matchcol)
3033 		    ++matchcol;
3034 		else if (pos.col <= regmatch.endpos[0].col)
3035 		    matchcol = pos.col;
3036 		else
3037 		    /* Be careful not to jump over the NUL at the end-of-line */
3038 		    for (matchcol = regmatch.endpos[0].col;
3039 			    matchcol < line_len && matchcol < pos.col;
3040 								   ++matchcol)
3041 			;
3042 
3043 		/* if the skip pattern includes end-of-line, break here */
3044 		if (matchcol >= line_len)
3045 		    break;
3046 
3047 		continue;	    /* start with first end pattern again */
3048 	    }
3049 	}
3050 
3051 	/*
3052 	 * Match from start pattern to end pattern.
3053 	 * Correct for match and highlight offset of end pattern.
3054 	 */
3055 	spp = &(SYN_ITEMS(syn_block)[best_idx]);
3056 	syn_add_end_off(m_endpos, &best_regmatch, spp, SPO_ME_OFF, 1);
3057 	/* can't end before the start */
3058 	if (m_endpos->lnum == startpos->lnum && m_endpos->col < startpos->col)
3059 	    m_endpos->col = startpos->col;
3060 
3061 	syn_add_end_off(end_endpos, &best_regmatch, spp, SPO_HE_OFF, 1);
3062 	/* can't end before the start */
3063 	if (end_endpos->lnum == startpos->lnum
3064 					   && end_endpos->col < startpos->col)
3065 	    end_endpos->col = startpos->col;
3066 	/* can't end after the match */
3067 	limit_pos(end_endpos, m_endpos);
3068 
3069 	/*
3070 	 * If the end group is highlighted differently, adjust the pointers.
3071 	 */
3072 	if (spp->sp_syn_match_id != spp->sp_syn.id && spp->sp_syn_match_id != 0)
3073 	{
3074 	    *end_idx = best_idx;
3075 	    if (spp->sp_off_flags & (1 << (SPO_RE_OFF + SPO_COUNT)))
3076 	    {
3077 		hl_endpos->lnum = best_regmatch.endpos[0].lnum;
3078 		hl_endpos->col = best_regmatch.endpos[0].col;
3079 	    }
3080 	    else
3081 	    {
3082 		hl_endpos->lnum = best_regmatch.startpos[0].lnum;
3083 		hl_endpos->col = best_regmatch.startpos[0].col;
3084 	    }
3085 	    hl_endpos->col += spp->sp_offsets[SPO_RE_OFF];
3086 
3087 	    /* can't end before the start */
3088 	    if (hl_endpos->lnum == startpos->lnum
3089 					    && hl_endpos->col < startpos->col)
3090 		hl_endpos->col = startpos->col;
3091 	    limit_pos(hl_endpos, m_endpos);
3092 
3093 	    /* now the match ends where the highlighting ends, it is turned
3094 	     * into the matchgroup for the end */
3095 	    *m_endpos = *hl_endpos;
3096 	}
3097 	else
3098 	{
3099 	    *end_idx = 0;
3100 	    *hl_endpos = *end_endpos;
3101 	}
3102 
3103 	*flagsp = spp->sp_flags;
3104 
3105 	had_match = TRUE;
3106 	break;
3107     }
3108 
3109     /* no match for an END pattern in this line */
3110     if (!had_match)
3111 	m_endpos->lnum = 0;
3112 
3113     restore_chartab(buf_chartab);
3114 
3115     /* Remove external matches. */
3116     unref_extmatch(re_extmatch_in);
3117     re_extmatch_in = NULL;
3118 }
3119 
3120 /*
3121  * Limit "pos" not to be after "limit".
3122  */
3123     static void
3124 limit_pos(lpos_T *pos, lpos_T *limit)
3125 {
3126     if (pos->lnum > limit->lnum)
3127 	*pos = *limit;
3128     else if (pos->lnum == limit->lnum && pos->col > limit->col)
3129 	pos->col = limit->col;
3130 }
3131 
3132 /*
3133  * Limit "pos" not to be after "limit", unless pos->lnum is zero.
3134  */
3135     static void
3136 limit_pos_zero(
3137     lpos_T	*pos,
3138     lpos_T	*limit)
3139 {
3140     if (pos->lnum == 0)
3141 	*pos = *limit;
3142     else
3143 	limit_pos(pos, limit);
3144 }
3145 
3146 /*
3147  * Add offset to matched text for end of match or highlight.
3148  */
3149     static void
3150 syn_add_end_off(
3151     lpos_T	*result,	/* returned position */
3152     regmmatch_T	*regmatch,	/* start/end of match */
3153     synpat_T	*spp,		/* matched pattern */
3154     int		idx,		/* index of offset */
3155     int		extra)		/* extra chars for offset to start */
3156 {
3157     int		col;
3158     int		off;
3159     char_u	*base;
3160     char_u	*p;
3161 
3162     if (spp->sp_off_flags & (1 << idx))
3163     {
3164 	result->lnum = regmatch->startpos[0].lnum;
3165 	col = regmatch->startpos[0].col;
3166 	off = spp->sp_offsets[idx] + extra;
3167     }
3168     else
3169     {
3170 	result->lnum = regmatch->endpos[0].lnum;
3171 	col = regmatch->endpos[0].col;
3172 	off = spp->sp_offsets[idx];
3173     }
3174     /* Don't go past the end of the line.  Matters for "rs=e+2" when there
3175      * is a matchgroup. Watch out for match with last NL in the buffer. */
3176     if (result->lnum > syn_buf->b_ml.ml_line_count)
3177 	col = 0;
3178     else if (off != 0)
3179     {
3180 	base = ml_get_buf(syn_buf, result->lnum, FALSE);
3181 	p = base + col;
3182 	if (off > 0)
3183 	{
3184 	    while (off-- > 0 && *p != NUL)
3185 		MB_PTR_ADV(p);
3186 	}
3187 	else if (off < 0)
3188 	{
3189 	    while (off++ < 0 && base < p)
3190 		MB_PTR_BACK(base, p);
3191 	}
3192 	col = (int)(p - base);
3193     }
3194     result->col = col;
3195 }
3196 
3197 /*
3198  * Add offset to matched text for start of match or highlight.
3199  * Avoid resulting column to become negative.
3200  */
3201     static void
3202 syn_add_start_off(
3203     lpos_T	*result,	/* returned position */
3204     regmmatch_T	*regmatch,	/* start/end of match */
3205     synpat_T	*spp,
3206     int		idx,
3207     int		extra)	    /* extra chars for offset to end */
3208 {
3209     int		col;
3210     int		off;
3211     char_u	*base;
3212     char_u	*p;
3213 
3214     if (spp->sp_off_flags & (1 << (idx + SPO_COUNT)))
3215     {
3216 	result->lnum = regmatch->endpos[0].lnum;
3217 	col = regmatch->endpos[0].col;
3218 	off = spp->sp_offsets[idx] + extra;
3219     }
3220     else
3221     {
3222 	result->lnum = regmatch->startpos[0].lnum;
3223 	col = regmatch->startpos[0].col;
3224 	off = spp->sp_offsets[idx];
3225     }
3226     if (result->lnum > syn_buf->b_ml.ml_line_count)
3227     {
3228 	/* a "\n" at the end of the pattern may take us below the last line */
3229 	result->lnum = syn_buf->b_ml.ml_line_count;
3230 	col = (int)STRLEN(ml_get_buf(syn_buf, result->lnum, FALSE));
3231     }
3232     if (off != 0)
3233     {
3234 	base = ml_get_buf(syn_buf, result->lnum, FALSE);
3235 	p = base + col;
3236 	if (off > 0)
3237 	{
3238 	    while (off-- && *p != NUL)
3239 		MB_PTR_ADV(p);
3240 	}
3241 	else if (off < 0)
3242 	{
3243 	    while (off++ && base < p)
3244 		MB_PTR_BACK(base, p);
3245 	}
3246 	col = (int)(p - base);
3247     }
3248     result->col = col;
3249 }
3250 
3251 /*
3252  * Get current line in syntax buffer.
3253  */
3254     static char_u *
3255 syn_getcurline(void)
3256 {
3257     return ml_get_buf(syn_buf, current_lnum, FALSE);
3258 }
3259 
3260 /*
3261  * Call vim_regexec() to find a match with "rmp" in "syn_buf".
3262  * Returns TRUE when there is a match.
3263  */
3264     static int
3265 syn_regexec(
3266     regmmatch_T	*rmp,
3267     linenr_T	lnum,
3268     colnr_T	col,
3269     syn_time_T  *st UNUSED)
3270 {
3271     int r;
3272 #ifdef FEAT_RELTIME
3273     int timed_out = FALSE;
3274 #endif
3275 #ifdef FEAT_PROFILE
3276     proftime_T	pt;
3277 
3278     if (syn_time_on)
3279 	profile_start(&pt);
3280 #endif
3281 
3282     if (rmp->regprog == NULL)
3283 	// This can happen if a previous call to vim_regexec_multi() tried to
3284 	// use the NFA engine, which resulted in NFA_TOO_EXPENSIVE, and
3285 	// compiling the pattern with the other engine fails.
3286 	return FALSE;
3287 
3288     rmp->rmm_maxcol = syn_buf->b_p_smc;
3289     r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col,
3290 #ifdef FEAT_RELTIME
3291 	    syn_tm, &timed_out
3292 #else
3293 	    NULL, NULL
3294 #endif
3295 	    );
3296 
3297 #ifdef FEAT_PROFILE
3298     if (syn_time_on)
3299     {
3300 	profile_end(&pt);
3301 	profile_add(&st->total, &pt);
3302 	if (profile_cmp(&pt, &st->slowest) < 0)
3303 	    st->slowest = pt;
3304 	++st->count;
3305 	if (r > 0)
3306 	    ++st->match;
3307     }
3308 #endif
3309 #ifdef FEAT_RELTIME
3310     if (timed_out && !syn_win->w_s->b_syn_slow)
3311     {
3312 	syn_win->w_s->b_syn_slow = TRUE;
3313 	msg(_("'redrawtime' exceeded, syntax highlighting disabled"));
3314     }
3315 #endif
3316 
3317     if (r > 0)
3318     {
3319 	rmp->startpos[0].lnum += lnum;
3320 	rmp->endpos[0].lnum += lnum;
3321 	return TRUE;
3322     }
3323     return FALSE;
3324 }
3325 
3326 /*
3327  * Check one position in a line for a matching keyword.
3328  * The caller must check if a keyword can start at startcol.
3329  * Return its ID if found, 0 otherwise.
3330  */
3331     static int
3332 check_keyword_id(
3333     char_u	*line,
3334     int		startcol,	/* position in line to check for keyword */
3335     int		*endcolp,	/* return: character after found keyword */
3336     long	*flagsp,	/* return: flags of matching keyword */
3337     short	**next_listp,	/* return: next_list of matching keyword */
3338     stateitem_T	*cur_si,	/* item at the top of the stack */
3339     int		*ccharp UNUSED)	/* conceal substitution char */
3340 {
3341     keyentry_T	*kp;
3342     char_u	*kwp;
3343     int		round;
3344     int		kwlen;
3345     char_u	keyword[MAXKEYWLEN + 1]; /* assume max. keyword len is 80 */
3346     hashtab_T	*ht;
3347     hashitem_T	*hi;
3348 
3349     /* Find first character after the keyword.  First character was already
3350      * checked. */
3351     kwp = line + startcol;
3352     kwlen = 0;
3353     do
3354     {
3355 	if (has_mbyte)
3356 	    kwlen += (*mb_ptr2len)(kwp + kwlen);
3357 	else
3358 	    ++kwlen;
3359     }
3360     while (vim_iswordp_buf(kwp + kwlen, syn_buf));
3361 
3362     if (kwlen > MAXKEYWLEN)
3363 	return 0;
3364 
3365     /*
3366      * Must make a copy of the keyword, so we can add a NUL and make it
3367      * lowercase.
3368      */
3369     vim_strncpy(keyword, kwp, kwlen);
3370 
3371     /*
3372      * Try twice:
3373      * 1. matching case
3374      * 2. ignoring case
3375      */
3376     for (round = 1; round <= 2; ++round)
3377     {
3378 	ht = round == 1 ? &syn_block->b_keywtab : &syn_block->b_keywtab_ic;
3379 	if (ht->ht_used == 0)
3380 	    continue;
3381 	if (round == 2)	/* ignore case */
3382 	    (void)str_foldcase(kwp, kwlen, keyword, MAXKEYWLEN + 1);
3383 
3384 	/*
3385 	 * Find keywords that match.  There can be several with different
3386 	 * attributes.
3387 	 * When current_next_list is non-zero accept only that group, otherwise:
3388 	 *  Accept a not-contained keyword at toplevel.
3389 	 *  Accept a keyword at other levels only if it is in the contains list.
3390 	 */
3391 	hi = hash_find(ht, keyword);
3392 	if (!HASHITEM_EMPTY(hi))
3393 	    for (kp = HI2KE(hi); kp != NULL; kp = kp->ke_next)
3394 	    {
3395 		if (current_next_list != 0
3396 			? in_id_list(NULL, current_next_list, &kp->k_syn, 0)
3397 			: (cur_si == NULL
3398 			    ? !(kp->flags & HL_CONTAINED)
3399 			    : in_id_list(cur_si, cur_si->si_cont_list,
3400 				      &kp->k_syn, kp->flags & HL_CONTAINED)))
3401 		{
3402 		    *endcolp = startcol + kwlen;
3403 		    *flagsp = kp->flags;
3404 		    *next_listp = kp->next_list;
3405 #ifdef FEAT_CONCEAL
3406 		    *ccharp = kp->k_char;
3407 #endif
3408 		    return kp->k_syn.id;
3409 		}
3410 	    }
3411     }
3412     return 0;
3413 }
3414 
3415 /*
3416  * Handle ":syntax conceal" command.
3417  */
3418     static void
3419 syn_cmd_conceal(exarg_T *eap UNUSED, int syncing UNUSED)
3420 {
3421 #ifdef FEAT_CONCEAL
3422     char_u	*arg = eap->arg;
3423     char_u	*next;
3424 
3425     eap->nextcmd = find_nextcmd(arg);
3426     if (eap->skip)
3427 	return;
3428 
3429     next = skiptowhite(arg);
3430     if (*arg == NUL)
3431     {
3432 	if (curwin->w_s->b_syn_conceal)
3433 	    msg(_("syntax conceal on"));
3434 	else
3435 	    msg(_("syntax conceal off"));
3436     }
3437     else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2)
3438 	curwin->w_s->b_syn_conceal = TRUE;
3439     else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3)
3440 	curwin->w_s->b_syn_conceal = FALSE;
3441     else
3442 	semsg(_("E390: Illegal argument: %s"), arg);
3443 #endif
3444 }
3445 
3446 /*
3447  * Handle ":syntax case" command.
3448  */
3449     static void
3450 syn_cmd_case(exarg_T *eap, int syncing UNUSED)
3451 {
3452     char_u	*arg = eap->arg;
3453     char_u	*next;
3454 
3455     eap->nextcmd = find_nextcmd(arg);
3456     if (eap->skip)
3457 	return;
3458 
3459     next = skiptowhite(arg);
3460     if (*arg == NUL)
3461     {
3462 	if (curwin->w_s->b_syn_ic)
3463 	    msg(_("syntax case ignore"));
3464 	else
3465 	    msg(_("syntax case match"));
3466     }
3467     else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5)
3468 	curwin->w_s->b_syn_ic = FALSE;
3469     else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6)
3470 	curwin->w_s->b_syn_ic = TRUE;
3471     else
3472 	semsg(_("E390: Illegal argument: %s"), arg);
3473 }
3474 
3475 /*
3476  * Handle ":syntax spell" command.
3477  */
3478     static void
3479 syn_cmd_spell(exarg_T *eap, int syncing UNUSED)
3480 {
3481     char_u	*arg = eap->arg;
3482     char_u	*next;
3483 
3484     eap->nextcmd = find_nextcmd(arg);
3485     if (eap->skip)
3486 	return;
3487 
3488     next = skiptowhite(arg);
3489     if (*arg == NUL)
3490     {
3491 	if (curwin->w_s->b_syn_spell == SYNSPL_TOP)
3492 	    msg(_("syntax spell toplevel"));
3493 	else if (curwin->w_s->b_syn_spell == SYNSPL_NOTOP)
3494 	    msg(_("syntax spell notoplevel"));
3495 	else
3496 	    msg(_("syntax spell default"));
3497     }
3498     else if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8)
3499 	curwin->w_s->b_syn_spell = SYNSPL_TOP;
3500     else if (STRNICMP(arg, "notoplevel", 10) == 0 && next - arg == 10)
3501 	curwin->w_s->b_syn_spell = SYNSPL_NOTOP;
3502     else if (STRNICMP(arg, "default", 7) == 0 && next - arg == 7)
3503 	curwin->w_s->b_syn_spell = SYNSPL_DEFAULT;
3504     else
3505     {
3506 	semsg(_("E390: Illegal argument: %s"), arg);
3507 	return;
3508     }
3509 
3510     /* assume spell checking changed, force a redraw */
3511     redraw_win_later(curwin, NOT_VALID);
3512 }
3513 
3514 /*
3515  * Handle ":syntax iskeyword" command.
3516  */
3517     static void
3518 syn_cmd_iskeyword(exarg_T *eap, int syncing UNUSED)
3519 {
3520     char_u	*arg = eap->arg;
3521     char_u	save_chartab[32];
3522     char_u	*save_isk;
3523 
3524     if (eap->skip)
3525 	return;
3526 
3527     arg = skipwhite(arg);
3528     if (*arg == NUL)
3529     {
3530 	msg_puts("\n");
3531 	if (curwin->w_s->b_syn_isk != empty_option)
3532 	{
3533 	    msg_puts(_("syntax iskeyword "));
3534 	    msg_outtrans(curwin->w_s->b_syn_isk);
3535 	}
3536 	else
3537 	    msg_outtrans((char_u *)_("syntax iskeyword not set"));
3538     }
3539     else
3540     {
3541 	if (STRNICMP(arg, "clear", 5) == 0)
3542 	{
3543 	    mch_memmove(curwin->w_s->b_syn_chartab, curbuf->b_chartab,
3544 								  (size_t)32);
3545 	    clear_string_option(&curwin->w_s->b_syn_isk);
3546 	}
3547 	else
3548 	{
3549 	    mch_memmove(save_chartab, curbuf->b_chartab, (size_t)32);
3550 	    save_isk = curbuf->b_p_isk;
3551 	    curbuf->b_p_isk = vim_strsave(arg);
3552 
3553 	    buf_init_chartab(curbuf, FALSE);
3554 	    mch_memmove(curwin->w_s->b_syn_chartab, curbuf->b_chartab,
3555 								  (size_t)32);
3556 	    mch_memmove(curbuf->b_chartab, save_chartab, (size_t)32);
3557 	    clear_string_option(&curwin->w_s->b_syn_isk);
3558 	    curwin->w_s->b_syn_isk = curbuf->b_p_isk;
3559 	    curbuf->b_p_isk = save_isk;
3560 	}
3561     }
3562     redraw_win_later(curwin, NOT_VALID);
3563 }
3564 
3565 /*
3566  * Clear all syntax info for one buffer.
3567  */
3568     void
3569 syntax_clear(synblock_T *block)
3570 {
3571     int i;
3572 
3573     block->b_syn_error = FALSE;	    /* clear previous error */
3574 #ifdef FEAT_RELTIME
3575     block->b_syn_slow = FALSE;	    /* clear previous timeout */
3576 #endif
3577     block->b_syn_ic = FALSE;	    /* Use case, by default */
3578     block->b_syn_spell = SYNSPL_DEFAULT; /* default spell checking */
3579     block->b_syn_containedin = FALSE;
3580 #ifdef FEAT_CONCEAL
3581     block->b_syn_conceal = FALSE;
3582 #endif
3583 
3584     /* free the keywords */
3585     clear_keywtab(&block->b_keywtab);
3586     clear_keywtab(&block->b_keywtab_ic);
3587 
3588     /* free the syntax patterns */
3589     for (i = block->b_syn_patterns.ga_len; --i >= 0; )
3590 	syn_clear_pattern(block, i);
3591     ga_clear(&block->b_syn_patterns);
3592 
3593     /* free the syntax clusters */
3594     for (i = block->b_syn_clusters.ga_len; --i >= 0; )
3595 	syn_clear_cluster(block, i);
3596     ga_clear(&block->b_syn_clusters);
3597     block->b_spell_cluster_id = 0;
3598     block->b_nospell_cluster_id = 0;
3599 
3600     block->b_syn_sync_flags = 0;
3601     block->b_syn_sync_minlines = 0;
3602     block->b_syn_sync_maxlines = 0;
3603     block->b_syn_sync_linebreaks = 0;
3604 
3605     vim_regfree(block->b_syn_linecont_prog);
3606     block->b_syn_linecont_prog = NULL;
3607     VIM_CLEAR(block->b_syn_linecont_pat);
3608 #ifdef FEAT_FOLDING
3609     block->b_syn_folditems = 0;
3610 #endif
3611     clear_string_option(&block->b_syn_isk);
3612 
3613     /* free the stored states */
3614     syn_stack_free_all(block);
3615     invalidate_current_state();
3616 
3617     /* Reset the counter for ":syn include" */
3618     running_syn_inc_tag = 0;
3619 }
3620 
3621 /*
3622  * Get rid of ownsyntax for window "wp".
3623  */
3624     void
3625 reset_synblock(win_T *wp)
3626 {
3627     if (wp->w_s != &wp->w_buffer->b_s)
3628     {
3629 	syntax_clear(wp->w_s);
3630 	vim_free(wp->w_s);
3631 	wp->w_s = &wp->w_buffer->b_s;
3632     }
3633 }
3634 
3635 /*
3636  * Clear syncing info for one buffer.
3637  */
3638     static void
3639 syntax_sync_clear(void)
3640 {
3641     int i;
3642 
3643     /* free the syntax patterns */
3644     for (i = curwin->w_s->b_syn_patterns.ga_len; --i >= 0; )
3645 	if (SYN_ITEMS(curwin->w_s)[i].sp_syncing)
3646 	    syn_remove_pattern(curwin->w_s, i);
3647 
3648     curwin->w_s->b_syn_sync_flags = 0;
3649     curwin->w_s->b_syn_sync_minlines = 0;
3650     curwin->w_s->b_syn_sync_maxlines = 0;
3651     curwin->w_s->b_syn_sync_linebreaks = 0;
3652 
3653     vim_regfree(curwin->w_s->b_syn_linecont_prog);
3654     curwin->w_s->b_syn_linecont_prog = NULL;
3655     VIM_CLEAR(curwin->w_s->b_syn_linecont_pat);
3656     clear_string_option(&curwin->w_s->b_syn_isk);
3657 
3658     syn_stack_free_all(curwin->w_s);	/* Need to recompute all syntax. */
3659 }
3660 
3661 /*
3662  * Remove one pattern from the buffer's pattern list.
3663  */
3664     static void
3665 syn_remove_pattern(
3666     synblock_T	*block,
3667     int		idx)
3668 {
3669     synpat_T	*spp;
3670 
3671     spp = &(SYN_ITEMS(block)[idx]);
3672 #ifdef FEAT_FOLDING
3673     if (spp->sp_flags & HL_FOLD)
3674 	--block->b_syn_folditems;
3675 #endif
3676     syn_clear_pattern(block, idx);
3677     mch_memmove(spp, spp + 1,
3678 		   sizeof(synpat_T) * (block->b_syn_patterns.ga_len - idx - 1));
3679     --block->b_syn_patterns.ga_len;
3680 }
3681 
3682 /*
3683  * Clear and free one syntax pattern.  When clearing all, must be called from
3684  * last to first!
3685  */
3686     static void
3687 syn_clear_pattern(synblock_T *block, int i)
3688 {
3689     vim_free(SYN_ITEMS(block)[i].sp_pattern);
3690     vim_regfree(SYN_ITEMS(block)[i].sp_prog);
3691     /* Only free sp_cont_list and sp_next_list of first start pattern */
3692     if (i == 0 || SYN_ITEMS(block)[i - 1].sp_type != SPTYPE_START)
3693     {
3694 	vim_free(SYN_ITEMS(block)[i].sp_cont_list);
3695 	vim_free(SYN_ITEMS(block)[i].sp_next_list);
3696 	vim_free(SYN_ITEMS(block)[i].sp_syn.cont_in_list);
3697     }
3698 }
3699 
3700 /*
3701  * Clear and free one syntax cluster.
3702  */
3703     static void
3704 syn_clear_cluster(synblock_T *block, int i)
3705 {
3706     vim_free(SYN_CLSTR(block)[i].scl_name);
3707     vim_free(SYN_CLSTR(block)[i].scl_name_u);
3708     vim_free(SYN_CLSTR(block)[i].scl_list);
3709 }
3710 
3711 /*
3712  * Handle ":syntax clear" command.
3713  */
3714     static void
3715 syn_cmd_clear(exarg_T *eap, int syncing)
3716 {
3717     char_u	*arg = eap->arg;
3718     char_u	*arg_end;
3719     int		id;
3720 
3721     eap->nextcmd = find_nextcmd(arg);
3722     if (eap->skip)
3723 	return;
3724 
3725     /*
3726      * We have to disable this within ":syn include @group filename",
3727      * because otherwise @group would get deleted.
3728      * Only required for Vim 5.x syntax files, 6.0 ones don't contain ":syn
3729      * clear".
3730      */
3731     if (curwin->w_s->b_syn_topgrp != 0)
3732 	return;
3733 
3734     if (ends_excmd(*arg))
3735     {
3736 	/*
3737 	 * No argument: Clear all syntax items.
3738 	 */
3739 	if (syncing)
3740 	    syntax_sync_clear();
3741 	else
3742 	{
3743 	    syntax_clear(curwin->w_s);
3744 	    if (curwin->w_s == &curwin->w_buffer->b_s)
3745 		do_unlet((char_u *)"b:current_syntax", TRUE);
3746 	    do_unlet((char_u *)"w:current_syntax", TRUE);
3747 	}
3748     }
3749     else
3750     {
3751 	/*
3752 	 * Clear the group IDs that are in the argument.
3753 	 */
3754 	while (!ends_excmd(*arg))
3755 	{
3756 	    arg_end = skiptowhite(arg);
3757 	    if (*arg == '@')
3758 	    {
3759 		id = syn_scl_namen2id(arg + 1, (int)(arg_end - arg - 1));
3760 		if (id == 0)
3761 		{
3762 		    semsg(_("E391: No such syntax cluster: %s"), arg);
3763 		    break;
3764 		}
3765 		else
3766 		{
3767 		    /*
3768 		     * We can't physically delete a cluster without changing
3769 		     * the IDs of other clusters, so we do the next best thing
3770 		     * and make it empty.
3771 		     */
3772 		    short scl_id = id - SYNID_CLUSTER;
3773 
3774 		    VIM_CLEAR(SYN_CLSTR(curwin->w_s)[scl_id].scl_list);
3775 		}
3776 	    }
3777 	    else
3778 	    {
3779 		id = syn_namen2id(arg, (int)(arg_end - arg));
3780 		if (id == 0)
3781 		{
3782 		    semsg(_(e_nogroup), arg);
3783 		    break;
3784 		}
3785 		else
3786 		    syn_clear_one(id, syncing);
3787 	    }
3788 	    arg = skipwhite(arg_end);
3789 	}
3790     }
3791     redraw_curbuf_later(SOME_VALID);
3792     syn_stack_free_all(curwin->w_s);		/* Need to recompute all syntax. */
3793 }
3794 
3795 /*
3796  * Clear one syntax group for the current buffer.
3797  */
3798     static void
3799 syn_clear_one(int id, int syncing)
3800 {
3801     synpat_T	*spp;
3802     int		idx;
3803 
3804     /* Clear keywords only when not ":syn sync clear group-name" */
3805     if (!syncing)
3806     {
3807 	(void)syn_clear_keyword(id, &curwin->w_s->b_keywtab);
3808 	(void)syn_clear_keyword(id, &curwin->w_s->b_keywtab_ic);
3809     }
3810 
3811     /* clear the patterns for "id" */
3812     for (idx = curwin->w_s->b_syn_patterns.ga_len; --idx >= 0; )
3813     {
3814 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
3815 	if (spp->sp_syn.id != id || spp->sp_syncing != syncing)
3816 	    continue;
3817 	syn_remove_pattern(curwin->w_s, idx);
3818     }
3819 }
3820 
3821 /*
3822  * Handle ":syntax on" command.
3823  */
3824     static void
3825 syn_cmd_on(exarg_T *eap, int syncing UNUSED)
3826 {
3827     syn_cmd_onoff(eap, "syntax");
3828 }
3829 
3830 /*
3831  * Handle ":syntax enable" command.
3832  */
3833     static void
3834 syn_cmd_enable(exarg_T *eap, int syncing UNUSED)
3835 {
3836     set_internal_string_var((char_u *)"syntax_cmd", (char_u *)"enable");
3837     syn_cmd_onoff(eap, "syntax");
3838     do_unlet((char_u *)"g:syntax_cmd", TRUE);
3839 }
3840 
3841 /*
3842  * Handle ":syntax reset" command.
3843  * It actually resets highlighting, not syntax.
3844  */
3845     static void
3846 syn_cmd_reset(exarg_T *eap, int syncing UNUSED)
3847 {
3848     eap->nextcmd = check_nextcmd(eap->arg);
3849     if (!eap->skip)
3850     {
3851 	set_internal_string_var((char_u *)"syntax_cmd", (char_u *)"reset");
3852 	do_cmdline_cmd((char_u *)"runtime! syntax/syncolor.vim");
3853 	do_unlet((char_u *)"g:syntax_cmd", TRUE);
3854     }
3855 }
3856 
3857 /*
3858  * Handle ":syntax manual" command.
3859  */
3860     static void
3861 syn_cmd_manual(exarg_T *eap, int syncing UNUSED)
3862 {
3863     syn_cmd_onoff(eap, "manual");
3864 }
3865 
3866 /*
3867  * Handle ":syntax off" command.
3868  */
3869     static void
3870 syn_cmd_off(exarg_T *eap, int syncing UNUSED)
3871 {
3872     syn_cmd_onoff(eap, "nosyntax");
3873 }
3874 
3875     static void
3876 syn_cmd_onoff(exarg_T *eap, char *name)
3877 {
3878     char_u	buf[100];
3879 
3880     eap->nextcmd = check_nextcmd(eap->arg);
3881     if (!eap->skip)
3882     {
3883 	STRCPY(buf, "so ");
3884 	vim_snprintf((char *)buf + 3, sizeof(buf) - 3, SYNTAX_FNAME, name);
3885 	do_cmdline_cmd(buf);
3886     }
3887 }
3888 
3889 /*
3890  * Handle ":syntax [list]" command: list current syntax words.
3891  */
3892     static void
3893 syn_cmd_list(
3894     exarg_T	*eap,
3895     int		syncing)	    /* when TRUE: list syncing items */
3896 {
3897     char_u	*arg = eap->arg;
3898     int		id;
3899     char_u	*arg_end;
3900 
3901     eap->nextcmd = find_nextcmd(arg);
3902     if (eap->skip)
3903 	return;
3904 
3905     if (!syntax_present(curwin))
3906     {
3907 	msg(_(msg_no_items));
3908 	return;
3909     }
3910 
3911     if (syncing)
3912     {
3913 	if (curwin->w_s->b_syn_sync_flags & SF_CCOMMENT)
3914 	{
3915 	    msg_puts(_("syncing on C-style comments"));
3916 	    syn_lines_msg();
3917 	    syn_match_msg();
3918 	    return;
3919 	}
3920 	else if (!(curwin->w_s->b_syn_sync_flags & SF_MATCH))
3921 	{
3922 	    if (curwin->w_s->b_syn_sync_minlines == 0)
3923 		msg_puts(_("no syncing"));
3924 	    else
3925 	    {
3926 		msg_puts(_("syncing starts "));
3927 		msg_outnum(curwin->w_s->b_syn_sync_minlines);
3928 		msg_puts(_(" lines before top line"));
3929 		syn_match_msg();
3930 	    }
3931 	    return;
3932 	}
3933 	msg_puts_title(_("\n--- Syntax sync items ---"));
3934 	if (curwin->w_s->b_syn_sync_minlines > 0
3935 		|| curwin->w_s->b_syn_sync_maxlines > 0
3936 		|| curwin->w_s->b_syn_sync_linebreaks > 0)
3937 	{
3938 	    msg_puts(_("\nsyncing on items"));
3939 	    syn_lines_msg();
3940 	    syn_match_msg();
3941 	}
3942     }
3943     else
3944 	msg_puts_title(_("\n--- Syntax items ---"));
3945     if (ends_excmd(*arg))
3946     {
3947 	/*
3948 	 * No argument: List all group IDs and all syntax clusters.
3949 	 */
3950 	for (id = 1; id <= highlight_ga.ga_len && !got_int; ++id)
3951 	    syn_list_one(id, syncing, FALSE);
3952 	for (id = 0; id < curwin->w_s->b_syn_clusters.ga_len && !got_int; ++id)
3953 	    syn_list_cluster(id);
3954     }
3955     else
3956     {
3957 	/*
3958 	 * List the group IDs and syntax clusters that are in the argument.
3959 	 */
3960 	while (!ends_excmd(*arg) && !got_int)
3961 	{
3962 	    arg_end = skiptowhite(arg);
3963 	    if (*arg == '@')
3964 	    {
3965 		id = syn_scl_namen2id(arg + 1, (int)(arg_end - arg - 1));
3966 		if (id == 0)
3967 		    semsg(_("E392: No such syntax cluster: %s"), arg);
3968 		else
3969 		    syn_list_cluster(id - SYNID_CLUSTER);
3970 	    }
3971 	    else
3972 	    {
3973 		id = syn_namen2id(arg, (int)(arg_end - arg));
3974 		if (id == 0)
3975 		    semsg(_(e_nogroup), arg);
3976 		else
3977 		    syn_list_one(id, syncing, TRUE);
3978 	    }
3979 	    arg = skipwhite(arg_end);
3980 	}
3981     }
3982     eap->nextcmd = check_nextcmd(arg);
3983 }
3984 
3985     static void
3986 syn_lines_msg(void)
3987 {
3988     if (curwin->w_s->b_syn_sync_maxlines > 0
3989 				      || curwin->w_s->b_syn_sync_minlines > 0)
3990     {
3991 	msg_puts("; ");
3992 	if (curwin->w_s->b_syn_sync_minlines > 0)
3993 	{
3994 	    msg_puts(_("minimal "));
3995 	    msg_outnum(curwin->w_s->b_syn_sync_minlines);
3996 	    if (curwin->w_s->b_syn_sync_maxlines)
3997 		msg_puts(", ");
3998 	}
3999 	if (curwin->w_s->b_syn_sync_maxlines > 0)
4000 	{
4001 	    msg_puts(_("maximal "));
4002 	    msg_outnum(curwin->w_s->b_syn_sync_maxlines);
4003 	}
4004 	msg_puts(_(" lines before top line"));
4005     }
4006 }
4007 
4008     static void
4009 syn_match_msg(void)
4010 {
4011     if (curwin->w_s->b_syn_sync_linebreaks > 0)
4012     {
4013 	msg_puts(_("; match "));
4014 	msg_outnum(curwin->w_s->b_syn_sync_linebreaks);
4015 	msg_puts(_(" line breaks"));
4016     }
4017 }
4018 
4019 static int  last_matchgroup;
4020 
4021 struct name_list
4022 {
4023     int		flag;
4024     char	*name;
4025 };
4026 
4027 static void syn_list_flags(struct name_list *nl, int flags, int attr);
4028 
4029 /*
4030  * List one syntax item, for ":syntax" or "syntax list syntax_name".
4031  */
4032     static void
4033 syn_list_one(
4034     int		id,
4035     int		syncing,	    /* when TRUE: list syncing items */
4036     int		link_only)	    /* when TRUE; list link-only too */
4037 {
4038     int		attr;
4039     int		idx;
4040     int		did_header = FALSE;
4041     synpat_T	*spp;
4042     static struct name_list namelist1[] =
4043 		{
4044 		    {HL_DISPLAY, "display"},
4045 		    {HL_CONTAINED, "contained"},
4046 		    {HL_ONELINE, "oneline"},
4047 		    {HL_KEEPEND, "keepend"},
4048 		    {HL_EXTEND, "extend"},
4049 		    {HL_EXCLUDENL, "excludenl"},
4050 		    {HL_TRANSP, "transparent"},
4051 		    {HL_FOLD, "fold"},
4052 #ifdef FEAT_CONCEAL
4053 		    {HL_CONCEAL, "conceal"},
4054 		    {HL_CONCEALENDS, "concealends"},
4055 #endif
4056 		    {0, NULL}
4057 		};
4058     static struct name_list namelist2[] =
4059 		{
4060 		    {HL_SKIPWHITE, "skipwhite"},
4061 		    {HL_SKIPNL, "skipnl"},
4062 		    {HL_SKIPEMPTY, "skipempty"},
4063 		    {0, NULL}
4064 		};
4065 
4066     attr = HL_ATTR(HLF_D);		/* highlight like directories */
4067 
4068     /* list the keywords for "id" */
4069     if (!syncing)
4070     {
4071 	did_header = syn_list_keywords(id, &curwin->w_s->b_keywtab, FALSE, attr);
4072 	did_header = syn_list_keywords(id, &curwin->w_s->b_keywtab_ic,
4073 							    did_header, attr);
4074     }
4075 
4076     /* list the patterns for "id" */
4077     for (idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len && !got_int; ++idx)
4078     {
4079 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
4080 	if (spp->sp_syn.id != id || spp->sp_syncing != syncing)
4081 	    continue;
4082 
4083 	(void)syn_list_header(did_header, 999, id);
4084 	did_header = TRUE;
4085 	last_matchgroup = 0;
4086 	if (spp->sp_type == SPTYPE_MATCH)
4087 	{
4088 	    put_pattern("match", ' ', spp, attr);
4089 	    msg_putchar(' ');
4090 	}
4091 	else if (spp->sp_type == SPTYPE_START)
4092 	{
4093 	    while (SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_START)
4094 		put_pattern("start", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr);
4095 	    if (SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_SKIP)
4096 		put_pattern("skip", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr);
4097 	    while (idx < curwin->w_s->b_syn_patterns.ga_len
4098 			      && SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_END)
4099 		put_pattern("end", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr);
4100 	    --idx;
4101 	    msg_putchar(' ');
4102 	}
4103 	syn_list_flags(namelist1, spp->sp_flags, attr);
4104 
4105 	if (spp->sp_cont_list != NULL)
4106 	    put_id_list((char_u *)"contains", spp->sp_cont_list, attr);
4107 
4108 	if (spp->sp_syn.cont_in_list != NULL)
4109 	    put_id_list((char_u *)"containedin",
4110 					      spp->sp_syn.cont_in_list, attr);
4111 
4112 	if (spp->sp_next_list != NULL)
4113 	{
4114 	    put_id_list((char_u *)"nextgroup", spp->sp_next_list, attr);
4115 	    syn_list_flags(namelist2, spp->sp_flags, attr);
4116 	}
4117 	if (spp->sp_flags & (HL_SYNC_HERE|HL_SYNC_THERE))
4118 	{
4119 	    if (spp->sp_flags & HL_SYNC_HERE)
4120 		msg_puts_attr("grouphere", attr);
4121 	    else
4122 		msg_puts_attr("groupthere", attr);
4123 	    msg_putchar(' ');
4124 	    if (spp->sp_sync_idx >= 0)
4125 		msg_outtrans(HL_TABLE()[SYN_ITEMS(curwin->w_s)
4126 				   [spp->sp_sync_idx].sp_syn.id - 1].sg_name);
4127 	    else
4128 		msg_puts("NONE");
4129 	    msg_putchar(' ');
4130 	}
4131     }
4132 
4133     /* list the link, if there is one */
4134     if (HL_TABLE()[id - 1].sg_link && (did_header || link_only) && !got_int)
4135     {
4136 	(void)syn_list_header(did_header, 999, id);
4137 	msg_puts_attr("links to", attr);
4138 	msg_putchar(' ');
4139 	msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name);
4140     }
4141 }
4142 
4143     static void
4144 syn_list_flags(struct name_list *nlist, int flags, int attr)
4145 {
4146     int		i;
4147 
4148     for (i = 0; nlist[i].flag != 0; ++i)
4149 	if (flags & nlist[i].flag)
4150 	{
4151 	    msg_puts_attr(nlist[i].name, attr);
4152 	    msg_putchar(' ');
4153 	}
4154 }
4155 
4156 /*
4157  * List one syntax cluster, for ":syntax" or "syntax list syntax_name".
4158  */
4159     static void
4160 syn_list_cluster(int id)
4161 {
4162     int	    endcol = 15;
4163 
4164     /* slight hack:  roughly duplicate the guts of syn_list_header() */
4165     msg_putchar('\n');
4166     msg_outtrans(SYN_CLSTR(curwin->w_s)[id].scl_name);
4167 
4168     if (msg_col >= endcol)	/* output at least one space */
4169 	endcol = msg_col + 1;
4170     if (Columns <= endcol)	/* avoid hang for tiny window */
4171 	endcol = Columns - 1;
4172 
4173     msg_advance(endcol);
4174     if (SYN_CLSTR(curwin->w_s)[id].scl_list != NULL)
4175     {
4176 	put_id_list((char_u *)"cluster", SYN_CLSTR(curwin->w_s)[id].scl_list,
4177 		    HL_ATTR(HLF_D));
4178     }
4179     else
4180     {
4181 	msg_puts_attr("cluster", HL_ATTR(HLF_D));
4182 	msg_puts("=NONE");
4183     }
4184 }
4185 
4186     static void
4187 put_id_list(char_u *name, short *list, int attr)
4188 {
4189     short		*p;
4190 
4191     msg_puts_attr((char *)name, attr);
4192     msg_putchar('=');
4193     for (p = list; *p; ++p)
4194     {
4195 	if (*p >= SYNID_ALLBUT && *p < SYNID_TOP)
4196 	{
4197 	    if (p[1])
4198 		msg_puts("ALLBUT");
4199 	    else
4200 		msg_puts("ALL");
4201 	}
4202 	else if (*p >= SYNID_TOP && *p < SYNID_CONTAINED)
4203 	{
4204 	    msg_puts("TOP");
4205 	}
4206 	else if (*p >= SYNID_CONTAINED && *p < SYNID_CLUSTER)
4207 	{
4208 	    msg_puts("CONTAINED");
4209 	}
4210 	else if (*p >= SYNID_CLUSTER)
4211 	{
4212 	    short scl_id = *p - SYNID_CLUSTER;
4213 
4214 	    msg_putchar('@');
4215 	    msg_outtrans(SYN_CLSTR(curwin->w_s)[scl_id].scl_name);
4216 	}
4217 	else
4218 	    msg_outtrans(HL_TABLE()[*p - 1].sg_name);
4219 	if (p[1])
4220 	    msg_putchar(',');
4221     }
4222     msg_putchar(' ');
4223 }
4224 
4225     static void
4226 put_pattern(
4227     char	*s,
4228     int		c,
4229     synpat_T	*spp,
4230     int		attr)
4231 {
4232     long	n;
4233     int		mask;
4234     int		first;
4235     static char	*sepchars = "/+=-#@\"|'^&";
4236     int		i;
4237 
4238     /* May have to write "matchgroup=group" */
4239     if (last_matchgroup != spp->sp_syn_match_id)
4240     {
4241 	last_matchgroup = spp->sp_syn_match_id;
4242 	msg_puts_attr("matchgroup", attr);
4243 	msg_putchar('=');
4244 	if (last_matchgroup == 0)
4245 	    msg_outtrans((char_u *)"NONE");
4246 	else
4247 	    msg_outtrans(HL_TABLE()[last_matchgroup - 1].sg_name);
4248 	msg_putchar(' ');
4249     }
4250 
4251     /* output the name of the pattern and an '=' or ' ' */
4252     msg_puts_attr(s, attr);
4253     msg_putchar(c);
4254 
4255     /* output the pattern, in between a char that is not in the pattern */
4256     for (i = 0; vim_strchr(spp->sp_pattern, sepchars[i]) != NULL; )
4257 	if (sepchars[++i] == NUL)
4258 	{
4259 	    i = 0;	/* no good char found, just use the first one */
4260 	    break;
4261 	}
4262     msg_putchar(sepchars[i]);
4263     msg_outtrans(spp->sp_pattern);
4264     msg_putchar(sepchars[i]);
4265 
4266     /* output any pattern options */
4267     first = TRUE;
4268     for (i = 0; i < SPO_COUNT; ++i)
4269     {
4270 	mask = (1 << i);
4271 	if (spp->sp_off_flags & (mask + (mask << SPO_COUNT)))
4272 	{
4273 	    if (!first)
4274 		msg_putchar(',');	/* separate with commas */
4275 	    msg_puts(spo_name_tab[i]);
4276 	    n = spp->sp_offsets[i];
4277 	    if (i != SPO_LC_OFF)
4278 	    {
4279 		if (spp->sp_off_flags & mask)
4280 		    msg_putchar('s');
4281 		else
4282 		    msg_putchar('e');
4283 		if (n > 0)
4284 		    msg_putchar('+');
4285 	    }
4286 	    if (n || i == SPO_LC_OFF)
4287 		msg_outnum(n);
4288 	    first = FALSE;
4289 	}
4290     }
4291     msg_putchar(' ');
4292 }
4293 
4294 /*
4295  * List or clear the keywords for one syntax group.
4296  * Return TRUE if the header has been printed.
4297  */
4298     static int
4299 syn_list_keywords(
4300     int		id,
4301     hashtab_T	*ht,
4302     int		did_header,		/* header has already been printed */
4303     int		attr)
4304 {
4305     int		outlen;
4306     hashitem_T	*hi;
4307     keyentry_T	*kp;
4308     int		todo;
4309     int		prev_contained = 0;
4310     short	*prev_next_list = NULL;
4311     short	*prev_cont_in_list = NULL;
4312     int		prev_skipnl = 0;
4313     int		prev_skipwhite = 0;
4314     int		prev_skipempty = 0;
4315 
4316     /*
4317      * Unfortunately, this list of keywords is not sorted on alphabet but on
4318      * hash value...
4319      */
4320     todo = (int)ht->ht_used;
4321     for (hi = ht->ht_array; todo > 0 && !got_int; ++hi)
4322     {
4323 	if (!HASHITEM_EMPTY(hi))
4324 	{
4325 	    --todo;
4326 	    for (kp = HI2KE(hi); kp != NULL && !got_int; kp = kp->ke_next)
4327 	    {
4328 		if (kp->k_syn.id == id)
4329 		{
4330 		    if (prev_contained != (kp->flags & HL_CONTAINED)
4331 			    || prev_skipnl != (kp->flags & HL_SKIPNL)
4332 			    || prev_skipwhite != (kp->flags & HL_SKIPWHITE)
4333 			    || prev_skipempty != (kp->flags & HL_SKIPEMPTY)
4334 			    || prev_cont_in_list != kp->k_syn.cont_in_list
4335 			    || prev_next_list != kp->next_list)
4336 			outlen = 9999;
4337 		    else
4338 			outlen = (int)STRLEN(kp->keyword);
4339 		    /* output "contained" and "nextgroup" on each line */
4340 		    if (syn_list_header(did_header, outlen, id))
4341 		    {
4342 			prev_contained = 0;
4343 			prev_next_list = NULL;
4344 			prev_cont_in_list = NULL;
4345 			prev_skipnl = 0;
4346 			prev_skipwhite = 0;
4347 			prev_skipempty = 0;
4348 		    }
4349 		    did_header = TRUE;
4350 		    if (prev_contained != (kp->flags & HL_CONTAINED))
4351 		    {
4352 			msg_puts_attr("contained", attr);
4353 			msg_putchar(' ');
4354 			prev_contained = (kp->flags & HL_CONTAINED);
4355 		    }
4356 		    if (kp->k_syn.cont_in_list != prev_cont_in_list)
4357 		    {
4358 			put_id_list((char_u *)"containedin",
4359 						kp->k_syn.cont_in_list, attr);
4360 			msg_putchar(' ');
4361 			prev_cont_in_list = kp->k_syn.cont_in_list;
4362 		    }
4363 		    if (kp->next_list != prev_next_list)
4364 		    {
4365 			put_id_list((char_u *)"nextgroup", kp->next_list, attr);
4366 			msg_putchar(' ');
4367 			prev_next_list = kp->next_list;
4368 			if (kp->flags & HL_SKIPNL)
4369 			{
4370 			    msg_puts_attr("skipnl", attr);
4371 			    msg_putchar(' ');
4372 			    prev_skipnl = (kp->flags & HL_SKIPNL);
4373 			}
4374 			if (kp->flags & HL_SKIPWHITE)
4375 			{
4376 			    msg_puts_attr("skipwhite", attr);
4377 			    msg_putchar(' ');
4378 			    prev_skipwhite = (kp->flags & HL_SKIPWHITE);
4379 			}
4380 			if (kp->flags & HL_SKIPEMPTY)
4381 			{
4382 			    msg_puts_attr("skipempty", attr);
4383 			    msg_putchar(' ');
4384 			    prev_skipempty = (kp->flags & HL_SKIPEMPTY);
4385 			}
4386 		    }
4387 		    msg_outtrans(kp->keyword);
4388 		}
4389 	    }
4390 	}
4391     }
4392 
4393     return did_header;
4394 }
4395 
4396     static void
4397 syn_clear_keyword(int id, hashtab_T *ht)
4398 {
4399     hashitem_T	*hi;
4400     keyentry_T	*kp;
4401     keyentry_T	*kp_prev;
4402     keyentry_T	*kp_next;
4403     int		todo;
4404 
4405     hash_lock(ht);
4406     todo = (int)ht->ht_used;
4407     for (hi = ht->ht_array; todo > 0; ++hi)
4408     {
4409 	if (!HASHITEM_EMPTY(hi))
4410 	{
4411 	    --todo;
4412 	    kp_prev = NULL;
4413 	    for (kp = HI2KE(hi); kp != NULL; )
4414 	    {
4415 		if (kp->k_syn.id == id)
4416 		{
4417 		    kp_next = kp->ke_next;
4418 		    if (kp_prev == NULL)
4419 		    {
4420 			if (kp_next == NULL)
4421 			    hash_remove(ht, hi);
4422 			else
4423 			    hi->hi_key = KE2HIKEY(kp_next);
4424 		    }
4425 		    else
4426 			kp_prev->ke_next = kp_next;
4427 		    vim_free(kp->next_list);
4428 		    vim_free(kp->k_syn.cont_in_list);
4429 		    vim_free(kp);
4430 		    kp = kp_next;
4431 		}
4432 		else
4433 		{
4434 		    kp_prev = kp;
4435 		    kp = kp->ke_next;
4436 		}
4437 	    }
4438 	}
4439     }
4440     hash_unlock(ht);
4441 }
4442 
4443 /*
4444  * Clear a whole keyword table.
4445  */
4446     static void
4447 clear_keywtab(hashtab_T *ht)
4448 {
4449     hashitem_T	*hi;
4450     int		todo;
4451     keyentry_T	*kp;
4452     keyentry_T	*kp_next;
4453 
4454     todo = (int)ht->ht_used;
4455     for (hi = ht->ht_array; todo > 0; ++hi)
4456     {
4457 	if (!HASHITEM_EMPTY(hi))
4458 	{
4459 	    --todo;
4460 	    for (kp = HI2KE(hi); kp != NULL; kp = kp_next)
4461 	    {
4462 		kp_next = kp->ke_next;
4463 		vim_free(kp->next_list);
4464 		vim_free(kp->k_syn.cont_in_list);
4465 		vim_free(kp);
4466 	    }
4467 	}
4468     }
4469     hash_clear(ht);
4470     hash_init(ht);
4471 }
4472 
4473 /*
4474  * Add a keyword to the list of keywords.
4475  */
4476     static void
4477 add_keyword(
4478     char_u	*name,	    /* name of keyword */
4479     int		id,	    /* group ID for this keyword */
4480     int		flags,	    /* flags for this keyword */
4481     short	*cont_in_list, /* containedin for this keyword */
4482     short	*next_list, /* nextgroup for this keyword */
4483     int		conceal_char)
4484 {
4485     keyentry_T	*kp;
4486     hashtab_T	*ht;
4487     hashitem_T	*hi;
4488     char_u	*name_ic;
4489     long_u	hash;
4490     char_u	name_folded[MAXKEYWLEN + 1];
4491 
4492     if (curwin->w_s->b_syn_ic)
4493 	name_ic = str_foldcase(name, (int)STRLEN(name),
4494 						 name_folded, MAXKEYWLEN + 1);
4495     else
4496 	name_ic = name;
4497     kp = alloc(sizeof(keyentry_T) + STRLEN(name_ic));
4498     if (kp == NULL)
4499 	return;
4500     STRCPY(kp->keyword, name_ic);
4501     kp->k_syn.id = id;
4502     kp->k_syn.inc_tag = current_syn_inc_tag;
4503     kp->flags = flags;
4504     kp->k_char = conceal_char;
4505     kp->k_syn.cont_in_list = copy_id_list(cont_in_list);
4506     if (cont_in_list != NULL)
4507 	curwin->w_s->b_syn_containedin = TRUE;
4508     kp->next_list = copy_id_list(next_list);
4509 
4510     if (curwin->w_s->b_syn_ic)
4511 	ht = &curwin->w_s->b_keywtab_ic;
4512     else
4513 	ht = &curwin->w_s->b_keywtab;
4514 
4515     hash = hash_hash(kp->keyword);
4516     hi = hash_lookup(ht, kp->keyword, hash);
4517     if (HASHITEM_EMPTY(hi))
4518     {
4519 	/* new keyword, add to hashtable */
4520 	kp->ke_next = NULL;
4521 	hash_add_item(ht, hi, kp->keyword, hash);
4522     }
4523     else
4524     {
4525 	/* keyword already exists, prepend to list */
4526 	kp->ke_next = HI2KE(hi);
4527 	hi->hi_key = KE2HIKEY(kp);
4528     }
4529 }
4530 
4531 /*
4532  * Get the start and end of the group name argument.
4533  * Return a pointer to the first argument.
4534  * Return NULL if the end of the command was found instead of further args.
4535  */
4536     static char_u *
4537 get_group_name(
4538     char_u	*arg,		/* start of the argument */
4539     char_u	**name_end)	/* pointer to end of the name */
4540 {
4541     char_u	*rest;
4542 
4543     *name_end = skiptowhite(arg);
4544     rest = skipwhite(*name_end);
4545 
4546     /*
4547      * Check if there are enough arguments.  The first argument may be a
4548      * pattern, where '|' is allowed, so only check for NUL.
4549      */
4550     if (ends_excmd(*arg) || *rest == NUL)
4551 	return NULL;
4552     return rest;
4553 }
4554 
4555 /*
4556  * Check for syntax command option arguments.
4557  * This can be called at any place in the list of arguments, and just picks
4558  * out the arguments that are known.  Can be called several times in a row to
4559  * collect all options in between other arguments.
4560  * Return a pointer to the next argument (which isn't an option).
4561  * Return NULL for any error;
4562  */
4563     static char_u *
4564 get_syn_options(
4565     char_u	    *arg,		/* next argument to be checked */
4566     syn_opt_arg_T   *opt,		/* various things */
4567     int		    *conceal_char UNUSED,
4568     int		    skip)		/* TRUE if skipping over command */
4569 {
4570     char_u	*gname_start, *gname;
4571     int		syn_id;
4572     int		len;
4573     char	*p;
4574     int		i;
4575     int		fidx;
4576     static struct flag
4577     {
4578 	char	*name;
4579 	int	argtype;
4580 	int	flags;
4581     } flagtab[] = { {"cCoOnNtTaAiInNeEdD",	0,	HL_CONTAINED},
4582 		    {"oOnNeElLiInNeE",		0,	HL_ONELINE},
4583 		    {"kKeEeEpPeEnNdD",		0,	HL_KEEPEND},
4584 		    {"eExXtTeEnNdD",		0,	HL_EXTEND},
4585 		    {"eExXcClLuUdDeEnNlL",	0,	HL_EXCLUDENL},
4586 		    {"tTrRaAnNsSpPaArReEnNtT",	0,	HL_TRANSP},
4587 		    {"sSkKiIpPnNlL",		0,	HL_SKIPNL},
4588 		    {"sSkKiIpPwWhHiItTeE",	0,	HL_SKIPWHITE},
4589 		    {"sSkKiIpPeEmMpPtTyY",	0,	HL_SKIPEMPTY},
4590 		    {"gGrRoOuUpPhHeErReE",	0,	HL_SYNC_HERE},
4591 		    {"gGrRoOuUpPtThHeErReE",	0,	HL_SYNC_THERE},
4592 		    {"dDiIsSpPlLaAyY",		0,	HL_DISPLAY},
4593 		    {"fFoOlLdD",		0,	HL_FOLD},
4594 		    {"cCoOnNcCeEaAlL",		0,	HL_CONCEAL},
4595 		    {"cCoOnNcCeEaAlLeEnNdDsS",	0,	HL_CONCEALENDS},
4596 		    {"cCcChHaArR",		11,	0},
4597 		    {"cCoOnNtTaAiInNsS",	1,	0},
4598 		    {"cCoOnNtTaAiInNeEdDiInN",	2,	0},
4599 		    {"nNeExXtTgGrRoOuUpP",	3,	0},
4600 		};
4601     static char *first_letters = "cCoOkKeEtTsSgGdDfFnN";
4602 
4603     if (arg == NULL)		/* already detected error */
4604 	return NULL;
4605 
4606 #ifdef FEAT_CONCEAL
4607     if (curwin->w_s->b_syn_conceal)
4608 	opt->flags |= HL_CONCEAL;
4609 #endif
4610 
4611     for (;;)
4612     {
4613 	/*
4614 	 * This is used very often when a large number of keywords is defined.
4615 	 * Need to skip quickly when no option name is found.
4616 	 * Also avoid tolower(), it's slow.
4617 	 */
4618 	if (strchr(first_letters, *arg) == NULL)
4619 	    break;
4620 
4621 	for (fidx = sizeof(flagtab) / sizeof(struct flag); --fidx >= 0; )
4622 	{
4623 	    p = flagtab[fidx].name;
4624 	    for (i = 0, len = 0; p[i] != NUL; i += 2, ++len)
4625 		if (arg[len] != p[i] && arg[len] != p[i + 1])
4626 		    break;
4627 	    if (p[i] == NUL && (VIM_ISWHITE(arg[len])
4628 				    || (flagtab[fidx].argtype > 0
4629 					 ? arg[len] == '='
4630 					 : ends_excmd(arg[len]))))
4631 	    {
4632 		if (opt->keyword
4633 			&& (flagtab[fidx].flags == HL_DISPLAY
4634 			    || flagtab[fidx].flags == HL_FOLD
4635 			    || flagtab[fidx].flags == HL_EXTEND))
4636 		    /* treat "display", "fold" and "extend" as a keyword */
4637 		    fidx = -1;
4638 		break;
4639 	    }
4640 	}
4641 	if (fidx < 0)	    /* no match found */
4642 	    break;
4643 
4644 	if (flagtab[fidx].argtype == 1)
4645 	{
4646 	    if (!opt->has_cont_list)
4647 	    {
4648 		emsg(_("E395: contains argument not accepted here"));
4649 		return NULL;
4650 	    }
4651 	    if (get_id_list(&arg, 8, &opt->cont_list, skip) == FAIL)
4652 		return NULL;
4653 	}
4654 	else if (flagtab[fidx].argtype == 2)
4655 	{
4656 	    if (get_id_list(&arg, 11, &opt->cont_in_list, skip) == FAIL)
4657 		return NULL;
4658 	}
4659 	else if (flagtab[fidx].argtype == 3)
4660 	{
4661 	    if (get_id_list(&arg, 9, &opt->next_list, skip) == FAIL)
4662 		return NULL;
4663 	}
4664 	else if (flagtab[fidx].argtype == 11 && arg[5] == '=')
4665 	{
4666 	    /* cchar=? */
4667 	    if (has_mbyte)
4668 	    {
4669 #ifdef FEAT_CONCEAL
4670 		*conceal_char = mb_ptr2char(arg + 6);
4671 #endif
4672 		arg += mb_ptr2len(arg + 6) - 1;
4673 	    }
4674 	    else
4675 	    {
4676 #ifdef FEAT_CONCEAL
4677 		*conceal_char = arg[6];
4678 #else
4679 		;
4680 #endif
4681 	    }
4682 #ifdef FEAT_CONCEAL
4683 	    if (!vim_isprintc_strict(*conceal_char))
4684 	    {
4685 		emsg(_("E844: invalid cchar value"));
4686 		return NULL;
4687 	    }
4688 #endif
4689 	    arg = skipwhite(arg + 7);
4690 	}
4691 	else
4692 	{
4693 	    opt->flags |= flagtab[fidx].flags;
4694 	    arg = skipwhite(arg + len);
4695 
4696 	    if (flagtab[fidx].flags == HL_SYNC_HERE
4697 		    || flagtab[fidx].flags == HL_SYNC_THERE)
4698 	    {
4699 		if (opt->sync_idx == NULL)
4700 		{
4701 		    emsg(_("E393: group[t]here not accepted here"));
4702 		    return NULL;
4703 		}
4704 		gname_start = arg;
4705 		arg = skiptowhite(arg);
4706 		if (gname_start == arg)
4707 		    return NULL;
4708 		gname = vim_strnsave(gname_start, (int)(arg - gname_start));
4709 		if (gname == NULL)
4710 		    return NULL;
4711 		if (STRCMP(gname, "NONE") == 0)
4712 		    *opt->sync_idx = NONE_IDX;
4713 		else
4714 		{
4715 		    syn_id = syn_name2id(gname);
4716 		    for (i = curwin->w_s->b_syn_patterns.ga_len; --i >= 0; )
4717 			if (SYN_ITEMS(curwin->w_s)[i].sp_syn.id == syn_id
4718 			      && SYN_ITEMS(curwin->w_s)[i].sp_type == SPTYPE_START)
4719 			{
4720 			    *opt->sync_idx = i;
4721 			    break;
4722 			}
4723 		    if (i < 0)
4724 		    {
4725 			semsg(_("E394: Didn't find region item for %s"), gname);
4726 			vim_free(gname);
4727 			return NULL;
4728 		    }
4729 		}
4730 
4731 		vim_free(gname);
4732 		arg = skipwhite(arg);
4733 	    }
4734 #ifdef FEAT_FOLDING
4735 	    else if (flagtab[fidx].flags == HL_FOLD
4736 						&& foldmethodIsSyntax(curwin))
4737 		/* Need to update folds later. */
4738 		foldUpdateAll(curwin);
4739 #endif
4740 	}
4741     }
4742 
4743     return arg;
4744 }
4745 
4746 /*
4747  * Adjustments to syntax item when declared in a ":syn include"'d file.
4748  * Set the contained flag, and if the item is not already contained, add it
4749  * to the specified top-level group, if any.
4750  */
4751     static void
4752 syn_incl_toplevel(int id, int *flagsp)
4753 {
4754     if ((*flagsp & HL_CONTAINED) || curwin->w_s->b_syn_topgrp == 0)
4755 	return;
4756     *flagsp |= HL_CONTAINED;
4757     if (curwin->w_s->b_syn_topgrp >= SYNID_CLUSTER)
4758     {
4759 	/* We have to alloc this, because syn_combine_list() will free it. */
4760 	short	    *grp_list = ALLOC_MULT(short, 2);
4761 	int	    tlg_id = curwin->w_s->b_syn_topgrp - SYNID_CLUSTER;
4762 
4763 	if (grp_list != NULL)
4764 	{
4765 	    grp_list[0] = id;
4766 	    grp_list[1] = 0;
4767 	    syn_combine_list(&SYN_CLSTR(curwin->w_s)[tlg_id].scl_list,
4768 						       &grp_list, CLUSTER_ADD);
4769 	}
4770     }
4771 }
4772 
4773 /*
4774  * Handle ":syntax include [@{group-name}] filename" command.
4775  */
4776     static void
4777 syn_cmd_include(exarg_T *eap, int syncing UNUSED)
4778 {
4779     char_u	*arg = eap->arg;
4780     int		sgl_id = 1;
4781     char_u	*group_name_end;
4782     char_u	*rest;
4783     char	*errormsg = NULL;
4784     int		prev_toplvl_grp;
4785     int		prev_syn_inc_tag;
4786     int		source = FALSE;
4787 
4788     eap->nextcmd = find_nextcmd(arg);
4789     if (eap->skip)
4790 	return;
4791 
4792     if (arg[0] == '@')
4793     {
4794 	++arg;
4795 	rest = get_group_name(arg, &group_name_end);
4796 	if (rest == NULL)
4797 	{
4798 	    emsg(_("E397: Filename required"));
4799 	    return;
4800 	}
4801 	sgl_id = syn_check_cluster(arg, (int)(group_name_end - arg));
4802 	if (sgl_id == 0)
4803 	    return;
4804 	/* separate_nextcmd() and expand_filename() depend on this */
4805 	eap->arg = rest;
4806     }
4807 
4808     /*
4809      * Everything that's left, up to the next command, should be the
4810      * filename to include.
4811      */
4812     eap->argt |= (XFILE | NOSPC);
4813     separate_nextcmd(eap);
4814     if (*eap->arg == '<' || *eap->arg == '$' || mch_isFullName(eap->arg))
4815     {
4816 	/* For an absolute path, "$VIM/..." or "<sfile>.." we ":source" the
4817 	 * file.  Need to expand the file name first.  In other cases
4818 	 * ":runtime!" is used. */
4819 	source = TRUE;
4820 	if (expand_filename(eap, syn_cmdlinep, &errormsg) == FAIL)
4821 	{
4822 	    if (errormsg != NULL)
4823 		emsg(errormsg);
4824 	    return;
4825 	}
4826     }
4827 
4828     /*
4829      * Save and restore the existing top-level grouplist id and ":syn
4830      * include" tag around the actual inclusion.
4831      */
4832     if (running_syn_inc_tag >= MAX_SYN_INC_TAG)
4833     {
4834 	emsg(_("E847: Too many syntax includes"));
4835 	return;
4836     }
4837     prev_syn_inc_tag = current_syn_inc_tag;
4838     current_syn_inc_tag = ++running_syn_inc_tag;
4839     prev_toplvl_grp = curwin->w_s->b_syn_topgrp;
4840     curwin->w_s->b_syn_topgrp = sgl_id;
4841     if (source ? do_source(eap->arg, FALSE, DOSO_NONE) == FAIL
4842 				: source_runtime(eap->arg, DIP_ALL) == FAIL)
4843 	semsg(_(e_notopen), eap->arg);
4844     curwin->w_s->b_syn_topgrp = prev_toplvl_grp;
4845     current_syn_inc_tag = prev_syn_inc_tag;
4846 }
4847 
4848 /*
4849  * Handle ":syntax keyword {group-name} [{option}] keyword .." command.
4850  */
4851     static void
4852 syn_cmd_keyword(exarg_T *eap, int syncing UNUSED)
4853 {
4854     char_u	*arg = eap->arg;
4855     char_u	*group_name_end;
4856     int		syn_id;
4857     char_u	*rest;
4858     char_u	*keyword_copy = NULL;
4859     char_u	*p;
4860     char_u	*kw;
4861     syn_opt_arg_T syn_opt_arg;
4862     int		cnt;
4863     int		conceal_char = NUL;
4864 
4865     rest = get_group_name(arg, &group_name_end);
4866 
4867     if (rest != NULL)
4868     {
4869 	if (eap->skip)
4870 	    syn_id = -1;
4871 	else
4872 	    syn_id = syn_check_group(arg, (int)(group_name_end - arg));
4873 	if (syn_id != 0)
4874 	    /* allocate a buffer, for removing backslashes in the keyword */
4875 	    keyword_copy = alloc(STRLEN(rest) + 1);
4876 	if (keyword_copy != NULL)
4877 	{
4878 	    syn_opt_arg.flags = 0;
4879 	    syn_opt_arg.keyword = TRUE;
4880 	    syn_opt_arg.sync_idx = NULL;
4881 	    syn_opt_arg.has_cont_list = FALSE;
4882 	    syn_opt_arg.cont_in_list = NULL;
4883 	    syn_opt_arg.next_list = NULL;
4884 
4885 	    /*
4886 	     * The options given apply to ALL keywords, so all options must be
4887 	     * found before keywords can be created.
4888 	     * 1: collect the options and copy the keywords to keyword_copy.
4889 	     */
4890 	    cnt = 0;
4891 	    p = keyword_copy;
4892 	    for ( ; rest != NULL && !ends_excmd(*rest); rest = skipwhite(rest))
4893 	    {
4894 		rest = get_syn_options(rest, &syn_opt_arg, &conceal_char,
4895 								    eap->skip);
4896 		if (rest == NULL || ends_excmd(*rest))
4897 		    break;
4898 		/* Copy the keyword, removing backslashes, and add a NUL. */
4899 		while (*rest != NUL && !VIM_ISWHITE(*rest))
4900 		{
4901 		    if (*rest == '\\' && rest[1] != NUL)
4902 			++rest;
4903 		    *p++ = *rest++;
4904 		}
4905 		*p++ = NUL;
4906 		++cnt;
4907 	    }
4908 
4909 	    if (!eap->skip)
4910 	    {
4911 		/* Adjust flags for use of ":syn include". */
4912 		syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
4913 
4914 		/*
4915 		 * 2: Add an entry for each keyword.
4916 		 */
4917 		for (kw = keyword_copy; --cnt >= 0; kw += STRLEN(kw) + 1)
4918 		{
4919 		    for (p = vim_strchr(kw, '['); ; )
4920 		    {
4921 			if (p != NULL)
4922 			    *p = NUL;
4923 			add_keyword(kw, syn_id, syn_opt_arg.flags,
4924 				syn_opt_arg.cont_in_list,
4925 					 syn_opt_arg.next_list, conceal_char);
4926 			if (p == NULL)
4927 			    break;
4928 			if (p[1] == NUL)
4929 			{
4930 			    semsg(_("E789: Missing ']': %s"), kw);
4931 			    goto error;
4932 			}
4933 			if (p[1] == ']')
4934 			{
4935 			    if (p[2] != NUL)
4936 			    {
4937 				semsg(_("E890: trailing char after ']': %s]%s"),
4938 								kw, &p[2]);
4939 				goto error;
4940 			    }
4941 			    kw = p + 1;		/* skip over the "]" */
4942 			    break;
4943 			}
4944 			if (has_mbyte)
4945 			{
4946 			    int l = (*mb_ptr2len)(p + 1);
4947 
4948 			    mch_memmove(p, p + 1, l);
4949 			    p += l;
4950 			}
4951 			else
4952 			{
4953 			    p[0] = p[1];
4954 			    ++p;
4955 			}
4956 		    }
4957 		}
4958 	    }
4959 error:
4960 	    vim_free(keyword_copy);
4961 	    vim_free(syn_opt_arg.cont_in_list);
4962 	    vim_free(syn_opt_arg.next_list);
4963 	}
4964     }
4965 
4966     if (rest != NULL)
4967 	eap->nextcmd = check_nextcmd(rest);
4968     else
4969 	semsg(_(e_invarg2), arg);
4970 
4971     redraw_curbuf_later(SOME_VALID);
4972     syn_stack_free_all(curwin->w_s);		/* Need to recompute all syntax. */
4973 }
4974 
4975 /*
4976  * Handle ":syntax match {name} [{options}] {pattern} [{options}]".
4977  *
4978  * Also ":syntax sync match {name} [[grouphere | groupthere] {group-name}] .."
4979  */
4980     static void
4981 syn_cmd_match(
4982     exarg_T	*eap,
4983     int		syncing)	    /* TRUE for ":syntax sync match .. " */
4984 {
4985     char_u	*arg = eap->arg;
4986     char_u	*group_name_end;
4987     char_u	*rest;
4988     synpat_T	item;		/* the item found in the line */
4989     int		syn_id;
4990     int		idx;
4991     syn_opt_arg_T syn_opt_arg;
4992     int		sync_idx = 0;
4993     int		conceal_char = NUL;
4994 
4995     /* Isolate the group name, check for validity */
4996     rest = get_group_name(arg, &group_name_end);
4997 
4998     /* Get options before the pattern */
4999     syn_opt_arg.flags = 0;
5000     syn_opt_arg.keyword = FALSE;
5001     syn_opt_arg.sync_idx = syncing ? &sync_idx : NULL;
5002     syn_opt_arg.has_cont_list = TRUE;
5003     syn_opt_arg.cont_list = NULL;
5004     syn_opt_arg.cont_in_list = NULL;
5005     syn_opt_arg.next_list = NULL;
5006     rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
5007 
5008     /* get the pattern. */
5009     init_syn_patterns();
5010     vim_memset(&item, 0, sizeof(item));
5011     rest = get_syn_pattern(rest, &item);
5012     if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL))
5013 	syn_opt_arg.flags |= HL_HAS_EOL;
5014 
5015     /* Get options after the pattern */
5016     rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
5017 
5018     if (rest != NULL)		/* all arguments are valid */
5019     {
5020 	/*
5021 	 * Check for trailing command and illegal trailing arguments.
5022 	 */
5023 	eap->nextcmd = check_nextcmd(rest);
5024 	if (!ends_excmd(*rest) || eap->skip)
5025 	    rest = NULL;
5026 	else if (ga_grow(&curwin->w_s->b_syn_patterns, 1) != FAIL
5027 		&& (syn_id = syn_check_group(arg,
5028 					   (int)(group_name_end - arg))) != 0)
5029 	{
5030 	    syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
5031 	    /*
5032 	     * Store the pattern in the syn_items list
5033 	     */
5034 	    idx = curwin->w_s->b_syn_patterns.ga_len;
5035 	    SYN_ITEMS(curwin->w_s)[idx] = item;
5036 	    SYN_ITEMS(curwin->w_s)[idx].sp_syncing = syncing;
5037 	    SYN_ITEMS(curwin->w_s)[idx].sp_type = SPTYPE_MATCH;
5038 	    SYN_ITEMS(curwin->w_s)[idx].sp_syn.id = syn_id;
5039 	    SYN_ITEMS(curwin->w_s)[idx].sp_syn.inc_tag = current_syn_inc_tag;
5040 	    SYN_ITEMS(curwin->w_s)[idx].sp_flags = syn_opt_arg.flags;
5041 	    SYN_ITEMS(curwin->w_s)[idx].sp_sync_idx = sync_idx;
5042 	    SYN_ITEMS(curwin->w_s)[idx].sp_cont_list = syn_opt_arg.cont_list;
5043 	    SYN_ITEMS(curwin->w_s)[idx].sp_syn.cont_in_list =
5044 						     syn_opt_arg.cont_in_list;
5045 #ifdef FEAT_CONCEAL
5046 	    SYN_ITEMS(curwin->w_s)[idx].sp_cchar = conceal_char;
5047 #endif
5048 	    if (syn_opt_arg.cont_in_list != NULL)
5049 		curwin->w_s->b_syn_containedin = TRUE;
5050 	    SYN_ITEMS(curwin->w_s)[idx].sp_next_list = syn_opt_arg.next_list;
5051 	    ++curwin->w_s->b_syn_patterns.ga_len;
5052 
5053 	    /* remember that we found a match for syncing on */
5054 	    if (syn_opt_arg.flags & (HL_SYNC_HERE|HL_SYNC_THERE))
5055 		curwin->w_s->b_syn_sync_flags |= SF_MATCH;
5056 #ifdef FEAT_FOLDING
5057 	    if (syn_opt_arg.flags & HL_FOLD)
5058 		++curwin->w_s->b_syn_folditems;
5059 #endif
5060 
5061 	    redraw_curbuf_later(SOME_VALID);
5062 	    syn_stack_free_all(curwin->w_s);	/* Need to recompute all syntax. */
5063 	    return;	/* don't free the progs and patterns now */
5064 	}
5065     }
5066 
5067     /*
5068      * Something failed, free the allocated memory.
5069      */
5070     vim_regfree(item.sp_prog);
5071     vim_free(item.sp_pattern);
5072     vim_free(syn_opt_arg.cont_list);
5073     vim_free(syn_opt_arg.cont_in_list);
5074     vim_free(syn_opt_arg.next_list);
5075 
5076     if (rest == NULL)
5077 	semsg(_(e_invarg2), arg);
5078 }
5079 
5080 /*
5081  * Handle ":syntax region {group-name} [matchgroup={group-name}]
5082  *		start {start} .. [skip {skip}] end {end} .. [{options}]".
5083  */
5084     static void
5085 syn_cmd_region(
5086     exarg_T	*eap,
5087     int		syncing)	    /* TRUE for ":syntax sync region .." */
5088 {
5089     char_u		*arg = eap->arg;
5090     char_u		*group_name_end;
5091     char_u		*rest;			/* next arg, NULL on error */
5092     char_u		*key_end;
5093     char_u		*key = NULL;
5094     char_u		*p;
5095     int			item;
5096 #define ITEM_START	    0
5097 #define ITEM_SKIP	    1
5098 #define ITEM_END	    2
5099 #define ITEM_MATCHGROUP	    3
5100     struct pat_ptr
5101     {
5102 	synpat_T	*pp_synp;		/* pointer to syn_pattern */
5103 	int		pp_matchgroup_id;	/* matchgroup ID */
5104 	struct pat_ptr	*pp_next;		/* pointer to next pat_ptr */
5105     }			*(pat_ptrs[3]);
5106 					/* patterns found in the line */
5107     struct pat_ptr	*ppp;
5108     struct pat_ptr	*ppp_next;
5109     int			pat_count = 0;		/* nr of syn_patterns found */
5110     int			syn_id;
5111     int			matchgroup_id = 0;
5112     int			not_enough = FALSE;	/* not enough arguments */
5113     int			illegal = FALSE;	/* illegal arguments */
5114     int			success = FALSE;
5115     int			idx;
5116     syn_opt_arg_T	syn_opt_arg;
5117     int			conceal_char = NUL;
5118 
5119     /* Isolate the group name, check for validity */
5120     rest = get_group_name(arg, &group_name_end);
5121 
5122     pat_ptrs[0] = NULL;
5123     pat_ptrs[1] = NULL;
5124     pat_ptrs[2] = NULL;
5125 
5126     init_syn_patterns();
5127 
5128     syn_opt_arg.flags = 0;
5129     syn_opt_arg.keyword = FALSE;
5130     syn_opt_arg.sync_idx = NULL;
5131     syn_opt_arg.has_cont_list = TRUE;
5132     syn_opt_arg.cont_list = NULL;
5133     syn_opt_arg.cont_in_list = NULL;
5134     syn_opt_arg.next_list = NULL;
5135 
5136     /*
5137      * get the options, patterns and matchgroup.
5138      */
5139     while (rest != NULL && !ends_excmd(*rest))
5140     {
5141 	/* Check for option arguments */
5142 	rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
5143 	if (rest == NULL || ends_excmd(*rest))
5144 	    break;
5145 
5146 	/* must be a pattern or matchgroup then */
5147 	key_end = rest;
5148 	while (*key_end && !VIM_ISWHITE(*key_end) && *key_end != '=')
5149 	    ++key_end;
5150 	vim_free(key);
5151 	key = vim_strnsave_up(rest, (int)(key_end - rest));
5152 	if (key == NULL)			/* out of memory */
5153 	{
5154 	    rest = NULL;
5155 	    break;
5156 	}
5157 	if (STRCMP(key, "MATCHGROUP") == 0)
5158 	    item = ITEM_MATCHGROUP;
5159 	else if (STRCMP(key, "START") == 0)
5160 	    item = ITEM_START;
5161 	else if (STRCMP(key, "END") == 0)
5162 	    item = ITEM_END;
5163 	else if (STRCMP(key, "SKIP") == 0)
5164 	{
5165 	    if (pat_ptrs[ITEM_SKIP] != NULL)	/* one skip pattern allowed */
5166 	    {
5167 		illegal = TRUE;
5168 		break;
5169 	    }
5170 	    item = ITEM_SKIP;
5171 	}
5172 	else
5173 	    break;
5174 	rest = skipwhite(key_end);
5175 	if (*rest != '=')
5176 	{
5177 	    rest = NULL;
5178 	    semsg(_("E398: Missing '=': %s"), arg);
5179 	    break;
5180 	}
5181 	rest = skipwhite(rest + 1);
5182 	if (*rest == NUL)
5183 	{
5184 	    not_enough = TRUE;
5185 	    break;
5186 	}
5187 
5188 	if (item == ITEM_MATCHGROUP)
5189 	{
5190 	    p = skiptowhite(rest);
5191 	    if ((p - rest == 4 && STRNCMP(rest, "NONE", 4) == 0) || eap->skip)
5192 		matchgroup_id = 0;
5193 	    else
5194 	    {
5195 		matchgroup_id = syn_check_group(rest, (int)(p - rest));
5196 		if (matchgroup_id == 0)
5197 		{
5198 		    illegal = TRUE;
5199 		    break;
5200 		}
5201 	    }
5202 	    rest = skipwhite(p);
5203 	}
5204 	else
5205 	{
5206 	    /*
5207 	     * Allocate room for a syn_pattern, and link it in the list of
5208 	     * syn_patterns for this item, at the start (because the list is
5209 	     * used from end to start).
5210 	     */
5211 	    ppp = ALLOC_ONE(struct pat_ptr);
5212 	    if (ppp == NULL)
5213 	    {
5214 		rest = NULL;
5215 		break;
5216 	    }
5217 	    ppp->pp_next = pat_ptrs[item];
5218 	    pat_ptrs[item] = ppp;
5219 	    ppp->pp_synp = ALLOC_CLEAR_ONE(synpat_T);
5220 	    if (ppp->pp_synp == NULL)
5221 	    {
5222 		rest = NULL;
5223 		break;
5224 	    }
5225 
5226 	    /*
5227 	     * Get the syntax pattern and the following offset(s).
5228 	     */
5229 	    /* Enable the appropriate \z specials. */
5230 	    if (item == ITEM_START)
5231 		reg_do_extmatch = REX_SET;
5232 	    else if (item == ITEM_SKIP || item == ITEM_END)
5233 		reg_do_extmatch = REX_USE;
5234 	    rest = get_syn_pattern(rest, ppp->pp_synp);
5235 	    reg_do_extmatch = 0;
5236 	    if (item == ITEM_END && vim_regcomp_had_eol()
5237 				       && !(syn_opt_arg.flags & HL_EXCLUDENL))
5238 		ppp->pp_synp->sp_flags |= HL_HAS_EOL;
5239 	    ppp->pp_matchgroup_id = matchgroup_id;
5240 	    ++pat_count;
5241 	}
5242     }
5243     vim_free(key);
5244     if (illegal || not_enough)
5245 	rest = NULL;
5246 
5247     /*
5248      * Must have a "start" and "end" pattern.
5249      */
5250     if (rest != NULL && (pat_ptrs[ITEM_START] == NULL ||
5251 						  pat_ptrs[ITEM_END] == NULL))
5252     {
5253 	not_enough = TRUE;
5254 	rest = NULL;
5255     }
5256 
5257     if (rest != NULL)
5258     {
5259 	/*
5260 	 * Check for trailing garbage or command.
5261 	 * If OK, add the item.
5262 	 */
5263 	eap->nextcmd = check_nextcmd(rest);
5264 	if (!ends_excmd(*rest) || eap->skip)
5265 	    rest = NULL;
5266 	else if (ga_grow(&(curwin->w_s->b_syn_patterns), pat_count) != FAIL
5267 		&& (syn_id = syn_check_group(arg,
5268 					   (int)(group_name_end - arg))) != 0)
5269 	{
5270 	    syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
5271 	    /*
5272 	     * Store the start/skip/end in the syn_items list
5273 	     */
5274 	    idx = curwin->w_s->b_syn_patterns.ga_len;
5275 	    for (item = ITEM_START; item <= ITEM_END; ++item)
5276 	    {
5277 		for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp->pp_next)
5278 		{
5279 		    SYN_ITEMS(curwin->w_s)[idx] = *(ppp->pp_synp);
5280 		    SYN_ITEMS(curwin->w_s)[idx].sp_syncing = syncing;
5281 		    SYN_ITEMS(curwin->w_s)[idx].sp_type =
5282 			    (item == ITEM_START) ? SPTYPE_START :
5283 			    (item == ITEM_SKIP) ? SPTYPE_SKIP : SPTYPE_END;
5284 		    SYN_ITEMS(curwin->w_s)[idx].sp_flags |= syn_opt_arg.flags;
5285 		    SYN_ITEMS(curwin->w_s)[idx].sp_syn.id = syn_id;
5286 		    SYN_ITEMS(curwin->w_s)[idx].sp_syn.inc_tag =
5287 							  current_syn_inc_tag;
5288 		    SYN_ITEMS(curwin->w_s)[idx].sp_syn_match_id =
5289 							ppp->pp_matchgroup_id;
5290 #ifdef FEAT_CONCEAL
5291 		    SYN_ITEMS(curwin->w_s)[idx].sp_cchar = conceal_char;
5292 #endif
5293 		    if (item == ITEM_START)
5294 		    {
5295 			SYN_ITEMS(curwin->w_s)[idx].sp_cont_list =
5296 							syn_opt_arg.cont_list;
5297 			SYN_ITEMS(curwin->w_s)[idx].sp_syn.cont_in_list =
5298 						     syn_opt_arg.cont_in_list;
5299 			if (syn_opt_arg.cont_in_list != NULL)
5300 			    curwin->w_s->b_syn_containedin = TRUE;
5301 			SYN_ITEMS(curwin->w_s)[idx].sp_next_list =
5302 							syn_opt_arg.next_list;
5303 		    }
5304 		    ++curwin->w_s->b_syn_patterns.ga_len;
5305 		    ++idx;
5306 #ifdef FEAT_FOLDING
5307 		    if (syn_opt_arg.flags & HL_FOLD)
5308 			++curwin->w_s->b_syn_folditems;
5309 #endif
5310 		}
5311 	    }
5312 
5313 	    redraw_curbuf_later(SOME_VALID);
5314 	    syn_stack_free_all(curwin->w_s);	/* Need to recompute all syntax. */
5315 	    success = TRUE;	    /* don't free the progs and patterns now */
5316 	}
5317     }
5318 
5319     /*
5320      * Free the allocated memory.
5321      */
5322     for (item = ITEM_START; item <= ITEM_END; ++item)
5323 	for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp_next)
5324 	{
5325 	    if (!success)
5326 	    {
5327 		vim_regfree(ppp->pp_synp->sp_prog);
5328 		vim_free(ppp->pp_synp->sp_pattern);
5329 	    }
5330 	    vim_free(ppp->pp_synp);
5331 	    ppp_next = ppp->pp_next;
5332 	    vim_free(ppp);
5333 	}
5334 
5335     if (!success)
5336     {
5337 	vim_free(syn_opt_arg.cont_list);
5338 	vim_free(syn_opt_arg.cont_in_list);
5339 	vim_free(syn_opt_arg.next_list);
5340 	if (not_enough)
5341 	    semsg(_("E399: Not enough arguments: syntax region %s"), arg);
5342 	else if (illegal || rest == NULL)
5343 	    semsg(_(e_invarg2), arg);
5344     }
5345 }
5346 
5347 /*
5348  * A simple syntax group ID comparison function suitable for use in qsort()
5349  */
5350     static int
5351 syn_compare_stub(const void *v1, const void *v2)
5352 {
5353     const short	*s1 = v1;
5354     const short	*s2 = v2;
5355 
5356     return (*s1 > *s2 ? 1 : *s1 < *s2 ? -1 : 0);
5357 }
5358 
5359 /*
5360  * Combines lists of syntax clusters.
5361  * *clstr1 and *clstr2 must both be allocated memory; they will be consumed.
5362  */
5363     static void
5364 syn_combine_list(short **clstr1, short **clstr2, int list_op)
5365 {
5366     int		count1 = 0;
5367     int		count2 = 0;
5368     short	*g1;
5369     short	*g2;
5370     short	*clstr = NULL;
5371     int		count;
5372     int		round;
5373 
5374     /*
5375      * Handle degenerate cases.
5376      */
5377     if (*clstr2 == NULL)
5378 	return;
5379     if (*clstr1 == NULL || list_op == CLUSTER_REPLACE)
5380     {
5381 	if (list_op == CLUSTER_REPLACE)
5382 	    vim_free(*clstr1);
5383 	if (list_op == CLUSTER_REPLACE || list_op == CLUSTER_ADD)
5384 	    *clstr1 = *clstr2;
5385 	else
5386 	    vim_free(*clstr2);
5387 	return;
5388     }
5389 
5390     for (g1 = *clstr1; *g1; g1++)
5391 	++count1;
5392     for (g2 = *clstr2; *g2; g2++)
5393 	++count2;
5394 
5395     /*
5396      * For speed purposes, sort both lists.
5397      */
5398     qsort(*clstr1, (size_t)count1, sizeof(short), syn_compare_stub);
5399     qsort(*clstr2, (size_t)count2, sizeof(short), syn_compare_stub);
5400 
5401     /*
5402      * We proceed in two passes; in round 1, we count the elements to place
5403      * in the new list, and in round 2, we allocate and populate the new
5404      * list.  For speed, we use a mergesort-like method, adding the smaller
5405      * of the current elements in each list to the new list.
5406      */
5407     for (round = 1; round <= 2; round++)
5408     {
5409 	g1 = *clstr1;
5410 	g2 = *clstr2;
5411 	count = 0;
5412 
5413 	/*
5414 	 * First, loop through the lists until one of them is empty.
5415 	 */
5416 	while (*g1 && *g2)
5417 	{
5418 	    /*
5419 	     * We always want to add from the first list.
5420 	     */
5421 	    if (*g1 < *g2)
5422 	    {
5423 		if (round == 2)
5424 		    clstr[count] = *g1;
5425 		count++;
5426 		g1++;
5427 		continue;
5428 	    }
5429 	    /*
5430 	     * We only want to add from the second list if we're adding the
5431 	     * lists.
5432 	     */
5433 	    if (list_op == CLUSTER_ADD)
5434 	    {
5435 		if (round == 2)
5436 		    clstr[count] = *g2;
5437 		count++;
5438 	    }
5439 	    if (*g1 == *g2)
5440 		g1++;
5441 	    g2++;
5442 	}
5443 
5444 	/*
5445 	 * Now add the leftovers from whichever list didn't get finished
5446 	 * first.  As before, we only want to add from the second list if
5447 	 * we're adding the lists.
5448 	 */
5449 	for (; *g1; g1++, count++)
5450 	    if (round == 2)
5451 		clstr[count] = *g1;
5452 	if (list_op == CLUSTER_ADD)
5453 	    for (; *g2; g2++, count++)
5454 		if (round == 2)
5455 		    clstr[count] = *g2;
5456 
5457 	if (round == 1)
5458 	{
5459 	    /*
5460 	     * If the group ended up empty, we don't need to allocate any
5461 	     * space for it.
5462 	     */
5463 	    if (count == 0)
5464 	    {
5465 		clstr = NULL;
5466 		break;
5467 	    }
5468 	    clstr = ALLOC_MULT(short, count + 1);
5469 	    if (clstr == NULL)
5470 		break;
5471 	    clstr[count] = 0;
5472 	}
5473     }
5474 
5475     /*
5476      * Finally, put the new list in place.
5477      */
5478     vim_free(*clstr1);
5479     vim_free(*clstr2);
5480     *clstr1 = clstr;
5481 }
5482 
5483 /*
5484  * Lookup a syntax cluster name and return its ID.
5485  * If it is not found, 0 is returned.
5486  */
5487     static int
5488 syn_scl_name2id(char_u *name)
5489 {
5490     int		i;
5491     char_u	*name_u;
5492 
5493     /* Avoid using stricmp() too much, it's slow on some systems */
5494     name_u = vim_strsave_up(name);
5495     if (name_u == NULL)
5496 	return 0;
5497     for (i = curwin->w_s->b_syn_clusters.ga_len; --i >= 0; )
5498 	if (SYN_CLSTR(curwin->w_s)[i].scl_name_u != NULL
5499 		&& STRCMP(name_u, SYN_CLSTR(curwin->w_s)[i].scl_name_u) == 0)
5500 	    break;
5501     vim_free(name_u);
5502     return (i < 0 ? 0 : i + SYNID_CLUSTER);
5503 }
5504 
5505 /*
5506  * Like syn_scl_name2id(), but take a pointer + length argument.
5507  */
5508     static int
5509 syn_scl_namen2id(char_u *linep, int len)
5510 {
5511     char_u  *name;
5512     int	    id = 0;
5513 
5514     name = vim_strnsave(linep, len);
5515     if (name != NULL)
5516     {
5517 	id = syn_scl_name2id(name);
5518 	vim_free(name);
5519     }
5520     return id;
5521 }
5522 
5523 /*
5524  * Find syntax cluster name in the table and return its ID.
5525  * The argument is a pointer to the name and the length of the name.
5526  * If it doesn't exist yet, a new entry is created.
5527  * Return 0 for failure.
5528  */
5529     static int
5530 syn_check_cluster(char_u *pp, int len)
5531 {
5532     int		id;
5533     char_u	*name;
5534 
5535     name = vim_strnsave(pp, len);
5536     if (name == NULL)
5537 	return 0;
5538 
5539     id = syn_scl_name2id(name);
5540     if (id == 0)			/* doesn't exist yet */
5541 	id = syn_add_cluster(name);
5542     else
5543 	vim_free(name);
5544     return id;
5545 }
5546 
5547 /*
5548  * Add new syntax cluster and return its ID.
5549  * "name" must be an allocated string, it will be consumed.
5550  * Return 0 for failure.
5551  */
5552     static int
5553 syn_add_cluster(char_u *name)
5554 {
5555     int		len;
5556 
5557     /*
5558      * First call for this growarray: init growing array.
5559      */
5560     if (curwin->w_s->b_syn_clusters.ga_data == NULL)
5561     {
5562 	curwin->w_s->b_syn_clusters.ga_itemsize = sizeof(syn_cluster_T);
5563 	curwin->w_s->b_syn_clusters.ga_growsize = 10;
5564     }
5565 
5566     len = curwin->w_s->b_syn_clusters.ga_len;
5567     if (len >= MAX_CLUSTER_ID)
5568     {
5569 	emsg(_("E848: Too many syntax clusters"));
5570 	vim_free(name);
5571 	return 0;
5572     }
5573 
5574     /*
5575      * Make room for at least one other cluster entry.
5576      */
5577     if (ga_grow(&curwin->w_s->b_syn_clusters, 1) == FAIL)
5578     {
5579 	vim_free(name);
5580 	return 0;
5581     }
5582 
5583     vim_memset(&(SYN_CLSTR(curwin->w_s)[len]), 0, sizeof(syn_cluster_T));
5584     SYN_CLSTR(curwin->w_s)[len].scl_name = name;
5585     SYN_CLSTR(curwin->w_s)[len].scl_name_u = vim_strsave_up(name);
5586     SYN_CLSTR(curwin->w_s)[len].scl_list = NULL;
5587     ++curwin->w_s->b_syn_clusters.ga_len;
5588 
5589     if (STRICMP(name, "Spell") == 0)
5590 	curwin->w_s->b_spell_cluster_id = len + SYNID_CLUSTER;
5591     if (STRICMP(name, "NoSpell") == 0)
5592 	curwin->w_s->b_nospell_cluster_id = len + SYNID_CLUSTER;
5593 
5594     return len + SYNID_CLUSTER;
5595 }
5596 
5597 /*
5598  * Handle ":syntax cluster {cluster-name} [contains={groupname},..]
5599  *		[add={groupname},..] [remove={groupname},..]".
5600  */
5601     static void
5602 syn_cmd_cluster(exarg_T *eap, int syncing UNUSED)
5603 {
5604     char_u	*arg = eap->arg;
5605     char_u	*group_name_end;
5606     char_u	*rest;
5607     int		scl_id;
5608     short	*clstr_list;
5609     int		got_clstr = FALSE;
5610     int		opt_len;
5611     int		list_op;
5612 
5613     eap->nextcmd = find_nextcmd(arg);
5614     if (eap->skip)
5615 	return;
5616 
5617     rest = get_group_name(arg, &group_name_end);
5618 
5619     if (rest != NULL)
5620     {
5621 	scl_id = syn_check_cluster(arg, (int)(group_name_end - arg));
5622 	if (scl_id == 0)
5623 	    return;
5624 	scl_id -= SYNID_CLUSTER;
5625 
5626 	for (;;)
5627 	{
5628 	    if (STRNICMP(rest, "add", 3) == 0
5629 		    && (VIM_ISWHITE(rest[3]) || rest[3] == '='))
5630 	    {
5631 		opt_len = 3;
5632 		list_op = CLUSTER_ADD;
5633 	    }
5634 	    else if (STRNICMP(rest, "remove", 6) == 0
5635 		    && (VIM_ISWHITE(rest[6]) || rest[6] == '='))
5636 	    {
5637 		opt_len = 6;
5638 		list_op = CLUSTER_SUBTRACT;
5639 	    }
5640 	    else if (STRNICMP(rest, "contains", 8) == 0
5641 			&& (VIM_ISWHITE(rest[8]) || rest[8] == '='))
5642 	    {
5643 		opt_len = 8;
5644 		list_op = CLUSTER_REPLACE;
5645 	    }
5646 	    else
5647 		break;
5648 
5649 	    clstr_list = NULL;
5650 	    if (get_id_list(&rest, opt_len, &clstr_list, eap->skip) == FAIL)
5651 	    {
5652 		semsg(_(e_invarg2), rest);
5653 		break;
5654 	    }
5655 	    if (scl_id >= 0)
5656 		syn_combine_list(&SYN_CLSTR(curwin->w_s)[scl_id].scl_list,
5657 			     &clstr_list, list_op);
5658 	    else
5659 		vim_free(clstr_list);
5660 	    got_clstr = TRUE;
5661 	}
5662 
5663 	if (got_clstr)
5664 	{
5665 	    redraw_curbuf_later(SOME_VALID);
5666 	    syn_stack_free_all(curwin->w_s);	/* Need to recompute all. */
5667 	}
5668     }
5669 
5670     if (!got_clstr)
5671 	emsg(_("E400: No cluster specified"));
5672     if (rest == NULL || !ends_excmd(*rest))
5673 	semsg(_(e_invarg2), arg);
5674 }
5675 
5676 /*
5677  * On first call for current buffer: Init growing array.
5678  */
5679     static void
5680 init_syn_patterns(void)
5681 {
5682     curwin->w_s->b_syn_patterns.ga_itemsize = sizeof(synpat_T);
5683     curwin->w_s->b_syn_patterns.ga_growsize = 10;
5684 }
5685 
5686 /*
5687  * Get one pattern for a ":syntax match" or ":syntax region" command.
5688  * Stores the pattern and program in a synpat_T.
5689  * Returns a pointer to the next argument, or NULL in case of an error.
5690  */
5691     static char_u *
5692 get_syn_pattern(char_u *arg, synpat_T *ci)
5693 {
5694     char_u	*end;
5695     int		*p;
5696     int		idx;
5697     char_u	*cpo_save;
5698 
5699     /* need at least three chars */
5700     if (arg == NULL || arg[0] == NUL || arg[1] == NUL || arg[2] == NUL)
5701 	return NULL;
5702 
5703     end = skip_regexp(arg + 1, *arg, TRUE, NULL);
5704     if (*end != *arg)			    /* end delimiter not found */
5705     {
5706 	semsg(_("E401: Pattern delimiter not found: %s"), arg);
5707 	return NULL;
5708     }
5709     /* store the pattern and compiled regexp program */
5710     if ((ci->sp_pattern = vim_strnsave(arg + 1, (int)(end - arg - 1))) == NULL)
5711 	return NULL;
5712 
5713     /* Make 'cpoptions' empty, to avoid the 'l' flag */
5714     cpo_save = p_cpo;
5715     p_cpo = (char_u *)"";
5716     ci->sp_prog = vim_regcomp(ci->sp_pattern, RE_MAGIC);
5717     p_cpo = cpo_save;
5718 
5719     if (ci->sp_prog == NULL)
5720 	return NULL;
5721     ci->sp_ic = curwin->w_s->b_syn_ic;
5722 #ifdef FEAT_PROFILE
5723     syn_clear_time(&ci->sp_time);
5724 #endif
5725 
5726     /*
5727      * Check for a match, highlight or region offset.
5728      */
5729     ++end;
5730     do
5731     {
5732 	for (idx = SPO_COUNT; --idx >= 0; )
5733 	    if (STRNCMP(end, spo_name_tab[idx], 3) == 0)
5734 		break;
5735 	if (idx >= 0)
5736 	{
5737 	    p = &(ci->sp_offsets[idx]);
5738 	    if (idx != SPO_LC_OFF)
5739 		switch (end[3])
5740 		{
5741 		    case 's':   break;
5742 		    case 'b':   break;
5743 		    case 'e':   idx += SPO_COUNT; break;
5744 		    default:    idx = -1; break;
5745 		}
5746 	    if (idx >= 0)
5747 	    {
5748 		ci->sp_off_flags |= (1 << idx);
5749 		if (idx == SPO_LC_OFF)	    /* lc=99 */
5750 		{
5751 		    end += 3;
5752 		    *p = getdigits(&end);
5753 
5754 		    /* "lc=" offset automatically sets "ms=" offset */
5755 		    if (!(ci->sp_off_flags & (1 << SPO_MS_OFF)))
5756 		    {
5757 			ci->sp_off_flags |= (1 << SPO_MS_OFF);
5758 			ci->sp_offsets[SPO_MS_OFF] = *p;
5759 		    }
5760 		}
5761 		else			    /* yy=x+99 */
5762 		{
5763 		    end += 4;
5764 		    if (*end == '+')
5765 		    {
5766 			++end;
5767 			*p = getdigits(&end);		/* positive offset */
5768 		    }
5769 		    else if (*end == '-')
5770 		    {
5771 			++end;
5772 			*p = -getdigits(&end);		/* negative offset */
5773 		    }
5774 		}
5775 		if (*end != ',')
5776 		    break;
5777 		++end;
5778 	    }
5779 	}
5780     } while (idx >= 0);
5781 
5782     if (!ends_excmd(*end) && !VIM_ISWHITE(*end))
5783     {
5784 	semsg(_("E402: Garbage after pattern: %s"), arg);
5785 	return NULL;
5786     }
5787     return skipwhite(end);
5788 }
5789 
5790 /*
5791  * Handle ":syntax sync .." command.
5792  */
5793     static void
5794 syn_cmd_sync(exarg_T *eap, int syncing UNUSED)
5795 {
5796     char_u	*arg_start = eap->arg;
5797     char_u	*arg_end;
5798     char_u	*key = NULL;
5799     char_u	*next_arg;
5800     int		illegal = FALSE;
5801     int		finished = FALSE;
5802     long	n;
5803     char_u	*cpo_save;
5804 
5805     if (ends_excmd(*arg_start))
5806     {
5807 	syn_cmd_list(eap, TRUE);
5808 	return;
5809     }
5810 
5811     while (!ends_excmd(*arg_start))
5812     {
5813 	arg_end = skiptowhite(arg_start);
5814 	next_arg = skipwhite(arg_end);
5815 	vim_free(key);
5816 	key = vim_strnsave_up(arg_start, (int)(arg_end - arg_start));
5817 	if (STRCMP(key, "CCOMMENT") == 0)
5818 	{
5819 	    if (!eap->skip)
5820 		curwin->w_s->b_syn_sync_flags |= SF_CCOMMENT;
5821 	    if (!ends_excmd(*next_arg))
5822 	    {
5823 		arg_end = skiptowhite(next_arg);
5824 		if (!eap->skip)
5825 		    curwin->w_s->b_syn_sync_id = syn_check_group(next_arg,
5826 						   (int)(arg_end - next_arg));
5827 		next_arg = skipwhite(arg_end);
5828 	    }
5829 	    else if (!eap->skip)
5830 		curwin->w_s->b_syn_sync_id = syn_name2id((char_u *)"Comment");
5831 	}
5832 	else if (  STRNCMP(key, "LINES", 5) == 0
5833 		|| STRNCMP(key, "MINLINES", 8) == 0
5834 		|| STRNCMP(key, "MAXLINES", 8) == 0
5835 		|| STRNCMP(key, "LINEBREAKS", 10) == 0)
5836 	{
5837 	    if (key[4] == 'S')
5838 		arg_end = key + 6;
5839 	    else if (key[0] == 'L')
5840 		arg_end = key + 11;
5841 	    else
5842 		arg_end = key + 9;
5843 	    if (arg_end[-1] != '=' || !VIM_ISDIGIT(*arg_end))
5844 	    {
5845 		illegal = TRUE;
5846 		break;
5847 	    }
5848 	    n = getdigits(&arg_end);
5849 	    if (!eap->skip)
5850 	    {
5851 		if (key[4] == 'B')
5852 		    curwin->w_s->b_syn_sync_linebreaks = n;
5853 		else if (key[1] == 'A')
5854 		    curwin->w_s->b_syn_sync_maxlines = n;
5855 		else
5856 		    curwin->w_s->b_syn_sync_minlines = n;
5857 	    }
5858 	}
5859 	else if (STRCMP(key, "FROMSTART") == 0)
5860 	{
5861 	    if (!eap->skip)
5862 	    {
5863 		curwin->w_s->b_syn_sync_minlines = MAXLNUM;
5864 		curwin->w_s->b_syn_sync_maxlines = 0;
5865 	    }
5866 	}
5867 	else if (STRCMP(key, "LINECONT") == 0)
5868 	{
5869 	    if (*next_arg == NUL)	   /* missing pattern */
5870 	    {
5871 		illegal = TRUE;
5872 		break;
5873 	    }
5874 	    if (curwin->w_s->b_syn_linecont_pat != NULL)
5875 	    {
5876 		emsg(_("E403: syntax sync: line continuations pattern specified twice"));
5877 		finished = TRUE;
5878 		break;
5879 	    }
5880 	    arg_end = skip_regexp(next_arg + 1, *next_arg, TRUE, NULL);
5881 	    if (*arg_end != *next_arg)	    /* end delimiter not found */
5882 	    {
5883 		illegal = TRUE;
5884 		break;
5885 	    }
5886 
5887 	    if (!eap->skip)
5888 	    {
5889 		/* store the pattern and compiled regexp program */
5890 		if ((curwin->w_s->b_syn_linecont_pat = vim_strnsave(next_arg + 1,
5891 				      (int)(arg_end - next_arg - 1))) == NULL)
5892 		{
5893 		    finished = TRUE;
5894 		    break;
5895 		}
5896 		curwin->w_s->b_syn_linecont_ic = curwin->w_s->b_syn_ic;
5897 
5898 		/* Make 'cpoptions' empty, to avoid the 'l' flag */
5899 		cpo_save = p_cpo;
5900 		p_cpo = (char_u *)"";
5901 		curwin->w_s->b_syn_linecont_prog =
5902 		       vim_regcomp(curwin->w_s->b_syn_linecont_pat, RE_MAGIC);
5903 		p_cpo = cpo_save;
5904 #ifdef FEAT_PROFILE
5905 		syn_clear_time(&curwin->w_s->b_syn_linecont_time);
5906 #endif
5907 
5908 		if (curwin->w_s->b_syn_linecont_prog == NULL)
5909 		{
5910 		    VIM_CLEAR(curwin->w_s->b_syn_linecont_pat);
5911 		    finished = TRUE;
5912 		    break;
5913 		}
5914 	    }
5915 	    next_arg = skipwhite(arg_end + 1);
5916 	}
5917 	else
5918 	{
5919 	    eap->arg = next_arg;
5920 	    if (STRCMP(key, "MATCH") == 0)
5921 		syn_cmd_match(eap, TRUE);
5922 	    else if (STRCMP(key, "REGION") == 0)
5923 		syn_cmd_region(eap, TRUE);
5924 	    else if (STRCMP(key, "CLEAR") == 0)
5925 		syn_cmd_clear(eap, TRUE);
5926 	    else
5927 		illegal = TRUE;
5928 	    finished = TRUE;
5929 	    break;
5930 	}
5931 	arg_start = next_arg;
5932     }
5933     vim_free(key);
5934     if (illegal)
5935 	semsg(_("E404: Illegal arguments: %s"), arg_start);
5936     else if (!finished)
5937     {
5938 	eap->nextcmd = check_nextcmd(arg_start);
5939 	redraw_curbuf_later(SOME_VALID);
5940 	syn_stack_free_all(curwin->w_s);	/* Need to recompute all syntax. */
5941     }
5942 }
5943 
5944 /*
5945  * Convert a line of highlight group names into a list of group ID numbers.
5946  * "arg" should point to the "contains" or "nextgroup" keyword.
5947  * "arg" is advanced to after the last group name.
5948  * Careful: the argument is modified (NULs added).
5949  * returns FAIL for some error, OK for success.
5950  */
5951     static int
5952 get_id_list(
5953     char_u	**arg,
5954     int		keylen,		/* length of keyword */
5955     short	**list,		/* where to store the resulting list, if not
5956 				   NULL, the list is silently skipped! */
5957     int		skip)
5958 {
5959     char_u	*p = NULL;
5960     char_u	*end;
5961     int		round;
5962     int		count;
5963     int		total_count = 0;
5964     short	*retval = NULL;
5965     char_u	*name;
5966     regmatch_T	regmatch;
5967     int		id;
5968     int		i;
5969     int		failed = FALSE;
5970 
5971     /*
5972      * We parse the list twice:
5973      * round == 1: count the number of items, allocate the array.
5974      * round == 2: fill the array with the items.
5975      * In round 1 new groups may be added, causing the number of items to
5976      * grow when a regexp is used.  In that case round 1 is done once again.
5977      */
5978     for (round = 1; round <= 2; ++round)
5979     {
5980 	/*
5981 	 * skip "contains"
5982 	 */
5983 	p = skipwhite(*arg + keylen);
5984 	if (*p != '=')
5985 	{
5986 	    semsg(_("E405: Missing equal sign: %s"), *arg);
5987 	    break;
5988 	}
5989 	p = skipwhite(p + 1);
5990 	if (ends_excmd(*p))
5991 	{
5992 	    semsg(_("E406: Empty argument: %s"), *arg);
5993 	    break;
5994 	}
5995 
5996 	/*
5997 	 * parse the arguments after "contains"
5998 	 */
5999 	count = 0;
6000 	while (!ends_excmd(*p))
6001 	{
6002 	    for (end = p; *end && !VIM_ISWHITE(*end) && *end != ','; ++end)
6003 		;
6004 	    name = alloc(end - p + 3);	    /* leave room for "^$" */
6005 	    if (name == NULL)
6006 	    {
6007 		failed = TRUE;
6008 		break;
6009 	    }
6010 	    vim_strncpy(name + 1, p, end - p);
6011 	    if (       STRCMP(name + 1, "ALLBUT") == 0
6012 		    || STRCMP(name + 1, "ALL") == 0
6013 		    || STRCMP(name + 1, "TOP") == 0
6014 		    || STRCMP(name + 1, "CONTAINED") == 0)
6015 	    {
6016 		if (TOUPPER_ASC(**arg) != 'C')
6017 		{
6018 		    semsg(_("E407: %s not allowed here"), name + 1);
6019 		    failed = TRUE;
6020 		    vim_free(name);
6021 		    break;
6022 		}
6023 		if (count != 0)
6024 		{
6025 		    semsg(_("E408: %s must be first in contains list"),
6026 								     name + 1);
6027 		    failed = TRUE;
6028 		    vim_free(name);
6029 		    break;
6030 		}
6031 		if (name[1] == 'A')
6032 		    id = SYNID_ALLBUT;
6033 		else if (name[1] == 'T')
6034 		    id = SYNID_TOP;
6035 		else
6036 		    id = SYNID_CONTAINED;
6037 		id += current_syn_inc_tag;
6038 	    }
6039 	    else if (name[1] == '@')
6040 	    {
6041 		if (skip)
6042 		    id = -1;
6043 		else
6044 		    id = syn_check_cluster(name + 2, (int)(end - p - 1));
6045 	    }
6046 	    else
6047 	    {
6048 		/*
6049 		 * Handle full group name.
6050 		 */
6051 		if (vim_strpbrk(name + 1, (char_u *)"\\.*^$~[") == NULL)
6052 		    id = syn_check_group(name + 1, (int)(end - p));
6053 		else
6054 		{
6055 		    /*
6056 		     * Handle match of regexp with group names.
6057 		     */
6058 		    *name = '^';
6059 		    STRCAT(name, "$");
6060 		    regmatch.regprog = vim_regcomp(name, RE_MAGIC);
6061 		    if (regmatch.regprog == NULL)
6062 		    {
6063 			failed = TRUE;
6064 			vim_free(name);
6065 			break;
6066 		    }
6067 
6068 		    regmatch.rm_ic = TRUE;
6069 		    id = 0;
6070 		    for (i = highlight_ga.ga_len; --i >= 0; )
6071 		    {
6072 			if (vim_regexec(&regmatch, HL_TABLE()[i].sg_name,
6073 								  (colnr_T)0))
6074 			{
6075 			    if (round == 2)
6076 			    {
6077 				/* Got more items than expected; can happen
6078 				 * when adding items that match:
6079 				 * "contains=a.*b,axb".
6080 				 * Go back to first round */
6081 				if (count >= total_count)
6082 				{
6083 				    vim_free(retval);
6084 				    round = 1;
6085 				}
6086 				else
6087 				    retval[count] = i + 1;
6088 			    }
6089 			    ++count;
6090 			    id = -1;	    /* remember that we found one */
6091 			}
6092 		    }
6093 		    vim_regfree(regmatch.regprog);
6094 		}
6095 	    }
6096 	    vim_free(name);
6097 	    if (id == 0)
6098 	    {
6099 		semsg(_("E409: Unknown group name: %s"), p);
6100 		failed = TRUE;
6101 		break;
6102 	    }
6103 	    if (id > 0)
6104 	    {
6105 		if (round == 2)
6106 		{
6107 		    /* Got more items than expected, go back to first round */
6108 		    if (count >= total_count)
6109 		    {
6110 			vim_free(retval);
6111 			round = 1;
6112 		    }
6113 		    else
6114 			retval[count] = id;
6115 		}
6116 		++count;
6117 	    }
6118 	    p = skipwhite(end);
6119 	    if (*p != ',')
6120 		break;
6121 	    p = skipwhite(p + 1);	/* skip comma in between arguments */
6122 	}
6123 	if (failed)
6124 	    break;
6125 	if (round == 1)
6126 	{
6127 	    retval = ALLOC_MULT(short, count + 1);
6128 	    if (retval == NULL)
6129 		break;
6130 	    retval[count] = 0;	    /* zero means end of the list */
6131 	    total_count = count;
6132 	}
6133     }
6134 
6135     *arg = p;
6136     if (failed || retval == NULL)
6137     {
6138 	vim_free(retval);
6139 	return FAIL;
6140     }
6141 
6142     if (*list == NULL)
6143 	*list = retval;
6144     else
6145 	vim_free(retval);	/* list already found, don't overwrite it */
6146 
6147     return OK;
6148 }
6149 
6150 /*
6151  * Make a copy of an ID list.
6152  */
6153     static short *
6154 copy_id_list(short *list)
6155 {
6156     int	    len;
6157     int	    count;
6158     short   *retval;
6159 
6160     if (list == NULL)
6161 	return NULL;
6162 
6163     for (count = 0; list[count]; ++count)
6164 	;
6165     len = (count + 1) * sizeof(short);
6166     retval = alloc(len);
6167     if (retval != NULL)
6168 	mch_memmove(retval, list, (size_t)len);
6169 
6170     return retval;
6171 }
6172 
6173 /*
6174  * Check if syntax group "ssp" is in the ID list "list" of "cur_si".
6175  * "cur_si" can be NULL if not checking the "containedin" list.
6176  * Used to check if a syntax item is in the "contains" or "nextgroup" list of
6177  * the current item.
6178  * This function is called very often, keep it fast!!
6179  */
6180     static int
6181 in_id_list(
6182     stateitem_T	*cur_si,	/* current item or NULL */
6183     short	*list,		/* id list */
6184     struct sp_syn *ssp,		/* group id and ":syn include" tag of group */
6185     int		contained)	/* group id is contained */
6186 {
6187     int		retval;
6188     short	*scl_list;
6189     short	item;
6190     short	id = ssp->id;
6191     static int	depth = 0;
6192     int		r;
6193 
6194     /* If ssp has a "containedin" list and "cur_si" is in it, return TRUE. */
6195     if (cur_si != NULL && ssp->cont_in_list != NULL
6196 					    && !(cur_si->si_flags & HL_MATCH))
6197     {
6198 	/* Ignore transparent items without a contains argument.  Double check
6199 	 * that we don't go back past the first one. */
6200 	while ((cur_si->si_flags & HL_TRANS_CONT)
6201 		&& cur_si > (stateitem_T *)(current_state.ga_data))
6202 	    --cur_si;
6203 	/* cur_si->si_idx is -1 for keywords, these never contain anything. */
6204 	if (cur_si->si_idx >= 0 && in_id_list(NULL, ssp->cont_in_list,
6205 		&(SYN_ITEMS(syn_block)[cur_si->si_idx].sp_syn),
6206 		  SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags & HL_CONTAINED))
6207 	    return TRUE;
6208     }
6209 
6210     if (list == NULL)
6211 	return FALSE;
6212 
6213     /*
6214      * If list is ID_LIST_ALL, we are in a transparent item that isn't
6215      * inside anything.  Only allow not-contained groups.
6216      */
6217     if (list == ID_LIST_ALL)
6218 	return !contained;
6219 
6220     /*
6221      * If the first item is "ALLBUT", return TRUE if "id" is NOT in the
6222      * contains list.  We also require that "id" is at the same ":syn include"
6223      * level as the list.
6224      */
6225     item = *list;
6226     if (item >= SYNID_ALLBUT && item < SYNID_CLUSTER)
6227     {
6228 	if (item < SYNID_TOP)
6229 	{
6230 	    /* ALL or ALLBUT: accept all groups in the same file */
6231 	    if (item - SYNID_ALLBUT != ssp->inc_tag)
6232 		return FALSE;
6233 	}
6234 	else if (item < SYNID_CONTAINED)
6235 	{
6236 	    /* TOP: accept all not-contained groups in the same file */
6237 	    if (item - SYNID_TOP != ssp->inc_tag || contained)
6238 		return FALSE;
6239 	}
6240 	else
6241 	{
6242 	    /* CONTAINED: accept all contained groups in the same file */
6243 	    if (item - SYNID_CONTAINED != ssp->inc_tag || !contained)
6244 		return FALSE;
6245 	}
6246 	item = *++list;
6247 	retval = FALSE;
6248     }
6249     else
6250 	retval = TRUE;
6251 
6252     /*
6253      * Return "retval" if id is in the contains list.
6254      */
6255     while (item != 0)
6256     {
6257 	if (item == id)
6258 	    return retval;
6259 	if (item >= SYNID_CLUSTER)
6260 	{
6261 	    scl_list = SYN_CLSTR(syn_block)[item - SYNID_CLUSTER].scl_list;
6262 	    /* restrict recursiveness to 30 to avoid an endless loop for a
6263 	     * cluster that includes itself (indirectly) */
6264 	    if (scl_list != NULL && depth < 30)
6265 	    {
6266 		++depth;
6267 		r = in_id_list(NULL, scl_list, ssp, contained);
6268 		--depth;
6269 		if (r)
6270 		    return retval;
6271 	    }
6272 	}
6273 	item = *++list;
6274     }
6275     return !retval;
6276 }
6277 
6278 struct subcommand
6279 {
6280     char    *name;			/* subcommand name */
6281     void    (*func)(exarg_T *, int);	/* function to call */
6282 };
6283 
6284 static struct subcommand subcommands[] =
6285 {
6286     {"case",		syn_cmd_case},
6287     {"clear",		syn_cmd_clear},
6288     {"cluster",		syn_cmd_cluster},
6289     {"conceal",		syn_cmd_conceal},
6290     {"enable",		syn_cmd_enable},
6291     {"include",		syn_cmd_include},
6292     {"iskeyword",	syn_cmd_iskeyword},
6293     {"keyword",		syn_cmd_keyword},
6294     {"list",		syn_cmd_list},
6295     {"manual",		syn_cmd_manual},
6296     {"match",		syn_cmd_match},
6297     {"on",		syn_cmd_on},
6298     {"off",		syn_cmd_off},
6299     {"region",		syn_cmd_region},
6300     {"reset",		syn_cmd_reset},
6301     {"spell",		syn_cmd_spell},
6302     {"sync",		syn_cmd_sync},
6303     {"",		syn_cmd_list},
6304     {NULL, NULL}
6305 };
6306 
6307 /*
6308  * ":syntax".
6309  * This searches the subcommands[] table for the subcommand name, and calls a
6310  * syntax_subcommand() function to do the rest.
6311  */
6312     void
6313 ex_syntax(exarg_T *eap)
6314 {
6315     char_u	*arg = eap->arg;
6316     char_u	*subcmd_end;
6317     char_u	*subcmd_name;
6318     int		i;
6319 
6320     syn_cmdlinep = eap->cmdlinep;
6321 
6322     /* isolate subcommand name */
6323     for (subcmd_end = arg; ASCII_ISALPHA(*subcmd_end); ++subcmd_end)
6324 	;
6325     subcmd_name = vim_strnsave(arg, (int)(subcmd_end - arg));
6326     if (subcmd_name != NULL)
6327     {
6328 	if (eap->skip)		/* skip error messages for all subcommands */
6329 	    ++emsg_skip;
6330 	for (i = 0; ; ++i)
6331 	{
6332 	    if (subcommands[i].name == NULL)
6333 	    {
6334 		semsg(_("E410: Invalid :syntax subcommand: %s"), subcmd_name);
6335 		break;
6336 	    }
6337 	    if (STRCMP(subcmd_name, (char_u *)subcommands[i].name) == 0)
6338 	    {
6339 		eap->arg = skipwhite(subcmd_end);
6340 		(subcommands[i].func)(eap, FALSE);
6341 		break;
6342 	    }
6343 	}
6344 	vim_free(subcmd_name);
6345 	if (eap->skip)
6346 	    --emsg_skip;
6347     }
6348 }
6349 
6350     void
6351 ex_ownsyntax(exarg_T *eap)
6352 {
6353     char_u	*old_value;
6354     char_u	*new_value;
6355 
6356     if (curwin->w_s == &curwin->w_buffer->b_s)
6357     {
6358 	curwin->w_s = ALLOC_ONE(synblock_T);
6359 	memset(curwin->w_s, 0, sizeof(synblock_T));
6360 	hash_init(&curwin->w_s->b_keywtab);
6361 	hash_init(&curwin->w_s->b_keywtab_ic);
6362 #ifdef FEAT_SPELL
6363 	/* TODO: keep the spell checking as it was. */
6364 	curwin->w_p_spell = FALSE;	/* No spell checking */
6365 	clear_string_option(&curwin->w_s->b_p_spc);
6366 	clear_string_option(&curwin->w_s->b_p_spf);
6367 	clear_string_option(&curwin->w_s->b_p_spl);
6368 #endif
6369 	clear_string_option(&curwin->w_s->b_syn_isk);
6370     }
6371 
6372     /* save value of b:current_syntax */
6373     old_value = get_var_value((char_u *)"b:current_syntax");
6374     if (old_value != NULL)
6375 	old_value = vim_strsave(old_value);
6376 
6377     /* Apply the "syntax" autocommand event, this finds and loads the syntax
6378      * file. */
6379     apply_autocmds(EVENT_SYNTAX, eap->arg, curbuf->b_fname, TRUE, curbuf);
6380 
6381     /* move value of b:current_syntax to w:current_syntax */
6382     new_value = get_var_value((char_u *)"b:current_syntax");
6383     if (new_value != NULL)
6384 	set_internal_string_var((char_u *)"w:current_syntax", new_value);
6385 
6386     /* restore value of b:current_syntax */
6387     if (old_value == NULL)
6388 	do_unlet((char_u *)"b:current_syntax", TRUE);
6389     else
6390     {
6391 	set_internal_string_var((char_u *)"b:current_syntax", old_value);
6392 	vim_free(old_value);
6393     }
6394 }
6395 
6396     int
6397 syntax_present(win_T *win)
6398 {
6399     return (win->w_s->b_syn_patterns.ga_len != 0
6400 	    || win->w_s->b_syn_clusters.ga_len != 0
6401 	    || win->w_s->b_keywtab.ht_used > 0
6402 	    || win->w_s->b_keywtab_ic.ht_used > 0);
6403 }
6404 
6405 #if defined(FEAT_CMDL_COMPL) || defined(PROTO)
6406 
6407 static enum
6408 {
6409     EXP_SUBCMD,	    /* expand ":syn" sub-commands */
6410     EXP_CASE,	    /* expand ":syn case" arguments */
6411     EXP_SPELL,	    /* expand ":syn spell" arguments */
6412     EXP_SYNC	    /* expand ":syn sync" arguments */
6413 } expand_what;
6414 
6415 /*
6416  * Reset include_link, include_default, include_none to 0.
6417  * Called when we are done expanding.
6418  */
6419     void
6420 reset_expand_highlight(void)
6421 {
6422     include_link = include_default = include_none = 0;
6423 }
6424 
6425 /*
6426  * Handle command line completion for :match and :echohl command: Add "None"
6427  * as highlight group.
6428  */
6429     void
6430 set_context_in_echohl_cmd(expand_T *xp, char_u *arg)
6431 {
6432     xp->xp_context = EXPAND_HIGHLIGHT;
6433     xp->xp_pattern = arg;
6434     include_none = 1;
6435 }
6436 
6437 /*
6438  * Handle command line completion for :syntax command.
6439  */
6440     void
6441 set_context_in_syntax_cmd(expand_T *xp, char_u *arg)
6442 {
6443     char_u	*p;
6444 
6445     /* Default: expand subcommands */
6446     xp->xp_context = EXPAND_SYNTAX;
6447     expand_what = EXP_SUBCMD;
6448     xp->xp_pattern = arg;
6449     include_link = 0;
6450     include_default = 0;
6451 
6452     /* (part of) subcommand already typed */
6453     if (*arg != NUL)
6454     {
6455 	p = skiptowhite(arg);
6456 	if (*p != NUL)		    /* past first word */
6457 	{
6458 	    xp->xp_pattern = skipwhite(p);
6459 	    if (*skiptowhite(xp->xp_pattern) != NUL)
6460 		xp->xp_context = EXPAND_NOTHING;
6461 	    else if (STRNICMP(arg, "case", p - arg) == 0)
6462 		expand_what = EXP_CASE;
6463 	    else if (STRNICMP(arg, "spell", p - arg) == 0)
6464 		expand_what = EXP_SPELL;
6465 	    else if (STRNICMP(arg, "sync", p - arg) == 0)
6466 		expand_what = EXP_SYNC;
6467 	    else if (  STRNICMP(arg, "keyword", p - arg) == 0
6468 		    || STRNICMP(arg, "region", p - arg) == 0
6469 		    || STRNICMP(arg, "match", p - arg) == 0
6470 		    || STRNICMP(arg, "list", p - arg) == 0)
6471 		xp->xp_context = EXPAND_HIGHLIGHT;
6472 	    else
6473 		xp->xp_context = EXPAND_NOTHING;
6474 	}
6475     }
6476 }
6477 
6478 /*
6479  * Function given to ExpandGeneric() to obtain the list syntax names for
6480  * expansion.
6481  */
6482     char_u *
6483 get_syntax_name(expand_T *xp UNUSED, int idx)
6484 {
6485     switch (expand_what)
6486     {
6487 	case EXP_SUBCMD:
6488 	    return (char_u *)subcommands[idx].name;
6489 	case EXP_CASE:
6490 	{
6491 	    static char *case_args[] = {"match", "ignore", NULL};
6492 	    return (char_u *)case_args[idx];
6493 	}
6494 	case EXP_SPELL:
6495 	{
6496 	    static char *spell_args[] =
6497 		{"toplevel", "notoplevel", "default", NULL};
6498 	    return (char_u *)spell_args[idx];
6499 	}
6500 	case EXP_SYNC:
6501 	{
6502 	    static char *sync_args[] =
6503 		{"ccomment", "clear", "fromstart",
6504 		 "linebreaks=", "linecont", "lines=", "match",
6505 		 "maxlines=", "minlines=", "region", NULL};
6506 	    return (char_u *)sync_args[idx];
6507 	}
6508     }
6509     return NULL;
6510 }
6511 
6512 #endif /* FEAT_CMDL_COMPL */
6513 
6514 /*
6515  * Function called for expression evaluation: get syntax ID at file position.
6516  */
6517     int
6518 syn_get_id(
6519     win_T	*wp,
6520     long	lnum,
6521     colnr_T	col,
6522     int		trans,	     /* remove transparency */
6523     int		*spellp,     /* return: can do spell checking */
6524     int		keep_state)  /* keep state of char at "col" */
6525 {
6526     /* When the position is not after the current position and in the same
6527      * line of the same buffer, need to restart parsing. */
6528     if (wp->w_buffer != syn_buf
6529 	    || lnum != current_lnum
6530 	    || col < current_col)
6531 	syntax_start(wp, lnum);
6532     else if (wp->w_buffer == syn_buf
6533 	    && lnum == current_lnum
6534 	    && col > current_col)
6535 	/* next_match may not be correct when moving around, e.g. with the
6536 	 * "skip" expression in searchpair() */
6537 	next_match_idx = -1;
6538 
6539     (void)get_syntax_attr(col, spellp, keep_state);
6540 
6541     return (trans ? current_trans_id : current_id);
6542 }
6543 
6544 #if defined(FEAT_CONCEAL) || defined(PROTO)
6545 /*
6546  * Get extra information about the syntax item.  Must be called right after
6547  * get_syntax_attr().
6548  * Stores the current item sequence nr in "*seqnrp".
6549  * Returns the current flags.
6550  */
6551     int
6552 get_syntax_info(int *seqnrp)
6553 {
6554     *seqnrp = current_seqnr;
6555     return current_flags;
6556 }
6557 
6558 /*
6559  * Return conceal substitution character
6560  */
6561     int
6562 syn_get_sub_char(void)
6563 {
6564     return current_sub_char;
6565 }
6566 #endif
6567 
6568 #if defined(FEAT_EVAL) || defined(PROTO)
6569 /*
6570  * Return the syntax ID at position "i" in the current stack.
6571  * The caller must have called syn_get_id() before to fill the stack.
6572  * Returns -1 when "i" is out of range.
6573  */
6574     int
6575 syn_get_stack_item(int i)
6576 {
6577     if (i >= current_state.ga_len)
6578     {
6579 	/* Need to invalidate the state, because we didn't properly finish it
6580 	 * for the last character, "keep_state" was TRUE. */
6581 	invalidate_current_state();
6582 	current_col = MAXCOL;
6583 	return -1;
6584     }
6585     return CUR_STATE(i).si_id;
6586 }
6587 #endif
6588 
6589 #if defined(FEAT_FOLDING) || defined(PROTO)
6590 /*
6591  * Function called to get folding level for line "lnum" in window "wp".
6592  */
6593     int
6594 syn_get_foldlevel(win_T *wp, long lnum)
6595 {
6596     int		level = 0;
6597     int		i;
6598 
6599     /* Return quickly when there are no fold items at all. */
6600     if (wp->w_s->b_syn_folditems != 0
6601 	    && !wp->w_s->b_syn_error
6602 # ifdef SYN_TIME_LIMIT
6603 	    && !wp->w_s->b_syn_slow
6604 # endif
6605 	    )
6606     {
6607 	syntax_start(wp, lnum);
6608 
6609 	for (i = 0; i < current_state.ga_len; ++i)
6610 	    if (CUR_STATE(i).si_flags & HL_FOLD)
6611 		++level;
6612     }
6613     if (level > wp->w_p_fdn)
6614     {
6615 	level = wp->w_p_fdn;
6616 	if (level < 0)
6617 	    level = 0;
6618     }
6619     return level;
6620 }
6621 #endif
6622 
6623 #if defined(FEAT_PROFILE) || defined(PROTO)
6624 /*
6625  * ":syntime".
6626  */
6627     void
6628 ex_syntime(exarg_T *eap)
6629 {
6630     if (STRCMP(eap->arg, "on") == 0)
6631 	syn_time_on = TRUE;
6632     else if (STRCMP(eap->arg, "off") == 0)
6633 	syn_time_on = FALSE;
6634     else if (STRCMP(eap->arg, "clear") == 0)
6635 	syntime_clear();
6636     else if (STRCMP(eap->arg, "report") == 0)
6637 	syntime_report();
6638     else
6639 	semsg(_(e_invarg2), eap->arg);
6640 }
6641 
6642     static void
6643 syn_clear_time(syn_time_T *st)
6644 {
6645     profile_zero(&st->total);
6646     profile_zero(&st->slowest);
6647     st->count = 0;
6648     st->match = 0;
6649 }
6650 
6651 /*
6652  * Clear the syntax timing for the current buffer.
6653  */
6654     static void
6655 syntime_clear(void)
6656 {
6657     int		idx;
6658     synpat_T	*spp;
6659 
6660     if (!syntax_present(curwin))
6661     {
6662 	msg(_(msg_no_items));
6663 	return;
6664     }
6665     for (idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx)
6666     {
6667 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
6668 	syn_clear_time(&spp->sp_time);
6669     }
6670 }
6671 
6672 #if defined(FEAT_CMDL_COMPL) || defined(PROTO)
6673 /*
6674  * Function given to ExpandGeneric() to obtain the possible arguments of the
6675  * ":syntime {on,off,clear,report}" command.
6676  */
6677     char_u *
6678 get_syntime_arg(expand_T *xp UNUSED, int idx)
6679 {
6680     switch (idx)
6681     {
6682 	case 0: return (char_u *)"on";
6683 	case 1: return (char_u *)"off";
6684 	case 2: return (char_u *)"clear";
6685 	case 3: return (char_u *)"report";
6686     }
6687     return NULL;
6688 }
6689 #endif
6690 
6691 typedef struct
6692 {
6693     proftime_T	total;
6694     int		count;
6695     int		match;
6696     proftime_T	slowest;
6697     proftime_T	average;
6698     int		id;
6699     char_u	*pattern;
6700 } time_entry_T;
6701 
6702     static int
6703 syn_compare_syntime(const void *v1, const void *v2)
6704 {
6705     const time_entry_T	*s1 = v1;
6706     const time_entry_T	*s2 = v2;
6707 
6708     return profile_cmp(&s1->total, &s2->total);
6709 }
6710 
6711 /*
6712  * Clear the syntax timing for the current buffer.
6713  */
6714     static void
6715 syntime_report(void)
6716 {
6717     int		idx;
6718     synpat_T	*spp;
6719 # ifdef FEAT_FLOAT
6720     proftime_T	tm;
6721 # endif
6722     int		len;
6723     proftime_T	total_total;
6724     int		total_count = 0;
6725     garray_T    ga;
6726     time_entry_T *p;
6727 
6728     if (!syntax_present(curwin))
6729     {
6730 	msg(_(msg_no_items));
6731 	return;
6732     }
6733 
6734     ga_init2(&ga, sizeof(time_entry_T), 50);
6735     profile_zero(&total_total);
6736     for (idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx)
6737     {
6738 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
6739 	if (spp->sp_time.count > 0)
6740 	{
6741 	    (void)ga_grow(&ga, 1);
6742 	    p = ((time_entry_T *)ga.ga_data) + ga.ga_len;
6743 	    p->total = spp->sp_time.total;
6744 	    profile_add(&total_total, &spp->sp_time.total);
6745 	    p->count = spp->sp_time.count;
6746 	    p->match = spp->sp_time.match;
6747 	    total_count += spp->sp_time.count;
6748 	    p->slowest = spp->sp_time.slowest;
6749 # ifdef FEAT_FLOAT
6750 	    profile_divide(&spp->sp_time.total, spp->sp_time.count, &tm);
6751 	    p->average = tm;
6752 # endif
6753 	    p->id = spp->sp_syn.id;
6754 	    p->pattern = spp->sp_pattern;
6755 	    ++ga.ga_len;
6756 	}
6757     }
6758 
6759     /* Sort on total time. Skip if there are no items to avoid passing NULL
6760      * pointer to qsort(). */
6761     if (ga.ga_len > 1)
6762 	qsort(ga.ga_data, (size_t)ga.ga_len, sizeof(time_entry_T),
6763 							 syn_compare_syntime);
6764 
6765     msg_puts_title(_("  TOTAL      COUNT  MATCH   SLOWEST     AVERAGE   NAME               PATTERN"));
6766     msg_puts("\n");
6767     for (idx = 0; idx < ga.ga_len && !got_int; ++idx)
6768     {
6769 	p = ((time_entry_T *)ga.ga_data) + idx;
6770 
6771 	msg_puts(profile_msg(&p->total));
6772 	msg_puts(" "); /* make sure there is always a separating space */
6773 	msg_advance(13);
6774 	msg_outnum(p->count);
6775 	msg_puts(" ");
6776 	msg_advance(20);
6777 	msg_outnum(p->match);
6778 	msg_puts(" ");
6779 	msg_advance(26);
6780 	msg_puts(profile_msg(&p->slowest));
6781 	msg_puts(" ");
6782 	msg_advance(38);
6783 # ifdef FEAT_FLOAT
6784 	msg_puts(profile_msg(&p->average));
6785 	msg_puts(" ");
6786 # endif
6787 	msg_advance(50);
6788 	msg_outtrans(HL_TABLE()[p->id - 1].sg_name);
6789 	msg_puts(" ");
6790 
6791 	msg_advance(69);
6792 	if (Columns < 80)
6793 	    len = 20; /* will wrap anyway */
6794 	else
6795 	    len = Columns - 70;
6796 	if (len > (int)STRLEN(p->pattern))
6797 	    len = (int)STRLEN(p->pattern);
6798 	msg_outtrans_len(p->pattern, len);
6799 	msg_puts("\n");
6800     }
6801     ga_clear(&ga);
6802     if (!got_int)
6803     {
6804 	msg_puts("\n");
6805 	msg_puts(profile_msg(&total_total));
6806 	msg_advance(13);
6807 	msg_outnum(total_count);
6808 	msg_puts("\n");
6809     }
6810 }
6811 #endif
6812 
6813 #endif /* FEAT_SYN_HL */
6814 
6815 /**************************************
6816  *  Highlighting stuff		      *
6817  **************************************/
6818 
6819 /*
6820  * The default highlight groups.  These are compiled-in for fast startup and
6821  * they still work when the runtime files can't be found.
6822  * When making changes here, also change runtime/colors/default.vim!
6823  * The #ifdefs are needed to reduce the amount of static data.  Helps to make
6824  * the 16 bit DOS (museum) version compile.
6825  */
6826 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
6827 # define CENT(a, b) b
6828 #else
6829 # define CENT(a, b) a
6830 #endif
6831 static char *(highlight_init_both[]) = {
6832     CENT("ErrorMsg term=standout ctermbg=DarkRed ctermfg=White",
6833 	 "ErrorMsg term=standout ctermbg=DarkRed ctermfg=White guibg=Red guifg=White"),
6834     CENT("IncSearch term=reverse cterm=reverse",
6835 	 "IncSearch term=reverse cterm=reverse gui=reverse"),
6836     CENT("ModeMsg term=bold cterm=bold",
6837 	 "ModeMsg term=bold cterm=bold gui=bold"),
6838     CENT("NonText term=bold ctermfg=Blue",
6839 	 "NonText term=bold ctermfg=Blue gui=bold guifg=Blue"),
6840     CENT("StatusLine term=reverse,bold cterm=reverse,bold",
6841 	 "StatusLine term=reverse,bold cterm=reverse,bold gui=reverse,bold"),
6842     CENT("StatusLineNC term=reverse cterm=reverse",
6843 	 "StatusLineNC term=reverse cterm=reverse gui=reverse"),
6844     "default link EndOfBuffer NonText",
6845     CENT("VertSplit term=reverse cterm=reverse",
6846 	 "VertSplit term=reverse cterm=reverse gui=reverse"),
6847 #ifdef FEAT_CLIPBOARD
6848     CENT("VisualNOS term=underline,bold cterm=underline,bold",
6849 	 "VisualNOS term=underline,bold cterm=underline,bold gui=underline,bold"),
6850 #endif
6851 #ifdef FEAT_DIFF
6852     CENT("DiffText term=reverse cterm=bold ctermbg=Red",
6853 	 "DiffText term=reverse cterm=bold ctermbg=Red gui=bold guibg=Red"),
6854 #endif
6855 #ifdef FEAT_INS_EXPAND
6856     CENT("PmenuSbar ctermbg=Grey",
6857 	 "PmenuSbar ctermbg=Grey guibg=Grey"),
6858 #endif
6859     CENT("TabLineSel term=bold cterm=bold",
6860 	 "TabLineSel term=bold cterm=bold gui=bold"),
6861     CENT("TabLineFill term=reverse cterm=reverse",
6862 	 "TabLineFill term=reverse cterm=reverse gui=reverse"),
6863 #ifdef FEAT_GUI
6864     "Cursor guibg=fg guifg=bg",
6865     "lCursor guibg=fg guifg=bg", /* should be different, but what? */
6866 #endif
6867     "default link QuickFixLine Search",
6868     CENT("Normal cterm=NONE", "Normal gui=NONE"),
6869     NULL
6870 };
6871 
6872 /* Default colors only used with a light background. */
6873 static char *(highlight_init_light[]) = {
6874     CENT("Directory term=bold ctermfg=DarkBlue",
6875 	 "Directory term=bold ctermfg=DarkBlue guifg=Blue"),
6876     CENT("LineNr term=underline ctermfg=Brown",
6877 	 "LineNr term=underline ctermfg=Brown guifg=Brown"),
6878     CENT("CursorLineNr term=bold ctermfg=Brown",
6879 	 "CursorLineNr term=bold ctermfg=Brown gui=bold guifg=Brown"),
6880     CENT("MoreMsg term=bold ctermfg=DarkGreen",
6881 	 "MoreMsg term=bold ctermfg=DarkGreen gui=bold guifg=SeaGreen"),
6882     CENT("Question term=standout ctermfg=DarkGreen",
6883 	 "Question term=standout ctermfg=DarkGreen gui=bold guifg=SeaGreen"),
6884     CENT("Search term=reverse ctermbg=Yellow ctermfg=NONE",
6885 	 "Search term=reverse ctermbg=Yellow ctermfg=NONE guibg=Yellow guifg=NONE"),
6886 #ifdef FEAT_SPELL
6887     CENT("SpellBad term=reverse ctermbg=LightRed",
6888 	 "SpellBad term=reverse ctermbg=LightRed guisp=Red gui=undercurl"),
6889     CENT("SpellCap term=reverse ctermbg=LightBlue",
6890 	 "SpellCap term=reverse ctermbg=LightBlue guisp=Blue gui=undercurl"),
6891     CENT("SpellRare term=reverse ctermbg=LightMagenta",
6892 	 "SpellRare term=reverse ctermbg=LightMagenta guisp=Magenta gui=undercurl"),
6893     CENT("SpellLocal term=underline ctermbg=Cyan",
6894 	 "SpellLocal term=underline ctermbg=Cyan guisp=DarkCyan gui=undercurl"),
6895 #endif
6896 #ifdef FEAT_INS_EXPAND
6897     CENT("PmenuThumb ctermbg=Black",
6898 	 "PmenuThumb ctermbg=Black guibg=Black"),
6899     CENT("Pmenu ctermbg=LightMagenta ctermfg=Black",
6900 	 "Pmenu ctermbg=LightMagenta ctermfg=Black guibg=LightMagenta"),
6901     CENT("PmenuSel ctermbg=LightGrey ctermfg=Black",
6902 	 "PmenuSel ctermbg=LightGrey ctermfg=Black guibg=Grey"),
6903 #endif
6904     CENT("SpecialKey term=bold ctermfg=DarkBlue",
6905 	 "SpecialKey term=bold ctermfg=DarkBlue guifg=Blue"),
6906     CENT("Title term=bold ctermfg=DarkMagenta",
6907 	 "Title term=bold ctermfg=DarkMagenta gui=bold guifg=Magenta"),
6908     CENT("WarningMsg term=standout ctermfg=DarkRed",
6909 	 "WarningMsg term=standout ctermfg=DarkRed guifg=Red"),
6910 #ifdef FEAT_WILDMENU
6911     CENT("WildMenu term=standout ctermbg=Yellow ctermfg=Black",
6912 	 "WildMenu term=standout ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black"),
6913 #endif
6914 #ifdef FEAT_FOLDING
6915     CENT("Folded term=standout ctermbg=Grey ctermfg=DarkBlue",
6916 	 "Folded term=standout ctermbg=Grey ctermfg=DarkBlue guibg=LightGrey guifg=DarkBlue"),
6917     CENT("FoldColumn term=standout ctermbg=Grey ctermfg=DarkBlue",
6918 	 "FoldColumn term=standout ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue"),
6919 #endif
6920 #ifdef FEAT_SIGNS
6921     CENT("SignColumn term=standout ctermbg=Grey ctermfg=DarkBlue",
6922 	 "SignColumn term=standout ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue"),
6923 #endif
6924     CENT("Visual term=reverse",
6925 	 "Visual term=reverse guibg=LightGrey"),
6926 #ifdef FEAT_DIFF
6927     CENT("DiffAdd term=bold ctermbg=LightBlue",
6928 	 "DiffAdd term=bold ctermbg=LightBlue guibg=LightBlue"),
6929     CENT("DiffChange term=bold ctermbg=LightMagenta",
6930 	 "DiffChange term=bold ctermbg=LightMagenta guibg=LightMagenta"),
6931     CENT("DiffDelete term=bold ctermfg=Blue ctermbg=LightCyan",
6932 	 "DiffDelete term=bold ctermfg=Blue ctermbg=LightCyan gui=bold guifg=Blue guibg=LightCyan"),
6933 #endif
6934     CENT("TabLine term=underline cterm=underline ctermfg=black ctermbg=LightGrey",
6935 	 "TabLine term=underline cterm=underline ctermfg=black ctermbg=LightGrey gui=underline guibg=LightGrey"),
6936 #ifdef FEAT_SYN_HL
6937     CENT("CursorColumn term=reverse ctermbg=LightGrey",
6938 	 "CursorColumn term=reverse ctermbg=LightGrey guibg=Grey90"),
6939     CENT("CursorLine term=underline cterm=underline",
6940 	 "CursorLine term=underline cterm=underline guibg=Grey90"),
6941     CENT("ColorColumn term=reverse ctermbg=LightRed",
6942 	 "ColorColumn term=reverse ctermbg=LightRed guibg=LightRed"),
6943 #endif
6944 #ifdef FEAT_CONCEAL
6945     CENT("Conceal ctermbg=DarkGrey ctermfg=LightGrey",
6946 	 "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey"),
6947 #endif
6948     CENT("MatchParen term=reverse ctermbg=Cyan",
6949 	 "MatchParen term=reverse ctermbg=Cyan guibg=Cyan"),
6950 #ifdef FEAT_TERMINAL
6951     CENT("StatusLineTerm term=reverse,bold cterm=bold ctermfg=White ctermbg=DarkGreen",
6952 	 "StatusLineTerm term=reverse,bold cterm=bold ctermfg=White ctermbg=DarkGreen gui=bold guifg=bg guibg=DarkGreen"),
6953     CENT("StatusLineTermNC term=reverse ctermfg=White ctermbg=DarkGreen",
6954 	 "StatusLineTermNC term=reverse ctermfg=White ctermbg=DarkGreen guifg=bg guibg=DarkGreen"),
6955 #endif
6956 #ifdef FEAT_MENU
6957     CENT("ToolbarLine term=underline ctermbg=LightGrey",
6958 	 "ToolbarLine term=underline ctermbg=LightGrey guibg=LightGrey"),
6959     CENT("ToolbarButton cterm=bold ctermfg=White ctermbg=DarkGrey",
6960 	 "ToolbarButton cterm=bold ctermfg=White ctermbg=DarkGrey gui=bold guifg=White guibg=Grey40"),
6961 #endif
6962     NULL
6963 };
6964 
6965 /* Default colors only used with a dark background. */
6966 static char *(highlight_init_dark[]) = {
6967     CENT("Directory term=bold ctermfg=LightCyan",
6968 	 "Directory term=bold ctermfg=LightCyan guifg=Cyan"),
6969     CENT("LineNr term=underline ctermfg=Yellow",
6970 	 "LineNr term=underline ctermfg=Yellow guifg=Yellow"),
6971     CENT("CursorLineNr term=bold ctermfg=Yellow",
6972 	 "CursorLineNr term=bold ctermfg=Yellow gui=bold guifg=Yellow"),
6973     CENT("MoreMsg term=bold ctermfg=LightGreen",
6974 	 "MoreMsg term=bold ctermfg=LightGreen gui=bold guifg=SeaGreen"),
6975     CENT("Question term=standout ctermfg=LightGreen",
6976 	 "Question term=standout ctermfg=LightGreen gui=bold guifg=Green"),
6977     CENT("Search term=reverse ctermbg=Yellow ctermfg=Black",
6978 	 "Search term=reverse ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black"),
6979     CENT("SpecialKey term=bold ctermfg=LightBlue",
6980 	 "SpecialKey term=bold ctermfg=LightBlue guifg=Cyan"),
6981 #ifdef FEAT_SPELL
6982     CENT("SpellBad term=reverse ctermbg=Red",
6983 	 "SpellBad term=reverse ctermbg=Red guisp=Red gui=undercurl"),
6984     CENT("SpellCap term=reverse ctermbg=Blue",
6985 	 "SpellCap term=reverse ctermbg=Blue guisp=Blue gui=undercurl"),
6986     CENT("SpellRare term=reverse ctermbg=Magenta",
6987 	 "SpellRare term=reverse ctermbg=Magenta guisp=Magenta gui=undercurl"),
6988     CENT("SpellLocal term=underline ctermbg=Cyan",
6989 	 "SpellLocal term=underline ctermbg=Cyan guisp=Cyan gui=undercurl"),
6990 #endif
6991 #ifdef FEAT_INS_EXPAND
6992     CENT("PmenuThumb ctermbg=White",
6993 	 "PmenuThumb ctermbg=White guibg=White"),
6994     CENT("Pmenu ctermbg=Magenta ctermfg=Black",
6995 	 "Pmenu ctermbg=Magenta ctermfg=Black guibg=Magenta"),
6996     CENT("PmenuSel ctermbg=Black ctermfg=DarkGrey",
6997 	 "PmenuSel ctermbg=Black ctermfg=DarkGrey guibg=DarkGrey"),
6998 #endif
6999     CENT("Title term=bold ctermfg=LightMagenta",
7000 	 "Title term=bold ctermfg=LightMagenta gui=bold guifg=Magenta"),
7001     CENT("WarningMsg term=standout ctermfg=LightRed",
7002 	 "WarningMsg term=standout ctermfg=LightRed guifg=Red"),
7003 #ifdef FEAT_WILDMENU
7004     CENT("WildMenu term=standout ctermbg=Yellow ctermfg=Black",
7005 	 "WildMenu term=standout ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black"),
7006 #endif
7007 #ifdef FEAT_FOLDING
7008     CENT("Folded term=standout ctermbg=DarkGrey ctermfg=Cyan",
7009 	 "Folded term=standout ctermbg=DarkGrey ctermfg=Cyan guibg=DarkGrey guifg=Cyan"),
7010     CENT("FoldColumn term=standout ctermbg=DarkGrey ctermfg=Cyan",
7011 	 "FoldColumn term=standout ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan"),
7012 #endif
7013 #ifdef FEAT_SIGNS
7014     CENT("SignColumn term=standout ctermbg=DarkGrey ctermfg=Cyan",
7015 	 "SignColumn term=standout ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan"),
7016 #endif
7017     CENT("Visual term=reverse",
7018 	 "Visual term=reverse guibg=DarkGrey"),
7019 #ifdef FEAT_DIFF
7020     CENT("DiffAdd term=bold ctermbg=DarkBlue",
7021 	 "DiffAdd term=bold ctermbg=DarkBlue guibg=DarkBlue"),
7022     CENT("DiffChange term=bold ctermbg=DarkMagenta",
7023 	 "DiffChange term=bold ctermbg=DarkMagenta guibg=DarkMagenta"),
7024     CENT("DiffDelete term=bold ctermfg=Blue ctermbg=DarkCyan",
7025 	 "DiffDelete term=bold ctermfg=Blue ctermbg=DarkCyan gui=bold guifg=Blue guibg=DarkCyan"),
7026 #endif
7027     CENT("TabLine term=underline cterm=underline ctermfg=white ctermbg=DarkGrey",
7028 	 "TabLine term=underline cterm=underline ctermfg=white ctermbg=DarkGrey gui=underline guibg=DarkGrey"),
7029 #ifdef FEAT_SYN_HL
7030     CENT("CursorColumn term=reverse ctermbg=DarkGrey",
7031 	 "CursorColumn term=reverse ctermbg=DarkGrey guibg=Grey40"),
7032     CENT("CursorLine term=underline cterm=underline",
7033 	 "CursorLine term=underline cterm=underline guibg=Grey40"),
7034     CENT("ColorColumn term=reverse ctermbg=DarkRed",
7035 	 "ColorColumn term=reverse ctermbg=DarkRed guibg=DarkRed"),
7036 #endif
7037     CENT("MatchParen term=reverse ctermbg=DarkCyan",
7038 	 "MatchParen term=reverse ctermbg=DarkCyan guibg=DarkCyan"),
7039 #ifdef FEAT_CONCEAL
7040     CENT("Conceal ctermbg=DarkGrey ctermfg=LightGrey",
7041 	 "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey"),
7042 #endif
7043 #ifdef FEAT_TERMINAL
7044     CENT("StatusLineTerm term=reverse,bold cterm=bold ctermfg=Black ctermbg=LightGreen",
7045 	 "StatusLineTerm term=reverse,bold cterm=bold ctermfg=Black ctermbg=LightGreen gui=bold guifg=bg guibg=LightGreen"),
7046     CENT("StatusLineTermNC term=reverse ctermfg=Black ctermbg=LightGreen",
7047 	 "StatusLineTermNC term=reverse ctermfg=Black ctermbg=LightGreen guifg=bg guibg=LightGreen"),
7048 #endif
7049 #ifdef FEAT_MENU
7050     CENT("ToolbarLine term=underline ctermbg=DarkGrey",
7051 	 "ToolbarLine term=underline ctermbg=DarkGrey guibg=Grey50"),
7052     CENT("ToolbarButton cterm=bold ctermfg=Black ctermbg=LightGrey",
7053 	 "ToolbarButton cterm=bold ctermfg=Black ctermbg=LightGrey gui=bold guifg=Black guibg=LightGrey"),
7054 #endif
7055     NULL
7056 };
7057 
7058     void
7059 init_highlight(
7060     int		both,	    /* include groups where 'bg' doesn't matter */
7061     int		reset)	    /* clear group first */
7062 {
7063     int		i;
7064     char	**pp;
7065     static int	had_both = FALSE;
7066 #ifdef FEAT_EVAL
7067     char_u	*p;
7068 
7069     /*
7070      * Try finding the color scheme file.  Used when a color file was loaded
7071      * and 'background' or 't_Co' is changed.
7072      */
7073     p = get_var_value((char_u *)"g:colors_name");
7074     if (p != NULL)
7075     {
7076        /* The value of g:colors_name could be freed when sourcing the script,
7077 	* making "p" invalid, so copy it. */
7078        char_u *copy_p = vim_strsave(p);
7079        int    r;
7080 
7081        if (copy_p != NULL)
7082        {
7083 	   r = load_colors(copy_p);
7084 	   vim_free(copy_p);
7085 	   if (r == OK)
7086 	       return;
7087        }
7088     }
7089 
7090 #endif
7091 
7092     /*
7093      * Didn't use a color file, use the compiled-in colors.
7094      */
7095     if (both)
7096     {
7097 	had_both = TRUE;
7098 	pp = highlight_init_both;
7099 	for (i = 0; pp[i] != NULL; ++i)
7100 	    do_highlight((char_u *)pp[i], reset, TRUE);
7101     }
7102     else if (!had_both)
7103 	/* Don't do anything before the call with both == TRUE from main().
7104 	 * Not everything has been setup then, and that call will overrule
7105 	 * everything anyway. */
7106 	return;
7107 
7108     if (*p_bg == 'l')
7109 	pp = highlight_init_light;
7110     else
7111 	pp = highlight_init_dark;
7112     for (i = 0; pp[i] != NULL; ++i)
7113 	do_highlight((char_u *)pp[i], reset, TRUE);
7114 
7115     /* Reverse looks ugly, but grey may not work for 8 colors.  Thus let it
7116      * depend on the number of colors available.
7117      * With 8 colors brown is equal to yellow, need to use black for Search fg
7118      * to avoid Statement highlighted text disappears.
7119      * Clear the attributes, needed when changing the t_Co value. */
7120     if (t_colors > 8)
7121 	do_highlight((char_u *)(*p_bg == 'l'
7122 		    ? "Visual cterm=NONE ctermbg=LightGrey"
7123 		    : "Visual cterm=NONE ctermbg=DarkGrey"), FALSE, TRUE);
7124     else
7125     {
7126 	do_highlight((char_u *)"Visual cterm=reverse ctermbg=NONE",
7127 								 FALSE, TRUE);
7128 	if (*p_bg == 'l')
7129 	    do_highlight((char_u *)"Search ctermfg=black", FALSE, TRUE);
7130     }
7131 
7132 #ifdef FEAT_SYN_HL
7133     /*
7134      * If syntax highlighting is enabled load the highlighting for it.
7135      */
7136     if (get_var_value((char_u *)"g:syntax_on") != NULL)
7137     {
7138 	static int	recursive = 0;
7139 
7140 	if (recursive >= 5)
7141 	    emsg(_("E679: recursive loop loading syncolor.vim"));
7142 	else
7143 	{
7144 	    ++recursive;
7145 	    (void)source_runtime((char_u *)"syntax/syncolor.vim", DIP_ALL);
7146 	    --recursive;
7147 	}
7148     }
7149 #endif
7150 }
7151 
7152 /*
7153  * Load color file "name".
7154  * Return OK for success, FAIL for failure.
7155  */
7156     int
7157 load_colors(char_u *name)
7158 {
7159     char_u	*buf;
7160     int		retval = FAIL;
7161     static int	recursive = FALSE;
7162 
7163     /* When being called recursively, this is probably because setting
7164      * 'background' caused the highlighting to be reloaded.  This means it is
7165      * working, thus we should return OK. */
7166     if (recursive)
7167 	return OK;
7168 
7169     recursive = TRUE;
7170     buf = alloc(STRLEN(name) + 12);
7171     if (buf != NULL)
7172     {
7173 	apply_autocmds(EVENT_COLORSCHEMEPRE, name,
7174 					       curbuf->b_fname, FALSE, curbuf);
7175 	sprintf((char *)buf, "colors/%s.vim", name);
7176 	retval = source_runtime(buf, DIP_START + DIP_OPT);
7177 	vim_free(buf);
7178 	apply_autocmds(EVENT_COLORSCHEME, name, curbuf->b_fname, FALSE, curbuf);
7179     }
7180     recursive = FALSE;
7181 
7182     return retval;
7183 }
7184 
7185 static char *(color_names[28]) = {
7186 	    "Black", "DarkBlue", "DarkGreen", "DarkCyan",
7187 	    "DarkRed", "DarkMagenta", "Brown", "DarkYellow",
7188 	    "Gray", "Grey", "LightGray", "LightGrey",
7189 	    "DarkGray", "DarkGrey",
7190 	    "Blue", "LightBlue", "Green", "LightGreen",
7191 	    "Cyan", "LightCyan", "Red", "LightRed", "Magenta",
7192 	    "LightMagenta", "Yellow", "LightYellow", "White", "NONE"};
7193 	    /* indices:
7194 	     * 0, 1, 2, 3,
7195 	     * 4, 5, 6, 7,
7196 	     * 8, 9, 10, 11,
7197 	     * 12, 13,
7198 	     * 14, 15, 16, 17,
7199 	     * 18, 19, 20, 21, 22,
7200 	     * 23, 24, 25, 26, 27 */
7201 static int color_numbers_16[28] = {0, 1, 2, 3,
7202 				 4, 5, 6, 6,
7203 				 7, 7, 7, 7,
7204 				 8, 8,
7205 				 9, 9, 10, 10,
7206 				 11, 11, 12, 12, 13,
7207 				 13, 14, 14, 15, -1};
7208 /* for xterm with 88 colors... */
7209 static int color_numbers_88[28] = {0, 4, 2, 6,
7210 				 1, 5, 32, 72,
7211 				 84, 84, 7, 7,
7212 				 82, 82,
7213 				 12, 43, 10, 61,
7214 				 14, 63, 9, 74, 13,
7215 				 75, 11, 78, 15, -1};
7216 /* for xterm with 256 colors... */
7217 static int color_numbers_256[28] = {0, 4, 2, 6,
7218 				 1, 5, 130, 130,
7219 				 248, 248, 7, 7,
7220 				 242, 242,
7221 				 12, 81, 10, 121,
7222 				 14, 159, 9, 224, 13,
7223 				 225, 11, 229, 15, -1};
7224 /* for terminals with less than 16 colors... */
7225 static int color_numbers_8[28] = {0, 4, 2, 6,
7226 				 1, 5, 3, 3,
7227 				 7, 7, 7, 7,
7228 				 0+8, 0+8,
7229 				 4+8, 4+8, 2+8, 2+8,
7230 				 6+8, 6+8, 1+8, 1+8, 5+8,
7231 				 5+8, 3+8, 3+8, 7+8, -1};
7232 
7233 /*
7234  * Lookup the "cterm" value to be used for color with index "idx" in
7235  * color_names[].
7236  * "boldp" will be set to TRUE or FALSE for a foreground color when using 8
7237  * colors, otherwise it will be unchanged.
7238  */
7239     int
7240 lookup_color(int idx, int foreground, int *boldp)
7241 {
7242     int		color = color_numbers_16[idx];
7243     char_u	*p;
7244 
7245     /* Use the _16 table to check if it's a valid color name. */
7246     if (color < 0)
7247 	return -1;
7248 
7249     if (t_colors == 8)
7250     {
7251 	/* t_Co is 8: use the 8 colors table */
7252 #if defined(__QNXNTO__)
7253 	color = color_numbers_8_qansi[idx];
7254 #else
7255 	color = color_numbers_8[idx];
7256 #endif
7257 	if (foreground)
7258 	{
7259 	    /* set/reset bold attribute to get light foreground
7260 	     * colors (on some terminals, e.g. "linux") */
7261 	    if (color & 8)
7262 		*boldp = TRUE;
7263 	    else
7264 		*boldp = FALSE;
7265 	}
7266 	color &= 7;	/* truncate to 8 colors */
7267     }
7268     else if (t_colors == 16 || t_colors == 88
7269 					   || t_colors >= 256)
7270     {
7271 	/*
7272 	 * Guess: if the termcap entry ends in 'm', it is
7273 	 * probably an xterm-like terminal.  Use the changed
7274 	 * order for colors.
7275 	 */
7276 	if (*T_CAF != NUL)
7277 	    p = T_CAF;
7278 	else
7279 	    p = T_CSF;
7280 	if (*p != NUL && (t_colors > 256
7281 			      || *(p + STRLEN(p) - 1) == 'm'))
7282 	{
7283 	    if (t_colors == 88)
7284 		color = color_numbers_88[idx];
7285 	    else if (t_colors >= 256)
7286 		color = color_numbers_256[idx];
7287 	    else
7288 		color = color_numbers_8[idx];
7289 	}
7290 #ifdef FEAT_TERMRESPONSE
7291 	if (t_colors >= 256 && color == 15 && is_mac_terminal)
7292 	    /* Terminal.app has a bug: 15 is light grey. Use white
7293 	     * from the color cube instead. */
7294 	    color = 231;
7295 #endif
7296     }
7297     return color;
7298 }
7299 
7300 /*
7301  * Handle the ":highlight .." command.
7302  * When using ":hi clear" this is called recursively for each group with
7303  * "forceit" and "init" both TRUE.
7304  */
7305     void
7306 do_highlight(
7307     char_u	*line,
7308     int		forceit,
7309     int		init)	    /* TRUE when called for initializing */
7310 {
7311     char_u	*name_end;
7312     char_u	*p;
7313     char_u	*linep;
7314     char_u	*key_start;
7315     char_u	*arg_start;
7316     char_u	*key = NULL, *arg = NULL;
7317     long	i;
7318     int		off;
7319     int		len;
7320     int		attr;
7321     int		id;
7322     int		idx;
7323     struct hl_group item_before;
7324     int		did_change = FALSE;
7325     int		dodefault = FALSE;
7326     int		doclear = FALSE;
7327     int		dolink = FALSE;
7328     int		error = FALSE;
7329     int		color;
7330     int		is_normal_group = FALSE;	/* "Normal" group */
7331 #ifdef FEAT_TERMINAL
7332     int		is_terminal_group = FALSE;	/* "Terminal" group */
7333 #endif
7334 #ifdef FEAT_GUI_X11
7335     int		is_menu_group = FALSE;		/* "Menu" group */
7336     int		is_scrollbar_group = FALSE;	/* "Scrollbar" group */
7337     int		is_tooltip_group = FALSE;	/* "Tooltip" group */
7338     int		do_colors = FALSE;		/* need to update colors? */
7339 #else
7340 # define is_menu_group 0
7341 # define is_tooltip_group 0
7342 #endif
7343 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
7344     int		did_highlight_changed = FALSE;
7345 #endif
7346 
7347     /*
7348      * If no argument, list current highlighting.
7349      */
7350     if (ends_excmd(*line))
7351     {
7352 	for (i = 1; i <= highlight_ga.ga_len && !got_int; ++i)
7353 	    /* TODO: only call when the group has attributes set */
7354 	    highlight_list_one((int)i);
7355 	return;
7356     }
7357 
7358     /*
7359      * Isolate the name.
7360      */
7361     name_end = skiptowhite(line);
7362     linep = skipwhite(name_end);
7363 
7364     /*
7365      * Check for "default" argument.
7366      */
7367     if (STRNCMP(line, "default", name_end - line) == 0)
7368     {
7369 	dodefault = TRUE;
7370 	line = linep;
7371 	name_end = skiptowhite(line);
7372 	linep = skipwhite(name_end);
7373     }
7374 
7375     /*
7376      * Check for "clear" or "link" argument.
7377      */
7378     if (STRNCMP(line, "clear", name_end - line) == 0)
7379 	doclear = TRUE;
7380     if (STRNCMP(line, "link", name_end - line) == 0)
7381 	dolink = TRUE;
7382 
7383     /*
7384      * ":highlight {group-name}": list highlighting for one group.
7385      */
7386     if (!doclear && !dolink && ends_excmd(*linep))
7387     {
7388 	id = syn_namen2id(line, (int)(name_end - line));
7389 	if (id == 0)
7390 	    semsg(_("E411: highlight group not found: %s"), line);
7391 	else
7392 	    highlight_list_one(id);
7393 	return;
7394     }
7395 
7396     /*
7397      * Handle ":highlight link {from} {to}" command.
7398      */
7399     if (dolink)
7400     {
7401 	char_u	    *from_start = linep;
7402 	char_u	    *from_end;
7403 	char_u	    *to_start;
7404 	char_u	    *to_end;
7405 	int	    from_id;
7406 	int	    to_id;
7407 
7408 	from_end = skiptowhite(from_start);
7409 	to_start = skipwhite(from_end);
7410 	to_end	 = skiptowhite(to_start);
7411 
7412 	if (ends_excmd(*from_start) || ends_excmd(*to_start))
7413 	{
7414 	    semsg(_("E412: Not enough arguments: \":highlight link %s\""),
7415 								  from_start);
7416 	    return;
7417 	}
7418 
7419 	if (!ends_excmd(*skipwhite(to_end)))
7420 	{
7421 	    semsg(_("E413: Too many arguments: \":highlight link %s\""), from_start);
7422 	    return;
7423 	}
7424 
7425 	from_id = syn_check_group(from_start, (int)(from_end - from_start));
7426 	if (STRNCMP(to_start, "NONE", 4) == 0)
7427 	    to_id = 0;
7428 	else
7429 	    to_id = syn_check_group(to_start, (int)(to_end - to_start));
7430 
7431 	if (from_id > 0 && (!init || HL_TABLE()[from_id - 1].sg_set == 0))
7432 	{
7433 	    /*
7434 	     * Don't allow a link when there already is some highlighting
7435 	     * for the group, unless '!' is used
7436 	     */
7437 	    if (to_id > 0 && !forceit && !init
7438 				   && hl_has_settings(from_id - 1, dodefault))
7439 	    {
7440 		if (sourcing_name == NULL && !dodefault)
7441 		    emsg(_("E414: group has settings, highlight link ignored"));
7442 	    }
7443 	    else if (HL_TABLE()[from_id - 1].sg_link != to_id
7444 #ifdef FEAT_EVAL
7445 		    || HL_TABLE()[from_id - 1].sg_script_ctx.sc_sid
7446 							 != current_sctx.sc_sid
7447 #endif
7448 		    || HL_TABLE()[from_id - 1].sg_cleared)
7449 	    {
7450 		if (!init)
7451 		    HL_TABLE()[from_id - 1].sg_set |= SG_LINK;
7452 		HL_TABLE()[from_id - 1].sg_link = to_id;
7453 #ifdef FEAT_EVAL
7454 		HL_TABLE()[from_id - 1].sg_script_ctx = current_sctx;
7455 		HL_TABLE()[from_id - 1].sg_script_ctx.sc_lnum += sourcing_lnum;
7456 #endif
7457 		HL_TABLE()[from_id - 1].sg_cleared = FALSE;
7458 		redraw_all_later(SOME_VALID);
7459 
7460 		/* Only call highlight_changed() once after multiple changes. */
7461 		need_highlight_changed = TRUE;
7462 	    }
7463 	}
7464 
7465 	return;
7466     }
7467 
7468     if (doclear)
7469     {
7470 	/*
7471 	 * ":highlight clear [group]" command.
7472 	 */
7473 	line = linep;
7474 	if (ends_excmd(*line))
7475 	{
7476 #ifdef FEAT_GUI
7477 	    /* First, we do not destroy the old values, but allocate the new
7478 	     * ones and update the display. THEN we destroy the old values.
7479 	     * If we destroy the old values first, then the old values
7480 	     * (such as GuiFont's or GuiFontset's) will still be displayed but
7481 	     * invalid because they were free'd.
7482 	     */
7483 	    if (gui.in_use)
7484 	    {
7485 # ifdef FEAT_BEVAL_TIP
7486 		gui_init_tooltip_font();
7487 # endif
7488 # if defined(FEAT_MENU) && (defined(FEAT_GUI_ATHENA) || defined(FEAT_GUI_MOTIF))
7489 		gui_init_menu_font();
7490 # endif
7491 	    }
7492 # if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_X11)
7493 	    gui_mch_def_colors();
7494 # endif
7495 # ifdef FEAT_GUI_X11
7496 #  ifdef FEAT_MENU
7497 
7498 	    /* This only needs to be done when there is no Menu highlight
7499 	     * group defined by default, which IS currently the case.
7500 	     */
7501 	    gui_mch_new_menu_colors();
7502 #  endif
7503 	    if (gui.in_use)
7504 	    {
7505 		gui_new_scrollbar_colors();
7506 #  ifdef FEAT_BEVAL_GUI
7507 		gui_mch_new_tooltip_colors();
7508 #  endif
7509 #  ifdef FEAT_MENU
7510 		gui_mch_new_menu_font();
7511 #  endif
7512 	    }
7513 # endif
7514 
7515 	    /* Ok, we're done allocating the new default graphics items.
7516 	     * The screen should already be refreshed at this point.
7517 	     * It is now Ok to clear out the old data.
7518 	     */
7519 #endif
7520 #ifdef FEAT_EVAL
7521 	    do_unlet((char_u *)"colors_name", TRUE);
7522 #endif
7523 	    restore_cterm_colors();
7524 
7525 	    /*
7526 	     * Clear all default highlight groups and load the defaults.
7527 	     */
7528 	    for (idx = 0; idx < highlight_ga.ga_len; ++idx)
7529 		highlight_clear(idx);
7530 	    init_highlight(TRUE, TRUE);
7531 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
7532 	    if (USE_24BIT)
7533 		highlight_gui_started();
7534 	    else
7535 #endif
7536 		highlight_changed();
7537 	    redraw_later_clear();
7538 	    return;
7539 	}
7540 	name_end = skiptowhite(line);
7541 	linep = skipwhite(name_end);
7542     }
7543 
7544     /*
7545      * Find the group name in the table.  If it does not exist yet, add it.
7546      */
7547     id = syn_check_group(line, (int)(name_end - line));
7548     if (id == 0)			/* failed (out of memory) */
7549 	return;
7550     idx = id - 1;			/* index is ID minus one */
7551 
7552     /* Return if "default" was used and the group already has settings. */
7553     if (dodefault && hl_has_settings(idx, TRUE))
7554 	return;
7555 
7556     /* Make a copy so we can check if any attribute actually changed. */
7557     item_before = HL_TABLE()[idx];
7558 
7559     if (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0)
7560 	is_normal_group = TRUE;
7561 #ifdef FEAT_TERMINAL
7562     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "TERMINAL") == 0)
7563 	is_terminal_group = TRUE;
7564 #endif
7565 #ifdef FEAT_GUI_X11
7566     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "MENU") == 0)
7567 	is_menu_group = TRUE;
7568     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "SCROLLBAR") == 0)
7569 	is_scrollbar_group = TRUE;
7570     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "TOOLTIP") == 0)
7571 	is_tooltip_group = TRUE;
7572 #endif
7573 
7574     /* Clear the highlighting for ":hi clear {group}" and ":hi clear". */
7575     if (doclear || (forceit && init))
7576     {
7577 	highlight_clear(idx);
7578 	if (!doclear)
7579 	    HL_TABLE()[idx].sg_set = 0;
7580     }
7581 
7582     if (!doclear)
7583       while (!ends_excmd(*linep))
7584       {
7585 	key_start = linep;
7586 	if (*linep == '=')
7587 	{
7588 	    semsg(_("E415: unexpected equal sign: %s"), key_start);
7589 	    error = TRUE;
7590 	    break;
7591 	}
7592 
7593 	/*
7594 	 * Isolate the key ("term", "ctermfg", "ctermbg", "font", "guifg" or
7595 	 * "guibg").
7596 	 */
7597 	while (*linep && !VIM_ISWHITE(*linep) && *linep != '=')
7598 	    ++linep;
7599 	vim_free(key);
7600 	key = vim_strnsave_up(key_start, (int)(linep - key_start));
7601 	if (key == NULL)
7602 	{
7603 	    error = TRUE;
7604 	    break;
7605 	}
7606 	linep = skipwhite(linep);
7607 
7608 	if (STRCMP(key, "NONE") == 0)
7609 	{
7610 	    if (!init || HL_TABLE()[idx].sg_set == 0)
7611 	    {
7612 		if (!init)
7613 		    HL_TABLE()[idx].sg_set |= SG_TERM+SG_CTERM+SG_GUI;
7614 		highlight_clear(idx);
7615 	    }
7616 	    continue;
7617 	}
7618 
7619 	/*
7620 	 * Check for the equal sign.
7621 	 */
7622 	if (*linep != '=')
7623 	{
7624 	    semsg(_("E416: missing equal sign: %s"), key_start);
7625 	    error = TRUE;
7626 	    break;
7627 	}
7628 	++linep;
7629 
7630 	/*
7631 	 * Isolate the argument.
7632 	 */
7633 	linep = skipwhite(linep);
7634 	if (*linep == '\'')		/* guifg='color name' */
7635 	{
7636 	    arg_start = ++linep;
7637 	    linep = vim_strchr(linep, '\'');
7638 	    if (linep == NULL)
7639 	    {
7640 		semsg(_(e_invarg2), key_start);
7641 		error = TRUE;
7642 		break;
7643 	    }
7644 	}
7645 	else
7646 	{
7647 	    arg_start = linep;
7648 	    linep = skiptowhite(linep);
7649 	}
7650 	if (linep == arg_start)
7651 	{
7652 	    semsg(_("E417: missing argument: %s"), key_start);
7653 	    error = TRUE;
7654 	    break;
7655 	}
7656 	vim_free(arg);
7657 	arg = vim_strnsave(arg_start, (int)(linep - arg_start));
7658 	if (arg == NULL)
7659 	{
7660 	    error = TRUE;
7661 	    break;
7662 	}
7663 	if (*linep == '\'')
7664 	    ++linep;
7665 
7666 	/*
7667 	 * Store the argument.
7668 	 */
7669 	if (  STRCMP(key, "TERM") == 0
7670 		|| STRCMP(key, "CTERM") == 0
7671 		|| STRCMP(key, "GUI") == 0)
7672 	{
7673 	    attr = 0;
7674 	    off = 0;
7675 	    while (arg[off] != NUL)
7676 	    {
7677 		for (i = sizeof(hl_attr_table) / sizeof(int); --i >= 0; )
7678 		{
7679 		    len = (int)STRLEN(hl_name_table[i]);
7680 		    if (STRNICMP(arg + off, hl_name_table[i], len) == 0)
7681 		    {
7682 			attr |= hl_attr_table[i];
7683 			off += len;
7684 			break;
7685 		    }
7686 		}
7687 		if (i < 0)
7688 		{
7689 		    semsg(_("E418: Illegal value: %s"), arg);
7690 		    error = TRUE;
7691 		    break;
7692 		}
7693 		if (arg[off] == ',')		/* another one follows */
7694 		    ++off;
7695 	    }
7696 	    if (error)
7697 		break;
7698 	    if (*key == 'T')
7699 	    {
7700 		if (!init || !(HL_TABLE()[idx].sg_set & SG_TERM))
7701 		{
7702 		    if (!init)
7703 			HL_TABLE()[idx].sg_set |= SG_TERM;
7704 		    HL_TABLE()[idx].sg_term = attr;
7705 		}
7706 	    }
7707 	    else if (*key == 'C')
7708 	    {
7709 		if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM))
7710 		{
7711 		    if (!init)
7712 			HL_TABLE()[idx].sg_set |= SG_CTERM;
7713 		    HL_TABLE()[idx].sg_cterm = attr;
7714 		    HL_TABLE()[idx].sg_cterm_bold = FALSE;
7715 		}
7716 	    }
7717 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
7718 	    else
7719 	    {
7720 		if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
7721 		{
7722 		    if (!init)
7723 			HL_TABLE()[idx].sg_set |= SG_GUI;
7724 		    HL_TABLE()[idx].sg_gui = attr;
7725 		}
7726 	    }
7727 #endif
7728 	}
7729 	else if (STRCMP(key, "FONT") == 0)
7730 	{
7731 	    /* in non-GUI fonts are simply ignored */
7732 #ifdef FEAT_GUI
7733 	    if (HL_TABLE()[idx].sg_font_name != NULL
7734 			     && STRCMP(HL_TABLE()[idx].sg_font_name, arg) == 0)
7735 	    {
7736 		/* Font name didn't change, ignore. */
7737 	    }
7738 	    else if (!gui.shell_created)
7739 	    {
7740 		/* GUI not started yet, always accept the name. */
7741 		vim_free(HL_TABLE()[idx].sg_font_name);
7742 		HL_TABLE()[idx].sg_font_name = vim_strsave(arg);
7743 		did_change = TRUE;
7744 	    }
7745 	    else
7746 	    {
7747 		GuiFont temp_sg_font = HL_TABLE()[idx].sg_font;
7748 # ifdef FEAT_XFONTSET
7749 		GuiFontset temp_sg_fontset = HL_TABLE()[idx].sg_fontset;
7750 # endif
7751 		/* First, save the current font/fontset.
7752 		 * Then try to allocate the font/fontset.
7753 		 * If the allocation fails, HL_TABLE()[idx].sg_font OR
7754 		 * sg_fontset will be set to NOFONT or NOFONTSET respectively.
7755 		 */
7756 
7757 		HL_TABLE()[idx].sg_font = NOFONT;
7758 # ifdef FEAT_XFONTSET
7759 		HL_TABLE()[idx].sg_fontset = NOFONTSET;
7760 # endif
7761 		hl_do_font(idx, arg, is_normal_group, is_menu_group,
7762 						     is_tooltip_group, FALSE);
7763 
7764 # ifdef FEAT_XFONTSET
7765 		if (HL_TABLE()[idx].sg_fontset != NOFONTSET)
7766 		{
7767 		    /* New fontset was accepted. Free the old one, if there
7768 		     * was one. */
7769 		    gui_mch_free_fontset(temp_sg_fontset);
7770 		    vim_free(HL_TABLE()[idx].sg_font_name);
7771 		    HL_TABLE()[idx].sg_font_name = vim_strsave(arg);
7772 		    did_change = TRUE;
7773 		}
7774 		else
7775 		    HL_TABLE()[idx].sg_fontset = temp_sg_fontset;
7776 # endif
7777 		if (HL_TABLE()[idx].sg_font != NOFONT)
7778 		{
7779 		    /* New font was accepted. Free the old one, if there was
7780 		     * one. */
7781 		    gui_mch_free_font(temp_sg_font);
7782 		    vim_free(HL_TABLE()[idx].sg_font_name);
7783 		    HL_TABLE()[idx].sg_font_name = vim_strsave(arg);
7784 		    did_change = TRUE;
7785 		}
7786 		else
7787 		    HL_TABLE()[idx].sg_font = temp_sg_font;
7788 	    }
7789 #endif
7790 	}
7791 	else if (STRCMP(key, "CTERMFG") == 0 || STRCMP(key, "CTERMBG") == 0)
7792 	{
7793 	  if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM))
7794 	  {
7795 	    if (!init)
7796 		HL_TABLE()[idx].sg_set |= SG_CTERM;
7797 
7798 	    /* When setting the foreground color, and previously the "bold"
7799 	     * flag was set for a light color, reset it now */
7800 	    if (key[5] == 'F' && HL_TABLE()[idx].sg_cterm_bold)
7801 	    {
7802 		HL_TABLE()[idx].sg_cterm &= ~HL_BOLD;
7803 		HL_TABLE()[idx].sg_cterm_bold = FALSE;
7804 	    }
7805 
7806 	    if (VIM_ISDIGIT(*arg))
7807 		color = atoi((char *)arg);
7808 	    else if (STRICMP(arg, "fg") == 0)
7809 	    {
7810 		if (cterm_normal_fg_color)
7811 		    color = cterm_normal_fg_color - 1;
7812 		else
7813 		{
7814 		    emsg(_("E419: FG color unknown"));
7815 		    error = TRUE;
7816 		    break;
7817 		}
7818 	    }
7819 	    else if (STRICMP(arg, "bg") == 0)
7820 	    {
7821 		if (cterm_normal_bg_color > 0)
7822 		    color = cterm_normal_bg_color - 1;
7823 		else
7824 		{
7825 		    emsg(_("E420: BG color unknown"));
7826 		    error = TRUE;
7827 		    break;
7828 		}
7829 	    }
7830 	    else
7831 	    {
7832 		int bold = MAYBE;
7833 
7834 #if defined(__QNXNTO__)
7835 		static int *color_numbers_8_qansi = color_numbers_8;
7836 		/* On qnx, the 8 & 16 color arrays are the same */
7837 		if (STRNCMP(T_NAME, "qansi", 5) == 0)
7838 		    color_numbers_8_qansi = color_numbers_16;
7839 #endif
7840 
7841 		/* reduce calls to STRICMP a bit, it can be slow */
7842 		off = TOUPPER_ASC(*arg);
7843 		for (i = (sizeof(color_names) / sizeof(char *)); --i >= 0; )
7844 		    if (off == color_names[i][0]
7845 				 && STRICMP(arg + 1, color_names[i] + 1) == 0)
7846 			break;
7847 		if (i < 0)
7848 		{
7849 		    semsg(_("E421: Color name or number not recognized: %s"), key_start);
7850 		    error = TRUE;
7851 		    break;
7852 		}
7853 
7854 		color = lookup_color(i, key[5] == 'F', &bold);
7855 
7856 		/* set/reset bold attribute to get light foreground
7857 		 * colors (on some terminals, e.g. "linux") */
7858 		if (bold == TRUE)
7859 		{
7860 		    HL_TABLE()[idx].sg_cterm |= HL_BOLD;
7861 		    HL_TABLE()[idx].sg_cterm_bold = TRUE;
7862 		}
7863 		else if (bold == FALSE)
7864 		    HL_TABLE()[idx].sg_cterm &= ~HL_BOLD;
7865 	    }
7866 
7867 	    /* Add one to the argument, to avoid zero.  Zero is used for
7868 	     * "NONE", then "color" is -1. */
7869 	    if (key[5] == 'F')
7870 	    {
7871 		HL_TABLE()[idx].sg_cterm_fg = color + 1;
7872 		if (is_normal_group)
7873 		{
7874 		    cterm_normal_fg_color = color + 1;
7875 		    cterm_normal_fg_bold = (HL_TABLE()[idx].sg_cterm & HL_BOLD);
7876 #ifdef FEAT_GUI
7877 		    /* Don't do this if the GUI is used. */
7878 		    if (!gui.in_use && !gui.starting)
7879 #endif
7880 		    {
7881 			must_redraw = CLEAR;
7882 			if (termcap_active && color >= 0)
7883 			    term_fg_color(color);
7884 		    }
7885 		}
7886 	    }
7887 	    else
7888 	    {
7889 		HL_TABLE()[idx].sg_cterm_bg = color + 1;
7890 		if (is_normal_group)
7891 		{
7892 		    cterm_normal_bg_color = color + 1;
7893 #ifdef FEAT_GUI
7894 		    /* Don't mess with 'background' if the GUI is used. */
7895 		    if (!gui.in_use && !gui.starting)
7896 #endif
7897 		    {
7898 			must_redraw = CLEAR;
7899 			if (color >= 0)
7900 			{
7901 			    int dark = -1;
7902 
7903 			    if (termcap_active)
7904 				term_bg_color(color);
7905 			    if (t_colors < 16)
7906 				dark = (color == 0 || color == 4);
7907 			    /* Limit the heuristic to the standard 16 colors */
7908 			    else if (color < 16)
7909 				dark = (color < 7 || color == 8);
7910 			    /* Set the 'background' option if the value is
7911 			     * wrong. */
7912 			    if (dark != -1
7913 				    && dark != (*p_bg == 'd')
7914 				    && !option_was_set((char_u *)"bg"))
7915 			    {
7916 				set_option_value((char_u *)"bg", 0L,
7917 				       (char_u *)(dark ? "dark" : "light"), 0);
7918 				reset_option_was_set((char_u *)"bg");
7919 			    }
7920 			}
7921 		    }
7922 		}
7923 	    }
7924 	  }
7925 	}
7926 	else if (STRCMP(key, "GUIFG") == 0)
7927 	{
7928 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
7929 	    char_u **namep = &HL_TABLE()[idx].sg_gui_fg_name;
7930 
7931 	    if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
7932 	    {
7933 		if (!init)
7934 		    HL_TABLE()[idx].sg_set |= SG_GUI;
7935 
7936 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
7937 		/* In GUI guifg colors are only used when recognized */
7938 		i = color_name2handle(arg);
7939 		if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !USE_24BIT)
7940 		{
7941 		    HL_TABLE()[idx].sg_gui_fg = i;
7942 # endif
7943 		    if (*namep == NULL || STRCMP(*namep, arg) != 0)
7944 		    {
7945 			vim_free(*namep);
7946 			if (STRCMP(arg, "NONE") != 0)
7947 			    *namep = vim_strsave(arg);
7948 			else
7949 			    *namep = NULL;
7950 			did_change = TRUE;
7951 		    }
7952 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
7953 #  ifdef FEAT_GUI_X11
7954 		    if (is_menu_group && gui.menu_fg_pixel != i)
7955 		    {
7956 			gui.menu_fg_pixel = i;
7957 			do_colors = TRUE;
7958 		    }
7959 		    if (is_scrollbar_group && gui.scroll_fg_pixel != i)
7960 		    {
7961 			gui.scroll_fg_pixel = i;
7962 			do_colors = TRUE;
7963 		    }
7964 #   ifdef FEAT_BEVAL_GUI
7965 		    if (is_tooltip_group && gui.tooltip_fg_pixel != i)
7966 		    {
7967 			gui.tooltip_fg_pixel = i;
7968 			do_colors = TRUE;
7969 		    }
7970 #   endif
7971 #  endif
7972 		}
7973 # endif
7974 	    }
7975 #endif
7976 	}
7977 	else if (STRCMP(key, "GUIBG") == 0)
7978 	{
7979 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
7980 	    char_u **namep = &HL_TABLE()[idx].sg_gui_bg_name;
7981 
7982 	    if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
7983 	    {
7984 		if (!init)
7985 		    HL_TABLE()[idx].sg_set |= SG_GUI;
7986 
7987 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
7988 		/* In GUI guifg colors are only used when recognized */
7989 		i = color_name2handle(arg);
7990 		if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !USE_24BIT)
7991 		{
7992 		    HL_TABLE()[idx].sg_gui_bg = i;
7993 # endif
7994 		    if (*namep == NULL || STRCMP(*namep, arg) != 0)
7995 		    {
7996 			vim_free(*namep);
7997 			if (STRCMP(arg, "NONE") != 0)
7998 			    *namep = vim_strsave(arg);
7999 			else
8000 			    *namep = NULL;
8001 			did_change = TRUE;
8002 		    }
8003 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8004 #  ifdef FEAT_GUI_X11
8005 		    if (is_menu_group && gui.menu_bg_pixel != i)
8006 		    {
8007 			gui.menu_bg_pixel = i;
8008 			do_colors = TRUE;
8009 		    }
8010 		    if (is_scrollbar_group && gui.scroll_bg_pixel != i)
8011 		    {
8012 			gui.scroll_bg_pixel = i;
8013 			do_colors = TRUE;
8014 		    }
8015 #   ifdef FEAT_BEVAL_GUI
8016 		    if (is_tooltip_group && gui.tooltip_bg_pixel != i)
8017 		    {
8018 			gui.tooltip_bg_pixel = i;
8019 			do_colors = TRUE;
8020 		    }
8021 #   endif
8022 #  endif
8023 		}
8024 # endif
8025 	    }
8026 #endif
8027 	}
8028 	else if (STRCMP(key, "GUISP") == 0)
8029 	{
8030 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
8031 	    char_u **namep = &HL_TABLE()[idx].sg_gui_sp_name;
8032 
8033 	    if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
8034 	    {
8035 		if (!init)
8036 		    HL_TABLE()[idx].sg_set |= SG_GUI;
8037 
8038 # ifdef FEAT_GUI
8039 		i = color_name2handle(arg);
8040 		if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !gui.in_use)
8041 		{
8042 		    HL_TABLE()[idx].sg_gui_sp = i;
8043 # endif
8044 		    if (*namep == NULL || STRCMP(*namep, arg) != 0)
8045 		    {
8046 			vim_free(*namep);
8047 			if (STRCMP(arg, "NONE") != 0)
8048 			    *namep = vim_strsave(arg);
8049 			else
8050 			    *namep = NULL;
8051 			did_change = TRUE;
8052 		    }
8053 # ifdef FEAT_GUI
8054 		}
8055 # endif
8056 	    }
8057 #endif
8058 	}
8059 	else if (STRCMP(key, "START") == 0 || STRCMP(key, "STOP") == 0)
8060 	{
8061 	    char_u	buf[100];
8062 	    char_u	*tname;
8063 
8064 	    if (!init)
8065 		HL_TABLE()[idx].sg_set |= SG_TERM;
8066 
8067 	    /*
8068 	     * The "start" and "stop"  arguments can be a literal escape
8069 	     * sequence, or a comma separated list of terminal codes.
8070 	     */
8071 	    if (STRNCMP(arg, "t_", 2) == 0)
8072 	    {
8073 		off = 0;
8074 		buf[0] = 0;
8075 		while (arg[off] != NUL)
8076 		{
8077 		    /* Isolate one termcap name */
8078 		    for (len = 0; arg[off + len] &&
8079 						 arg[off + len] != ','; ++len)
8080 			;
8081 		    tname = vim_strnsave(arg + off, len);
8082 		    if (tname == NULL)		/* out of memory */
8083 		    {
8084 			error = TRUE;
8085 			break;
8086 		    }
8087 		    /* lookup the escape sequence for the item */
8088 		    p = get_term_code(tname);
8089 		    vim_free(tname);
8090 		    if (p == NULL)	    /* ignore non-existing things */
8091 			p = (char_u *)"";
8092 
8093 		    /* Append it to the already found stuff */
8094 		    if ((int)(STRLEN(buf) + STRLEN(p)) >= 99)
8095 		    {
8096 			semsg(_("E422: terminal code too long: %s"), arg);
8097 			error = TRUE;
8098 			break;
8099 		    }
8100 		    STRCAT(buf, p);
8101 
8102 		    /* Advance to the next item */
8103 		    off += len;
8104 		    if (arg[off] == ',')	    /* another one follows */
8105 			++off;
8106 		}
8107 	    }
8108 	    else
8109 	    {
8110 		/*
8111 		 * Copy characters from arg[] to buf[], translating <> codes.
8112 		 */
8113 		for (p = arg, off = 0; off < 100 - 6 && *p; )
8114 		{
8115 		    len = trans_special(&p, buf + off, FALSE, FALSE);
8116 		    if (len > 0)	    /* recognized special char */
8117 			off += len;
8118 		    else		    /* copy as normal char */
8119 			buf[off++] = *p++;
8120 		}
8121 		buf[off] = NUL;
8122 	    }
8123 	    if (error)
8124 		break;
8125 
8126 	    if (STRCMP(buf, "NONE") == 0)	/* resetting the value */
8127 		p = NULL;
8128 	    else
8129 		p = vim_strsave(buf);
8130 	    if (key[2] == 'A')
8131 	    {
8132 		vim_free(HL_TABLE()[idx].sg_start);
8133 		HL_TABLE()[idx].sg_start = p;
8134 	    }
8135 	    else
8136 	    {
8137 		vim_free(HL_TABLE()[idx].sg_stop);
8138 		HL_TABLE()[idx].sg_stop = p;
8139 	    }
8140 	}
8141 	else
8142 	{
8143 	    semsg(_("E423: Illegal argument: %s"), key_start);
8144 	    error = TRUE;
8145 	    break;
8146 	}
8147 	HL_TABLE()[idx].sg_cleared = FALSE;
8148 
8149 	/*
8150 	 * When highlighting has been given for a group, don't link it.
8151 	 */
8152 	if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK))
8153 	    HL_TABLE()[idx].sg_link = 0;
8154 
8155 	/*
8156 	 * Continue with next argument.
8157 	 */
8158 	linep = skipwhite(linep);
8159       }
8160 
8161     /*
8162      * If there is an error, and it's a new entry, remove it from the table.
8163      */
8164     if (error && idx == highlight_ga.ga_len)
8165 	syn_unadd_group();
8166     else
8167     {
8168 	if (is_normal_group)
8169 	{
8170 	    HL_TABLE()[idx].sg_term_attr = 0;
8171 	    HL_TABLE()[idx].sg_cterm_attr = 0;
8172 #ifdef FEAT_GUI
8173 	    HL_TABLE()[idx].sg_gui_attr = 0;
8174 	    /*
8175 	     * Need to update all groups, because they might be using "bg"
8176 	     * and/or "fg", which have been changed now.
8177 	     */
8178 #endif
8179 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8180 	    if (USE_24BIT)
8181 	    {
8182 		highlight_gui_started();
8183 		did_highlight_changed = TRUE;
8184 		redraw_all_later(NOT_VALID);
8185 	    }
8186 #endif
8187 	}
8188 #ifdef FEAT_TERMINAL
8189 	else if (is_terminal_group)
8190 	    set_terminal_default_colors(
8191 		    HL_TABLE()[idx].sg_cterm_fg, HL_TABLE()[idx].sg_cterm_bg);
8192 #endif
8193 #ifdef FEAT_GUI_X11
8194 # ifdef FEAT_MENU
8195 	else if (is_menu_group)
8196 	{
8197 	    if (gui.in_use && do_colors)
8198 		gui_mch_new_menu_colors();
8199 	}
8200 # endif
8201 	else if (is_scrollbar_group)
8202 	{
8203 	    if (gui.in_use && do_colors)
8204 		gui_new_scrollbar_colors();
8205 	}
8206 # ifdef FEAT_BEVAL_GUI
8207 	else if (is_tooltip_group)
8208 	{
8209 	    if (gui.in_use && do_colors)
8210 		gui_mch_new_tooltip_colors();
8211 	}
8212 # endif
8213 #endif
8214 	else
8215 	    set_hl_attr(idx);
8216 #ifdef FEAT_EVAL
8217 	HL_TABLE()[idx].sg_script_ctx = current_sctx;
8218 	HL_TABLE()[idx].sg_script_ctx.sc_lnum += sourcing_lnum;
8219 #endif
8220     }
8221 
8222     vim_free(key);
8223     vim_free(arg);
8224 
8225     /* Only call highlight_changed() once, after a sequence of highlight
8226      * commands, and only if an attribute actually changed. */
8227     if ((did_change
8228 	   || memcmp(&HL_TABLE()[idx], &item_before, sizeof(item_before)) != 0)
8229 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8230 	    && !did_highlight_changed
8231 #endif
8232        )
8233     {
8234 	/* Do not trigger a redraw when highlighting is changed while
8235 	 * redrawing.  This may happen when evaluating 'statusline' changes the
8236 	 * StatusLine group. */
8237 	if (!updating_screen)
8238 	    redraw_all_later(NOT_VALID);
8239 	need_highlight_changed = TRUE;
8240     }
8241 }
8242 
8243 #if defined(EXITFREE) || defined(PROTO)
8244     void
8245 free_highlight(void)
8246 {
8247     int	    i;
8248 
8249     for (i = 0; i < highlight_ga.ga_len; ++i)
8250     {
8251 	highlight_clear(i);
8252 	vim_free(HL_TABLE()[i].sg_name);
8253 	vim_free(HL_TABLE()[i].sg_name_u);
8254     }
8255     ga_clear(&highlight_ga);
8256 }
8257 #endif
8258 
8259 /*
8260  * Reset the cterm colors to what they were before Vim was started, if
8261  * possible.  Otherwise reset them to zero.
8262  */
8263     void
8264 restore_cterm_colors(void)
8265 {
8266 #if defined(MSWIN) && !defined(FEAT_GUI_MSWIN)
8267     /* Since t_me has been set, this probably means that the user
8268      * wants to use this as default colors.  Need to reset default
8269      * background/foreground colors. */
8270     mch_set_normal_colors();
8271 #else
8272 # ifdef VIMDLL
8273     if (!gui.in_use)
8274     {
8275 	mch_set_normal_colors();
8276 	return;
8277     }
8278 # endif
8279     cterm_normal_fg_color = 0;
8280     cterm_normal_fg_bold = 0;
8281     cterm_normal_bg_color = 0;
8282 # ifdef FEAT_TERMGUICOLORS
8283     cterm_normal_fg_gui_color = INVALCOLOR;
8284     cterm_normal_bg_gui_color = INVALCOLOR;
8285 # endif
8286 #endif
8287 }
8288 
8289 /*
8290  * Return TRUE if highlight group "idx" has any settings.
8291  * When "check_link" is TRUE also check for an existing link.
8292  */
8293     static int
8294 hl_has_settings(int idx, int check_link)
8295 {
8296     return (   HL_TABLE()[idx].sg_term_attr != 0
8297 	    || HL_TABLE()[idx].sg_cterm_attr != 0
8298 	    || HL_TABLE()[idx].sg_cterm_fg != 0
8299 	    || HL_TABLE()[idx].sg_cterm_bg != 0
8300 #ifdef FEAT_GUI
8301 	    || HL_TABLE()[idx].sg_gui_attr != 0
8302 	    || HL_TABLE()[idx].sg_gui_fg_name != NULL
8303 	    || HL_TABLE()[idx].sg_gui_bg_name != NULL
8304 	    || HL_TABLE()[idx].sg_gui_sp_name != NULL
8305 	    || HL_TABLE()[idx].sg_font_name != NULL
8306 #endif
8307 	    || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK)));
8308 }
8309 
8310 /*
8311  * Clear highlighting for one group.
8312  */
8313     static void
8314 highlight_clear(int idx)
8315 {
8316     HL_TABLE()[idx].sg_cleared = TRUE;
8317 
8318     HL_TABLE()[idx].sg_term = 0;
8319     VIM_CLEAR(HL_TABLE()[idx].sg_start);
8320     VIM_CLEAR(HL_TABLE()[idx].sg_stop);
8321     HL_TABLE()[idx].sg_term_attr = 0;
8322     HL_TABLE()[idx].sg_cterm = 0;
8323     HL_TABLE()[idx].sg_cterm_bold = FALSE;
8324     HL_TABLE()[idx].sg_cterm_fg = 0;
8325     HL_TABLE()[idx].sg_cterm_bg = 0;
8326     HL_TABLE()[idx].sg_cterm_attr = 0;
8327 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
8328     HL_TABLE()[idx].sg_gui = 0;
8329     VIM_CLEAR(HL_TABLE()[idx].sg_gui_fg_name);
8330     VIM_CLEAR(HL_TABLE()[idx].sg_gui_bg_name);
8331     VIM_CLEAR(HL_TABLE()[idx].sg_gui_sp_name);
8332 #endif
8333 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8334     HL_TABLE()[idx].sg_gui_fg = INVALCOLOR;
8335     HL_TABLE()[idx].sg_gui_bg = INVALCOLOR;
8336 #endif
8337 #ifdef FEAT_GUI
8338     HL_TABLE()[idx].sg_gui_sp = INVALCOLOR;
8339     gui_mch_free_font(HL_TABLE()[idx].sg_font);
8340     HL_TABLE()[idx].sg_font = NOFONT;
8341 # ifdef FEAT_XFONTSET
8342     gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
8343     HL_TABLE()[idx].sg_fontset = NOFONTSET;
8344 # endif
8345     VIM_CLEAR(HL_TABLE()[idx].sg_font_name);
8346     HL_TABLE()[idx].sg_gui_attr = 0;
8347 #endif
8348 #ifdef FEAT_EVAL
8349     /* Clear the script ID only when there is no link, since that is not
8350      * cleared. */
8351     if (HL_TABLE()[idx].sg_link == 0)
8352     {
8353 	HL_TABLE()[idx].sg_script_ctx.sc_sid = 0;
8354 	HL_TABLE()[idx].sg_script_ctx.sc_lnum = 0;
8355     }
8356 #endif
8357 }
8358 
8359 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
8360 /*
8361  * Set the normal foreground and background colors according to the "Normal"
8362  * highlighting group.  For X11 also set "Menu", "Scrollbar", and
8363  * "Tooltip" colors.
8364  */
8365     void
8366 set_normal_colors(void)
8367 {
8368 # ifdef FEAT_GUI
8369 #  ifdef FEAT_TERMGUICOLORS
8370     if (gui.in_use)
8371 #  endif
8372     {
8373 	if (set_group_colors((char_u *)"Normal",
8374 				 &gui.norm_pixel, &gui.back_pixel,
8375 				 FALSE, TRUE, FALSE))
8376 	{
8377 	    gui_mch_new_colors();
8378 	    must_redraw = CLEAR;
8379 	}
8380 #  ifdef FEAT_GUI_X11
8381 	if (set_group_colors((char_u *)"Menu",
8382 			     &gui.menu_fg_pixel, &gui.menu_bg_pixel,
8383 			     TRUE, FALSE, FALSE))
8384 	{
8385 #   ifdef FEAT_MENU
8386 	    gui_mch_new_menu_colors();
8387 #   endif
8388 	    must_redraw = CLEAR;
8389 	}
8390 #   ifdef FEAT_BEVAL_GUI
8391 	if (set_group_colors((char_u *)"Tooltip",
8392 			     &gui.tooltip_fg_pixel, &gui.tooltip_bg_pixel,
8393 			     FALSE, FALSE, TRUE))
8394 	{
8395 #    ifdef FEAT_TOOLBAR
8396 	    gui_mch_new_tooltip_colors();
8397 #    endif
8398 	    must_redraw = CLEAR;
8399 	}
8400 #   endif
8401 	if (set_group_colors((char_u *)"Scrollbar",
8402 			&gui.scroll_fg_pixel, &gui.scroll_bg_pixel,
8403 			FALSE, FALSE, FALSE))
8404 	{
8405 	    gui_new_scrollbar_colors();
8406 	    must_redraw = CLEAR;
8407 	}
8408 #  endif
8409     }
8410 # endif
8411 # ifdef FEAT_TERMGUICOLORS
8412 #  ifdef FEAT_GUI
8413     else
8414 #  endif
8415     {
8416 	int		idx;
8417 
8418 	idx = syn_name2id((char_u *)"Normal") - 1;
8419 	if (idx >= 0)
8420 	{
8421 	    gui_do_one_color(idx, FALSE, FALSE);
8422 
8423 	    /* If the normal fg or bg color changed a complete redraw is
8424 	     * required. */
8425 	    if (cterm_normal_fg_gui_color != HL_TABLE()[idx].sg_gui_fg
8426 		    || cterm_normal_bg_gui_color != HL_TABLE()[idx].sg_gui_bg)
8427 	    {
8428 		/* if the GUI color is INVALCOLOR then we use the default cterm
8429 		 * color */
8430 		cterm_normal_fg_gui_color = HL_TABLE()[idx].sg_gui_fg;
8431 		cterm_normal_bg_gui_color = HL_TABLE()[idx].sg_gui_bg;
8432 		must_redraw = CLEAR;
8433 	    }
8434 	}
8435     }
8436 # endif
8437 }
8438 #endif
8439 
8440 #if defined(FEAT_GUI) || defined(PROTO)
8441 /*
8442  * Set the colors for "Normal", "Menu", "Tooltip" or "Scrollbar".
8443  */
8444     static int
8445 set_group_colors(
8446     char_u	*name,
8447     guicolor_T	*fgp,
8448     guicolor_T	*bgp,
8449     int		do_menu,
8450     int		use_norm,
8451     int		do_tooltip)
8452 {
8453     int		idx;
8454 
8455     idx = syn_name2id(name) - 1;
8456     if (idx >= 0)
8457     {
8458 	gui_do_one_color(idx, do_menu, do_tooltip);
8459 
8460 	if (HL_TABLE()[idx].sg_gui_fg != INVALCOLOR)
8461 	    *fgp = HL_TABLE()[idx].sg_gui_fg;
8462 	else if (use_norm)
8463 	    *fgp = gui.def_norm_pixel;
8464 	if (HL_TABLE()[idx].sg_gui_bg != INVALCOLOR)
8465 	    *bgp = HL_TABLE()[idx].sg_gui_bg;
8466 	else if (use_norm)
8467 	    *bgp = gui.def_back_pixel;
8468 	return TRUE;
8469     }
8470     return FALSE;
8471 }
8472 
8473 /*
8474  * Get the font of the "Normal" group.
8475  * Returns "" when it's not found or not set.
8476  */
8477     char_u *
8478 hl_get_font_name(void)
8479 {
8480     int		id;
8481     char_u	*s;
8482 
8483     id = syn_name2id((char_u *)"Normal");
8484     if (id > 0)
8485     {
8486 	s = HL_TABLE()[id - 1].sg_font_name;
8487 	if (s != NULL)
8488 	    return s;
8489     }
8490     return (char_u *)"";
8491 }
8492 
8493 /*
8494  * Set font for "Normal" group.  Called by gui_mch_init_font() when a font has
8495  * actually chosen to be used.
8496  */
8497     void
8498 hl_set_font_name(char_u *font_name)
8499 {
8500     int	    id;
8501 
8502     id = syn_name2id((char_u *)"Normal");
8503     if (id > 0)
8504     {
8505 	vim_free(HL_TABLE()[id - 1].sg_font_name);
8506 	HL_TABLE()[id - 1].sg_font_name = vim_strsave(font_name);
8507     }
8508 }
8509 
8510 /*
8511  * Set background color for "Normal" group.  Called by gui_set_bg_color()
8512  * when the color is known.
8513  */
8514     void
8515 hl_set_bg_color_name(
8516     char_u  *name)	    /* must have been allocated */
8517 {
8518     int	    id;
8519 
8520     if (name != NULL)
8521     {
8522 	id = syn_name2id((char_u *)"Normal");
8523 	if (id > 0)
8524 	{
8525 	    vim_free(HL_TABLE()[id - 1].sg_gui_bg_name);
8526 	    HL_TABLE()[id - 1].sg_gui_bg_name = name;
8527 	}
8528     }
8529 }
8530 
8531 /*
8532  * Set foreground color for "Normal" group.  Called by gui_set_fg_color()
8533  * when the color is known.
8534  */
8535     void
8536 hl_set_fg_color_name(
8537     char_u  *name)	    /* must have been allocated */
8538 {
8539     int	    id;
8540 
8541     if (name != NULL)
8542     {
8543 	id = syn_name2id((char_u *)"Normal");
8544 	if (id > 0)
8545 	{
8546 	    vim_free(HL_TABLE()[id - 1].sg_gui_fg_name);
8547 	    HL_TABLE()[id - 1].sg_gui_fg_name = name;
8548 	}
8549     }
8550 }
8551 
8552 /*
8553  * Return the handle for a font name.
8554  * Returns NOFONT when failed.
8555  */
8556     static GuiFont
8557 font_name2handle(char_u *name)
8558 {
8559     if (STRCMP(name, "NONE") == 0)
8560 	return NOFONT;
8561 
8562     return gui_mch_get_font(name, TRUE);
8563 }
8564 
8565 # ifdef FEAT_XFONTSET
8566 /*
8567  * Return the handle for a fontset name.
8568  * Returns NOFONTSET when failed.
8569  */
8570     static GuiFontset
8571 fontset_name2handle(char_u *name, int fixed_width)
8572 {
8573     if (STRCMP(name, "NONE") == 0)
8574 	return NOFONTSET;
8575 
8576     return gui_mch_get_fontset(name, TRUE, fixed_width);
8577 }
8578 # endif
8579 
8580 /*
8581  * Get the font or fontset for one highlight group.
8582  */
8583     static void
8584 hl_do_font(
8585     int		idx,
8586     char_u	*arg,
8587     int		do_normal,		/* set normal font */
8588     int		do_menu UNUSED,		/* set menu font */
8589     int		do_tooltip UNUSED,	/* set tooltip font */
8590     int		free_font)		/* free current font/fontset */
8591 {
8592 # ifdef FEAT_XFONTSET
8593     /* If 'guifontset' is not empty, first try using the name as a
8594      * fontset.  If that doesn't work, use it as a font name. */
8595     if (*p_guifontset != NUL
8596 #  ifdef FONTSET_ALWAYS
8597 	|| do_menu
8598 #  endif
8599 #  ifdef FEAT_BEVAL_TIP
8600 	/* In Athena & Motif, the Tooltip highlight group is always a fontset */
8601 	|| do_tooltip
8602 #  endif
8603 	    )
8604     {
8605 	if (free_font)
8606 	    gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
8607 	HL_TABLE()[idx].sg_fontset = fontset_name2handle(arg, 0
8608 #  ifdef FONTSET_ALWAYS
8609 		|| do_menu
8610 #  endif
8611 #  ifdef FEAT_BEVAL_TIP
8612 		|| do_tooltip
8613 #  endif
8614 		);
8615     }
8616     if (HL_TABLE()[idx].sg_fontset != NOFONTSET)
8617     {
8618 	/* If it worked and it's the Normal group, use it as the normal
8619 	 * fontset.  Same for the Menu group. */
8620 	if (do_normal)
8621 	    gui_init_font(arg, TRUE);
8622 #   if (defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_ATHENA)) && defined(FEAT_MENU)
8623 	if (do_menu)
8624 	{
8625 #    ifdef FONTSET_ALWAYS
8626 	    gui.menu_fontset = HL_TABLE()[idx].sg_fontset;
8627 #    else
8628 	    /* YIKES!  This is a bug waiting to crash the program */
8629 	    gui.menu_font = HL_TABLE()[idx].sg_fontset;
8630 #    endif
8631 	    gui_mch_new_menu_font();
8632 	}
8633 #    ifdef FEAT_BEVAL_GUI
8634 	if (do_tooltip)
8635 	{
8636 	    /* The Athena widget set cannot currently handle switching between
8637 	     * displaying a single font and a fontset.
8638 	     * If the XtNinternational resource is set to True at widget
8639 	     * creation, then a fontset is always used, otherwise an
8640 	     * XFontStruct is used.
8641 	     */
8642 	    gui.tooltip_fontset = (XFontSet)HL_TABLE()[idx].sg_fontset;
8643 	    gui_mch_new_tooltip_font();
8644 	}
8645 #    endif
8646 #   endif
8647     }
8648     else
8649 # endif
8650     {
8651 	if (free_font)
8652 	    gui_mch_free_font(HL_TABLE()[idx].sg_font);
8653 	HL_TABLE()[idx].sg_font = font_name2handle(arg);
8654 	/* If it worked and it's the Normal group, use it as the
8655 	 * normal font.  Same for the Menu group. */
8656 	if (HL_TABLE()[idx].sg_font != NOFONT)
8657 	{
8658 	    if (do_normal)
8659 		gui_init_font(arg, FALSE);
8660 #ifndef FONTSET_ALWAYS
8661 # if (defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_ATHENA)) && defined(FEAT_MENU)
8662 	    if (do_menu)
8663 	    {
8664 		gui.menu_font = HL_TABLE()[idx].sg_font;
8665 		gui_mch_new_menu_font();
8666 	    }
8667 # endif
8668 #endif
8669 	}
8670     }
8671 }
8672 
8673 #endif /* FEAT_GUI */
8674 
8675 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
8676 /*
8677  * Return the handle for a color name.
8678  * Returns INVALCOLOR when failed.
8679  */
8680     guicolor_T
8681 color_name2handle(char_u *name)
8682 {
8683     if (STRCMP(name, "NONE") == 0)
8684 	return INVALCOLOR;
8685 
8686     if (STRICMP(name, "fg") == 0 || STRICMP(name, "foreground") == 0)
8687     {
8688 #if defined(FEAT_TERMGUICOLORS) && defined(FEAT_GUI)
8689 	if (gui.in_use)
8690 #endif
8691 #ifdef FEAT_GUI
8692 	    return gui.norm_pixel;
8693 #endif
8694 #ifdef FEAT_TERMGUICOLORS
8695 	if (cterm_normal_fg_gui_color != INVALCOLOR)
8696 	    return cterm_normal_fg_gui_color;
8697 	/* Guess that the foreground is black or white. */
8698 	return GUI_GET_COLOR((char_u *)(*p_bg == 'l' ? "black" : "white"));
8699 #endif
8700     }
8701     if (STRICMP(name, "bg") == 0 || STRICMP(name, "background") == 0)
8702     {
8703 #if defined(FEAT_TERMGUICOLORS) && defined(FEAT_GUI)
8704 	if (gui.in_use)
8705 #endif
8706 #ifdef FEAT_GUI
8707 	    return gui.back_pixel;
8708 #endif
8709 #ifdef FEAT_TERMGUICOLORS
8710 	if (cterm_normal_bg_gui_color != INVALCOLOR)
8711 	    return cterm_normal_bg_gui_color;
8712 	/* Guess that the background is white or black. */
8713 	return GUI_GET_COLOR((char_u *)(*p_bg == 'l' ? "white" : "black"));
8714 #endif
8715     }
8716 
8717     return GUI_GET_COLOR(name);
8718 }
8719 #endif
8720 
8721 /*
8722  * Table with the specifications for an attribute number.
8723  * Note that this table is used by ALL buffers.  This is required because the
8724  * GUI can redraw at any time for any buffer.
8725  */
8726 static garray_T	term_attr_table = {0, 0, 0, 0, NULL};
8727 
8728 #define TERM_ATTR_ENTRY(idx) ((attrentry_T *)term_attr_table.ga_data)[idx]
8729 
8730 static garray_T	cterm_attr_table = {0, 0, 0, 0, NULL};
8731 
8732 #define CTERM_ATTR_ENTRY(idx) ((attrentry_T *)cterm_attr_table.ga_data)[idx]
8733 
8734 #ifdef FEAT_GUI
8735 static garray_T	gui_attr_table = {0, 0, 0, 0, NULL};
8736 
8737 #define GUI_ATTR_ENTRY(idx) ((attrentry_T *)gui_attr_table.ga_data)[idx]
8738 #endif
8739 
8740 /*
8741  * Return the attr number for a set of colors and font.
8742  * Add a new entry to the term_attr_table, cterm_attr_table or gui_attr_table
8743  * if the combination is new.
8744  * Return 0 for error (no more room).
8745  */
8746     static int
8747 get_attr_entry(garray_T *table, attrentry_T *aep)
8748 {
8749     int		i;
8750     attrentry_T	*taep;
8751     static int	recursive = FALSE;
8752 
8753     /*
8754      * Init the table, in case it wasn't done yet.
8755      */
8756     table->ga_itemsize = sizeof(attrentry_T);
8757     table->ga_growsize = 7;
8758 
8759     /*
8760      * Try to find an entry with the same specifications.
8761      */
8762     for (i = 0; i < table->ga_len; ++i)
8763     {
8764 	taep = &(((attrentry_T *)table->ga_data)[i]);
8765 	if (	   aep->ae_attr == taep->ae_attr
8766 		&& (
8767 #ifdef FEAT_GUI
8768 		       (table == &gui_attr_table
8769 			&& (aep->ae_u.gui.fg_color == taep->ae_u.gui.fg_color
8770 			    && aep->ae_u.gui.bg_color
8771 						    == taep->ae_u.gui.bg_color
8772 			    && aep->ae_u.gui.sp_color
8773 						    == taep->ae_u.gui.sp_color
8774 			    && aep->ae_u.gui.font == taep->ae_u.gui.font
8775 #  ifdef FEAT_XFONTSET
8776 			    && aep->ae_u.gui.fontset == taep->ae_u.gui.fontset
8777 #  endif
8778 			    ))
8779 		    ||
8780 #endif
8781 		       (table == &term_attr_table
8782 			&& (aep->ae_u.term.start == NULL)
8783 					    == (taep->ae_u.term.start == NULL)
8784 			&& (aep->ae_u.term.start == NULL
8785 			    || STRCMP(aep->ae_u.term.start,
8786 						  taep->ae_u.term.start) == 0)
8787 			&& (aep->ae_u.term.stop == NULL)
8788 					     == (taep->ae_u.term.stop == NULL)
8789 			&& (aep->ae_u.term.stop == NULL
8790 			    || STRCMP(aep->ae_u.term.stop,
8791 						  taep->ae_u.term.stop) == 0))
8792 		    || (table == &cterm_attr_table
8793 			    && aep->ae_u.cterm.fg_color
8794 						  == taep->ae_u.cterm.fg_color
8795 			    && aep->ae_u.cterm.bg_color
8796 						  == taep->ae_u.cterm.bg_color
8797 #ifdef FEAT_TERMGUICOLORS
8798 			    && aep->ae_u.cterm.fg_rgb
8799 						    == taep->ae_u.cterm.fg_rgb
8800 			    && aep->ae_u.cterm.bg_rgb
8801 						    == taep->ae_u.cterm.bg_rgb
8802 #endif
8803 		       )))
8804 
8805 	return i + ATTR_OFF;
8806     }
8807 
8808     if (table->ga_len + ATTR_OFF > MAX_TYPENR)
8809     {
8810 	/*
8811 	 * Running out of attribute entries!  remove all attributes, and
8812 	 * compute new ones for all groups.
8813 	 * When called recursively, we are really out of numbers.
8814 	 */
8815 	if (recursive)
8816 	{
8817 	    emsg(_("E424: Too many different highlighting attributes in use"));
8818 	    return 0;
8819 	}
8820 	recursive = TRUE;
8821 
8822 	clear_hl_tables();
8823 
8824 	must_redraw = CLEAR;
8825 
8826 	for (i = 0; i < highlight_ga.ga_len; ++i)
8827 	    set_hl_attr(i);
8828 
8829 	recursive = FALSE;
8830     }
8831 
8832     /*
8833      * This is a new combination of colors and font, add an entry.
8834      */
8835     if (ga_grow(table, 1) == FAIL)
8836 	return 0;
8837 
8838     taep = &(((attrentry_T *)table->ga_data)[table->ga_len]);
8839     vim_memset(taep, 0, sizeof(attrentry_T));
8840     taep->ae_attr = aep->ae_attr;
8841 #ifdef FEAT_GUI
8842     if (table == &gui_attr_table)
8843     {
8844 	taep->ae_u.gui.fg_color = aep->ae_u.gui.fg_color;
8845 	taep->ae_u.gui.bg_color = aep->ae_u.gui.bg_color;
8846 	taep->ae_u.gui.sp_color = aep->ae_u.gui.sp_color;
8847 	taep->ae_u.gui.font = aep->ae_u.gui.font;
8848 # ifdef FEAT_XFONTSET
8849 	taep->ae_u.gui.fontset = aep->ae_u.gui.fontset;
8850 # endif
8851     }
8852 #endif
8853     if (table == &term_attr_table)
8854     {
8855 	if (aep->ae_u.term.start == NULL)
8856 	    taep->ae_u.term.start = NULL;
8857 	else
8858 	    taep->ae_u.term.start = vim_strsave(aep->ae_u.term.start);
8859 	if (aep->ae_u.term.stop == NULL)
8860 	    taep->ae_u.term.stop = NULL;
8861 	else
8862 	    taep->ae_u.term.stop = vim_strsave(aep->ae_u.term.stop);
8863     }
8864     else if (table == &cterm_attr_table)
8865     {
8866 	taep->ae_u.cterm.fg_color = aep->ae_u.cterm.fg_color;
8867 	taep->ae_u.cterm.bg_color = aep->ae_u.cterm.bg_color;
8868 #ifdef FEAT_TERMGUICOLORS
8869 	taep->ae_u.cterm.fg_rgb = aep->ae_u.cterm.fg_rgb;
8870 	taep->ae_u.cterm.bg_rgb = aep->ae_u.cterm.bg_rgb;
8871 #endif
8872     }
8873     ++table->ga_len;
8874     return (table->ga_len - 1 + ATTR_OFF);
8875 }
8876 
8877 #if defined(FEAT_TERMINAL) || defined(PROTO)
8878 /*
8879  * Get an attribute index for a cterm entry.
8880  * Uses an existing entry when possible or adds one when needed.
8881  */
8882     int
8883 get_cterm_attr_idx(int attr, int fg, int bg)
8884 {
8885     attrentry_T		at_en;
8886 
8887     vim_memset(&at_en, 0, sizeof(attrentry_T));
8888 #ifdef FEAT_TERMGUICOLORS
8889     at_en.ae_u.cterm.fg_rgb = INVALCOLOR;
8890     at_en.ae_u.cterm.bg_rgb = INVALCOLOR;
8891 #endif
8892     at_en.ae_attr = attr;
8893     at_en.ae_u.cterm.fg_color = fg;
8894     at_en.ae_u.cterm.bg_color = bg;
8895     return get_attr_entry(&cterm_attr_table, &at_en);
8896 }
8897 #endif
8898 
8899 #if (defined(FEAT_TERMINAL) && defined(FEAT_TERMGUICOLORS)) || defined(PROTO)
8900 /*
8901  * Get an attribute index for a 'termguicolors' entry.
8902  * Uses an existing entry when possible or adds one when needed.
8903  */
8904     int
8905 get_tgc_attr_idx(int attr, guicolor_T fg, guicolor_T bg)
8906 {
8907     attrentry_T		at_en;
8908 
8909     vim_memset(&at_en, 0, sizeof(attrentry_T));
8910     at_en.ae_attr = attr;
8911     if (fg == INVALCOLOR && bg == INVALCOLOR)
8912     {
8913 	/* If both GUI colors are not set fall back to the cterm colors.  Helps
8914 	 * if the GUI only has an attribute, such as undercurl. */
8915 	at_en.ae_u.cterm.fg_rgb = CTERMCOLOR;
8916 	at_en.ae_u.cterm.bg_rgb = CTERMCOLOR;
8917     }
8918     else
8919     {
8920 	at_en.ae_u.cterm.fg_rgb = fg;
8921 	at_en.ae_u.cterm.bg_rgb = bg;
8922     }
8923     return get_attr_entry(&cterm_attr_table, &at_en);
8924 }
8925 #endif
8926 
8927 #if (defined(FEAT_TERMINAL) && defined(FEAT_GUI)) || defined(PROTO)
8928 /*
8929  * Get an attribute index for a cterm entry.
8930  * Uses an existing entry when possible or adds one when needed.
8931  */
8932     int
8933 get_gui_attr_idx(int attr, guicolor_T fg, guicolor_T bg)
8934 {
8935     attrentry_T		at_en;
8936 
8937     vim_memset(&at_en, 0, sizeof(attrentry_T));
8938     at_en.ae_attr = attr;
8939     at_en.ae_u.gui.fg_color = fg;
8940     at_en.ae_u.gui.bg_color = bg;
8941     return get_attr_entry(&gui_attr_table, &at_en);
8942 }
8943 #endif
8944 
8945 /*
8946  * Clear all highlight tables.
8947  */
8948     void
8949 clear_hl_tables(void)
8950 {
8951     int		i;
8952     attrentry_T	*taep;
8953 
8954 #ifdef FEAT_GUI
8955     ga_clear(&gui_attr_table);
8956 #endif
8957     for (i = 0; i < term_attr_table.ga_len; ++i)
8958     {
8959 	taep = &(((attrentry_T *)term_attr_table.ga_data)[i]);
8960 	vim_free(taep->ae_u.term.start);
8961 	vim_free(taep->ae_u.term.stop);
8962     }
8963     ga_clear(&term_attr_table);
8964     ga_clear(&cterm_attr_table);
8965 }
8966 
8967 /*
8968  * Combine special attributes (e.g., for spelling) with other attributes
8969  * (e.g., for syntax highlighting).
8970  * "prim_attr" overrules "char_attr".
8971  * This creates a new group when required.
8972  * Since we expect there to be few spelling mistakes we don't cache the
8973  * result.
8974  * Return the resulting attributes.
8975  */
8976     int
8977 hl_combine_attr(int char_attr, int prim_attr)
8978 {
8979     attrentry_T *char_aep = NULL;
8980     attrentry_T *spell_aep;
8981     attrentry_T new_en;
8982 
8983     if (char_attr == 0)
8984 	return prim_attr;
8985     if (char_attr <= HL_ALL && prim_attr <= HL_ALL)
8986 	return ATTR_COMBINE(char_attr, prim_attr);
8987 #ifdef FEAT_GUI
8988     if (gui.in_use)
8989     {
8990 	if (char_attr > HL_ALL)
8991 	    char_aep = syn_gui_attr2entry(char_attr);
8992 	if (char_aep != NULL)
8993 	    new_en = *char_aep;
8994 	else
8995 	{
8996 	    vim_memset(&new_en, 0, sizeof(new_en));
8997 	    new_en.ae_u.gui.fg_color = INVALCOLOR;
8998 	    new_en.ae_u.gui.bg_color = INVALCOLOR;
8999 	    new_en.ae_u.gui.sp_color = INVALCOLOR;
9000 	    if (char_attr <= HL_ALL)
9001 		new_en.ae_attr = char_attr;
9002 	}
9003 
9004 	if (prim_attr <= HL_ALL)
9005 	    new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, prim_attr);
9006 	else
9007 	{
9008 	    spell_aep = syn_gui_attr2entry(prim_attr);
9009 	    if (spell_aep != NULL)
9010 	    {
9011 		new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr,
9012 							   spell_aep->ae_attr);
9013 		if (spell_aep->ae_u.gui.fg_color != INVALCOLOR)
9014 		    new_en.ae_u.gui.fg_color = spell_aep->ae_u.gui.fg_color;
9015 		if (spell_aep->ae_u.gui.bg_color != INVALCOLOR)
9016 		    new_en.ae_u.gui.bg_color = spell_aep->ae_u.gui.bg_color;
9017 		if (spell_aep->ae_u.gui.sp_color != INVALCOLOR)
9018 		    new_en.ae_u.gui.sp_color = spell_aep->ae_u.gui.sp_color;
9019 		if (spell_aep->ae_u.gui.font != NOFONT)
9020 		    new_en.ae_u.gui.font = spell_aep->ae_u.gui.font;
9021 # ifdef FEAT_XFONTSET
9022 		if (spell_aep->ae_u.gui.fontset != NOFONTSET)
9023 		    new_en.ae_u.gui.fontset = spell_aep->ae_u.gui.fontset;
9024 # endif
9025 	    }
9026 	}
9027 	return get_attr_entry(&gui_attr_table, &new_en);
9028     }
9029 #endif
9030 
9031     if (IS_CTERM)
9032     {
9033 	if (char_attr > HL_ALL)
9034 	    char_aep = syn_cterm_attr2entry(char_attr);
9035 	if (char_aep != NULL)
9036 	    new_en = *char_aep;
9037 	else
9038 	{
9039 	    vim_memset(&new_en, 0, sizeof(new_en));
9040 #ifdef FEAT_TERMGUICOLORS
9041 	    new_en.ae_u.cterm.bg_rgb = INVALCOLOR;
9042 	    new_en.ae_u.cterm.fg_rgb = INVALCOLOR;
9043 #endif
9044 	    if (char_attr <= HL_ALL)
9045 		new_en.ae_attr = char_attr;
9046 	}
9047 
9048 	if (prim_attr <= HL_ALL)
9049 		new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, prim_attr);
9050 	else
9051 	{
9052 	    spell_aep = syn_cterm_attr2entry(prim_attr);
9053 	    if (spell_aep != NULL)
9054 	    {
9055 		new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr,
9056 							   spell_aep->ae_attr);
9057 		if (spell_aep->ae_u.cterm.fg_color > 0)
9058 		    new_en.ae_u.cterm.fg_color = spell_aep->ae_u.cterm.fg_color;
9059 		if (spell_aep->ae_u.cterm.bg_color > 0)
9060 		    new_en.ae_u.cterm.bg_color = spell_aep->ae_u.cterm.bg_color;
9061 #ifdef FEAT_TERMGUICOLORS
9062 		/* If both fg and bg are not set fall back to cterm colors.
9063 		 * Helps for SpellBad which uses undercurl in the GUI. */
9064 		if (COLOR_INVALID(spell_aep->ae_u.cterm.fg_rgb)
9065 			&& COLOR_INVALID(spell_aep->ae_u.cterm.bg_rgb))
9066 		{
9067 		    if (spell_aep->ae_u.cterm.fg_color > 0)
9068 			new_en.ae_u.cterm.fg_rgb = CTERMCOLOR;
9069 		    if (spell_aep->ae_u.cterm.bg_color > 0)
9070 			new_en.ae_u.cterm.bg_rgb = CTERMCOLOR;
9071 		}
9072 		else
9073 		{
9074 		    if (spell_aep->ae_u.cterm.fg_rgb != INVALCOLOR)
9075 			new_en.ae_u.cterm.fg_rgb = spell_aep->ae_u.cterm.fg_rgb;
9076 		    if (spell_aep->ae_u.cterm.bg_rgb != INVALCOLOR)
9077 			new_en.ae_u.cterm.bg_rgb = spell_aep->ae_u.cterm.bg_rgb;
9078 		}
9079 #endif
9080 	    }
9081 	}
9082 	return get_attr_entry(&cterm_attr_table, &new_en);
9083     }
9084 
9085     if (char_attr > HL_ALL)
9086 	char_aep = syn_term_attr2entry(char_attr);
9087     if (char_aep != NULL)
9088 	new_en = *char_aep;
9089     else
9090     {
9091 	vim_memset(&new_en, 0, sizeof(new_en));
9092 	if (char_attr <= HL_ALL)
9093 	    new_en.ae_attr = char_attr;
9094     }
9095 
9096     if (prim_attr <= HL_ALL)
9097 	new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, prim_attr);
9098     else
9099     {
9100 	spell_aep = syn_term_attr2entry(prim_attr);
9101 	if (spell_aep != NULL)
9102 	{
9103 	    new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, spell_aep->ae_attr);
9104 	    if (spell_aep->ae_u.term.start != NULL)
9105 	    {
9106 		new_en.ae_u.term.start = spell_aep->ae_u.term.start;
9107 		new_en.ae_u.term.stop = spell_aep->ae_u.term.stop;
9108 	    }
9109 	}
9110     }
9111     return get_attr_entry(&term_attr_table, &new_en);
9112 }
9113 
9114 #ifdef FEAT_GUI
9115 
9116     attrentry_T *
9117 syn_gui_attr2entry(int attr)
9118 {
9119     attr -= ATTR_OFF;
9120     if (attr >= gui_attr_table.ga_len)	    /* did ":syntax clear" */
9121 	return NULL;
9122     return &(GUI_ATTR_ENTRY(attr));
9123 }
9124 #endif /* FEAT_GUI */
9125 
9126 /*
9127  * Get the highlight attributes (HL_BOLD etc.) from an attribute nr.
9128  * Only to be used when "attr" > HL_ALL.
9129  */
9130     int
9131 syn_attr2attr(int attr)
9132 {
9133     attrentry_T	*aep;
9134 
9135 #ifdef FEAT_GUI
9136     if (gui.in_use)
9137 	aep = syn_gui_attr2entry(attr);
9138     else
9139 #endif
9140 	if (IS_CTERM)
9141 	    aep = syn_cterm_attr2entry(attr);
9142 	else
9143 	    aep = syn_term_attr2entry(attr);
9144 
9145     if (aep == NULL)	    /* highlighting not set */
9146 	return 0;
9147     return aep->ae_attr;
9148 }
9149 
9150 
9151     attrentry_T *
9152 syn_term_attr2entry(int attr)
9153 {
9154     attr -= ATTR_OFF;
9155     if (attr >= term_attr_table.ga_len)	    /* did ":syntax clear" */
9156 	return NULL;
9157     return &(TERM_ATTR_ENTRY(attr));
9158 }
9159 
9160     attrentry_T *
9161 syn_cterm_attr2entry(int attr)
9162 {
9163     attr -= ATTR_OFF;
9164     if (attr >= cterm_attr_table.ga_len)	/* did ":syntax clear" */
9165 	return NULL;
9166     return &(CTERM_ATTR_ENTRY(attr));
9167 }
9168 
9169 #define LIST_ATTR   1
9170 #define LIST_STRING 2
9171 #define LIST_INT    3
9172 
9173     static void
9174 highlight_list_one(int id)
9175 {
9176     struct hl_group	*sgp;
9177     int			didh = FALSE;
9178 
9179     sgp = &HL_TABLE()[id - 1];	    // index is ID minus one
9180 
9181     if (message_filtered(sgp->sg_name))
9182 	return;
9183 
9184     didh = highlight_list_arg(id, didh, LIST_ATTR,
9185 				    sgp->sg_term, NULL, "term");
9186     didh = highlight_list_arg(id, didh, LIST_STRING,
9187 				    0, sgp->sg_start, "start");
9188     didh = highlight_list_arg(id, didh, LIST_STRING,
9189 				    0, sgp->sg_stop, "stop");
9190 
9191     didh = highlight_list_arg(id, didh, LIST_ATTR,
9192 				    sgp->sg_cterm, NULL, "cterm");
9193     didh = highlight_list_arg(id, didh, LIST_INT,
9194 				    sgp->sg_cterm_fg, NULL, "ctermfg");
9195     didh = highlight_list_arg(id, didh, LIST_INT,
9196 				    sgp->sg_cterm_bg, NULL, "ctermbg");
9197 
9198 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
9199     didh = highlight_list_arg(id, didh, LIST_ATTR,
9200 				    sgp->sg_gui, NULL, "gui");
9201     didh = highlight_list_arg(id, didh, LIST_STRING,
9202 				    0, sgp->sg_gui_fg_name, "guifg");
9203     didh = highlight_list_arg(id, didh, LIST_STRING,
9204 				    0, sgp->sg_gui_bg_name, "guibg");
9205     didh = highlight_list_arg(id, didh, LIST_STRING,
9206 				    0, sgp->sg_gui_sp_name, "guisp");
9207 #endif
9208 #ifdef FEAT_GUI
9209     didh = highlight_list_arg(id, didh, LIST_STRING,
9210 				    0, sgp->sg_font_name, "font");
9211 #endif
9212 
9213     if (sgp->sg_link && !got_int)
9214     {
9215 	(void)syn_list_header(didh, 9999, id);
9216 	didh = TRUE;
9217 	msg_puts_attr("links to", HL_ATTR(HLF_D));
9218 	msg_putchar(' ');
9219 	msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name);
9220     }
9221 
9222     if (!didh)
9223 	highlight_list_arg(id, didh, LIST_STRING, 0, (char_u *)"cleared", "");
9224 #ifdef FEAT_EVAL
9225     if (p_verbose > 0)
9226 	last_set_msg(sgp->sg_script_ctx);
9227 #endif
9228 }
9229 
9230     static int
9231 highlight_list_arg(
9232     int		id,
9233     int		didh,
9234     int		type,
9235     int		iarg,
9236     char_u	*sarg,
9237     char	*name)
9238 {
9239     char_u	buf[100];
9240     char_u	*ts;
9241     int		i;
9242 
9243     if (got_int)
9244 	return FALSE;
9245     if (type == LIST_STRING ? (sarg != NULL) : (iarg != 0))
9246     {
9247 	ts = buf;
9248 	if (type == LIST_INT)
9249 	    sprintf((char *)buf, "%d", iarg - 1);
9250 	else if (type == LIST_STRING)
9251 	    ts = sarg;
9252 	else /* type == LIST_ATTR */
9253 	{
9254 	    buf[0] = NUL;
9255 	    for (i = 0; hl_attr_table[i] != 0; ++i)
9256 	    {
9257 		if (iarg & hl_attr_table[i])
9258 		{
9259 		    if (buf[0] != NUL)
9260 			vim_strcat(buf, (char_u *)",", 100);
9261 		    vim_strcat(buf, (char_u *)hl_name_table[i], 100);
9262 		    iarg &= ~hl_attr_table[i];	    /* don't want "inverse" */
9263 		}
9264 	    }
9265 	}
9266 
9267 	(void)syn_list_header(didh,
9268 			       (int)(vim_strsize(ts) + STRLEN(name) + 1), id);
9269 	didh = TRUE;
9270 	if (!got_int)
9271 	{
9272 	    if (*name != NUL)
9273 	    {
9274 		msg_puts_attr(name, HL_ATTR(HLF_D));
9275 		msg_puts_attr("=", HL_ATTR(HLF_D));
9276 	    }
9277 	    msg_outtrans(ts);
9278 	}
9279     }
9280     return didh;
9281 }
9282 
9283 #if (((defined(FEAT_EVAL) || defined(FEAT_PRINTER))) && defined(FEAT_SYN_HL)) || defined(PROTO)
9284 /*
9285  * Return "1" if highlight group "id" has attribute "flag".
9286  * Return NULL otherwise.
9287  */
9288     char_u *
9289 highlight_has_attr(
9290     int		id,
9291     int		flag,
9292     int		modec)	/* 'g' for GUI, 'c' for cterm, 't' for term */
9293 {
9294     int		attr;
9295 
9296     if (id <= 0 || id > highlight_ga.ga_len)
9297 	return NULL;
9298 
9299 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
9300     if (modec == 'g')
9301 	attr = HL_TABLE()[id - 1].sg_gui;
9302     else
9303 #endif
9304 	 if (modec == 'c')
9305 	attr = HL_TABLE()[id - 1].sg_cterm;
9306     else
9307 	attr = HL_TABLE()[id - 1].sg_term;
9308 
9309     if (attr & flag)
9310 	return (char_u *)"1";
9311     return NULL;
9312 }
9313 #endif
9314 
9315 #if (defined(FEAT_SYN_HL) && defined(FEAT_EVAL)) || defined(PROTO)
9316 /*
9317  * Return color name of highlight group "id".
9318  */
9319     char_u *
9320 highlight_color(
9321     int		id,
9322     char_u	*what,	/* "font", "fg", "bg", "sp", "fg#", "bg#" or "sp#" */
9323     int		modec)	/* 'g' for GUI, 'c' for cterm, 't' for term */
9324 {
9325     static char_u	name[20];
9326     int			n;
9327     int			fg = FALSE;
9328     int			sp = FALSE;
9329     int			font = FALSE;
9330 
9331     if (id <= 0 || id > highlight_ga.ga_len)
9332 	return NULL;
9333 
9334     if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'g')
9335 	fg = TRUE;
9336     else if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'o'
9337 	     && TOLOWER_ASC(what[2]) == 'n' && TOLOWER_ASC(what[3]) == 't')
9338 	font = TRUE;
9339     else if (TOLOWER_ASC(what[0]) == 's' && TOLOWER_ASC(what[1]) == 'p')
9340 	sp = TRUE;
9341     else if (!(TOLOWER_ASC(what[0]) == 'b' && TOLOWER_ASC(what[1]) == 'g'))
9342 	return NULL;
9343     if (modec == 'g')
9344     {
9345 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
9346 #  ifdef FEAT_GUI
9347 	/* return font name */
9348 	if (font)
9349 	    return HL_TABLE()[id - 1].sg_font_name;
9350 #  endif
9351 
9352 	/* return #RRGGBB form (only possible when GUI is running) */
9353 	if ((USE_24BIT) && what[2] == '#')
9354 	{
9355 	    guicolor_T		color;
9356 	    long_u		rgb;
9357 	    static char_u	buf[10];
9358 
9359 	    if (fg)
9360 		color = HL_TABLE()[id - 1].sg_gui_fg;
9361 	    else if (sp)
9362 #  ifdef FEAT_GUI
9363 		color = HL_TABLE()[id - 1].sg_gui_sp;
9364 #  else
9365 		color = INVALCOLOR;
9366 #  endif
9367 	    else
9368 		color = HL_TABLE()[id - 1].sg_gui_bg;
9369 	    if (color == INVALCOLOR)
9370 		return NULL;
9371 	    rgb = (long_u)GUI_MCH_GET_RGB(color);
9372 	    sprintf((char *)buf, "#%02x%02x%02x",
9373 				      (unsigned)(rgb >> 16),
9374 				      (unsigned)(rgb >> 8) & 255,
9375 				      (unsigned)rgb & 255);
9376 	    return buf;
9377 	}
9378 # endif
9379 	if (fg)
9380 	    return (HL_TABLE()[id - 1].sg_gui_fg_name);
9381 	if (sp)
9382 	    return (HL_TABLE()[id - 1].sg_gui_sp_name);
9383 	return (HL_TABLE()[id - 1].sg_gui_bg_name);
9384     }
9385     if (font || sp)
9386 	return NULL;
9387     if (modec == 'c')
9388     {
9389 	if (fg)
9390 	    n = HL_TABLE()[id - 1].sg_cterm_fg - 1;
9391 	else
9392 	    n = HL_TABLE()[id - 1].sg_cterm_bg - 1;
9393 	if (n < 0)
9394 	    return NULL;
9395 	sprintf((char *)name, "%d", n);
9396 	return name;
9397     }
9398     /* term doesn't have color */
9399     return NULL;
9400 }
9401 #endif
9402 
9403 #if (defined(FEAT_SYN_HL) \
9404 	    && (defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)) \
9405 	&& defined(FEAT_PRINTER)) || defined(PROTO)
9406 /*
9407  * Return color name of highlight group "id" as RGB value.
9408  */
9409     long_u
9410 highlight_gui_color_rgb(
9411     int		id,
9412     int		fg)	/* TRUE = fg, FALSE = bg */
9413 {
9414     guicolor_T	color;
9415 
9416     if (id <= 0 || id > highlight_ga.ga_len)
9417 	return 0L;
9418 
9419     if (fg)
9420 	color = HL_TABLE()[id - 1].sg_gui_fg;
9421     else
9422 	color = HL_TABLE()[id - 1].sg_gui_bg;
9423 
9424     if (color == INVALCOLOR)
9425 	return 0L;
9426 
9427     return GUI_MCH_GET_RGB(color);
9428 }
9429 #endif
9430 
9431 /*
9432  * Output the syntax list header.
9433  * Return TRUE when started a new line.
9434  */
9435     static int
9436 syn_list_header(
9437     int	    did_header,		/* did header already */
9438     int	    outlen,		/* length of string that comes */
9439     int	    id)			/* highlight group id */
9440 {
9441     int	    endcol = 19;
9442     int	    newline = TRUE;
9443 
9444     if (!did_header)
9445     {
9446 	msg_putchar('\n');
9447 	if (got_int)
9448 	    return TRUE;
9449 	msg_outtrans(HL_TABLE()[id - 1].sg_name);
9450 	endcol = 15;
9451     }
9452     else if (msg_col + outlen + 1 >= Columns)
9453     {
9454 	msg_putchar('\n');
9455 	if (got_int)
9456 	    return TRUE;
9457     }
9458     else
9459     {
9460 	if (msg_col >= endcol)	/* wrap around is like starting a new line */
9461 	    newline = FALSE;
9462     }
9463 
9464     if (msg_col >= endcol)	/* output at least one space */
9465 	endcol = msg_col + 1;
9466     if (Columns <= endcol)	/* avoid hang for tiny window */
9467 	endcol = Columns - 1;
9468 
9469     msg_advance(endcol);
9470 
9471     /* Show "xxx" with the attributes. */
9472     if (!did_header)
9473     {
9474 	msg_puts_attr("xxx", syn_id2attr(id));
9475 	msg_putchar(' ');
9476     }
9477 
9478     return newline;
9479 }
9480 
9481 /*
9482  * Set the attribute numbers for a highlight group.
9483  * Called after one of the attributes has changed.
9484  */
9485     static void
9486 set_hl_attr(
9487     int		idx)	    /* index in array */
9488 {
9489     attrentry_T		at_en;
9490     struct hl_group	*sgp = HL_TABLE() + idx;
9491 
9492     /* The "Normal" group doesn't need an attribute number */
9493     if (sgp->sg_name_u != NULL && STRCMP(sgp->sg_name_u, "NORMAL") == 0)
9494 	return;
9495 
9496 #ifdef FEAT_GUI
9497     /*
9498      * For the GUI mode: If there are other than "normal" highlighting
9499      * attributes, need to allocate an attr number.
9500      */
9501     if (sgp->sg_gui_fg == INVALCOLOR
9502 	    && sgp->sg_gui_bg == INVALCOLOR
9503 	    && sgp->sg_gui_sp == INVALCOLOR
9504 	    && sgp->sg_font == NOFONT
9505 # ifdef FEAT_XFONTSET
9506 	    && sgp->sg_fontset == NOFONTSET
9507 # endif
9508 	    )
9509     {
9510 	sgp->sg_gui_attr = sgp->sg_gui;
9511     }
9512     else
9513     {
9514 	at_en.ae_attr = sgp->sg_gui;
9515 	at_en.ae_u.gui.fg_color = sgp->sg_gui_fg;
9516 	at_en.ae_u.gui.bg_color = sgp->sg_gui_bg;
9517 	at_en.ae_u.gui.sp_color = sgp->sg_gui_sp;
9518 	at_en.ae_u.gui.font = sgp->sg_font;
9519 # ifdef FEAT_XFONTSET
9520 	at_en.ae_u.gui.fontset = sgp->sg_fontset;
9521 # endif
9522 	sgp->sg_gui_attr = get_attr_entry(&gui_attr_table, &at_en);
9523     }
9524 #endif
9525     /*
9526      * For the term mode: If there are other than "normal" highlighting
9527      * attributes, need to allocate an attr number.
9528      */
9529     if (sgp->sg_start == NULL && sgp->sg_stop == NULL)
9530 	sgp->sg_term_attr = sgp->sg_term;
9531     else
9532     {
9533 	at_en.ae_attr = sgp->sg_term;
9534 	at_en.ae_u.term.start = sgp->sg_start;
9535 	at_en.ae_u.term.stop = sgp->sg_stop;
9536 	sgp->sg_term_attr = get_attr_entry(&term_attr_table, &at_en);
9537     }
9538 
9539     /*
9540      * For the color term mode: If there are other than "normal"
9541      * highlighting attributes, need to allocate an attr number.
9542      */
9543     if (sgp->sg_cterm_fg == 0 && sgp->sg_cterm_bg == 0
9544 # ifdef FEAT_TERMGUICOLORS
9545 	    && sgp->sg_gui_fg == INVALCOLOR
9546 	    && sgp->sg_gui_bg == INVALCOLOR
9547 # endif
9548 	    )
9549 	sgp->sg_cterm_attr = sgp->sg_cterm;
9550     else
9551     {
9552 	at_en.ae_attr = sgp->sg_cterm;
9553 	at_en.ae_u.cterm.fg_color = sgp->sg_cterm_fg;
9554 	at_en.ae_u.cterm.bg_color = sgp->sg_cterm_bg;
9555 # ifdef FEAT_TERMGUICOLORS
9556 #  ifdef MSWIN
9557 	{
9558 	    int id;
9559 	    guicolor_T fg, bg;
9560 
9561 	    id = syn_name2id((char_u *)"Normal");
9562 	    if (id > 0)
9563 	    {
9564 		syn_id2colors(id, &fg, &bg);
9565 		if (sgp->sg_gui_fg == INVALCOLOR)
9566 		    sgp->sg_gui_fg = fg;
9567 		if (sgp->sg_gui_bg == INVALCOLOR)
9568 		    sgp->sg_gui_bg = bg;
9569 	    }
9570 
9571 	}
9572 #  endif
9573 	at_en.ae_u.cterm.fg_rgb = GUI_MCH_GET_RGB2(sgp->sg_gui_fg);
9574 	at_en.ae_u.cterm.bg_rgb = GUI_MCH_GET_RGB2(sgp->sg_gui_bg);
9575 	if (at_en.ae_u.cterm.fg_rgb == INVALCOLOR
9576 		&& at_en.ae_u.cterm.bg_rgb == INVALCOLOR)
9577 	{
9578 	    /* If both fg and bg are invalid fall back to the cterm colors.
9579 	     * Helps when the GUI only uses an attribute, e.g. undercurl. */
9580 	    at_en.ae_u.cterm.fg_rgb = CTERMCOLOR;
9581 	    at_en.ae_u.cterm.bg_rgb = CTERMCOLOR;
9582 	}
9583 # endif
9584 	sgp->sg_cterm_attr = get_attr_entry(&cterm_attr_table, &at_en);
9585     }
9586 }
9587 
9588 /*
9589  * Lookup a highlight group name and return its ID.
9590  * If it is not found, 0 is returned.
9591  */
9592     int
9593 syn_name2id(char_u *name)
9594 {
9595     int		i;
9596     char_u	name_u[200];
9597 
9598     /* Avoid using stricmp() too much, it's slow on some systems */
9599     /* Avoid alloc()/free(), these are slow too.  ID names over 200 chars
9600      * don't deserve to be found! */
9601     vim_strncpy(name_u, name, 199);
9602     vim_strup(name_u);
9603     for (i = highlight_ga.ga_len; --i >= 0; )
9604 	if (HL_TABLE()[i].sg_name_u != NULL
9605 		&& STRCMP(name_u, HL_TABLE()[i].sg_name_u) == 0)
9606 	    break;
9607     return i + 1;
9608 }
9609 
9610 /*
9611  * Lookup a highlight group name and return its attributes.
9612  * Return zero if not found.
9613  */
9614     int
9615 syn_name2attr(char_u *name)
9616 {
9617     int id = syn_name2id(name);
9618 
9619     if (id != 0)
9620 	return syn_id2attr(id);
9621     return 0;
9622 }
9623 
9624 #if defined(FEAT_EVAL) || defined(PROTO)
9625 /*
9626  * Return TRUE if highlight group "name" exists.
9627  */
9628     int
9629 highlight_exists(char_u *name)
9630 {
9631     return (syn_name2id(name) > 0);
9632 }
9633 
9634 # if defined(FEAT_SEARCH_EXTRA) || defined(PROTO)
9635 /*
9636  * Return the name of highlight group "id".
9637  * When not a valid ID return an empty string.
9638  */
9639     char_u *
9640 syn_id2name(int id)
9641 {
9642     if (id <= 0 || id > highlight_ga.ga_len)
9643 	return (char_u *)"";
9644     return HL_TABLE()[id - 1].sg_name;
9645 }
9646 # endif
9647 #endif
9648 
9649 /*
9650  * Like syn_name2id(), but take a pointer + length argument.
9651  */
9652     int
9653 syn_namen2id(char_u *linep, int len)
9654 {
9655     char_u  *name;
9656     int	    id = 0;
9657 
9658     name = vim_strnsave(linep, len);
9659     if (name != NULL)
9660     {
9661 	id = syn_name2id(name);
9662 	vim_free(name);
9663     }
9664     return id;
9665 }
9666 
9667 /*
9668  * Find highlight group name in the table and return its ID.
9669  * The argument is a pointer to the name and the length of the name.
9670  * If it doesn't exist yet, a new entry is created.
9671  * Return 0 for failure.
9672  */
9673     int
9674 syn_check_group(char_u *pp, int len)
9675 {
9676     int	    id;
9677     char_u  *name;
9678 
9679     name = vim_strnsave(pp, len);
9680     if (name == NULL)
9681 	return 0;
9682 
9683     id = syn_name2id(name);
9684     if (id == 0)			/* doesn't exist yet */
9685 	id = syn_add_group(name);
9686     else
9687 	vim_free(name);
9688     return id;
9689 }
9690 
9691 /*
9692  * Add new highlight group and return its ID.
9693  * "name" must be an allocated string, it will be consumed.
9694  * Return 0 for failure.
9695  */
9696     static int
9697 syn_add_group(char_u *name)
9698 {
9699     char_u	*p;
9700 
9701     /* Check that the name is ASCII letters, digits and underscore. */
9702     for (p = name; *p != NUL; ++p)
9703     {
9704 	if (!vim_isprintc(*p))
9705 	{
9706 	    emsg(_("E669: Unprintable character in group name"));
9707 	    vim_free(name);
9708 	    return 0;
9709 	}
9710 	else if (!ASCII_ISALNUM(*p) && *p != '_')
9711 	{
9712 	    /* This is an error, but since there previously was no check only
9713 	     * give a warning. */
9714 	    msg_source(HL_ATTR(HLF_W));
9715 	    msg(_("W18: Invalid character in group name"));
9716 	    break;
9717 	}
9718     }
9719 
9720     /*
9721      * First call for this growarray: init growing array.
9722      */
9723     if (highlight_ga.ga_data == NULL)
9724     {
9725 	highlight_ga.ga_itemsize = sizeof(struct hl_group);
9726 	highlight_ga.ga_growsize = 10;
9727     }
9728 
9729     if (highlight_ga.ga_len >= MAX_HL_ID)
9730     {
9731 	emsg(_("E849: Too many highlight and syntax groups"));
9732 	vim_free(name);
9733 	return 0;
9734     }
9735 
9736     /*
9737      * Make room for at least one other syntax_highlight entry.
9738      */
9739     if (ga_grow(&highlight_ga, 1) == FAIL)
9740     {
9741 	vim_free(name);
9742 	return 0;
9743     }
9744 
9745     vim_memset(&(HL_TABLE()[highlight_ga.ga_len]), 0, sizeof(struct hl_group));
9746     HL_TABLE()[highlight_ga.ga_len].sg_name = name;
9747     HL_TABLE()[highlight_ga.ga_len].sg_name_u = vim_strsave_up(name);
9748 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
9749     HL_TABLE()[highlight_ga.ga_len].sg_gui_bg = INVALCOLOR;
9750     HL_TABLE()[highlight_ga.ga_len].sg_gui_fg = INVALCOLOR;
9751 # ifdef FEAT_GUI
9752     HL_TABLE()[highlight_ga.ga_len].sg_gui_sp = INVALCOLOR;
9753 # endif
9754 #endif
9755     ++highlight_ga.ga_len;
9756 
9757     return highlight_ga.ga_len;		    /* ID is index plus one */
9758 }
9759 
9760 /*
9761  * When, just after calling syn_add_group(), an error is discovered, this
9762  * function deletes the new name.
9763  */
9764     static void
9765 syn_unadd_group(void)
9766 {
9767     --highlight_ga.ga_len;
9768     vim_free(HL_TABLE()[highlight_ga.ga_len].sg_name);
9769     vim_free(HL_TABLE()[highlight_ga.ga_len].sg_name_u);
9770 }
9771 
9772 /*
9773  * Translate a group ID to highlight attributes.
9774  */
9775     int
9776 syn_id2attr(int hl_id)
9777 {
9778     int			attr;
9779     struct hl_group	*sgp;
9780 
9781     hl_id = syn_get_final_id(hl_id);
9782     sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
9783 
9784 #ifdef FEAT_GUI
9785     /*
9786      * Only use GUI attr when the GUI is being used.
9787      */
9788     if (gui.in_use)
9789 	attr = sgp->sg_gui_attr;
9790     else
9791 #endif
9792 	if (IS_CTERM)
9793 	    attr = sgp->sg_cterm_attr;
9794 	else
9795 	    attr = sgp->sg_term_attr;
9796 
9797     return attr;
9798 }
9799 
9800 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
9801 /*
9802  * Get the GUI colors and attributes for a group ID.
9803  * NOTE: the colors will be INVALCOLOR when not set, the color otherwise.
9804  */
9805     int
9806 syn_id2colors(int hl_id, guicolor_T *fgp, guicolor_T *bgp)
9807 {
9808     struct hl_group	*sgp;
9809 
9810     hl_id = syn_get_final_id(hl_id);
9811     sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
9812 
9813     *fgp = sgp->sg_gui_fg;
9814     *bgp = sgp->sg_gui_bg;
9815     return sgp->sg_gui;
9816 }
9817 #endif
9818 
9819 #if (defined(MSWIN) \
9820 	&& (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL)) \
9821 	&& defined(FEAT_TERMGUICOLORS)) || defined(PROTO)
9822     void
9823 syn_id2cterm_bg(int hl_id, int *fgp, int *bgp)
9824 {
9825     struct hl_group	*sgp;
9826 
9827     hl_id = syn_get_final_id(hl_id);
9828     sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
9829     *fgp = sgp->sg_cterm_fg - 1;
9830     *bgp = sgp->sg_cterm_bg - 1;
9831 }
9832 #endif
9833 
9834 /*
9835  * Translate a group ID to the final group ID (following links).
9836  */
9837     int
9838 syn_get_final_id(int hl_id)
9839 {
9840     int			count;
9841     struct hl_group	*sgp;
9842 
9843     if (hl_id > highlight_ga.ga_len || hl_id < 1)
9844 	return 0;			/* Can be called from eval!! */
9845 
9846     /*
9847      * Follow links until there is no more.
9848      * Look out for loops!  Break after 100 links.
9849      */
9850     for (count = 100; --count >= 0; )
9851     {
9852 	sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
9853 	if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len)
9854 	    break;
9855 	hl_id = sgp->sg_link;
9856     }
9857 
9858     return hl_id;
9859 }
9860 
9861 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
9862 /*
9863  * Call this function just after the GUI has started.
9864  * Also called when 'termguicolors' was set, gui.in_use will be FALSE then.
9865  * It finds the font and color handles for the highlighting groups.
9866  */
9867     void
9868 highlight_gui_started(void)
9869 {
9870     int	    idx;
9871 
9872     /* First get the colors from the "Normal" and "Menu" group, if set */
9873     if (USE_24BIT)
9874 	set_normal_colors();
9875 
9876     for (idx = 0; idx < highlight_ga.ga_len; ++idx)
9877 	gui_do_one_color(idx, FALSE, FALSE);
9878 
9879     highlight_changed();
9880 }
9881 
9882     static void
9883 gui_do_one_color(
9884     int		idx,
9885     int		do_menu UNUSED,	   /* TRUE: might set the menu font */
9886     int		do_tooltip UNUSED) /* TRUE: might set the tooltip font */
9887 {
9888     int		didit = FALSE;
9889 
9890 # ifdef FEAT_GUI
9891 #  ifdef FEAT_TERMGUICOLORS
9892     if (gui.in_use)
9893 #  endif
9894 	if (HL_TABLE()[idx].sg_font_name != NULL)
9895 	{
9896 	    hl_do_font(idx, HL_TABLE()[idx].sg_font_name, FALSE, do_menu,
9897 							    do_tooltip, TRUE);
9898 	    didit = TRUE;
9899 	}
9900 # endif
9901     if (HL_TABLE()[idx].sg_gui_fg_name != NULL)
9902     {
9903 	HL_TABLE()[idx].sg_gui_fg =
9904 			    color_name2handle(HL_TABLE()[idx].sg_gui_fg_name);
9905 	didit = TRUE;
9906     }
9907     if (HL_TABLE()[idx].sg_gui_bg_name != NULL)
9908     {
9909 	HL_TABLE()[idx].sg_gui_bg =
9910 			    color_name2handle(HL_TABLE()[idx].sg_gui_bg_name);
9911 	didit = TRUE;
9912     }
9913 # ifdef FEAT_GUI
9914     if (HL_TABLE()[idx].sg_gui_sp_name != NULL)
9915     {
9916 	HL_TABLE()[idx].sg_gui_sp =
9917 			    color_name2handle(HL_TABLE()[idx].sg_gui_sp_name);
9918 	didit = TRUE;
9919     }
9920 # endif
9921     if (didit)	/* need to get a new attr number */
9922 	set_hl_attr(idx);
9923 }
9924 #endif
9925 
9926 #if defined(USER_HIGHLIGHT) && defined(FEAT_STL_OPT)
9927 /*
9928  * Apply difference between User[1-9] and HLF_S to HLF_SNC, HLF_ST or HLF_STNC.
9929  */
9930     static void
9931 combine_stl_hlt(
9932 	int id,
9933 	int id_S,
9934 	int id_alt,
9935 	int hlcnt,
9936 	int i,
9937 	int hlf,
9938 	int *table)
9939 {
9940     struct hl_group *hlt = HL_TABLE();
9941 
9942     if (id_alt == 0)
9943     {
9944 	vim_memset(&hlt[hlcnt + i], 0, sizeof(struct hl_group));
9945 	hlt[hlcnt + i].sg_term = highlight_attr[hlf];
9946 	hlt[hlcnt + i].sg_cterm = highlight_attr[hlf];
9947 #  if defined(FEAT_GUI) || defined(FEAT_EVAL)
9948 	hlt[hlcnt + i].sg_gui = highlight_attr[hlf];
9949 #  endif
9950     }
9951     else
9952 	mch_memmove(&hlt[hlcnt + i],
9953 		    &hlt[id_alt - 1],
9954 		    sizeof(struct hl_group));
9955     hlt[hlcnt + i].sg_link = 0;
9956 
9957     hlt[hlcnt + i].sg_term ^=
9958 	hlt[id - 1].sg_term ^ hlt[id_S - 1].sg_term;
9959     if (hlt[id - 1].sg_start != hlt[id_S - 1].sg_start)
9960 	hlt[hlcnt + i].sg_start = hlt[id - 1].sg_start;
9961     if (hlt[id - 1].sg_stop != hlt[id_S - 1].sg_stop)
9962 	hlt[hlcnt + i].sg_stop = hlt[id - 1].sg_stop;
9963     hlt[hlcnt + i].sg_cterm ^=
9964 	hlt[id - 1].sg_cterm ^ hlt[id_S - 1].sg_cterm;
9965     if (hlt[id - 1].sg_cterm_fg != hlt[id_S - 1].sg_cterm_fg)
9966 	hlt[hlcnt + i].sg_cterm_fg = hlt[id - 1].sg_cterm_fg;
9967     if (hlt[id - 1].sg_cterm_bg != hlt[id_S - 1].sg_cterm_bg)
9968 	hlt[hlcnt + i].sg_cterm_bg = hlt[id - 1].sg_cterm_bg;
9969 #  if defined(FEAT_GUI) || defined(FEAT_EVAL)
9970     hlt[hlcnt + i].sg_gui ^=
9971 	hlt[id - 1].sg_gui ^ hlt[id_S - 1].sg_gui;
9972 #  endif
9973 #  ifdef FEAT_GUI
9974     if (hlt[id - 1].sg_gui_fg != hlt[id_S - 1].sg_gui_fg)
9975 	hlt[hlcnt + i].sg_gui_fg = hlt[id - 1].sg_gui_fg;
9976     if (hlt[id - 1].sg_gui_bg != hlt[id_S - 1].sg_gui_bg)
9977 	hlt[hlcnt + i].sg_gui_bg = hlt[id - 1].sg_gui_bg;
9978     if (hlt[id - 1].sg_gui_sp != hlt[id_S - 1].sg_gui_sp)
9979 	hlt[hlcnt + i].sg_gui_sp = hlt[id - 1].sg_gui_sp;
9980     if (hlt[id - 1].sg_font != hlt[id_S - 1].sg_font)
9981 	hlt[hlcnt + i].sg_font = hlt[id - 1].sg_font;
9982 #   ifdef FEAT_XFONTSET
9983     if (hlt[id - 1].sg_fontset != hlt[id_S - 1].sg_fontset)
9984 	hlt[hlcnt + i].sg_fontset = hlt[id - 1].sg_fontset;
9985 #   endif
9986 #  endif
9987     highlight_ga.ga_len = hlcnt + i + 1;
9988     set_hl_attr(hlcnt + i);	/* At long last we can apply */
9989     table[i] = syn_id2attr(hlcnt + i + 1);
9990 }
9991 #endif
9992 
9993 /*
9994  * Translate the 'highlight' option into attributes in highlight_attr[] and
9995  * set up the user highlights User1..9.  If FEAT_STL_OPT is in use, a set of
9996  * corresponding highlights to use on top of HLF_SNC is computed.
9997  * Called only when the 'highlight' option has been changed and upon first
9998  * screen redraw after any :highlight command.
9999  * Return FAIL when an invalid flag is found in 'highlight'.  OK otherwise.
10000  */
10001     int
10002 highlight_changed(void)
10003 {
10004     int		hlf;
10005     int		i;
10006     char_u	*p;
10007     int		attr;
10008     char_u	*end;
10009     int		id;
10010 #ifdef USER_HIGHLIGHT
10011     char_u      userhl[30];  // use 30 to avoid compiler warning
10012 # ifdef FEAT_STL_OPT
10013     int		id_S = -1;
10014     int		id_SNC = 0;
10015 #  ifdef FEAT_TERMINAL
10016     int		id_ST = 0;
10017     int		id_STNC = 0;
10018 #  endif
10019     int		hlcnt;
10020 # endif
10021 #endif
10022     static int	hl_flags[HLF_COUNT] = HL_FLAGS;
10023 
10024     need_highlight_changed = FALSE;
10025 
10026     /*
10027      * Clear all attributes.
10028      */
10029     for (hlf = 0; hlf < (int)HLF_COUNT; ++hlf)
10030 	highlight_attr[hlf] = 0;
10031 
10032     /*
10033      * First set all attributes to their default value.
10034      * Then use the attributes from the 'highlight' option.
10035      */
10036     for (i = 0; i < 2; ++i)
10037     {
10038 	if (i)
10039 	    p = p_hl;
10040 	else
10041 	    p = get_highlight_default();
10042 	if (p == NULL)	    /* just in case */
10043 	    continue;
10044 
10045 	while (*p)
10046 	{
10047 	    for (hlf = 0; hlf < (int)HLF_COUNT; ++hlf)
10048 		if (hl_flags[hlf] == *p)
10049 		    break;
10050 	    ++p;
10051 	    if (hlf == (int)HLF_COUNT || *p == NUL)
10052 		return FAIL;
10053 
10054 	    /*
10055 	     * Allow several hl_flags to be combined, like "bu" for
10056 	     * bold-underlined.
10057 	     */
10058 	    attr = 0;
10059 	    for ( ; *p && *p != ','; ++p)	    /* parse upto comma */
10060 	    {
10061 		if (VIM_ISWHITE(*p))		    /* ignore white space */
10062 		    continue;
10063 
10064 		if (attr > HL_ALL)  /* Combination with ':' is not allowed. */
10065 		    return FAIL;
10066 
10067 		switch (*p)
10068 		{
10069 		    case 'b':	attr |= HL_BOLD;
10070 				break;
10071 		    case 'i':	attr |= HL_ITALIC;
10072 				break;
10073 		    case '-':
10074 		    case 'n':			    /* no highlighting */
10075 				break;
10076 		    case 'r':	attr |= HL_INVERSE;
10077 				break;
10078 		    case 's':	attr |= HL_STANDOUT;
10079 				break;
10080 		    case 'u':	attr |= HL_UNDERLINE;
10081 				break;
10082 		    case 'c':	attr |= HL_UNDERCURL;
10083 				break;
10084 		    case 't':	attr |= HL_STRIKETHROUGH;
10085 				break;
10086 		    case ':':	++p;		    /* highlight group name */
10087 				if (attr || *p == NUL)	 /* no combinations */
10088 				    return FAIL;
10089 				end = vim_strchr(p, ',');
10090 				if (end == NULL)
10091 				    end = p + STRLEN(p);
10092 				id = syn_check_group(p, (int)(end - p));
10093 				if (id == 0)
10094 				    return FAIL;
10095 				attr = syn_id2attr(id);
10096 				p = end - 1;
10097 #if defined(FEAT_STL_OPT) && defined(USER_HIGHLIGHT)
10098 				if (hlf == (int)HLF_SNC)
10099 				    id_SNC = syn_get_final_id(id);
10100 # ifdef FEAT_TERMINAL
10101 				else if (hlf == (int)HLF_ST)
10102 				    id_ST = syn_get_final_id(id);
10103 				else if (hlf == (int)HLF_STNC)
10104 				    id_STNC = syn_get_final_id(id);
10105 # endif
10106 				else if (hlf == (int)HLF_S)
10107 				    id_S = syn_get_final_id(id);
10108 #endif
10109 				break;
10110 		    default:	return FAIL;
10111 		}
10112 	    }
10113 	    highlight_attr[hlf] = attr;
10114 
10115 	    p = skip_to_option_part(p);	    /* skip comma and spaces */
10116 	}
10117     }
10118 
10119 #ifdef USER_HIGHLIGHT
10120     /* Setup the user highlights
10121      *
10122      * Temporarily utilize 28 more hl entries:
10123      * 9 for User1-User9 combined with StatusLineNC
10124      * 9 for User1-User9 combined with StatusLineTerm
10125      * 9 for User1-User9 combined with StatusLineTermNC
10126      * 1 for StatusLine default
10127      * Have to be in there simultaneously in case of table overflows in
10128      * get_attr_entry()
10129      */
10130 # ifdef FEAT_STL_OPT
10131     if (ga_grow(&highlight_ga, 28) == FAIL)
10132 	return FAIL;
10133     hlcnt = highlight_ga.ga_len;
10134     if (id_S == -1)
10135     {
10136 	/* Make sure id_S is always valid to simplify code below. Use the last
10137 	 * entry. */
10138 	vim_memset(&HL_TABLE()[hlcnt + 27], 0, sizeof(struct hl_group));
10139 	HL_TABLE()[hlcnt + 18].sg_term = highlight_attr[HLF_S];
10140 	id_S = hlcnt + 19;
10141     }
10142 # endif
10143     for (i = 0; i < 9; i++)
10144     {
10145 	sprintf((char *)userhl, "User%d", i + 1);
10146 	id = syn_name2id(userhl);
10147 	if (id == 0)
10148 	{
10149 	    highlight_user[i] = 0;
10150 # ifdef FEAT_STL_OPT
10151 	    highlight_stlnc[i] = 0;
10152 #  ifdef FEAT_TERMINAL
10153 	    highlight_stlterm[i] = 0;
10154 	    highlight_stltermnc[i] = 0;
10155 #  endif
10156 # endif
10157 	}
10158 	else
10159 	{
10160 	    highlight_user[i] = syn_id2attr(id);
10161 # ifdef FEAT_STL_OPT
10162 	    combine_stl_hlt(id, id_S, id_SNC, hlcnt, i,
10163 						     HLF_SNC, highlight_stlnc);
10164 #  ifdef FEAT_TERMINAL
10165 	    combine_stl_hlt(id, id_S, id_ST, hlcnt + 9, i,
10166 						    HLF_ST, highlight_stlterm);
10167 	    combine_stl_hlt(id, id_S, id_STNC, hlcnt + 18, i,
10168 						HLF_STNC, highlight_stltermnc);
10169 #  endif
10170 # endif
10171 	}
10172     }
10173 # ifdef FEAT_STL_OPT
10174     highlight_ga.ga_len = hlcnt;
10175 # endif
10176 
10177 #endif /* USER_HIGHLIGHT */
10178 
10179     return OK;
10180 }
10181 
10182 #if defined(FEAT_CMDL_COMPL) || defined(PROTO)
10183 
10184 static void highlight_list(void);
10185 static void highlight_list_two(int cnt, int attr);
10186 
10187 /*
10188  * Handle command line completion for :highlight command.
10189  */
10190     void
10191 set_context_in_highlight_cmd(expand_T *xp, char_u *arg)
10192 {
10193     char_u	*p;
10194 
10195     /* Default: expand group names */
10196     xp->xp_context = EXPAND_HIGHLIGHT;
10197     xp->xp_pattern = arg;
10198     include_link = 2;
10199     include_default = 1;
10200 
10201     /* (part of) subcommand already typed */
10202     if (*arg != NUL)
10203     {
10204 	p = skiptowhite(arg);
10205 	if (*p != NUL)			/* past "default" or group name */
10206 	{
10207 	    include_default = 0;
10208 	    if (STRNCMP("default", arg, p - arg) == 0)
10209 	    {
10210 		arg = skipwhite(p);
10211 		xp->xp_pattern = arg;
10212 		p = skiptowhite(arg);
10213 	    }
10214 	    if (*p != NUL)			/* past group name */
10215 	    {
10216 		include_link = 0;
10217 		if (arg[1] == 'i' && arg[0] == 'N')
10218 		    highlight_list();
10219 		if (STRNCMP("link", arg, p - arg) == 0
10220 			|| STRNCMP("clear", arg, p - arg) == 0)
10221 		{
10222 		    xp->xp_pattern = skipwhite(p);
10223 		    p = skiptowhite(xp->xp_pattern);
10224 		    if (*p != NUL)		/* past first group name */
10225 		    {
10226 			xp->xp_pattern = skipwhite(p);
10227 			p = skiptowhite(xp->xp_pattern);
10228 		    }
10229 		}
10230 		if (*p != NUL)			/* past group name(s) */
10231 		    xp->xp_context = EXPAND_NOTHING;
10232 	    }
10233 	}
10234     }
10235 }
10236 
10237 /*
10238  * List highlighting matches in a nice way.
10239  */
10240     static void
10241 highlight_list(void)
10242 {
10243     int		i;
10244 
10245     for (i = 10; --i >= 0; )
10246 	highlight_list_two(i, HL_ATTR(HLF_D));
10247     for (i = 40; --i >= 0; )
10248 	highlight_list_two(99, 0);
10249 }
10250 
10251     static void
10252 highlight_list_two(int cnt, int attr)
10253 {
10254     msg_puts_attr(&("N \bI \b!  \b"[cnt / 11]), attr);
10255     msg_clr_eos();
10256     out_flush();
10257     ui_delay(cnt == 99 ? 40L : (long)cnt * 50L, FALSE);
10258 }
10259 
10260 #endif /* FEAT_CMDL_COMPL */
10261 
10262 #if defined(FEAT_CMDL_COMPL) || (defined(FEAT_SYN_HL) && defined(FEAT_EVAL)) \
10263     || defined(FEAT_SIGNS) || defined(PROTO)
10264 /*
10265  * Function given to ExpandGeneric() to obtain the list of group names.
10266  */
10267     char_u *
10268 get_highlight_name(expand_T *xp UNUSED, int idx)
10269 {
10270     return get_highlight_name_ext(xp, idx, TRUE);
10271 }
10272 
10273 /*
10274  * Obtain a highlight group name.
10275  * When "skip_cleared" is TRUE don't return a cleared entry.
10276  */
10277     char_u *
10278 get_highlight_name_ext(expand_T *xp UNUSED, int idx, int skip_cleared)
10279 {
10280     if (idx < 0)
10281 	return NULL;
10282 
10283     /* Items are never removed from the table, skip the ones that were
10284      * cleared. */
10285     if (skip_cleared && idx < highlight_ga.ga_len && HL_TABLE()[idx].sg_cleared)
10286 	return (char_u *)"";
10287 
10288 #ifdef FEAT_CMDL_COMPL
10289     if (idx == highlight_ga.ga_len && include_none != 0)
10290 	return (char_u *)"none";
10291     if (idx == highlight_ga.ga_len + include_none && include_default != 0)
10292 	return (char_u *)"default";
10293     if (idx == highlight_ga.ga_len + include_none + include_default
10294 							 && include_link != 0)
10295 	return (char_u *)"link";
10296     if (idx == highlight_ga.ga_len + include_none + include_default + 1
10297 							 && include_link != 0)
10298 	return (char_u *)"clear";
10299 #endif
10300     if (idx >= highlight_ga.ga_len)
10301 	return NULL;
10302     return HL_TABLE()[idx].sg_name;
10303 }
10304 #endif
10305 
10306 #if defined(FEAT_GUI) || defined(PROTO)
10307 /*
10308  * Free all the highlight group fonts.
10309  * Used when quitting for systems which need it.
10310  */
10311     void
10312 free_highlight_fonts(void)
10313 {
10314     int	    idx;
10315 
10316     for (idx = 0; idx < highlight_ga.ga_len; ++idx)
10317     {
10318 	gui_mch_free_font(HL_TABLE()[idx].sg_font);
10319 	HL_TABLE()[idx].sg_font = NOFONT;
10320 # ifdef FEAT_XFONTSET
10321 	gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
10322 	HL_TABLE()[idx].sg_fontset = NOFONTSET;
10323 # endif
10324     }
10325 
10326     gui_mch_free_font(gui.norm_font);
10327 # ifdef FEAT_XFONTSET
10328     gui_mch_free_fontset(gui.fontset);
10329 # endif
10330 # ifndef FEAT_GUI_GTK
10331     gui_mch_free_font(gui.bold_font);
10332     gui_mch_free_font(gui.ital_font);
10333     gui_mch_free_font(gui.boldital_font);
10334 # endif
10335 }
10336 #endif
10337 
10338 /**************************************
10339  *  End of Highlighting stuff	      *
10340  **************************************/
10341