xref: /vim-8.2.3635/src/syntax.c (revision 577fadfc)
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 = (synstate_T *)alloc_clear((unsigned)(len * sizeof(synstate_T)));
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 = (keyentry_T *)alloc((int)(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 = (short *)alloc((unsigned)(2 * sizeof(short)));
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, &grp_list,
4768 			 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((unsigned)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 = (struct pat_ptr *)alloc((unsigned)sizeof(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 = (synpat_T *)alloc_clear((unsigned)sizeof(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 #ifdef __BORLANDC__
5352 _RTLENTRYF
5353 #endif
5354 syn_compare_stub(const void *v1, const void *v2)
5355 {
5356     const short	*s1 = v1;
5357     const short	*s2 = v2;
5358 
5359     return (*s1 > *s2 ? 1 : *s1 < *s2 ? -1 : 0);
5360 }
5361 
5362 /*
5363  * Combines lists of syntax clusters.
5364  * *clstr1 and *clstr2 must both be allocated memory; they will be consumed.
5365  */
5366     static void
5367 syn_combine_list(short **clstr1, short **clstr2, int list_op)
5368 {
5369     int		count1 = 0;
5370     int		count2 = 0;
5371     short	*g1;
5372     short	*g2;
5373     short	*clstr = NULL;
5374     int		count;
5375     int		round;
5376 
5377     /*
5378      * Handle degenerate cases.
5379      */
5380     if (*clstr2 == NULL)
5381 	return;
5382     if (*clstr1 == NULL || list_op == CLUSTER_REPLACE)
5383     {
5384 	if (list_op == CLUSTER_REPLACE)
5385 	    vim_free(*clstr1);
5386 	if (list_op == CLUSTER_REPLACE || list_op == CLUSTER_ADD)
5387 	    *clstr1 = *clstr2;
5388 	else
5389 	    vim_free(*clstr2);
5390 	return;
5391     }
5392 
5393     for (g1 = *clstr1; *g1; g1++)
5394 	++count1;
5395     for (g2 = *clstr2; *g2; g2++)
5396 	++count2;
5397 
5398     /*
5399      * For speed purposes, sort both lists.
5400      */
5401     qsort(*clstr1, (size_t)count1, sizeof(short), syn_compare_stub);
5402     qsort(*clstr2, (size_t)count2, sizeof(short), syn_compare_stub);
5403 
5404     /*
5405      * We proceed in two passes; in round 1, we count the elements to place
5406      * in the new list, and in round 2, we allocate and populate the new
5407      * list.  For speed, we use a mergesort-like method, adding the smaller
5408      * of the current elements in each list to the new list.
5409      */
5410     for (round = 1; round <= 2; round++)
5411     {
5412 	g1 = *clstr1;
5413 	g2 = *clstr2;
5414 	count = 0;
5415 
5416 	/*
5417 	 * First, loop through the lists until one of them is empty.
5418 	 */
5419 	while (*g1 && *g2)
5420 	{
5421 	    /*
5422 	     * We always want to add from the first list.
5423 	     */
5424 	    if (*g1 < *g2)
5425 	    {
5426 		if (round == 2)
5427 		    clstr[count] = *g1;
5428 		count++;
5429 		g1++;
5430 		continue;
5431 	    }
5432 	    /*
5433 	     * We only want to add from the second list if we're adding the
5434 	     * lists.
5435 	     */
5436 	    if (list_op == CLUSTER_ADD)
5437 	    {
5438 		if (round == 2)
5439 		    clstr[count] = *g2;
5440 		count++;
5441 	    }
5442 	    if (*g1 == *g2)
5443 		g1++;
5444 	    g2++;
5445 	}
5446 
5447 	/*
5448 	 * Now add the leftovers from whichever list didn't get finished
5449 	 * first.  As before, we only want to add from the second list if
5450 	 * we're adding the lists.
5451 	 */
5452 	for (; *g1; g1++, count++)
5453 	    if (round == 2)
5454 		clstr[count] = *g1;
5455 	if (list_op == CLUSTER_ADD)
5456 	    for (; *g2; g2++, count++)
5457 		if (round == 2)
5458 		    clstr[count] = *g2;
5459 
5460 	if (round == 1)
5461 	{
5462 	    /*
5463 	     * If the group ended up empty, we don't need to allocate any
5464 	     * space for it.
5465 	     */
5466 	    if (count == 0)
5467 	    {
5468 		clstr = NULL;
5469 		break;
5470 	    }
5471 	    clstr = (short *)alloc((unsigned)((count + 1) * sizeof(short)));
5472 	    if (clstr == NULL)
5473 		break;
5474 	    clstr[count] = 0;
5475 	}
5476     }
5477 
5478     /*
5479      * Finally, put the new list in place.
5480      */
5481     vim_free(*clstr1);
5482     vim_free(*clstr2);
5483     *clstr1 = clstr;
5484 }
5485 
5486 /*
5487  * Lookup a syntax cluster name and return its ID.
5488  * If it is not found, 0 is returned.
5489  */
5490     static int
5491 syn_scl_name2id(char_u *name)
5492 {
5493     int		i;
5494     char_u	*name_u;
5495 
5496     /* Avoid using stricmp() too much, it's slow on some systems */
5497     name_u = vim_strsave_up(name);
5498     if (name_u == NULL)
5499 	return 0;
5500     for (i = curwin->w_s->b_syn_clusters.ga_len; --i >= 0; )
5501 	if (SYN_CLSTR(curwin->w_s)[i].scl_name_u != NULL
5502 		&& STRCMP(name_u, SYN_CLSTR(curwin->w_s)[i].scl_name_u) == 0)
5503 	    break;
5504     vim_free(name_u);
5505     return (i < 0 ? 0 : i + SYNID_CLUSTER);
5506 }
5507 
5508 /*
5509  * Like syn_scl_name2id(), but take a pointer + length argument.
5510  */
5511     static int
5512 syn_scl_namen2id(char_u *linep, int len)
5513 {
5514     char_u  *name;
5515     int	    id = 0;
5516 
5517     name = vim_strnsave(linep, len);
5518     if (name != NULL)
5519     {
5520 	id = syn_scl_name2id(name);
5521 	vim_free(name);
5522     }
5523     return id;
5524 }
5525 
5526 /*
5527  * Find syntax cluster name in the table and return its ID.
5528  * The argument is a pointer to the name and the length of the name.
5529  * If it doesn't exist yet, a new entry is created.
5530  * Return 0 for failure.
5531  */
5532     static int
5533 syn_check_cluster(char_u *pp, int len)
5534 {
5535     int		id;
5536     char_u	*name;
5537 
5538     name = vim_strnsave(pp, len);
5539     if (name == NULL)
5540 	return 0;
5541 
5542     id = syn_scl_name2id(name);
5543     if (id == 0)			/* doesn't exist yet */
5544 	id = syn_add_cluster(name);
5545     else
5546 	vim_free(name);
5547     return id;
5548 }
5549 
5550 /*
5551  * Add new syntax cluster and return its ID.
5552  * "name" must be an allocated string, it will be consumed.
5553  * Return 0 for failure.
5554  */
5555     static int
5556 syn_add_cluster(char_u *name)
5557 {
5558     int		len;
5559 
5560     /*
5561      * First call for this growarray: init growing array.
5562      */
5563     if (curwin->w_s->b_syn_clusters.ga_data == NULL)
5564     {
5565 	curwin->w_s->b_syn_clusters.ga_itemsize = sizeof(syn_cluster_T);
5566 	curwin->w_s->b_syn_clusters.ga_growsize = 10;
5567     }
5568 
5569     len = curwin->w_s->b_syn_clusters.ga_len;
5570     if (len >= MAX_CLUSTER_ID)
5571     {
5572 	emsg(_("E848: Too many syntax clusters"));
5573 	vim_free(name);
5574 	return 0;
5575     }
5576 
5577     /*
5578      * Make room for at least one other cluster entry.
5579      */
5580     if (ga_grow(&curwin->w_s->b_syn_clusters, 1) == FAIL)
5581     {
5582 	vim_free(name);
5583 	return 0;
5584     }
5585 
5586     vim_memset(&(SYN_CLSTR(curwin->w_s)[len]), 0, sizeof(syn_cluster_T));
5587     SYN_CLSTR(curwin->w_s)[len].scl_name = name;
5588     SYN_CLSTR(curwin->w_s)[len].scl_name_u = vim_strsave_up(name);
5589     SYN_CLSTR(curwin->w_s)[len].scl_list = NULL;
5590     ++curwin->w_s->b_syn_clusters.ga_len;
5591 
5592     if (STRICMP(name, "Spell") == 0)
5593 	curwin->w_s->b_spell_cluster_id = len + SYNID_CLUSTER;
5594     if (STRICMP(name, "NoSpell") == 0)
5595 	curwin->w_s->b_nospell_cluster_id = len + SYNID_CLUSTER;
5596 
5597     return len + SYNID_CLUSTER;
5598 }
5599 
5600 /*
5601  * Handle ":syntax cluster {cluster-name} [contains={groupname},..]
5602  *		[add={groupname},..] [remove={groupname},..]".
5603  */
5604     static void
5605 syn_cmd_cluster(exarg_T *eap, int syncing UNUSED)
5606 {
5607     char_u	*arg = eap->arg;
5608     char_u	*group_name_end;
5609     char_u	*rest;
5610     int		scl_id;
5611     short	*clstr_list;
5612     int		got_clstr = FALSE;
5613     int		opt_len;
5614     int		list_op;
5615 
5616     eap->nextcmd = find_nextcmd(arg);
5617     if (eap->skip)
5618 	return;
5619 
5620     rest = get_group_name(arg, &group_name_end);
5621 
5622     if (rest != NULL)
5623     {
5624 	scl_id = syn_check_cluster(arg, (int)(group_name_end - arg));
5625 	if (scl_id == 0)
5626 	    return;
5627 	scl_id -= SYNID_CLUSTER;
5628 
5629 	for (;;)
5630 	{
5631 	    if (STRNICMP(rest, "add", 3) == 0
5632 		    && (VIM_ISWHITE(rest[3]) || rest[3] == '='))
5633 	    {
5634 		opt_len = 3;
5635 		list_op = CLUSTER_ADD;
5636 	    }
5637 	    else if (STRNICMP(rest, "remove", 6) == 0
5638 		    && (VIM_ISWHITE(rest[6]) || rest[6] == '='))
5639 	    {
5640 		opt_len = 6;
5641 		list_op = CLUSTER_SUBTRACT;
5642 	    }
5643 	    else if (STRNICMP(rest, "contains", 8) == 0
5644 			&& (VIM_ISWHITE(rest[8]) || rest[8] == '='))
5645 	    {
5646 		opt_len = 8;
5647 		list_op = CLUSTER_REPLACE;
5648 	    }
5649 	    else
5650 		break;
5651 
5652 	    clstr_list = NULL;
5653 	    if (get_id_list(&rest, opt_len, &clstr_list, eap->skip) == FAIL)
5654 	    {
5655 		semsg(_(e_invarg2), rest);
5656 		break;
5657 	    }
5658 	    if (scl_id >= 0)
5659 		syn_combine_list(&SYN_CLSTR(curwin->w_s)[scl_id].scl_list,
5660 			     &clstr_list, list_op);
5661 	    else
5662 		vim_free(clstr_list);
5663 	    got_clstr = TRUE;
5664 	}
5665 
5666 	if (got_clstr)
5667 	{
5668 	    redraw_curbuf_later(SOME_VALID);
5669 	    syn_stack_free_all(curwin->w_s);	/* Need to recompute all. */
5670 	}
5671     }
5672 
5673     if (!got_clstr)
5674 	emsg(_("E400: No cluster specified"));
5675     if (rest == NULL || !ends_excmd(*rest))
5676 	semsg(_(e_invarg2), arg);
5677 }
5678 
5679 /*
5680  * On first call for current buffer: Init growing array.
5681  */
5682     static void
5683 init_syn_patterns(void)
5684 {
5685     curwin->w_s->b_syn_patterns.ga_itemsize = sizeof(synpat_T);
5686     curwin->w_s->b_syn_patterns.ga_growsize = 10;
5687 }
5688 
5689 /*
5690  * Get one pattern for a ":syntax match" or ":syntax region" command.
5691  * Stores the pattern and program in a synpat_T.
5692  * Returns a pointer to the next argument, or NULL in case of an error.
5693  */
5694     static char_u *
5695 get_syn_pattern(char_u *arg, synpat_T *ci)
5696 {
5697     char_u	*end;
5698     int		*p;
5699     int		idx;
5700     char_u	*cpo_save;
5701 
5702     /* need at least three chars */
5703     if (arg == NULL || arg[0] == NUL || arg[1] == NUL || arg[2] == NUL)
5704 	return NULL;
5705 
5706     end = skip_regexp(arg + 1, *arg, TRUE, NULL);
5707     if (*end != *arg)			    /* end delimiter not found */
5708     {
5709 	semsg(_("E401: Pattern delimiter not found: %s"), arg);
5710 	return NULL;
5711     }
5712     /* store the pattern and compiled regexp program */
5713     if ((ci->sp_pattern = vim_strnsave(arg + 1, (int)(end - arg - 1))) == NULL)
5714 	return NULL;
5715 
5716     /* Make 'cpoptions' empty, to avoid the 'l' flag */
5717     cpo_save = p_cpo;
5718     p_cpo = (char_u *)"";
5719     ci->sp_prog = vim_regcomp(ci->sp_pattern, RE_MAGIC);
5720     p_cpo = cpo_save;
5721 
5722     if (ci->sp_prog == NULL)
5723 	return NULL;
5724     ci->sp_ic = curwin->w_s->b_syn_ic;
5725 #ifdef FEAT_PROFILE
5726     syn_clear_time(&ci->sp_time);
5727 #endif
5728 
5729     /*
5730      * Check for a match, highlight or region offset.
5731      */
5732     ++end;
5733     do
5734     {
5735 	for (idx = SPO_COUNT; --idx >= 0; )
5736 	    if (STRNCMP(end, spo_name_tab[idx], 3) == 0)
5737 		break;
5738 	if (idx >= 0)
5739 	{
5740 	    p = &(ci->sp_offsets[idx]);
5741 	    if (idx != SPO_LC_OFF)
5742 		switch (end[3])
5743 		{
5744 		    case 's':   break;
5745 		    case 'b':   break;
5746 		    case 'e':   idx += SPO_COUNT; break;
5747 		    default:    idx = -1; break;
5748 		}
5749 	    if (idx >= 0)
5750 	    {
5751 		ci->sp_off_flags |= (1 << idx);
5752 		if (idx == SPO_LC_OFF)	    /* lc=99 */
5753 		{
5754 		    end += 3;
5755 		    *p = getdigits(&end);
5756 
5757 		    /* "lc=" offset automatically sets "ms=" offset */
5758 		    if (!(ci->sp_off_flags & (1 << SPO_MS_OFF)))
5759 		    {
5760 			ci->sp_off_flags |= (1 << SPO_MS_OFF);
5761 			ci->sp_offsets[SPO_MS_OFF] = *p;
5762 		    }
5763 		}
5764 		else			    /* yy=x+99 */
5765 		{
5766 		    end += 4;
5767 		    if (*end == '+')
5768 		    {
5769 			++end;
5770 			*p = getdigits(&end);		/* positive offset */
5771 		    }
5772 		    else if (*end == '-')
5773 		    {
5774 			++end;
5775 			*p = -getdigits(&end);		/* negative offset */
5776 		    }
5777 		}
5778 		if (*end != ',')
5779 		    break;
5780 		++end;
5781 	    }
5782 	}
5783     } while (idx >= 0);
5784 
5785     if (!ends_excmd(*end) && !VIM_ISWHITE(*end))
5786     {
5787 	semsg(_("E402: Garbage after pattern: %s"), arg);
5788 	return NULL;
5789     }
5790     return skipwhite(end);
5791 }
5792 
5793 /*
5794  * Handle ":syntax sync .." command.
5795  */
5796     static void
5797 syn_cmd_sync(exarg_T *eap, int syncing UNUSED)
5798 {
5799     char_u	*arg_start = eap->arg;
5800     char_u	*arg_end;
5801     char_u	*key = NULL;
5802     char_u	*next_arg;
5803     int		illegal = FALSE;
5804     int		finished = FALSE;
5805     long	n;
5806     char_u	*cpo_save;
5807 
5808     if (ends_excmd(*arg_start))
5809     {
5810 	syn_cmd_list(eap, TRUE);
5811 	return;
5812     }
5813 
5814     while (!ends_excmd(*arg_start))
5815     {
5816 	arg_end = skiptowhite(arg_start);
5817 	next_arg = skipwhite(arg_end);
5818 	vim_free(key);
5819 	key = vim_strnsave_up(arg_start, (int)(arg_end - arg_start));
5820 	if (STRCMP(key, "CCOMMENT") == 0)
5821 	{
5822 	    if (!eap->skip)
5823 		curwin->w_s->b_syn_sync_flags |= SF_CCOMMENT;
5824 	    if (!ends_excmd(*next_arg))
5825 	    {
5826 		arg_end = skiptowhite(next_arg);
5827 		if (!eap->skip)
5828 		    curwin->w_s->b_syn_sync_id = syn_check_group(next_arg,
5829 						   (int)(arg_end - next_arg));
5830 		next_arg = skipwhite(arg_end);
5831 	    }
5832 	    else if (!eap->skip)
5833 		curwin->w_s->b_syn_sync_id = syn_name2id((char_u *)"Comment");
5834 	}
5835 	else if (  STRNCMP(key, "LINES", 5) == 0
5836 		|| STRNCMP(key, "MINLINES", 8) == 0
5837 		|| STRNCMP(key, "MAXLINES", 8) == 0
5838 		|| STRNCMP(key, "LINEBREAKS", 10) == 0)
5839 	{
5840 	    if (key[4] == 'S')
5841 		arg_end = key + 6;
5842 	    else if (key[0] == 'L')
5843 		arg_end = key + 11;
5844 	    else
5845 		arg_end = key + 9;
5846 	    if (arg_end[-1] != '=' || !VIM_ISDIGIT(*arg_end))
5847 	    {
5848 		illegal = TRUE;
5849 		break;
5850 	    }
5851 	    n = getdigits(&arg_end);
5852 	    if (!eap->skip)
5853 	    {
5854 		if (key[4] == 'B')
5855 		    curwin->w_s->b_syn_sync_linebreaks = n;
5856 		else if (key[1] == 'A')
5857 		    curwin->w_s->b_syn_sync_maxlines = n;
5858 		else
5859 		    curwin->w_s->b_syn_sync_minlines = n;
5860 	    }
5861 	}
5862 	else if (STRCMP(key, "FROMSTART") == 0)
5863 	{
5864 	    if (!eap->skip)
5865 	    {
5866 		curwin->w_s->b_syn_sync_minlines = MAXLNUM;
5867 		curwin->w_s->b_syn_sync_maxlines = 0;
5868 	    }
5869 	}
5870 	else if (STRCMP(key, "LINECONT") == 0)
5871 	{
5872 	    if (*next_arg == NUL)	   /* missing pattern */
5873 	    {
5874 		illegal = TRUE;
5875 		break;
5876 	    }
5877 	    if (curwin->w_s->b_syn_linecont_pat != NULL)
5878 	    {
5879 		emsg(_("E403: syntax sync: line continuations pattern specified twice"));
5880 		finished = TRUE;
5881 		break;
5882 	    }
5883 	    arg_end = skip_regexp(next_arg + 1, *next_arg, TRUE, NULL);
5884 	    if (*arg_end != *next_arg)	    /* end delimiter not found */
5885 	    {
5886 		illegal = TRUE;
5887 		break;
5888 	    }
5889 
5890 	    if (!eap->skip)
5891 	    {
5892 		/* store the pattern and compiled regexp program */
5893 		if ((curwin->w_s->b_syn_linecont_pat = vim_strnsave(next_arg + 1,
5894 				      (int)(arg_end - next_arg - 1))) == NULL)
5895 		{
5896 		    finished = TRUE;
5897 		    break;
5898 		}
5899 		curwin->w_s->b_syn_linecont_ic = curwin->w_s->b_syn_ic;
5900 
5901 		/* Make 'cpoptions' empty, to avoid the 'l' flag */
5902 		cpo_save = p_cpo;
5903 		p_cpo = (char_u *)"";
5904 		curwin->w_s->b_syn_linecont_prog =
5905 		       vim_regcomp(curwin->w_s->b_syn_linecont_pat, RE_MAGIC);
5906 		p_cpo = cpo_save;
5907 #ifdef FEAT_PROFILE
5908 		syn_clear_time(&curwin->w_s->b_syn_linecont_time);
5909 #endif
5910 
5911 		if (curwin->w_s->b_syn_linecont_prog == NULL)
5912 		{
5913 		    VIM_CLEAR(curwin->w_s->b_syn_linecont_pat);
5914 		    finished = TRUE;
5915 		    break;
5916 		}
5917 	    }
5918 	    next_arg = skipwhite(arg_end + 1);
5919 	}
5920 	else
5921 	{
5922 	    eap->arg = next_arg;
5923 	    if (STRCMP(key, "MATCH") == 0)
5924 		syn_cmd_match(eap, TRUE);
5925 	    else if (STRCMP(key, "REGION") == 0)
5926 		syn_cmd_region(eap, TRUE);
5927 	    else if (STRCMP(key, "CLEAR") == 0)
5928 		syn_cmd_clear(eap, TRUE);
5929 	    else
5930 		illegal = TRUE;
5931 	    finished = TRUE;
5932 	    break;
5933 	}
5934 	arg_start = next_arg;
5935     }
5936     vim_free(key);
5937     if (illegal)
5938 	semsg(_("E404: Illegal arguments: %s"), arg_start);
5939     else if (!finished)
5940     {
5941 	eap->nextcmd = check_nextcmd(arg_start);
5942 	redraw_curbuf_later(SOME_VALID);
5943 	syn_stack_free_all(curwin->w_s);	/* Need to recompute all syntax. */
5944     }
5945 }
5946 
5947 /*
5948  * Convert a line of highlight group names into a list of group ID numbers.
5949  * "arg" should point to the "contains" or "nextgroup" keyword.
5950  * "arg" is advanced to after the last group name.
5951  * Careful: the argument is modified (NULs added).
5952  * returns FAIL for some error, OK for success.
5953  */
5954     static int
5955 get_id_list(
5956     char_u	**arg,
5957     int		keylen,		/* length of keyword */
5958     short	**list,		/* where to store the resulting list, if not
5959 				   NULL, the list is silently skipped! */
5960     int		skip)
5961 {
5962     char_u	*p = NULL;
5963     char_u	*end;
5964     int		round;
5965     int		count;
5966     int		total_count = 0;
5967     short	*retval = NULL;
5968     char_u	*name;
5969     regmatch_T	regmatch;
5970     int		id;
5971     int		i;
5972     int		failed = FALSE;
5973 
5974     /*
5975      * We parse the list twice:
5976      * round == 1: count the number of items, allocate the array.
5977      * round == 2: fill the array with the items.
5978      * In round 1 new groups may be added, causing the number of items to
5979      * grow when a regexp is used.  In that case round 1 is done once again.
5980      */
5981     for (round = 1; round <= 2; ++round)
5982     {
5983 	/*
5984 	 * skip "contains"
5985 	 */
5986 	p = skipwhite(*arg + keylen);
5987 	if (*p != '=')
5988 	{
5989 	    semsg(_("E405: Missing equal sign: %s"), *arg);
5990 	    break;
5991 	}
5992 	p = skipwhite(p + 1);
5993 	if (ends_excmd(*p))
5994 	{
5995 	    semsg(_("E406: Empty argument: %s"), *arg);
5996 	    break;
5997 	}
5998 
5999 	/*
6000 	 * parse the arguments after "contains"
6001 	 */
6002 	count = 0;
6003 	while (!ends_excmd(*p))
6004 	{
6005 	    for (end = p; *end && !VIM_ISWHITE(*end) && *end != ','; ++end)
6006 		;
6007 	    name = alloc((int)(end - p + 3));	    /* leave room for "^$" */
6008 	    if (name == NULL)
6009 	    {
6010 		failed = TRUE;
6011 		break;
6012 	    }
6013 	    vim_strncpy(name + 1, p, end - p);
6014 	    if (       STRCMP(name + 1, "ALLBUT") == 0
6015 		    || STRCMP(name + 1, "ALL") == 0
6016 		    || STRCMP(name + 1, "TOP") == 0
6017 		    || STRCMP(name + 1, "CONTAINED") == 0)
6018 	    {
6019 		if (TOUPPER_ASC(**arg) != 'C')
6020 		{
6021 		    semsg(_("E407: %s not allowed here"), name + 1);
6022 		    failed = TRUE;
6023 		    vim_free(name);
6024 		    break;
6025 		}
6026 		if (count != 0)
6027 		{
6028 		    semsg(_("E408: %s must be first in contains list"),
6029 								     name + 1);
6030 		    failed = TRUE;
6031 		    vim_free(name);
6032 		    break;
6033 		}
6034 		if (name[1] == 'A')
6035 		    id = SYNID_ALLBUT;
6036 		else if (name[1] == 'T')
6037 		    id = SYNID_TOP;
6038 		else
6039 		    id = SYNID_CONTAINED;
6040 		id += current_syn_inc_tag;
6041 	    }
6042 	    else if (name[1] == '@')
6043 	    {
6044 		if (skip)
6045 		    id = -1;
6046 		else
6047 		    id = syn_check_cluster(name + 2, (int)(end - p - 1));
6048 	    }
6049 	    else
6050 	    {
6051 		/*
6052 		 * Handle full group name.
6053 		 */
6054 		if (vim_strpbrk(name + 1, (char_u *)"\\.*^$~[") == NULL)
6055 		    id = syn_check_group(name + 1, (int)(end - p));
6056 		else
6057 		{
6058 		    /*
6059 		     * Handle match of regexp with group names.
6060 		     */
6061 		    *name = '^';
6062 		    STRCAT(name, "$");
6063 		    regmatch.regprog = vim_regcomp(name, RE_MAGIC);
6064 		    if (regmatch.regprog == NULL)
6065 		    {
6066 			failed = TRUE;
6067 			vim_free(name);
6068 			break;
6069 		    }
6070 
6071 		    regmatch.rm_ic = TRUE;
6072 		    id = 0;
6073 		    for (i = highlight_ga.ga_len; --i >= 0; )
6074 		    {
6075 			if (vim_regexec(&regmatch, HL_TABLE()[i].sg_name,
6076 								  (colnr_T)0))
6077 			{
6078 			    if (round == 2)
6079 			    {
6080 				/* Got more items than expected; can happen
6081 				 * when adding items that match:
6082 				 * "contains=a.*b,axb".
6083 				 * Go back to first round */
6084 				if (count >= total_count)
6085 				{
6086 				    vim_free(retval);
6087 				    round = 1;
6088 				}
6089 				else
6090 				    retval[count] = i + 1;
6091 			    }
6092 			    ++count;
6093 			    id = -1;	    /* remember that we found one */
6094 			}
6095 		    }
6096 		    vim_regfree(regmatch.regprog);
6097 		}
6098 	    }
6099 	    vim_free(name);
6100 	    if (id == 0)
6101 	    {
6102 		semsg(_("E409: Unknown group name: %s"), p);
6103 		failed = TRUE;
6104 		break;
6105 	    }
6106 	    if (id > 0)
6107 	    {
6108 		if (round == 2)
6109 		{
6110 		    /* Got more items than expected, go back to first round */
6111 		    if (count >= total_count)
6112 		    {
6113 			vim_free(retval);
6114 			round = 1;
6115 		    }
6116 		    else
6117 			retval[count] = id;
6118 		}
6119 		++count;
6120 	    }
6121 	    p = skipwhite(end);
6122 	    if (*p != ',')
6123 		break;
6124 	    p = skipwhite(p + 1);	/* skip comma in between arguments */
6125 	}
6126 	if (failed)
6127 	    break;
6128 	if (round == 1)
6129 	{
6130 	    retval = (short *)alloc((unsigned)((count + 1) * sizeof(short)));
6131 	    if (retval == NULL)
6132 		break;
6133 	    retval[count] = 0;	    /* zero means end of the list */
6134 	    total_count = count;
6135 	}
6136     }
6137 
6138     *arg = p;
6139     if (failed || retval == NULL)
6140     {
6141 	vim_free(retval);
6142 	return FAIL;
6143     }
6144 
6145     if (*list == NULL)
6146 	*list = retval;
6147     else
6148 	vim_free(retval);	/* list already found, don't overwrite it */
6149 
6150     return OK;
6151 }
6152 
6153 /*
6154  * Make a copy of an ID list.
6155  */
6156     static short *
6157 copy_id_list(short *list)
6158 {
6159     int	    len;
6160     int	    count;
6161     short   *retval;
6162 
6163     if (list == NULL)
6164 	return NULL;
6165 
6166     for (count = 0; list[count]; ++count)
6167 	;
6168     len = (count + 1) * sizeof(short);
6169     retval = (short *)alloc((unsigned)len);
6170     if (retval != NULL)
6171 	mch_memmove(retval, list, (size_t)len);
6172 
6173     return retval;
6174 }
6175 
6176 /*
6177  * Check if syntax group "ssp" is in the ID list "list" of "cur_si".
6178  * "cur_si" can be NULL if not checking the "containedin" list.
6179  * Used to check if a syntax item is in the "contains" or "nextgroup" list of
6180  * the current item.
6181  * This function is called very often, keep it fast!!
6182  */
6183     static int
6184 in_id_list(
6185     stateitem_T	*cur_si,	/* current item or NULL */
6186     short	*list,		/* id list */
6187     struct sp_syn *ssp,		/* group id and ":syn include" tag of group */
6188     int		contained)	/* group id is contained */
6189 {
6190     int		retval;
6191     short	*scl_list;
6192     short	item;
6193     short	id = ssp->id;
6194     static int	depth = 0;
6195     int		r;
6196 
6197     /* If ssp has a "containedin" list and "cur_si" is in it, return TRUE. */
6198     if (cur_si != NULL && ssp->cont_in_list != NULL
6199 					    && !(cur_si->si_flags & HL_MATCH))
6200     {
6201 	/* Ignore transparent items without a contains argument.  Double check
6202 	 * that we don't go back past the first one. */
6203 	while ((cur_si->si_flags & HL_TRANS_CONT)
6204 		&& cur_si > (stateitem_T *)(current_state.ga_data))
6205 	    --cur_si;
6206 	/* cur_si->si_idx is -1 for keywords, these never contain anything. */
6207 	if (cur_si->si_idx >= 0 && in_id_list(NULL, ssp->cont_in_list,
6208 		&(SYN_ITEMS(syn_block)[cur_si->si_idx].sp_syn),
6209 		  SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags & HL_CONTAINED))
6210 	    return TRUE;
6211     }
6212 
6213     if (list == NULL)
6214 	return FALSE;
6215 
6216     /*
6217      * If list is ID_LIST_ALL, we are in a transparent item that isn't
6218      * inside anything.  Only allow not-contained groups.
6219      */
6220     if (list == ID_LIST_ALL)
6221 	return !contained;
6222 
6223     /*
6224      * If the first item is "ALLBUT", return TRUE if "id" is NOT in the
6225      * contains list.  We also require that "id" is at the same ":syn include"
6226      * level as the list.
6227      */
6228     item = *list;
6229     if (item >= SYNID_ALLBUT && item < SYNID_CLUSTER)
6230     {
6231 	if (item < SYNID_TOP)
6232 	{
6233 	    /* ALL or ALLBUT: accept all groups in the same file */
6234 	    if (item - SYNID_ALLBUT != ssp->inc_tag)
6235 		return FALSE;
6236 	}
6237 	else if (item < SYNID_CONTAINED)
6238 	{
6239 	    /* TOP: accept all not-contained groups in the same file */
6240 	    if (item - SYNID_TOP != ssp->inc_tag || contained)
6241 		return FALSE;
6242 	}
6243 	else
6244 	{
6245 	    /* CONTAINED: accept all contained groups in the same file */
6246 	    if (item - SYNID_CONTAINED != ssp->inc_tag || !contained)
6247 		return FALSE;
6248 	}
6249 	item = *++list;
6250 	retval = FALSE;
6251     }
6252     else
6253 	retval = TRUE;
6254 
6255     /*
6256      * Return "retval" if id is in the contains list.
6257      */
6258     while (item != 0)
6259     {
6260 	if (item == id)
6261 	    return retval;
6262 	if (item >= SYNID_CLUSTER)
6263 	{
6264 	    scl_list = SYN_CLSTR(syn_block)[item - SYNID_CLUSTER].scl_list;
6265 	    /* restrict recursiveness to 30 to avoid an endless loop for a
6266 	     * cluster that includes itself (indirectly) */
6267 	    if (scl_list != NULL && depth < 30)
6268 	    {
6269 		++depth;
6270 		r = in_id_list(NULL, scl_list, ssp, contained);
6271 		--depth;
6272 		if (r)
6273 		    return retval;
6274 	    }
6275 	}
6276 	item = *++list;
6277     }
6278     return !retval;
6279 }
6280 
6281 struct subcommand
6282 {
6283     char    *name;			/* subcommand name */
6284     void    (*func)(exarg_T *, int);	/* function to call */
6285 };
6286 
6287 static struct subcommand subcommands[] =
6288 {
6289     {"case",		syn_cmd_case},
6290     {"clear",		syn_cmd_clear},
6291     {"cluster",		syn_cmd_cluster},
6292     {"conceal",		syn_cmd_conceal},
6293     {"enable",		syn_cmd_enable},
6294     {"include",		syn_cmd_include},
6295     {"iskeyword",	syn_cmd_iskeyword},
6296     {"keyword",		syn_cmd_keyword},
6297     {"list",		syn_cmd_list},
6298     {"manual",		syn_cmd_manual},
6299     {"match",		syn_cmd_match},
6300     {"on",		syn_cmd_on},
6301     {"off",		syn_cmd_off},
6302     {"region",		syn_cmd_region},
6303     {"reset",		syn_cmd_reset},
6304     {"spell",		syn_cmd_spell},
6305     {"sync",		syn_cmd_sync},
6306     {"",		syn_cmd_list},
6307     {NULL, NULL}
6308 };
6309 
6310 /*
6311  * ":syntax".
6312  * This searches the subcommands[] table for the subcommand name, and calls a
6313  * syntax_subcommand() function to do the rest.
6314  */
6315     void
6316 ex_syntax(exarg_T *eap)
6317 {
6318     char_u	*arg = eap->arg;
6319     char_u	*subcmd_end;
6320     char_u	*subcmd_name;
6321     int		i;
6322 
6323     syn_cmdlinep = eap->cmdlinep;
6324 
6325     /* isolate subcommand name */
6326     for (subcmd_end = arg; ASCII_ISALPHA(*subcmd_end); ++subcmd_end)
6327 	;
6328     subcmd_name = vim_strnsave(arg, (int)(subcmd_end - arg));
6329     if (subcmd_name != NULL)
6330     {
6331 	if (eap->skip)		/* skip error messages for all subcommands */
6332 	    ++emsg_skip;
6333 	for (i = 0; ; ++i)
6334 	{
6335 	    if (subcommands[i].name == NULL)
6336 	    {
6337 		semsg(_("E410: Invalid :syntax subcommand: %s"), subcmd_name);
6338 		break;
6339 	    }
6340 	    if (STRCMP(subcmd_name, (char_u *)subcommands[i].name) == 0)
6341 	    {
6342 		eap->arg = skipwhite(subcmd_end);
6343 		(subcommands[i].func)(eap, FALSE);
6344 		break;
6345 	    }
6346 	}
6347 	vim_free(subcmd_name);
6348 	if (eap->skip)
6349 	    --emsg_skip;
6350     }
6351 }
6352 
6353     void
6354 ex_ownsyntax(exarg_T *eap)
6355 {
6356     char_u	*old_value;
6357     char_u	*new_value;
6358 
6359     if (curwin->w_s == &curwin->w_buffer->b_s)
6360     {
6361 	curwin->w_s = (synblock_T *)alloc(sizeof(synblock_T));
6362 	memset(curwin->w_s, 0, sizeof(synblock_T));
6363 	hash_init(&curwin->w_s->b_keywtab);
6364 	hash_init(&curwin->w_s->b_keywtab_ic);
6365 #ifdef FEAT_SPELL
6366 	/* TODO: keep the spell checking as it was. */
6367 	curwin->w_p_spell = FALSE;	/* No spell checking */
6368 	clear_string_option(&curwin->w_s->b_p_spc);
6369 	clear_string_option(&curwin->w_s->b_p_spf);
6370 	clear_string_option(&curwin->w_s->b_p_spl);
6371 #endif
6372 	clear_string_option(&curwin->w_s->b_syn_isk);
6373     }
6374 
6375     /* save value of b:current_syntax */
6376     old_value = get_var_value((char_u *)"b:current_syntax");
6377     if (old_value != NULL)
6378 	old_value = vim_strsave(old_value);
6379 
6380     /* Apply the "syntax" autocommand event, this finds and loads the syntax
6381      * file. */
6382     apply_autocmds(EVENT_SYNTAX, eap->arg, curbuf->b_fname, TRUE, curbuf);
6383 
6384     /* move value of b:current_syntax to w:current_syntax */
6385     new_value = get_var_value((char_u *)"b:current_syntax");
6386     if (new_value != NULL)
6387 	set_internal_string_var((char_u *)"w:current_syntax", new_value);
6388 
6389     /* restore value of b:current_syntax */
6390     if (old_value == NULL)
6391 	do_unlet((char_u *)"b:current_syntax", TRUE);
6392     else
6393     {
6394 	set_internal_string_var((char_u *)"b:current_syntax", old_value);
6395 	vim_free(old_value);
6396     }
6397 }
6398 
6399     int
6400 syntax_present(win_T *win)
6401 {
6402     return (win->w_s->b_syn_patterns.ga_len != 0
6403 	    || win->w_s->b_syn_clusters.ga_len != 0
6404 	    || win->w_s->b_keywtab.ht_used > 0
6405 	    || win->w_s->b_keywtab_ic.ht_used > 0);
6406 }
6407 
6408 #if defined(FEAT_CMDL_COMPL) || defined(PROTO)
6409 
6410 static enum
6411 {
6412     EXP_SUBCMD,	    /* expand ":syn" sub-commands */
6413     EXP_CASE,	    /* expand ":syn case" arguments */
6414     EXP_SPELL,	    /* expand ":syn spell" arguments */
6415     EXP_SYNC	    /* expand ":syn sync" arguments */
6416 } expand_what;
6417 
6418 /*
6419  * Reset include_link, include_default, include_none to 0.
6420  * Called when we are done expanding.
6421  */
6422     void
6423 reset_expand_highlight(void)
6424 {
6425     include_link = include_default = include_none = 0;
6426 }
6427 
6428 /*
6429  * Handle command line completion for :match and :echohl command: Add "None"
6430  * as highlight group.
6431  */
6432     void
6433 set_context_in_echohl_cmd(expand_T *xp, char_u *arg)
6434 {
6435     xp->xp_context = EXPAND_HIGHLIGHT;
6436     xp->xp_pattern = arg;
6437     include_none = 1;
6438 }
6439 
6440 /*
6441  * Handle command line completion for :syntax command.
6442  */
6443     void
6444 set_context_in_syntax_cmd(expand_T *xp, char_u *arg)
6445 {
6446     char_u	*p;
6447 
6448     /* Default: expand subcommands */
6449     xp->xp_context = EXPAND_SYNTAX;
6450     expand_what = EXP_SUBCMD;
6451     xp->xp_pattern = arg;
6452     include_link = 0;
6453     include_default = 0;
6454 
6455     /* (part of) subcommand already typed */
6456     if (*arg != NUL)
6457     {
6458 	p = skiptowhite(arg);
6459 	if (*p != NUL)		    /* past first word */
6460 	{
6461 	    xp->xp_pattern = skipwhite(p);
6462 	    if (*skiptowhite(xp->xp_pattern) != NUL)
6463 		xp->xp_context = EXPAND_NOTHING;
6464 	    else if (STRNICMP(arg, "case", p - arg) == 0)
6465 		expand_what = EXP_CASE;
6466 	    else if (STRNICMP(arg, "spell", p - arg) == 0)
6467 		expand_what = EXP_SPELL;
6468 	    else if (STRNICMP(arg, "sync", p - arg) == 0)
6469 		expand_what = EXP_SYNC;
6470 	    else if (  STRNICMP(arg, "keyword", p - arg) == 0
6471 		    || STRNICMP(arg, "region", p - arg) == 0
6472 		    || STRNICMP(arg, "match", p - arg) == 0
6473 		    || STRNICMP(arg, "list", p - arg) == 0)
6474 		xp->xp_context = EXPAND_HIGHLIGHT;
6475 	    else
6476 		xp->xp_context = EXPAND_NOTHING;
6477 	}
6478     }
6479 }
6480 
6481 /*
6482  * Function given to ExpandGeneric() to obtain the list syntax names for
6483  * expansion.
6484  */
6485     char_u *
6486 get_syntax_name(expand_T *xp UNUSED, int idx)
6487 {
6488     switch (expand_what)
6489     {
6490 	case EXP_SUBCMD:
6491 	    return (char_u *)subcommands[idx].name;
6492 	case EXP_CASE:
6493 	{
6494 	    static char *case_args[] = {"match", "ignore", NULL};
6495 	    return (char_u *)case_args[idx];
6496 	}
6497 	case EXP_SPELL:
6498 	{
6499 	    static char *spell_args[] =
6500 		{"toplevel", "notoplevel", "default", NULL};
6501 	    return (char_u *)spell_args[idx];
6502 	}
6503 	case EXP_SYNC:
6504 	{
6505 	    static char *sync_args[] =
6506 		{"ccomment", "clear", "fromstart",
6507 		 "linebreaks=", "linecont", "lines=", "match",
6508 		 "maxlines=", "minlines=", "region", NULL};
6509 	    return (char_u *)sync_args[idx];
6510 	}
6511     }
6512     return NULL;
6513 }
6514 
6515 #endif /* FEAT_CMDL_COMPL */
6516 
6517 /*
6518  * Function called for expression evaluation: get syntax ID at file position.
6519  */
6520     int
6521 syn_get_id(
6522     win_T	*wp,
6523     long	lnum,
6524     colnr_T	col,
6525     int		trans,	     /* remove transparency */
6526     int		*spellp,     /* return: can do spell checking */
6527     int		keep_state)  /* keep state of char at "col" */
6528 {
6529     /* When the position is not after the current position and in the same
6530      * line of the same buffer, need to restart parsing. */
6531     if (wp->w_buffer != syn_buf
6532 	    || lnum != current_lnum
6533 	    || col < current_col)
6534 	syntax_start(wp, lnum);
6535     else if (wp->w_buffer == syn_buf
6536 	    && lnum == current_lnum
6537 	    && col > current_col)
6538 	/* next_match may not be correct when moving around, e.g. with the
6539 	 * "skip" expression in searchpair() */
6540 	next_match_idx = -1;
6541 
6542     (void)get_syntax_attr(col, spellp, keep_state);
6543 
6544     return (trans ? current_trans_id : current_id);
6545 }
6546 
6547 #if defined(FEAT_CONCEAL) || defined(PROTO)
6548 /*
6549  * Get extra information about the syntax item.  Must be called right after
6550  * get_syntax_attr().
6551  * Stores the current item sequence nr in "*seqnrp".
6552  * Returns the current flags.
6553  */
6554     int
6555 get_syntax_info(int *seqnrp)
6556 {
6557     *seqnrp = current_seqnr;
6558     return current_flags;
6559 }
6560 
6561 /*
6562  * Return conceal substitution character
6563  */
6564     int
6565 syn_get_sub_char(void)
6566 {
6567     return current_sub_char;
6568 }
6569 #endif
6570 
6571 #if defined(FEAT_EVAL) || defined(PROTO)
6572 /*
6573  * Return the syntax ID at position "i" in the current stack.
6574  * The caller must have called syn_get_id() before to fill the stack.
6575  * Returns -1 when "i" is out of range.
6576  */
6577     int
6578 syn_get_stack_item(int i)
6579 {
6580     if (i >= current_state.ga_len)
6581     {
6582 	/* Need to invalidate the state, because we didn't properly finish it
6583 	 * for the last character, "keep_state" was TRUE. */
6584 	invalidate_current_state();
6585 	current_col = MAXCOL;
6586 	return -1;
6587     }
6588     return CUR_STATE(i).si_id;
6589 }
6590 #endif
6591 
6592 #if defined(FEAT_FOLDING) || defined(PROTO)
6593 /*
6594  * Function called to get folding level for line "lnum" in window "wp".
6595  */
6596     int
6597 syn_get_foldlevel(win_T *wp, long lnum)
6598 {
6599     int		level = 0;
6600     int		i;
6601 
6602     /* Return quickly when there are no fold items at all. */
6603     if (wp->w_s->b_syn_folditems != 0
6604 	    && !wp->w_s->b_syn_error
6605 # ifdef SYN_TIME_LIMIT
6606 	    && !wp->w_s->b_syn_slow
6607 # endif
6608 	    )
6609     {
6610 	syntax_start(wp, lnum);
6611 
6612 	for (i = 0; i < current_state.ga_len; ++i)
6613 	    if (CUR_STATE(i).si_flags & HL_FOLD)
6614 		++level;
6615     }
6616     if (level > wp->w_p_fdn)
6617     {
6618 	level = wp->w_p_fdn;
6619 	if (level < 0)
6620 	    level = 0;
6621     }
6622     return level;
6623 }
6624 #endif
6625 
6626 #if defined(FEAT_PROFILE) || defined(PROTO)
6627 /*
6628  * ":syntime".
6629  */
6630     void
6631 ex_syntime(exarg_T *eap)
6632 {
6633     if (STRCMP(eap->arg, "on") == 0)
6634 	syn_time_on = TRUE;
6635     else if (STRCMP(eap->arg, "off") == 0)
6636 	syn_time_on = FALSE;
6637     else if (STRCMP(eap->arg, "clear") == 0)
6638 	syntime_clear();
6639     else if (STRCMP(eap->arg, "report") == 0)
6640 	syntime_report();
6641     else
6642 	semsg(_(e_invarg2), eap->arg);
6643 }
6644 
6645     static void
6646 syn_clear_time(syn_time_T *st)
6647 {
6648     profile_zero(&st->total);
6649     profile_zero(&st->slowest);
6650     st->count = 0;
6651     st->match = 0;
6652 }
6653 
6654 /*
6655  * Clear the syntax timing for the current buffer.
6656  */
6657     static void
6658 syntime_clear(void)
6659 {
6660     int		idx;
6661     synpat_T	*spp;
6662 
6663     if (!syntax_present(curwin))
6664     {
6665 	msg(_(msg_no_items));
6666 	return;
6667     }
6668     for (idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx)
6669     {
6670 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
6671 	syn_clear_time(&spp->sp_time);
6672     }
6673 }
6674 
6675 #if defined(FEAT_CMDL_COMPL) || defined(PROTO)
6676 /*
6677  * Function given to ExpandGeneric() to obtain the possible arguments of the
6678  * ":syntime {on,off,clear,report}" command.
6679  */
6680     char_u *
6681 get_syntime_arg(expand_T *xp UNUSED, int idx)
6682 {
6683     switch (idx)
6684     {
6685 	case 0: return (char_u *)"on";
6686 	case 1: return (char_u *)"off";
6687 	case 2: return (char_u *)"clear";
6688 	case 3: return (char_u *)"report";
6689     }
6690     return NULL;
6691 }
6692 #endif
6693 
6694 typedef struct
6695 {
6696     proftime_T	total;
6697     int		count;
6698     int		match;
6699     proftime_T	slowest;
6700     proftime_T	average;
6701     int		id;
6702     char_u	*pattern;
6703 } time_entry_T;
6704 
6705     static int
6706 #ifdef __BORLANDC__
6707 _RTLENTRYF
6708 #endif
6709 syn_compare_syntime(const void *v1, const void *v2)
6710 {
6711     const time_entry_T	*s1 = v1;
6712     const time_entry_T	*s2 = v2;
6713 
6714     return profile_cmp(&s1->total, &s2->total);
6715 }
6716 
6717 /*
6718  * Clear the syntax timing for the current buffer.
6719  */
6720     static void
6721 syntime_report(void)
6722 {
6723     int		idx;
6724     synpat_T	*spp;
6725 # ifdef FEAT_FLOAT
6726     proftime_T	tm;
6727 # endif
6728     int		len;
6729     proftime_T	total_total;
6730     int		total_count = 0;
6731     garray_T    ga;
6732     time_entry_T *p;
6733 
6734     if (!syntax_present(curwin))
6735     {
6736 	msg(_(msg_no_items));
6737 	return;
6738     }
6739 
6740     ga_init2(&ga, sizeof(time_entry_T), 50);
6741     profile_zero(&total_total);
6742     for (idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx)
6743     {
6744 	spp = &(SYN_ITEMS(curwin->w_s)[idx]);
6745 	if (spp->sp_time.count > 0)
6746 	{
6747 	    (void)ga_grow(&ga, 1);
6748 	    p = ((time_entry_T *)ga.ga_data) + ga.ga_len;
6749 	    p->total = spp->sp_time.total;
6750 	    profile_add(&total_total, &spp->sp_time.total);
6751 	    p->count = spp->sp_time.count;
6752 	    p->match = spp->sp_time.match;
6753 	    total_count += spp->sp_time.count;
6754 	    p->slowest = spp->sp_time.slowest;
6755 # ifdef FEAT_FLOAT
6756 	    profile_divide(&spp->sp_time.total, spp->sp_time.count, &tm);
6757 	    p->average = tm;
6758 # endif
6759 	    p->id = spp->sp_syn.id;
6760 	    p->pattern = spp->sp_pattern;
6761 	    ++ga.ga_len;
6762 	}
6763     }
6764 
6765     /* Sort on total time. Skip if there are no items to avoid passing NULL
6766      * pointer to qsort(). */
6767     if (ga.ga_len > 1)
6768 	qsort(ga.ga_data, (size_t)ga.ga_len, sizeof(time_entry_T),
6769 							 syn_compare_syntime);
6770 
6771     msg_puts_title(_("  TOTAL      COUNT  MATCH   SLOWEST     AVERAGE   NAME               PATTERN"));
6772     msg_puts("\n");
6773     for (idx = 0; idx < ga.ga_len && !got_int; ++idx)
6774     {
6775 	p = ((time_entry_T *)ga.ga_data) + idx;
6776 
6777 	msg_puts(profile_msg(&p->total));
6778 	msg_puts(" "); /* make sure there is always a separating space */
6779 	msg_advance(13);
6780 	msg_outnum(p->count);
6781 	msg_puts(" ");
6782 	msg_advance(20);
6783 	msg_outnum(p->match);
6784 	msg_puts(" ");
6785 	msg_advance(26);
6786 	msg_puts(profile_msg(&p->slowest));
6787 	msg_puts(" ");
6788 	msg_advance(38);
6789 # ifdef FEAT_FLOAT
6790 	msg_puts(profile_msg(&p->average));
6791 	msg_puts(" ");
6792 # endif
6793 	msg_advance(50);
6794 	msg_outtrans(HL_TABLE()[p->id - 1].sg_name);
6795 	msg_puts(" ");
6796 
6797 	msg_advance(69);
6798 	if (Columns < 80)
6799 	    len = 20; /* will wrap anyway */
6800 	else
6801 	    len = Columns - 70;
6802 	if (len > (int)STRLEN(p->pattern))
6803 	    len = (int)STRLEN(p->pattern);
6804 	msg_outtrans_len(p->pattern, len);
6805 	msg_puts("\n");
6806     }
6807     ga_clear(&ga);
6808     if (!got_int)
6809     {
6810 	msg_puts("\n");
6811 	msg_puts(profile_msg(&total_total));
6812 	msg_advance(13);
6813 	msg_outnum(total_count);
6814 	msg_puts("\n");
6815     }
6816 }
6817 #endif
6818 
6819 #endif /* FEAT_SYN_HL */
6820 
6821 /**************************************
6822  *  Highlighting stuff		      *
6823  **************************************/
6824 
6825 /*
6826  * The default highlight groups.  These are compiled-in for fast startup and
6827  * they still work when the runtime files can't be found.
6828  * When making changes here, also change runtime/colors/default.vim!
6829  * The #ifdefs are needed to reduce the amount of static data.  Helps to make
6830  * the 16 bit DOS (museum) version compile.
6831  */
6832 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
6833 # define CENT(a, b) b
6834 #else
6835 # define CENT(a, b) a
6836 #endif
6837 static char *(highlight_init_both[]) = {
6838     CENT("ErrorMsg term=standout ctermbg=DarkRed ctermfg=White",
6839 	 "ErrorMsg term=standout ctermbg=DarkRed ctermfg=White guibg=Red guifg=White"),
6840     CENT("IncSearch term=reverse cterm=reverse",
6841 	 "IncSearch term=reverse cterm=reverse gui=reverse"),
6842     CENT("ModeMsg term=bold cterm=bold",
6843 	 "ModeMsg term=bold cterm=bold gui=bold"),
6844     CENT("NonText term=bold ctermfg=Blue",
6845 	 "NonText term=bold ctermfg=Blue gui=bold guifg=Blue"),
6846     CENT("StatusLine term=reverse,bold cterm=reverse,bold",
6847 	 "StatusLine term=reverse,bold cterm=reverse,bold gui=reverse,bold"),
6848     CENT("StatusLineNC term=reverse cterm=reverse",
6849 	 "StatusLineNC term=reverse cterm=reverse gui=reverse"),
6850     "default link EndOfBuffer NonText",
6851     CENT("VertSplit term=reverse cterm=reverse",
6852 	 "VertSplit term=reverse cterm=reverse gui=reverse"),
6853 #ifdef FEAT_CLIPBOARD
6854     CENT("VisualNOS term=underline,bold cterm=underline,bold",
6855 	 "VisualNOS term=underline,bold cterm=underline,bold gui=underline,bold"),
6856 #endif
6857 #ifdef FEAT_DIFF
6858     CENT("DiffText term=reverse cterm=bold ctermbg=Red",
6859 	 "DiffText term=reverse cterm=bold ctermbg=Red gui=bold guibg=Red"),
6860 #endif
6861 #ifdef FEAT_INS_EXPAND
6862     CENT("PmenuSbar ctermbg=Grey",
6863 	 "PmenuSbar ctermbg=Grey guibg=Grey"),
6864 #endif
6865     CENT("TabLineSel term=bold cterm=bold",
6866 	 "TabLineSel term=bold cterm=bold gui=bold"),
6867     CENT("TabLineFill term=reverse cterm=reverse",
6868 	 "TabLineFill term=reverse cterm=reverse gui=reverse"),
6869 #ifdef FEAT_GUI
6870     "Cursor guibg=fg guifg=bg",
6871     "lCursor guibg=fg guifg=bg", /* should be different, but what? */
6872 #endif
6873     "default link QuickFixLine Search",
6874     NULL
6875 };
6876 
6877 /* Default colors only used with a light background. */
6878 static char *(highlight_init_light[]) = {
6879     CENT("Directory term=bold ctermfg=DarkBlue",
6880 	 "Directory term=bold ctermfg=DarkBlue guifg=Blue"),
6881     CENT("LineNr term=underline ctermfg=Brown",
6882 	 "LineNr term=underline ctermfg=Brown guifg=Brown"),
6883     CENT("CursorLineNr term=bold ctermfg=Brown",
6884 	 "CursorLineNr term=bold ctermfg=Brown gui=bold guifg=Brown"),
6885     CENT("MoreMsg term=bold ctermfg=DarkGreen",
6886 	 "MoreMsg term=bold ctermfg=DarkGreen gui=bold guifg=SeaGreen"),
6887     CENT("Question term=standout ctermfg=DarkGreen",
6888 	 "Question term=standout ctermfg=DarkGreen gui=bold guifg=SeaGreen"),
6889     CENT("Search term=reverse ctermbg=Yellow ctermfg=NONE",
6890 	 "Search term=reverse ctermbg=Yellow ctermfg=NONE guibg=Yellow guifg=NONE"),
6891 #ifdef FEAT_SPELL
6892     CENT("SpellBad term=reverse ctermbg=LightRed",
6893 	 "SpellBad term=reverse ctermbg=LightRed guisp=Red gui=undercurl"),
6894     CENT("SpellCap term=reverse ctermbg=LightBlue",
6895 	 "SpellCap term=reverse ctermbg=LightBlue guisp=Blue gui=undercurl"),
6896     CENT("SpellRare term=reverse ctermbg=LightMagenta",
6897 	 "SpellRare term=reverse ctermbg=LightMagenta guisp=Magenta gui=undercurl"),
6898     CENT("SpellLocal term=underline ctermbg=Cyan",
6899 	 "SpellLocal term=underline ctermbg=Cyan guisp=DarkCyan gui=undercurl"),
6900 #endif
6901 #ifdef FEAT_INS_EXPAND
6902     CENT("PmenuThumb ctermbg=Black",
6903 	 "PmenuThumb ctermbg=Black guibg=Black"),
6904     CENT("Pmenu ctermbg=LightMagenta ctermfg=Black",
6905 	 "Pmenu ctermbg=LightMagenta ctermfg=Black guibg=LightMagenta"),
6906     CENT("PmenuSel ctermbg=LightGrey ctermfg=Black",
6907 	 "PmenuSel ctermbg=LightGrey ctermfg=Black guibg=Grey"),
6908 #endif
6909     CENT("SpecialKey term=bold ctermfg=DarkBlue",
6910 	 "SpecialKey term=bold ctermfg=DarkBlue guifg=Blue"),
6911     CENT("Title term=bold ctermfg=DarkMagenta",
6912 	 "Title term=bold ctermfg=DarkMagenta gui=bold guifg=Magenta"),
6913     CENT("WarningMsg term=standout ctermfg=DarkRed",
6914 	 "WarningMsg term=standout ctermfg=DarkRed guifg=Red"),
6915 #ifdef FEAT_WILDMENU
6916     CENT("WildMenu term=standout ctermbg=Yellow ctermfg=Black",
6917 	 "WildMenu term=standout ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black"),
6918 #endif
6919 #ifdef FEAT_FOLDING
6920     CENT("Folded term=standout ctermbg=Grey ctermfg=DarkBlue",
6921 	 "Folded term=standout ctermbg=Grey ctermfg=DarkBlue guibg=LightGrey guifg=DarkBlue"),
6922     CENT("FoldColumn term=standout ctermbg=Grey ctermfg=DarkBlue",
6923 	 "FoldColumn term=standout ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue"),
6924 #endif
6925 #ifdef FEAT_SIGNS
6926     CENT("SignColumn term=standout ctermbg=Grey ctermfg=DarkBlue",
6927 	 "SignColumn term=standout ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue"),
6928 #endif
6929     CENT("Visual term=reverse",
6930 	 "Visual term=reverse guibg=LightGrey"),
6931 #ifdef FEAT_DIFF
6932     CENT("DiffAdd term=bold ctermbg=LightBlue",
6933 	 "DiffAdd term=bold ctermbg=LightBlue guibg=LightBlue"),
6934     CENT("DiffChange term=bold ctermbg=LightMagenta",
6935 	 "DiffChange term=bold ctermbg=LightMagenta guibg=LightMagenta"),
6936     CENT("DiffDelete term=bold ctermfg=Blue ctermbg=LightCyan",
6937 	 "DiffDelete term=bold ctermfg=Blue ctermbg=LightCyan gui=bold guifg=Blue guibg=LightCyan"),
6938 #endif
6939     CENT("TabLine term=underline cterm=underline ctermfg=black ctermbg=LightGrey",
6940 	 "TabLine term=underline cterm=underline ctermfg=black ctermbg=LightGrey gui=underline guibg=LightGrey"),
6941 #ifdef FEAT_SYN_HL
6942     CENT("CursorColumn term=reverse ctermbg=LightGrey",
6943 	 "CursorColumn term=reverse ctermbg=LightGrey guibg=Grey90"),
6944     CENT("CursorLine term=underline cterm=underline",
6945 	 "CursorLine term=underline cterm=underline guibg=Grey90"),
6946     CENT("ColorColumn term=reverse ctermbg=LightRed",
6947 	 "ColorColumn term=reverse ctermbg=LightRed guibg=LightRed"),
6948 #endif
6949 #ifdef FEAT_CONCEAL
6950     CENT("Conceal ctermbg=DarkGrey ctermfg=LightGrey",
6951 	 "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey"),
6952 #endif
6953     CENT("MatchParen term=reverse ctermbg=Cyan",
6954 	 "MatchParen term=reverse ctermbg=Cyan guibg=Cyan"),
6955 #ifdef FEAT_GUI
6956     "Normal gui=NONE",
6957 #endif
6958 #ifdef FEAT_TERMINAL
6959     CENT("StatusLineTerm term=reverse,bold cterm=bold ctermfg=White ctermbg=DarkGreen",
6960 	 "StatusLineTerm term=reverse,bold cterm=bold ctermfg=White ctermbg=DarkGreen gui=bold guifg=bg guibg=DarkGreen"),
6961     CENT("StatusLineTermNC term=reverse ctermfg=White ctermbg=DarkGreen",
6962 	 "StatusLineTermNC term=reverse ctermfg=White ctermbg=DarkGreen guifg=bg guibg=DarkGreen"),
6963 #endif
6964 #ifdef FEAT_MENU
6965     CENT("ToolbarLine term=underline ctermbg=LightGrey",
6966 	 "ToolbarLine term=underline ctermbg=LightGrey guibg=LightGrey"),
6967     CENT("ToolbarButton cterm=bold ctermfg=White ctermbg=DarkGrey",
6968 	 "ToolbarButton cterm=bold ctermfg=White ctermbg=DarkGrey gui=bold guifg=White guibg=Grey40"),
6969 #endif
6970     NULL
6971 };
6972 
6973 /* Default colors only used with a dark background. */
6974 static char *(highlight_init_dark[]) = {
6975     CENT("Directory term=bold ctermfg=LightCyan",
6976 	 "Directory term=bold ctermfg=LightCyan guifg=Cyan"),
6977     CENT("LineNr term=underline ctermfg=Yellow",
6978 	 "LineNr term=underline ctermfg=Yellow guifg=Yellow"),
6979     CENT("CursorLineNr term=bold ctermfg=Yellow",
6980 	 "CursorLineNr term=bold ctermfg=Yellow gui=bold guifg=Yellow"),
6981     CENT("MoreMsg term=bold ctermfg=LightGreen",
6982 	 "MoreMsg term=bold ctermfg=LightGreen gui=bold guifg=SeaGreen"),
6983     CENT("Question term=standout ctermfg=LightGreen",
6984 	 "Question term=standout ctermfg=LightGreen gui=bold guifg=Green"),
6985     CENT("Search term=reverse ctermbg=Yellow ctermfg=Black",
6986 	 "Search term=reverse ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black"),
6987     CENT("SpecialKey term=bold ctermfg=LightBlue",
6988 	 "SpecialKey term=bold ctermfg=LightBlue guifg=Cyan"),
6989 #ifdef FEAT_SPELL
6990     CENT("SpellBad term=reverse ctermbg=Red",
6991 	 "SpellBad term=reverse ctermbg=Red guisp=Red gui=undercurl"),
6992     CENT("SpellCap term=reverse ctermbg=Blue",
6993 	 "SpellCap term=reverse ctermbg=Blue guisp=Blue gui=undercurl"),
6994     CENT("SpellRare term=reverse ctermbg=Magenta",
6995 	 "SpellRare term=reverse ctermbg=Magenta guisp=Magenta gui=undercurl"),
6996     CENT("SpellLocal term=underline ctermbg=Cyan",
6997 	 "SpellLocal term=underline ctermbg=Cyan guisp=Cyan gui=undercurl"),
6998 #endif
6999 #ifdef FEAT_INS_EXPAND
7000     CENT("PmenuThumb ctermbg=White",
7001 	 "PmenuThumb ctermbg=White guibg=White"),
7002     CENT("Pmenu ctermbg=Magenta ctermfg=Black",
7003 	 "Pmenu ctermbg=Magenta ctermfg=Black guibg=Magenta"),
7004     CENT("PmenuSel ctermbg=Black ctermfg=DarkGrey",
7005 	 "PmenuSel ctermbg=Black ctermfg=DarkGrey guibg=DarkGrey"),
7006 #endif
7007     CENT("Title term=bold ctermfg=LightMagenta",
7008 	 "Title term=bold ctermfg=LightMagenta gui=bold guifg=Magenta"),
7009     CENT("WarningMsg term=standout ctermfg=LightRed",
7010 	 "WarningMsg term=standout ctermfg=LightRed guifg=Red"),
7011 #ifdef FEAT_WILDMENU
7012     CENT("WildMenu term=standout ctermbg=Yellow ctermfg=Black",
7013 	 "WildMenu term=standout ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black"),
7014 #endif
7015 #ifdef FEAT_FOLDING
7016     CENT("Folded term=standout ctermbg=DarkGrey ctermfg=Cyan",
7017 	 "Folded term=standout ctermbg=DarkGrey ctermfg=Cyan guibg=DarkGrey guifg=Cyan"),
7018     CENT("FoldColumn term=standout ctermbg=DarkGrey ctermfg=Cyan",
7019 	 "FoldColumn term=standout ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan"),
7020 #endif
7021 #ifdef FEAT_SIGNS
7022     CENT("SignColumn term=standout ctermbg=DarkGrey ctermfg=Cyan",
7023 	 "SignColumn term=standout ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan"),
7024 #endif
7025     CENT("Visual term=reverse",
7026 	 "Visual term=reverse guibg=DarkGrey"),
7027 #ifdef FEAT_DIFF
7028     CENT("DiffAdd term=bold ctermbg=DarkBlue",
7029 	 "DiffAdd term=bold ctermbg=DarkBlue guibg=DarkBlue"),
7030     CENT("DiffChange term=bold ctermbg=DarkMagenta",
7031 	 "DiffChange term=bold ctermbg=DarkMagenta guibg=DarkMagenta"),
7032     CENT("DiffDelete term=bold ctermfg=Blue ctermbg=DarkCyan",
7033 	 "DiffDelete term=bold ctermfg=Blue ctermbg=DarkCyan gui=bold guifg=Blue guibg=DarkCyan"),
7034 #endif
7035     CENT("TabLine term=underline cterm=underline ctermfg=white ctermbg=DarkGrey",
7036 	 "TabLine term=underline cterm=underline ctermfg=white ctermbg=DarkGrey gui=underline guibg=DarkGrey"),
7037 #ifdef FEAT_SYN_HL
7038     CENT("CursorColumn term=reverse ctermbg=DarkGrey",
7039 	 "CursorColumn term=reverse ctermbg=DarkGrey guibg=Grey40"),
7040     CENT("CursorLine term=underline cterm=underline",
7041 	 "CursorLine term=underline cterm=underline guibg=Grey40"),
7042     CENT("ColorColumn term=reverse ctermbg=DarkRed",
7043 	 "ColorColumn term=reverse ctermbg=DarkRed guibg=DarkRed"),
7044 #endif
7045     CENT("MatchParen term=reverse ctermbg=DarkCyan",
7046 	 "MatchParen term=reverse ctermbg=DarkCyan guibg=DarkCyan"),
7047 #ifdef FEAT_CONCEAL
7048     CENT("Conceal ctermbg=DarkGrey ctermfg=LightGrey",
7049 	 "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey"),
7050 #endif
7051 #ifdef FEAT_GUI
7052     "Normal gui=NONE",
7053 #endif
7054 #ifdef FEAT_TERMINAL
7055     CENT("StatusLineTerm term=reverse,bold cterm=bold ctermfg=Black ctermbg=LightGreen",
7056 	 "StatusLineTerm term=reverse,bold cterm=bold ctermfg=Black ctermbg=LightGreen gui=bold guifg=bg guibg=LightGreen"),
7057     CENT("StatusLineTermNC term=reverse ctermfg=Black ctermbg=LightGreen",
7058 	 "StatusLineTermNC term=reverse ctermfg=Black ctermbg=LightGreen guifg=bg guibg=LightGreen"),
7059 #endif
7060 #ifdef FEAT_MENU
7061     CENT("ToolbarLine term=underline ctermbg=DarkGrey",
7062 	 "ToolbarLine term=underline ctermbg=DarkGrey guibg=Grey50"),
7063     CENT("ToolbarButton cterm=bold ctermfg=Black ctermbg=LightGrey",
7064 	 "ToolbarButton cterm=bold ctermfg=Black ctermbg=LightGrey gui=bold guifg=Black guibg=LightGrey"),
7065 #endif
7066     NULL
7067 };
7068 
7069     void
7070 init_highlight(
7071     int		both,	    /* include groups where 'bg' doesn't matter */
7072     int		reset)	    /* clear group first */
7073 {
7074     int		i;
7075     char	**pp;
7076     static int	had_both = FALSE;
7077 #ifdef FEAT_EVAL
7078     char_u	*p;
7079 
7080     /*
7081      * Try finding the color scheme file.  Used when a color file was loaded
7082      * and 'background' or 't_Co' is changed.
7083      */
7084     p = get_var_value((char_u *)"g:colors_name");
7085     if (p != NULL)
7086     {
7087        /* The value of g:colors_name could be freed when sourcing the script,
7088 	* making "p" invalid, so copy it. */
7089        char_u *copy_p = vim_strsave(p);
7090        int    r;
7091 
7092        if (copy_p != NULL)
7093        {
7094 	   r = load_colors(copy_p);
7095 	   vim_free(copy_p);
7096 	   if (r == OK)
7097 	       return;
7098        }
7099     }
7100 
7101 #endif
7102 
7103     /*
7104      * Didn't use a color file, use the compiled-in colors.
7105      */
7106     if (both)
7107     {
7108 	had_both = TRUE;
7109 	pp = highlight_init_both;
7110 	for (i = 0; pp[i] != NULL; ++i)
7111 	    do_highlight((char_u *)pp[i], reset, TRUE);
7112     }
7113     else if (!had_both)
7114 	/* Don't do anything before the call with both == TRUE from main().
7115 	 * Not everything has been setup then, and that call will overrule
7116 	 * everything anyway. */
7117 	return;
7118 
7119     if (*p_bg == 'l')
7120 	pp = highlight_init_light;
7121     else
7122 	pp = highlight_init_dark;
7123     for (i = 0; pp[i] != NULL; ++i)
7124 	do_highlight((char_u *)pp[i], reset, TRUE);
7125 
7126     /* Reverse looks ugly, but grey may not work for 8 colors.  Thus let it
7127      * depend on the number of colors available.
7128      * With 8 colors brown is equal to yellow, need to use black for Search fg
7129      * to avoid Statement highlighted text disappears.
7130      * Clear the attributes, needed when changing the t_Co value. */
7131     if (t_colors > 8)
7132 	do_highlight((char_u *)(*p_bg == 'l'
7133 		    ? "Visual cterm=NONE ctermbg=LightGrey"
7134 		    : "Visual cterm=NONE ctermbg=DarkGrey"), FALSE, TRUE);
7135     else
7136     {
7137 	do_highlight((char_u *)"Visual cterm=reverse ctermbg=NONE",
7138 								 FALSE, TRUE);
7139 	if (*p_bg == 'l')
7140 	    do_highlight((char_u *)"Search ctermfg=black", FALSE, TRUE);
7141     }
7142 
7143 #ifdef FEAT_SYN_HL
7144     /*
7145      * If syntax highlighting is enabled load the highlighting for it.
7146      */
7147     if (get_var_value((char_u *)"g:syntax_on") != NULL)
7148     {
7149 	static int	recursive = 0;
7150 
7151 	if (recursive >= 5)
7152 	    emsg(_("E679: recursive loop loading syncolor.vim"));
7153 	else
7154 	{
7155 	    ++recursive;
7156 	    (void)source_runtime((char_u *)"syntax/syncolor.vim", DIP_ALL);
7157 	    --recursive;
7158 	}
7159     }
7160 #endif
7161 }
7162 
7163 /*
7164  * Load color file "name".
7165  * Return OK for success, FAIL for failure.
7166  */
7167     int
7168 load_colors(char_u *name)
7169 {
7170     char_u	*buf;
7171     int		retval = FAIL;
7172     static int	recursive = FALSE;
7173 
7174     /* When being called recursively, this is probably because setting
7175      * 'background' caused the highlighting to be reloaded.  This means it is
7176      * working, thus we should return OK. */
7177     if (recursive)
7178 	return OK;
7179 
7180     recursive = TRUE;
7181     buf = alloc((unsigned)(STRLEN(name) + 12));
7182     if (buf != NULL)
7183     {
7184 	apply_autocmds(EVENT_COLORSCHEMEPRE, name,
7185 					       curbuf->b_fname, FALSE, curbuf);
7186 	sprintf((char *)buf, "colors/%s.vim", name);
7187 	retval = source_runtime(buf, DIP_START + DIP_OPT);
7188 	vim_free(buf);
7189 	apply_autocmds(EVENT_COLORSCHEME, name, curbuf->b_fname, FALSE, curbuf);
7190     }
7191     recursive = FALSE;
7192 
7193     return retval;
7194 }
7195 
7196 static char *(color_names[28]) = {
7197 	    "Black", "DarkBlue", "DarkGreen", "DarkCyan",
7198 	    "DarkRed", "DarkMagenta", "Brown", "DarkYellow",
7199 	    "Gray", "Grey", "LightGray", "LightGrey",
7200 	    "DarkGray", "DarkGrey",
7201 	    "Blue", "LightBlue", "Green", "LightGreen",
7202 	    "Cyan", "LightCyan", "Red", "LightRed", "Magenta",
7203 	    "LightMagenta", "Yellow", "LightYellow", "White", "NONE"};
7204 	    /* indices:
7205 	     * 0, 1, 2, 3,
7206 	     * 4, 5, 6, 7,
7207 	     * 8, 9, 10, 11,
7208 	     * 12, 13,
7209 	     * 14, 15, 16, 17,
7210 	     * 18, 19, 20, 21, 22,
7211 	     * 23, 24, 25, 26, 27 */
7212 static int color_numbers_16[28] = {0, 1, 2, 3,
7213 				 4, 5, 6, 6,
7214 				 7, 7, 7, 7,
7215 				 8, 8,
7216 				 9, 9, 10, 10,
7217 				 11, 11, 12, 12, 13,
7218 				 13, 14, 14, 15, -1};
7219 /* for xterm with 88 colors... */
7220 static int color_numbers_88[28] = {0, 4, 2, 6,
7221 				 1, 5, 32, 72,
7222 				 84, 84, 7, 7,
7223 				 82, 82,
7224 				 12, 43, 10, 61,
7225 				 14, 63, 9, 74, 13,
7226 				 75, 11, 78, 15, -1};
7227 /* for xterm with 256 colors... */
7228 static int color_numbers_256[28] = {0, 4, 2, 6,
7229 				 1, 5, 130, 130,
7230 				 248, 248, 7, 7,
7231 				 242, 242,
7232 				 12, 81, 10, 121,
7233 				 14, 159, 9, 224, 13,
7234 				 225, 11, 229, 15, -1};
7235 /* for terminals with less than 16 colors... */
7236 static int color_numbers_8[28] = {0, 4, 2, 6,
7237 				 1, 5, 3, 3,
7238 				 7, 7, 7, 7,
7239 				 0+8, 0+8,
7240 				 4+8, 4+8, 2+8, 2+8,
7241 				 6+8, 6+8, 1+8, 1+8, 5+8,
7242 				 5+8, 3+8, 3+8, 7+8, -1};
7243 
7244 /*
7245  * Lookup the "cterm" value to be used for color with index "idx" in
7246  * color_names[].
7247  * "boldp" will be set to TRUE or FALSE for a foreground color when using 8
7248  * colors, otherwise it will be unchanged.
7249  */
7250     int
7251 lookup_color(int idx, int foreground, int *boldp)
7252 {
7253     int		color = color_numbers_16[idx];
7254     char_u	*p;
7255 
7256     /* Use the _16 table to check if it's a valid color name. */
7257     if (color < 0)
7258 	return -1;
7259 
7260     if (t_colors == 8)
7261     {
7262 	/* t_Co is 8: use the 8 colors table */
7263 #if defined(__QNXNTO__)
7264 	color = color_numbers_8_qansi[idx];
7265 #else
7266 	color = color_numbers_8[idx];
7267 #endif
7268 	if (foreground)
7269 	{
7270 	    /* set/reset bold attribute to get light foreground
7271 	     * colors (on some terminals, e.g. "linux") */
7272 	    if (color & 8)
7273 		*boldp = TRUE;
7274 	    else
7275 		*boldp = FALSE;
7276 	}
7277 	color &= 7;	/* truncate to 8 colors */
7278     }
7279     else if (t_colors == 16 || t_colors == 88
7280 					   || t_colors >= 256)
7281     {
7282 	/*
7283 	 * Guess: if the termcap entry ends in 'm', it is
7284 	 * probably an xterm-like terminal.  Use the changed
7285 	 * order for colors.
7286 	 */
7287 	if (*T_CAF != NUL)
7288 	    p = T_CAF;
7289 	else
7290 	    p = T_CSF;
7291 	if (*p != NUL && (t_colors > 256
7292 			      || *(p + STRLEN(p) - 1) == 'm'))
7293 	{
7294 	    if (t_colors == 88)
7295 		color = color_numbers_88[idx];
7296 	    else if (t_colors >= 256)
7297 		color = color_numbers_256[idx];
7298 	    else
7299 		color = color_numbers_8[idx];
7300 	}
7301 #ifdef FEAT_TERMRESPONSE
7302 	if (t_colors >= 256 && color == 15 && is_mac_terminal)
7303 	    /* Terminal.app has a bug: 15 is light grey. Use white
7304 	     * from the color cube instead. */
7305 	    color = 231;
7306 #endif
7307     }
7308     return color;
7309 }
7310 
7311 /*
7312  * Handle the ":highlight .." command.
7313  * When using ":hi clear" this is called recursively for each group with
7314  * "forceit" and "init" both TRUE.
7315  */
7316     void
7317 do_highlight(
7318     char_u	*line,
7319     int		forceit,
7320     int		init)	    /* TRUE when called for initializing */
7321 {
7322     char_u	*name_end;
7323     char_u	*p;
7324     char_u	*linep;
7325     char_u	*key_start;
7326     char_u	*arg_start;
7327     char_u	*key = NULL, *arg = NULL;
7328     long	i;
7329     int		off;
7330     int		len;
7331     int		attr;
7332     int		id;
7333     int		idx;
7334     struct hl_group item_before;
7335     int		did_change = FALSE;
7336     int		dodefault = FALSE;
7337     int		doclear = FALSE;
7338     int		dolink = FALSE;
7339     int		error = FALSE;
7340     int		color;
7341     int		is_normal_group = FALSE;	/* "Normal" group */
7342 #ifdef FEAT_TERMINAL
7343     int		is_terminal_group = FALSE;	/* "Terminal" group */
7344 #endif
7345 #ifdef FEAT_GUI_X11
7346     int		is_menu_group = FALSE;		/* "Menu" group */
7347     int		is_scrollbar_group = FALSE;	/* "Scrollbar" group */
7348     int		is_tooltip_group = FALSE;	/* "Tooltip" group */
7349     int		do_colors = FALSE;		/* need to update colors? */
7350 #else
7351 # define is_menu_group 0
7352 # define is_tooltip_group 0
7353 #endif
7354 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
7355     int		did_highlight_changed = FALSE;
7356 #endif
7357 
7358     /*
7359      * If no argument, list current highlighting.
7360      */
7361     if (ends_excmd(*line))
7362     {
7363 	for (i = 1; i <= highlight_ga.ga_len && !got_int; ++i)
7364 	    /* TODO: only call when the group has attributes set */
7365 	    highlight_list_one((int)i);
7366 	return;
7367     }
7368 
7369     /*
7370      * Isolate the name.
7371      */
7372     name_end = skiptowhite(line);
7373     linep = skipwhite(name_end);
7374 
7375     /*
7376      * Check for "default" argument.
7377      */
7378     if (STRNCMP(line, "default", name_end - line) == 0)
7379     {
7380 	dodefault = TRUE;
7381 	line = linep;
7382 	name_end = skiptowhite(line);
7383 	linep = skipwhite(name_end);
7384     }
7385 
7386     /*
7387      * Check for "clear" or "link" argument.
7388      */
7389     if (STRNCMP(line, "clear", name_end - line) == 0)
7390 	doclear = TRUE;
7391     if (STRNCMP(line, "link", name_end - line) == 0)
7392 	dolink = TRUE;
7393 
7394     /*
7395      * ":highlight {group-name}": list highlighting for one group.
7396      */
7397     if (!doclear && !dolink && ends_excmd(*linep))
7398     {
7399 	id = syn_namen2id(line, (int)(name_end - line));
7400 	if (id == 0)
7401 	    semsg(_("E411: highlight group not found: %s"), line);
7402 	else
7403 	    highlight_list_one(id);
7404 	return;
7405     }
7406 
7407     /*
7408      * Handle ":highlight link {from} {to}" command.
7409      */
7410     if (dolink)
7411     {
7412 	char_u	    *from_start = linep;
7413 	char_u	    *from_end;
7414 	char_u	    *to_start;
7415 	char_u	    *to_end;
7416 	int	    from_id;
7417 	int	    to_id;
7418 
7419 	from_end = skiptowhite(from_start);
7420 	to_start = skipwhite(from_end);
7421 	to_end	 = skiptowhite(to_start);
7422 
7423 	if (ends_excmd(*from_start) || ends_excmd(*to_start))
7424 	{
7425 	    semsg(_("E412: Not enough arguments: \":highlight link %s\""),
7426 								  from_start);
7427 	    return;
7428 	}
7429 
7430 	if (!ends_excmd(*skipwhite(to_end)))
7431 	{
7432 	    semsg(_("E413: Too many arguments: \":highlight link %s\""), from_start);
7433 	    return;
7434 	}
7435 
7436 	from_id = syn_check_group(from_start, (int)(from_end - from_start));
7437 	if (STRNCMP(to_start, "NONE", 4) == 0)
7438 	    to_id = 0;
7439 	else
7440 	    to_id = syn_check_group(to_start, (int)(to_end - to_start));
7441 
7442 	if (from_id > 0 && (!init || HL_TABLE()[from_id - 1].sg_set == 0))
7443 	{
7444 	    /*
7445 	     * Don't allow a link when there already is some highlighting
7446 	     * for the group, unless '!' is used
7447 	     */
7448 	    if (to_id > 0 && !forceit && !init
7449 				   && hl_has_settings(from_id - 1, dodefault))
7450 	    {
7451 		if (sourcing_name == NULL && !dodefault)
7452 		    emsg(_("E414: group has settings, highlight link ignored"));
7453 	    }
7454 	    else if (HL_TABLE()[from_id - 1].sg_link != to_id
7455 #ifdef FEAT_EVAL
7456 		    || HL_TABLE()[from_id - 1].sg_script_ctx.sc_sid
7457 							 != current_sctx.sc_sid
7458 #endif
7459 		    || HL_TABLE()[from_id - 1].sg_cleared)
7460 	    {
7461 		if (!init)
7462 		    HL_TABLE()[from_id - 1].sg_set |= SG_LINK;
7463 		HL_TABLE()[from_id - 1].sg_link = to_id;
7464 #ifdef FEAT_EVAL
7465 		HL_TABLE()[from_id - 1].sg_script_ctx = current_sctx;
7466 		HL_TABLE()[from_id - 1].sg_script_ctx.sc_lnum += sourcing_lnum;
7467 #endif
7468 		HL_TABLE()[from_id - 1].sg_cleared = FALSE;
7469 		redraw_all_later(SOME_VALID);
7470 
7471 		/* Only call highlight_changed() once after multiple changes. */
7472 		need_highlight_changed = TRUE;
7473 	    }
7474 	}
7475 
7476 	return;
7477     }
7478 
7479     if (doclear)
7480     {
7481 	/*
7482 	 * ":highlight clear [group]" command.
7483 	 */
7484 	line = linep;
7485 	if (ends_excmd(*line))
7486 	{
7487 #ifdef FEAT_GUI
7488 	    /* First, we do not destroy the old values, but allocate the new
7489 	     * ones and update the display. THEN we destroy the old values.
7490 	     * If we destroy the old values first, then the old values
7491 	     * (such as GuiFont's or GuiFontset's) will still be displayed but
7492 	     * invalid because they were free'd.
7493 	     */
7494 	    if (gui.in_use)
7495 	    {
7496 # ifdef FEAT_BEVAL_TIP
7497 		gui_init_tooltip_font();
7498 # endif
7499 # if defined(FEAT_MENU) && (defined(FEAT_GUI_ATHENA) || defined(FEAT_GUI_MOTIF))
7500 		gui_init_menu_font();
7501 # endif
7502 	    }
7503 # if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_X11)
7504 	    gui_mch_def_colors();
7505 # endif
7506 # ifdef FEAT_GUI_X11
7507 #  ifdef FEAT_MENU
7508 
7509 	    /* This only needs to be done when there is no Menu highlight
7510 	     * group defined by default, which IS currently the case.
7511 	     */
7512 	    gui_mch_new_menu_colors();
7513 #  endif
7514 	    if (gui.in_use)
7515 	    {
7516 		gui_new_scrollbar_colors();
7517 #  ifdef FEAT_BEVAL_GUI
7518 		gui_mch_new_tooltip_colors();
7519 #  endif
7520 #  ifdef FEAT_MENU
7521 		gui_mch_new_menu_font();
7522 #  endif
7523 	    }
7524 # endif
7525 
7526 	    /* Ok, we're done allocating the new default graphics items.
7527 	     * The screen should already be refreshed at this point.
7528 	     * It is now Ok to clear out the old data.
7529 	     */
7530 #endif
7531 #ifdef FEAT_EVAL
7532 	    do_unlet((char_u *)"colors_name", TRUE);
7533 #endif
7534 	    restore_cterm_colors();
7535 
7536 	    /*
7537 	     * Clear all default highlight groups and load the defaults.
7538 	     */
7539 	    for (idx = 0; idx < highlight_ga.ga_len; ++idx)
7540 		highlight_clear(idx);
7541 	    init_highlight(TRUE, TRUE);
7542 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
7543 	    if (USE_24BIT)
7544 		highlight_gui_started();
7545 	    else
7546 #endif
7547 		highlight_changed();
7548 	    redraw_later_clear();
7549 	    return;
7550 	}
7551 	name_end = skiptowhite(line);
7552 	linep = skipwhite(name_end);
7553     }
7554 
7555     /*
7556      * Find the group name in the table.  If it does not exist yet, add it.
7557      */
7558     id = syn_check_group(line, (int)(name_end - line));
7559     if (id == 0)			/* failed (out of memory) */
7560 	return;
7561     idx = id - 1;			/* index is ID minus one */
7562 
7563     /* Return if "default" was used and the group already has settings. */
7564     if (dodefault && hl_has_settings(idx, TRUE))
7565 	return;
7566 
7567     /* Make a copy so we can check if any attribute actually changed. */
7568     item_before = HL_TABLE()[idx];
7569 
7570     if (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0)
7571 	is_normal_group = TRUE;
7572 #ifdef FEAT_TERMINAL
7573     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "TERMINAL") == 0)
7574 	is_terminal_group = TRUE;
7575 #endif
7576 #ifdef FEAT_GUI_X11
7577     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "MENU") == 0)
7578 	is_menu_group = TRUE;
7579     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "SCROLLBAR") == 0)
7580 	is_scrollbar_group = TRUE;
7581     else if (STRCMP(HL_TABLE()[idx].sg_name_u, "TOOLTIP") == 0)
7582 	is_tooltip_group = TRUE;
7583 #endif
7584 
7585     /* Clear the highlighting for ":hi clear {group}" and ":hi clear". */
7586     if (doclear || (forceit && init))
7587     {
7588 	highlight_clear(idx);
7589 	if (!doclear)
7590 	    HL_TABLE()[idx].sg_set = 0;
7591     }
7592 
7593     if (!doclear)
7594       while (!ends_excmd(*linep))
7595       {
7596 	key_start = linep;
7597 	if (*linep == '=')
7598 	{
7599 	    semsg(_("E415: unexpected equal sign: %s"), key_start);
7600 	    error = TRUE;
7601 	    break;
7602 	}
7603 
7604 	/*
7605 	 * Isolate the key ("term", "ctermfg", "ctermbg", "font", "guifg" or
7606 	 * "guibg").
7607 	 */
7608 	while (*linep && !VIM_ISWHITE(*linep) && *linep != '=')
7609 	    ++linep;
7610 	vim_free(key);
7611 	key = vim_strnsave_up(key_start, (int)(linep - key_start));
7612 	if (key == NULL)
7613 	{
7614 	    error = TRUE;
7615 	    break;
7616 	}
7617 	linep = skipwhite(linep);
7618 
7619 	if (STRCMP(key, "NONE") == 0)
7620 	{
7621 	    if (!init || HL_TABLE()[idx].sg_set == 0)
7622 	    {
7623 		if (!init)
7624 		    HL_TABLE()[idx].sg_set |= SG_TERM+SG_CTERM+SG_GUI;
7625 		highlight_clear(idx);
7626 	    }
7627 	    continue;
7628 	}
7629 
7630 	/*
7631 	 * Check for the equal sign.
7632 	 */
7633 	if (*linep != '=')
7634 	{
7635 	    semsg(_("E416: missing equal sign: %s"), key_start);
7636 	    error = TRUE;
7637 	    break;
7638 	}
7639 	++linep;
7640 
7641 	/*
7642 	 * Isolate the argument.
7643 	 */
7644 	linep = skipwhite(linep);
7645 	if (*linep == '\'')		/* guifg='color name' */
7646 	{
7647 	    arg_start = ++linep;
7648 	    linep = vim_strchr(linep, '\'');
7649 	    if (linep == NULL)
7650 	    {
7651 		semsg(_(e_invarg2), key_start);
7652 		error = TRUE;
7653 		break;
7654 	    }
7655 	}
7656 	else
7657 	{
7658 	    arg_start = linep;
7659 	    linep = skiptowhite(linep);
7660 	}
7661 	if (linep == arg_start)
7662 	{
7663 	    semsg(_("E417: missing argument: %s"), key_start);
7664 	    error = TRUE;
7665 	    break;
7666 	}
7667 	vim_free(arg);
7668 	arg = vim_strnsave(arg_start, (int)(linep - arg_start));
7669 	if (arg == NULL)
7670 	{
7671 	    error = TRUE;
7672 	    break;
7673 	}
7674 	if (*linep == '\'')
7675 	    ++linep;
7676 
7677 	/*
7678 	 * Store the argument.
7679 	 */
7680 	if (  STRCMP(key, "TERM") == 0
7681 		|| STRCMP(key, "CTERM") == 0
7682 		|| STRCMP(key, "GUI") == 0)
7683 	{
7684 	    attr = 0;
7685 	    off = 0;
7686 	    while (arg[off] != NUL)
7687 	    {
7688 		for (i = sizeof(hl_attr_table) / sizeof(int); --i >= 0; )
7689 		{
7690 		    len = (int)STRLEN(hl_name_table[i]);
7691 		    if (STRNICMP(arg + off, hl_name_table[i], len) == 0)
7692 		    {
7693 			attr |= hl_attr_table[i];
7694 			off += len;
7695 			break;
7696 		    }
7697 		}
7698 		if (i < 0)
7699 		{
7700 		    semsg(_("E418: Illegal value: %s"), arg);
7701 		    error = TRUE;
7702 		    break;
7703 		}
7704 		if (arg[off] == ',')		/* another one follows */
7705 		    ++off;
7706 	    }
7707 	    if (error)
7708 		break;
7709 	    if (*key == 'T')
7710 	    {
7711 		if (!init || !(HL_TABLE()[idx].sg_set & SG_TERM))
7712 		{
7713 		    if (!init)
7714 			HL_TABLE()[idx].sg_set |= SG_TERM;
7715 		    HL_TABLE()[idx].sg_term = attr;
7716 		}
7717 	    }
7718 	    else if (*key == 'C')
7719 	    {
7720 		if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM))
7721 		{
7722 		    if (!init)
7723 			HL_TABLE()[idx].sg_set |= SG_CTERM;
7724 		    HL_TABLE()[idx].sg_cterm = attr;
7725 		    HL_TABLE()[idx].sg_cterm_bold = FALSE;
7726 		}
7727 	    }
7728 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
7729 	    else
7730 	    {
7731 		if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
7732 		{
7733 		    if (!init)
7734 			HL_TABLE()[idx].sg_set |= SG_GUI;
7735 		    HL_TABLE()[idx].sg_gui = attr;
7736 		}
7737 	    }
7738 #endif
7739 	}
7740 	else if (STRCMP(key, "FONT") == 0)
7741 	{
7742 	    /* in non-GUI fonts are simply ignored */
7743 #ifdef FEAT_GUI
7744 	    if (HL_TABLE()[idx].sg_font_name != NULL
7745 			     && STRCMP(HL_TABLE()[idx].sg_font_name, arg) == 0)
7746 	    {
7747 		/* Font name didn't change, ignore. */
7748 	    }
7749 	    else if (!gui.shell_created)
7750 	    {
7751 		/* GUI not started yet, always accept the name. */
7752 		vim_free(HL_TABLE()[idx].sg_font_name);
7753 		HL_TABLE()[idx].sg_font_name = vim_strsave(arg);
7754 		did_change = TRUE;
7755 	    }
7756 	    else
7757 	    {
7758 		GuiFont temp_sg_font = HL_TABLE()[idx].sg_font;
7759 # ifdef FEAT_XFONTSET
7760 		GuiFontset temp_sg_fontset = HL_TABLE()[idx].sg_fontset;
7761 # endif
7762 		/* First, save the current font/fontset.
7763 		 * Then try to allocate the font/fontset.
7764 		 * If the allocation fails, HL_TABLE()[idx].sg_font OR
7765 		 * sg_fontset will be set to NOFONT or NOFONTSET respectively.
7766 		 */
7767 
7768 		HL_TABLE()[idx].sg_font = NOFONT;
7769 # ifdef FEAT_XFONTSET
7770 		HL_TABLE()[idx].sg_fontset = NOFONTSET;
7771 # endif
7772 		hl_do_font(idx, arg, is_normal_group, is_menu_group,
7773 						     is_tooltip_group, FALSE);
7774 
7775 # ifdef FEAT_XFONTSET
7776 		if (HL_TABLE()[idx].sg_fontset != NOFONTSET)
7777 		{
7778 		    /* New fontset was accepted. Free the old one, if there
7779 		     * was one. */
7780 		    gui_mch_free_fontset(temp_sg_fontset);
7781 		    vim_free(HL_TABLE()[idx].sg_font_name);
7782 		    HL_TABLE()[idx].sg_font_name = vim_strsave(arg);
7783 		    did_change = TRUE;
7784 		}
7785 		else
7786 		    HL_TABLE()[idx].sg_fontset = temp_sg_fontset;
7787 # endif
7788 		if (HL_TABLE()[idx].sg_font != NOFONT)
7789 		{
7790 		    /* New font was accepted. Free the old one, if there was
7791 		     * one. */
7792 		    gui_mch_free_font(temp_sg_font);
7793 		    vim_free(HL_TABLE()[idx].sg_font_name);
7794 		    HL_TABLE()[idx].sg_font_name = vim_strsave(arg);
7795 		    did_change = TRUE;
7796 		}
7797 		else
7798 		    HL_TABLE()[idx].sg_font = temp_sg_font;
7799 	    }
7800 #endif
7801 	}
7802 	else if (STRCMP(key, "CTERMFG") == 0 || STRCMP(key, "CTERMBG") == 0)
7803 	{
7804 	  if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM))
7805 	  {
7806 	    if (!init)
7807 		HL_TABLE()[idx].sg_set |= SG_CTERM;
7808 
7809 	    /* When setting the foreground color, and previously the "bold"
7810 	     * flag was set for a light color, reset it now */
7811 	    if (key[5] == 'F' && HL_TABLE()[idx].sg_cterm_bold)
7812 	    {
7813 		HL_TABLE()[idx].sg_cterm &= ~HL_BOLD;
7814 		HL_TABLE()[idx].sg_cterm_bold = FALSE;
7815 	    }
7816 
7817 	    if (VIM_ISDIGIT(*arg))
7818 		color = atoi((char *)arg);
7819 	    else if (STRICMP(arg, "fg") == 0)
7820 	    {
7821 		if (cterm_normal_fg_color)
7822 		    color = cterm_normal_fg_color - 1;
7823 		else
7824 		{
7825 		    emsg(_("E419: FG color unknown"));
7826 		    error = TRUE;
7827 		    break;
7828 		}
7829 	    }
7830 	    else if (STRICMP(arg, "bg") == 0)
7831 	    {
7832 		if (cterm_normal_bg_color > 0)
7833 		    color = cterm_normal_bg_color - 1;
7834 		else
7835 		{
7836 		    emsg(_("E420: BG color unknown"));
7837 		    error = TRUE;
7838 		    break;
7839 		}
7840 	    }
7841 	    else
7842 	    {
7843 		int bold = MAYBE;
7844 
7845 #if defined(__QNXNTO__)
7846 		static int *color_numbers_8_qansi = color_numbers_8;
7847 		/* On qnx, the 8 & 16 color arrays are the same */
7848 		if (STRNCMP(T_NAME, "qansi", 5) == 0)
7849 		    color_numbers_8_qansi = color_numbers_16;
7850 #endif
7851 
7852 		/* reduce calls to STRICMP a bit, it can be slow */
7853 		off = TOUPPER_ASC(*arg);
7854 		for (i = (sizeof(color_names) / sizeof(char *)); --i >= 0; )
7855 		    if (off == color_names[i][0]
7856 				 && STRICMP(arg + 1, color_names[i] + 1) == 0)
7857 			break;
7858 		if (i < 0)
7859 		{
7860 		    semsg(_("E421: Color name or number not recognized: %s"), key_start);
7861 		    error = TRUE;
7862 		    break;
7863 		}
7864 
7865 		color = lookup_color(i, key[5] == 'F', &bold);
7866 
7867 		/* set/reset bold attribute to get light foreground
7868 		 * colors (on some terminals, e.g. "linux") */
7869 		if (bold == TRUE)
7870 		{
7871 		    HL_TABLE()[idx].sg_cterm |= HL_BOLD;
7872 		    HL_TABLE()[idx].sg_cterm_bold = TRUE;
7873 		}
7874 		else if (bold == FALSE)
7875 		    HL_TABLE()[idx].sg_cterm &= ~HL_BOLD;
7876 	    }
7877 
7878 	    /* Add one to the argument, to avoid zero.  Zero is used for
7879 	     * "NONE", then "color" is -1. */
7880 	    if (key[5] == 'F')
7881 	    {
7882 		HL_TABLE()[idx].sg_cterm_fg = color + 1;
7883 		if (is_normal_group)
7884 		{
7885 		    cterm_normal_fg_color = color + 1;
7886 		    cterm_normal_fg_bold = (HL_TABLE()[idx].sg_cterm & HL_BOLD);
7887 #ifdef FEAT_GUI
7888 		    /* Don't do this if the GUI is used. */
7889 		    if (!gui.in_use && !gui.starting)
7890 #endif
7891 		    {
7892 			must_redraw = CLEAR;
7893 			if (termcap_active && color >= 0)
7894 			    term_fg_color(color);
7895 		    }
7896 		}
7897 	    }
7898 	    else
7899 	    {
7900 		HL_TABLE()[idx].sg_cterm_bg = color + 1;
7901 		if (is_normal_group)
7902 		{
7903 		    cterm_normal_bg_color = color + 1;
7904 #ifdef FEAT_GUI
7905 		    /* Don't mess with 'background' if the GUI is used. */
7906 		    if (!gui.in_use && !gui.starting)
7907 #endif
7908 		    {
7909 			must_redraw = CLEAR;
7910 			if (color >= 0)
7911 			{
7912 			    int dark = -1;
7913 
7914 			    if (termcap_active)
7915 				term_bg_color(color);
7916 			    if (t_colors < 16)
7917 				dark = (color == 0 || color == 4);
7918 			    /* Limit the heuristic to the standard 16 colors */
7919 			    else if (color < 16)
7920 				dark = (color < 7 || color == 8);
7921 			    /* Set the 'background' option if the value is
7922 			     * wrong. */
7923 			    if (dark != -1
7924 				    && dark != (*p_bg == 'd')
7925 				    && !option_was_set((char_u *)"bg"))
7926 			    {
7927 				set_option_value((char_u *)"bg", 0L,
7928 				       (char_u *)(dark ? "dark" : "light"), 0);
7929 				reset_option_was_set((char_u *)"bg");
7930 			    }
7931 			}
7932 		    }
7933 		}
7934 	    }
7935 	  }
7936 	}
7937 	else if (STRCMP(key, "GUIFG") == 0)
7938 	{
7939 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
7940 	    char_u **namep = &HL_TABLE()[idx].sg_gui_fg_name;
7941 
7942 	    if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
7943 	    {
7944 		if (!init)
7945 		    HL_TABLE()[idx].sg_set |= SG_GUI;
7946 
7947 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
7948 		/* In GUI guifg colors are only used when recognized */
7949 		i = color_name2handle(arg);
7950 		if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !USE_24BIT)
7951 		{
7952 		    HL_TABLE()[idx].sg_gui_fg = i;
7953 # endif
7954 		    if (*namep == NULL || STRCMP(*namep, arg) != 0)
7955 		    {
7956 			vim_free(*namep);
7957 			if (STRCMP(arg, "NONE") != 0)
7958 			    *namep = vim_strsave(arg);
7959 			else
7960 			    *namep = NULL;
7961 			did_change = TRUE;
7962 		    }
7963 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
7964 #  ifdef FEAT_GUI_X11
7965 		    if (is_menu_group && gui.menu_fg_pixel != i)
7966 		    {
7967 			gui.menu_fg_pixel = i;
7968 			do_colors = TRUE;
7969 		    }
7970 		    if (is_scrollbar_group && gui.scroll_fg_pixel != i)
7971 		    {
7972 			gui.scroll_fg_pixel = i;
7973 			do_colors = TRUE;
7974 		    }
7975 #   ifdef FEAT_BEVAL_GUI
7976 		    if (is_tooltip_group && gui.tooltip_fg_pixel != i)
7977 		    {
7978 			gui.tooltip_fg_pixel = i;
7979 			do_colors = TRUE;
7980 		    }
7981 #   endif
7982 #  endif
7983 		}
7984 # endif
7985 	    }
7986 #endif
7987 	}
7988 	else if (STRCMP(key, "GUIBG") == 0)
7989 	{
7990 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
7991 	    char_u **namep = &HL_TABLE()[idx].sg_gui_bg_name;
7992 
7993 	    if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
7994 	    {
7995 		if (!init)
7996 		    HL_TABLE()[idx].sg_set |= SG_GUI;
7997 
7998 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
7999 		/* In GUI guifg colors are only used when recognized */
8000 		i = color_name2handle(arg);
8001 		if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !USE_24BIT)
8002 		{
8003 		    HL_TABLE()[idx].sg_gui_bg = i;
8004 # endif
8005 		    if (*namep == NULL || STRCMP(*namep, arg) != 0)
8006 		    {
8007 			vim_free(*namep);
8008 			if (STRCMP(arg, "NONE") != 0)
8009 			    *namep = vim_strsave(arg);
8010 			else
8011 			    *namep = NULL;
8012 			did_change = TRUE;
8013 		    }
8014 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8015 #  ifdef FEAT_GUI_X11
8016 		    if (is_menu_group && gui.menu_bg_pixel != i)
8017 		    {
8018 			gui.menu_bg_pixel = i;
8019 			do_colors = TRUE;
8020 		    }
8021 		    if (is_scrollbar_group && gui.scroll_bg_pixel != i)
8022 		    {
8023 			gui.scroll_bg_pixel = i;
8024 			do_colors = TRUE;
8025 		    }
8026 #   ifdef FEAT_BEVAL_GUI
8027 		    if (is_tooltip_group && gui.tooltip_bg_pixel != i)
8028 		    {
8029 			gui.tooltip_bg_pixel = i;
8030 			do_colors = TRUE;
8031 		    }
8032 #   endif
8033 #  endif
8034 		}
8035 # endif
8036 	    }
8037 #endif
8038 	}
8039 	else if (STRCMP(key, "GUISP") == 0)
8040 	{
8041 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
8042 	    char_u **namep = &HL_TABLE()[idx].sg_gui_sp_name;
8043 
8044 	    if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI))
8045 	    {
8046 		if (!init)
8047 		    HL_TABLE()[idx].sg_set |= SG_GUI;
8048 
8049 # ifdef FEAT_GUI
8050 		i = color_name2handle(arg);
8051 		if (i != INVALCOLOR || STRCMP(arg, "NONE") == 0 || !gui.in_use)
8052 		{
8053 		    HL_TABLE()[idx].sg_gui_sp = i;
8054 # endif
8055 		    if (*namep == NULL || STRCMP(*namep, arg) != 0)
8056 		    {
8057 			vim_free(*namep);
8058 			if (STRCMP(arg, "NONE") != 0)
8059 			    *namep = vim_strsave(arg);
8060 			else
8061 			    *namep = NULL;
8062 			did_change = TRUE;
8063 		    }
8064 # ifdef FEAT_GUI
8065 		}
8066 # endif
8067 	    }
8068 #endif
8069 	}
8070 	else if (STRCMP(key, "START") == 0 || STRCMP(key, "STOP") == 0)
8071 	{
8072 	    char_u	buf[100];
8073 	    char_u	*tname;
8074 
8075 	    if (!init)
8076 		HL_TABLE()[idx].sg_set |= SG_TERM;
8077 
8078 	    /*
8079 	     * The "start" and "stop"  arguments can be a literal escape
8080 	     * sequence, or a comma separated list of terminal codes.
8081 	     */
8082 	    if (STRNCMP(arg, "t_", 2) == 0)
8083 	    {
8084 		off = 0;
8085 		buf[0] = 0;
8086 		while (arg[off] != NUL)
8087 		{
8088 		    /* Isolate one termcap name */
8089 		    for (len = 0; arg[off + len] &&
8090 						 arg[off + len] != ','; ++len)
8091 			;
8092 		    tname = vim_strnsave(arg + off, len);
8093 		    if (tname == NULL)		/* out of memory */
8094 		    {
8095 			error = TRUE;
8096 			break;
8097 		    }
8098 		    /* lookup the escape sequence for the item */
8099 		    p = get_term_code(tname);
8100 		    vim_free(tname);
8101 		    if (p == NULL)	    /* ignore non-existing things */
8102 			p = (char_u *)"";
8103 
8104 		    /* Append it to the already found stuff */
8105 		    if ((int)(STRLEN(buf) + STRLEN(p)) >= 99)
8106 		    {
8107 			semsg(_("E422: terminal code too long: %s"), arg);
8108 			error = TRUE;
8109 			break;
8110 		    }
8111 		    STRCAT(buf, p);
8112 
8113 		    /* Advance to the next item */
8114 		    off += len;
8115 		    if (arg[off] == ',')	    /* another one follows */
8116 			++off;
8117 		}
8118 	    }
8119 	    else
8120 	    {
8121 		/*
8122 		 * Copy characters from arg[] to buf[], translating <> codes.
8123 		 */
8124 		for (p = arg, off = 0; off < 100 - 6 && *p; )
8125 		{
8126 		    len = trans_special(&p, buf + off, FALSE, FALSE);
8127 		    if (len > 0)	    /* recognized special char */
8128 			off += len;
8129 		    else		    /* copy as normal char */
8130 			buf[off++] = *p++;
8131 		}
8132 		buf[off] = NUL;
8133 	    }
8134 	    if (error)
8135 		break;
8136 
8137 	    if (STRCMP(buf, "NONE") == 0)	/* resetting the value */
8138 		p = NULL;
8139 	    else
8140 		p = vim_strsave(buf);
8141 	    if (key[2] == 'A')
8142 	    {
8143 		vim_free(HL_TABLE()[idx].sg_start);
8144 		HL_TABLE()[idx].sg_start = p;
8145 	    }
8146 	    else
8147 	    {
8148 		vim_free(HL_TABLE()[idx].sg_stop);
8149 		HL_TABLE()[idx].sg_stop = p;
8150 	    }
8151 	}
8152 	else
8153 	{
8154 	    semsg(_("E423: Illegal argument: %s"), key_start);
8155 	    error = TRUE;
8156 	    break;
8157 	}
8158 	HL_TABLE()[idx].sg_cleared = FALSE;
8159 
8160 	/*
8161 	 * When highlighting has been given for a group, don't link it.
8162 	 */
8163 	if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK))
8164 	    HL_TABLE()[idx].sg_link = 0;
8165 
8166 	/*
8167 	 * Continue with next argument.
8168 	 */
8169 	linep = skipwhite(linep);
8170       }
8171 
8172     /*
8173      * If there is an error, and it's a new entry, remove it from the table.
8174      */
8175     if (error && idx == highlight_ga.ga_len)
8176 	syn_unadd_group();
8177     else
8178     {
8179 	if (is_normal_group)
8180 	{
8181 	    HL_TABLE()[idx].sg_term_attr = 0;
8182 	    HL_TABLE()[idx].sg_cterm_attr = 0;
8183 #ifdef FEAT_GUI
8184 	    HL_TABLE()[idx].sg_gui_attr = 0;
8185 	    /*
8186 	     * Need to update all groups, because they might be using "bg"
8187 	     * and/or "fg", which have been changed now.
8188 	     */
8189 #endif
8190 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8191 	    if (USE_24BIT)
8192 	    {
8193 		highlight_gui_started();
8194 		did_highlight_changed = TRUE;
8195 		redraw_all_later(NOT_VALID);
8196 	    }
8197 #endif
8198 	}
8199 #ifdef FEAT_TERMINAL
8200 	else if (is_terminal_group)
8201 	    set_terminal_default_colors(
8202 		    HL_TABLE()[idx].sg_cterm_fg, HL_TABLE()[idx].sg_cterm_bg);
8203 #endif
8204 #ifdef FEAT_GUI_X11
8205 # ifdef FEAT_MENU
8206 	else if (is_menu_group)
8207 	{
8208 	    if (gui.in_use && do_colors)
8209 		gui_mch_new_menu_colors();
8210 	}
8211 # endif
8212 	else if (is_scrollbar_group)
8213 	{
8214 	    if (gui.in_use && do_colors)
8215 		gui_new_scrollbar_colors();
8216 	}
8217 # ifdef FEAT_BEVAL_GUI
8218 	else if (is_tooltip_group)
8219 	{
8220 	    if (gui.in_use && do_colors)
8221 		gui_mch_new_tooltip_colors();
8222 	}
8223 # endif
8224 #endif
8225 	else
8226 	    set_hl_attr(idx);
8227 #ifdef FEAT_EVAL
8228 	HL_TABLE()[idx].sg_script_ctx = current_sctx;
8229 	HL_TABLE()[idx].sg_script_ctx.sc_lnum += sourcing_lnum;
8230 #endif
8231     }
8232 
8233     vim_free(key);
8234     vim_free(arg);
8235 
8236     /* Only call highlight_changed() once, after a sequence of highlight
8237      * commands, and only if an attribute actually changed. */
8238     if ((did_change
8239 	   || memcmp(&HL_TABLE()[idx], &item_before, sizeof(item_before)) != 0)
8240 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8241 	    && !did_highlight_changed
8242 #endif
8243        )
8244     {
8245 	/* Do not trigger a redraw when highlighting is changed while
8246 	 * redrawing.  This may happen when evaluating 'statusline' changes the
8247 	 * StatusLine group. */
8248 	if (!updating_screen)
8249 	    redraw_all_later(NOT_VALID);
8250 	need_highlight_changed = TRUE;
8251     }
8252 }
8253 
8254 #if defined(EXITFREE) || defined(PROTO)
8255     void
8256 free_highlight(void)
8257 {
8258     int	    i;
8259 
8260     for (i = 0; i < highlight_ga.ga_len; ++i)
8261     {
8262 	highlight_clear(i);
8263 	vim_free(HL_TABLE()[i].sg_name);
8264 	vim_free(HL_TABLE()[i].sg_name_u);
8265     }
8266     ga_clear(&highlight_ga);
8267 }
8268 #endif
8269 
8270 /*
8271  * Reset the cterm colors to what they were before Vim was started, if
8272  * possible.  Otherwise reset them to zero.
8273  */
8274     void
8275 restore_cterm_colors(void)
8276 {
8277 #if defined(MSWIN) && !defined(FEAT_GUI_MSWIN)
8278     /* Since t_me has been set, this probably means that the user
8279      * wants to use this as default colors.  Need to reset default
8280      * background/foreground colors. */
8281     mch_set_normal_colors();
8282 #else
8283     cterm_normal_fg_color = 0;
8284     cterm_normal_fg_bold = 0;
8285     cterm_normal_bg_color = 0;
8286 # ifdef FEAT_TERMGUICOLORS
8287     cterm_normal_fg_gui_color = INVALCOLOR;
8288     cterm_normal_bg_gui_color = INVALCOLOR;
8289 # endif
8290 #endif
8291 }
8292 
8293 /*
8294  * Return TRUE if highlight group "idx" has any settings.
8295  * When "check_link" is TRUE also check for an existing link.
8296  */
8297     static int
8298 hl_has_settings(int idx, int check_link)
8299 {
8300     return (   HL_TABLE()[idx].sg_term_attr != 0
8301 	    || HL_TABLE()[idx].sg_cterm_attr != 0
8302 	    || HL_TABLE()[idx].sg_cterm_fg != 0
8303 	    || HL_TABLE()[idx].sg_cterm_bg != 0
8304 #ifdef FEAT_GUI
8305 	    || HL_TABLE()[idx].sg_gui_attr != 0
8306 	    || HL_TABLE()[idx].sg_gui_fg_name != NULL
8307 	    || HL_TABLE()[idx].sg_gui_bg_name != NULL
8308 	    || HL_TABLE()[idx].sg_gui_sp_name != NULL
8309 	    || HL_TABLE()[idx].sg_font_name != NULL
8310 #endif
8311 	    || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK)));
8312 }
8313 
8314 /*
8315  * Clear highlighting for one group.
8316  */
8317     static void
8318 highlight_clear(int idx)
8319 {
8320     HL_TABLE()[idx].sg_cleared = TRUE;
8321 
8322     HL_TABLE()[idx].sg_term = 0;
8323     VIM_CLEAR(HL_TABLE()[idx].sg_start);
8324     VIM_CLEAR(HL_TABLE()[idx].sg_stop);
8325     HL_TABLE()[idx].sg_term_attr = 0;
8326     HL_TABLE()[idx].sg_cterm = 0;
8327     HL_TABLE()[idx].sg_cterm_bold = FALSE;
8328     HL_TABLE()[idx].sg_cterm_fg = 0;
8329     HL_TABLE()[idx].sg_cterm_bg = 0;
8330     HL_TABLE()[idx].sg_cterm_attr = 0;
8331 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
8332     HL_TABLE()[idx].sg_gui = 0;
8333     VIM_CLEAR(HL_TABLE()[idx].sg_gui_fg_name);
8334     VIM_CLEAR(HL_TABLE()[idx].sg_gui_bg_name);
8335     VIM_CLEAR(HL_TABLE()[idx].sg_gui_sp_name);
8336 #endif
8337 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
8338     HL_TABLE()[idx].sg_gui_fg = INVALCOLOR;
8339     HL_TABLE()[idx].sg_gui_bg = INVALCOLOR;
8340 #endif
8341 #ifdef FEAT_GUI
8342     HL_TABLE()[idx].sg_gui_sp = INVALCOLOR;
8343     gui_mch_free_font(HL_TABLE()[idx].sg_font);
8344     HL_TABLE()[idx].sg_font = NOFONT;
8345 # ifdef FEAT_XFONTSET
8346     gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
8347     HL_TABLE()[idx].sg_fontset = NOFONTSET;
8348 # endif
8349     VIM_CLEAR(HL_TABLE()[idx].sg_font_name);
8350     HL_TABLE()[idx].sg_gui_attr = 0;
8351 #endif
8352 #ifdef FEAT_EVAL
8353     /* Clear the script ID only when there is no link, since that is not
8354      * cleared. */
8355     if (HL_TABLE()[idx].sg_link == 0)
8356     {
8357 	HL_TABLE()[idx].sg_script_ctx.sc_sid = 0;
8358 	HL_TABLE()[idx].sg_script_ctx.sc_lnum = 0;
8359     }
8360 #endif
8361 }
8362 
8363 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
8364 /*
8365  * Set the normal foreground and background colors according to the "Normal"
8366  * highlighting group.  For X11 also set "Menu", "Scrollbar", and
8367  * "Tooltip" colors.
8368  */
8369     void
8370 set_normal_colors(void)
8371 {
8372 # ifdef FEAT_GUI
8373 #  ifdef FEAT_TERMGUICOLORS
8374     if (gui.in_use)
8375 #  endif
8376     {
8377 	if (set_group_colors((char_u *)"Normal",
8378 				 &gui.norm_pixel, &gui.back_pixel,
8379 				 FALSE, TRUE, FALSE))
8380 	{
8381 	    gui_mch_new_colors();
8382 	    must_redraw = CLEAR;
8383 	}
8384 #  ifdef FEAT_GUI_X11
8385 	if (set_group_colors((char_u *)"Menu",
8386 			     &gui.menu_fg_pixel, &gui.menu_bg_pixel,
8387 			     TRUE, FALSE, FALSE))
8388 	{
8389 #   ifdef FEAT_MENU
8390 	    gui_mch_new_menu_colors();
8391 #   endif
8392 	    must_redraw = CLEAR;
8393 	}
8394 #   ifdef FEAT_BEVAL_GUI
8395 	if (set_group_colors((char_u *)"Tooltip",
8396 			     &gui.tooltip_fg_pixel, &gui.tooltip_bg_pixel,
8397 			     FALSE, FALSE, TRUE))
8398 	{
8399 #    ifdef FEAT_TOOLBAR
8400 	    gui_mch_new_tooltip_colors();
8401 #    endif
8402 	    must_redraw = CLEAR;
8403 	}
8404 #   endif
8405 	if (set_group_colors((char_u *)"Scrollbar",
8406 			&gui.scroll_fg_pixel, &gui.scroll_bg_pixel,
8407 			FALSE, FALSE, FALSE))
8408 	{
8409 	    gui_new_scrollbar_colors();
8410 	    must_redraw = CLEAR;
8411 	}
8412 #  endif
8413     }
8414 # endif
8415 # ifdef FEAT_TERMGUICOLORS
8416 #  ifdef FEAT_GUI
8417     else
8418 #  endif
8419     {
8420 	int		idx;
8421 
8422 	idx = syn_name2id((char_u *)"Normal") - 1;
8423 	if (idx >= 0)
8424 	{
8425 	    gui_do_one_color(idx, FALSE, FALSE);
8426 
8427 	    /* If the normal fg or bg color changed a complete redraw is
8428 	     * required. */
8429 	    if (cterm_normal_fg_gui_color != HL_TABLE()[idx].sg_gui_fg
8430 		    || cterm_normal_bg_gui_color != HL_TABLE()[idx].sg_gui_bg)
8431 	    {
8432 		/* if the GUI color is INVALCOLOR then we use the default cterm
8433 		 * color */
8434 		cterm_normal_fg_gui_color = HL_TABLE()[idx].sg_gui_fg;
8435 		cterm_normal_bg_gui_color = HL_TABLE()[idx].sg_gui_bg;
8436 		must_redraw = CLEAR;
8437 	    }
8438 	}
8439     }
8440 # endif
8441 }
8442 #endif
8443 
8444 #if defined(FEAT_GUI) || defined(PROTO)
8445 /*
8446  * Set the colors for "Normal", "Menu", "Tooltip" or "Scrollbar".
8447  */
8448     static int
8449 set_group_colors(
8450     char_u	*name,
8451     guicolor_T	*fgp,
8452     guicolor_T	*bgp,
8453     int		do_menu,
8454     int		use_norm,
8455     int		do_tooltip)
8456 {
8457     int		idx;
8458 
8459     idx = syn_name2id(name) - 1;
8460     if (idx >= 0)
8461     {
8462 	gui_do_one_color(idx, do_menu, do_tooltip);
8463 
8464 	if (HL_TABLE()[idx].sg_gui_fg != INVALCOLOR)
8465 	    *fgp = HL_TABLE()[idx].sg_gui_fg;
8466 	else if (use_norm)
8467 	    *fgp = gui.def_norm_pixel;
8468 	if (HL_TABLE()[idx].sg_gui_bg != INVALCOLOR)
8469 	    *bgp = HL_TABLE()[idx].sg_gui_bg;
8470 	else if (use_norm)
8471 	    *bgp = gui.def_back_pixel;
8472 	return TRUE;
8473     }
8474     return FALSE;
8475 }
8476 
8477 /*
8478  * Get the font of the "Normal" group.
8479  * Returns "" when it's not found or not set.
8480  */
8481     char_u *
8482 hl_get_font_name(void)
8483 {
8484     int		id;
8485     char_u	*s;
8486 
8487     id = syn_name2id((char_u *)"Normal");
8488     if (id > 0)
8489     {
8490 	s = HL_TABLE()[id - 1].sg_font_name;
8491 	if (s != NULL)
8492 	    return s;
8493     }
8494     return (char_u *)"";
8495 }
8496 
8497 /*
8498  * Set font for "Normal" group.  Called by gui_mch_init_font() when a font has
8499  * actually chosen to be used.
8500  */
8501     void
8502 hl_set_font_name(char_u *font_name)
8503 {
8504     int	    id;
8505 
8506     id = syn_name2id((char_u *)"Normal");
8507     if (id > 0)
8508     {
8509 	vim_free(HL_TABLE()[id - 1].sg_font_name);
8510 	HL_TABLE()[id - 1].sg_font_name = vim_strsave(font_name);
8511     }
8512 }
8513 
8514 /*
8515  * Set background color for "Normal" group.  Called by gui_set_bg_color()
8516  * when the color is known.
8517  */
8518     void
8519 hl_set_bg_color_name(
8520     char_u  *name)	    /* must have been allocated */
8521 {
8522     int	    id;
8523 
8524     if (name != NULL)
8525     {
8526 	id = syn_name2id((char_u *)"Normal");
8527 	if (id > 0)
8528 	{
8529 	    vim_free(HL_TABLE()[id - 1].sg_gui_bg_name);
8530 	    HL_TABLE()[id - 1].sg_gui_bg_name = name;
8531 	}
8532     }
8533 }
8534 
8535 /*
8536  * Set foreground color for "Normal" group.  Called by gui_set_fg_color()
8537  * when the color is known.
8538  */
8539     void
8540 hl_set_fg_color_name(
8541     char_u  *name)	    /* must have been allocated */
8542 {
8543     int	    id;
8544 
8545     if (name != NULL)
8546     {
8547 	id = syn_name2id((char_u *)"Normal");
8548 	if (id > 0)
8549 	{
8550 	    vim_free(HL_TABLE()[id - 1].sg_gui_fg_name);
8551 	    HL_TABLE()[id - 1].sg_gui_fg_name = name;
8552 	}
8553     }
8554 }
8555 
8556 /*
8557  * Return the handle for a font name.
8558  * Returns NOFONT when failed.
8559  */
8560     static GuiFont
8561 font_name2handle(char_u *name)
8562 {
8563     if (STRCMP(name, "NONE") == 0)
8564 	return NOFONT;
8565 
8566     return gui_mch_get_font(name, TRUE);
8567 }
8568 
8569 # ifdef FEAT_XFONTSET
8570 /*
8571  * Return the handle for a fontset name.
8572  * Returns NOFONTSET when failed.
8573  */
8574     static GuiFontset
8575 fontset_name2handle(char_u *name, int fixed_width)
8576 {
8577     if (STRCMP(name, "NONE") == 0)
8578 	return NOFONTSET;
8579 
8580     return gui_mch_get_fontset(name, TRUE, fixed_width);
8581 }
8582 # endif
8583 
8584 /*
8585  * Get the font or fontset for one highlight group.
8586  */
8587     static void
8588 hl_do_font(
8589     int		idx,
8590     char_u	*arg,
8591     int		do_normal,		/* set normal font */
8592     int		do_menu UNUSED,		/* set menu font */
8593     int		do_tooltip UNUSED,	/* set tooltip font */
8594     int		free_font)		/* free current font/fontset */
8595 {
8596 # ifdef FEAT_XFONTSET
8597     /* If 'guifontset' is not empty, first try using the name as a
8598      * fontset.  If that doesn't work, use it as a font name. */
8599     if (*p_guifontset != NUL
8600 #  ifdef FONTSET_ALWAYS
8601 	|| do_menu
8602 #  endif
8603 #  ifdef FEAT_BEVAL_TIP
8604 	/* In Athena & Motif, the Tooltip highlight group is always a fontset */
8605 	|| do_tooltip
8606 #  endif
8607 	    )
8608     {
8609 	if (free_font)
8610 	    gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
8611 	HL_TABLE()[idx].sg_fontset = fontset_name2handle(arg, 0
8612 #  ifdef FONTSET_ALWAYS
8613 		|| do_menu
8614 #  endif
8615 #  ifdef FEAT_BEVAL_TIP
8616 		|| do_tooltip
8617 #  endif
8618 		);
8619     }
8620     if (HL_TABLE()[idx].sg_fontset != NOFONTSET)
8621     {
8622 	/* If it worked and it's the Normal group, use it as the normal
8623 	 * fontset.  Same for the Menu group. */
8624 	if (do_normal)
8625 	    gui_init_font(arg, TRUE);
8626 #   if (defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_ATHENA)) && defined(FEAT_MENU)
8627 	if (do_menu)
8628 	{
8629 #    ifdef FONTSET_ALWAYS
8630 	    gui.menu_fontset = HL_TABLE()[idx].sg_fontset;
8631 #    else
8632 	    /* YIKES!  This is a bug waiting to crash the program */
8633 	    gui.menu_font = HL_TABLE()[idx].sg_fontset;
8634 #    endif
8635 	    gui_mch_new_menu_font();
8636 	}
8637 #    ifdef FEAT_BEVAL_GUI
8638 	if (do_tooltip)
8639 	{
8640 	    /* The Athena widget set cannot currently handle switching between
8641 	     * displaying a single font and a fontset.
8642 	     * If the XtNinternational resource is set to True at widget
8643 	     * creation, then a fontset is always used, otherwise an
8644 	     * XFontStruct is used.
8645 	     */
8646 	    gui.tooltip_fontset = (XFontSet)HL_TABLE()[idx].sg_fontset;
8647 	    gui_mch_new_tooltip_font();
8648 	}
8649 #    endif
8650 #   endif
8651     }
8652     else
8653 # endif
8654     {
8655 	if (free_font)
8656 	    gui_mch_free_font(HL_TABLE()[idx].sg_font);
8657 	HL_TABLE()[idx].sg_font = font_name2handle(arg);
8658 	/* If it worked and it's the Normal group, use it as the
8659 	 * normal font.  Same for the Menu group. */
8660 	if (HL_TABLE()[idx].sg_font != NOFONT)
8661 	{
8662 	    if (do_normal)
8663 		gui_init_font(arg, FALSE);
8664 #ifndef FONTSET_ALWAYS
8665 # if (defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_ATHENA)) && defined(FEAT_MENU)
8666 	    if (do_menu)
8667 	    {
8668 		gui.menu_font = HL_TABLE()[idx].sg_font;
8669 		gui_mch_new_menu_font();
8670 	    }
8671 # endif
8672 #endif
8673 	}
8674     }
8675 }
8676 
8677 #endif /* FEAT_GUI */
8678 
8679 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
8680 /*
8681  * Return the handle for a color name.
8682  * Returns INVALCOLOR when failed.
8683  */
8684     guicolor_T
8685 color_name2handle(char_u *name)
8686 {
8687     if (STRCMP(name, "NONE") == 0)
8688 	return INVALCOLOR;
8689 
8690     if (STRICMP(name, "fg") == 0 || STRICMP(name, "foreground") == 0)
8691     {
8692 #if defined(FEAT_TERMGUICOLORS) && defined(FEAT_GUI)
8693 	if (gui.in_use)
8694 #endif
8695 #ifdef FEAT_GUI
8696 	    return gui.norm_pixel;
8697 #endif
8698 #ifdef FEAT_TERMGUICOLORS
8699 	if (cterm_normal_fg_gui_color != INVALCOLOR)
8700 	    return cterm_normal_fg_gui_color;
8701 	/* Guess that the foreground is black or white. */
8702 	return GUI_GET_COLOR((char_u *)(*p_bg == 'l' ? "black" : "white"));
8703 #endif
8704     }
8705     if (STRICMP(name, "bg") == 0 || STRICMP(name, "background") == 0)
8706     {
8707 #if defined(FEAT_TERMGUICOLORS) && defined(FEAT_GUI)
8708 	if (gui.in_use)
8709 #endif
8710 #ifdef FEAT_GUI
8711 	    return gui.back_pixel;
8712 #endif
8713 #ifdef FEAT_TERMGUICOLORS
8714 	if (cterm_normal_bg_gui_color != INVALCOLOR)
8715 	    return cterm_normal_bg_gui_color;
8716 	/* Guess that the background is white or black. */
8717 	return GUI_GET_COLOR((char_u *)(*p_bg == 'l' ? "white" : "black"));
8718 #endif
8719     }
8720 
8721     return GUI_GET_COLOR(name);
8722 }
8723 #endif
8724 
8725 /*
8726  * Table with the specifications for an attribute number.
8727  * Note that this table is used by ALL buffers.  This is required because the
8728  * GUI can redraw at any time for any buffer.
8729  */
8730 static garray_T	term_attr_table = {0, 0, 0, 0, NULL};
8731 
8732 #define TERM_ATTR_ENTRY(idx) ((attrentry_T *)term_attr_table.ga_data)[idx]
8733 
8734 static garray_T	cterm_attr_table = {0, 0, 0, 0, NULL};
8735 
8736 #define CTERM_ATTR_ENTRY(idx) ((attrentry_T *)cterm_attr_table.ga_data)[idx]
8737 
8738 #ifdef FEAT_GUI
8739 static garray_T	gui_attr_table = {0, 0, 0, 0, NULL};
8740 
8741 #define GUI_ATTR_ENTRY(idx) ((attrentry_T *)gui_attr_table.ga_data)[idx]
8742 #endif
8743 
8744 /*
8745  * Return the attr number for a set of colors and font.
8746  * Add a new entry to the term_attr_table, cterm_attr_table or gui_attr_table
8747  * if the combination is new.
8748  * Return 0 for error (no more room).
8749  */
8750     static int
8751 get_attr_entry(garray_T *table, attrentry_T *aep)
8752 {
8753     int		i;
8754     attrentry_T	*taep;
8755     static int	recursive = FALSE;
8756 
8757     /*
8758      * Init the table, in case it wasn't done yet.
8759      */
8760     table->ga_itemsize = sizeof(attrentry_T);
8761     table->ga_growsize = 7;
8762 
8763     /*
8764      * Try to find an entry with the same specifications.
8765      */
8766     for (i = 0; i < table->ga_len; ++i)
8767     {
8768 	taep = &(((attrentry_T *)table->ga_data)[i]);
8769 	if (	   aep->ae_attr == taep->ae_attr
8770 		&& (
8771 #ifdef FEAT_GUI
8772 		       (table == &gui_attr_table
8773 			&& (aep->ae_u.gui.fg_color == taep->ae_u.gui.fg_color
8774 			    && aep->ae_u.gui.bg_color
8775 						    == taep->ae_u.gui.bg_color
8776 			    && aep->ae_u.gui.sp_color
8777 						    == taep->ae_u.gui.sp_color
8778 			    && aep->ae_u.gui.font == taep->ae_u.gui.font
8779 #  ifdef FEAT_XFONTSET
8780 			    && aep->ae_u.gui.fontset == taep->ae_u.gui.fontset
8781 #  endif
8782 			    ))
8783 		    ||
8784 #endif
8785 		       (table == &term_attr_table
8786 			&& (aep->ae_u.term.start == NULL)
8787 					    == (taep->ae_u.term.start == NULL)
8788 			&& (aep->ae_u.term.start == NULL
8789 			    || STRCMP(aep->ae_u.term.start,
8790 						  taep->ae_u.term.start) == 0)
8791 			&& (aep->ae_u.term.stop == NULL)
8792 					     == (taep->ae_u.term.stop == NULL)
8793 			&& (aep->ae_u.term.stop == NULL
8794 			    || STRCMP(aep->ae_u.term.stop,
8795 						  taep->ae_u.term.stop) == 0))
8796 		    || (table == &cterm_attr_table
8797 			    && aep->ae_u.cterm.fg_color
8798 						  == taep->ae_u.cterm.fg_color
8799 			    && aep->ae_u.cterm.bg_color
8800 						  == taep->ae_u.cterm.bg_color
8801 #ifdef FEAT_TERMGUICOLORS
8802 			    && aep->ae_u.cterm.fg_rgb
8803 						    == taep->ae_u.cterm.fg_rgb
8804 			    && aep->ae_u.cterm.bg_rgb
8805 						    == taep->ae_u.cterm.bg_rgb
8806 #endif
8807 		       )))
8808 
8809 	return i + ATTR_OFF;
8810     }
8811 
8812     if (table->ga_len + ATTR_OFF > MAX_TYPENR)
8813     {
8814 	/*
8815 	 * Running out of attribute entries!  remove all attributes, and
8816 	 * compute new ones for all groups.
8817 	 * When called recursively, we are really out of numbers.
8818 	 */
8819 	if (recursive)
8820 	{
8821 	    emsg(_("E424: Too many different highlighting attributes in use"));
8822 	    return 0;
8823 	}
8824 	recursive = TRUE;
8825 
8826 	clear_hl_tables();
8827 
8828 	must_redraw = CLEAR;
8829 
8830 	for (i = 0; i < highlight_ga.ga_len; ++i)
8831 	    set_hl_attr(i);
8832 
8833 	recursive = FALSE;
8834     }
8835 
8836     /*
8837      * This is a new combination of colors and font, add an entry.
8838      */
8839     if (ga_grow(table, 1) == FAIL)
8840 	return 0;
8841 
8842     taep = &(((attrentry_T *)table->ga_data)[table->ga_len]);
8843     vim_memset(taep, 0, sizeof(attrentry_T));
8844     taep->ae_attr = aep->ae_attr;
8845 #ifdef FEAT_GUI
8846     if (table == &gui_attr_table)
8847     {
8848 	taep->ae_u.gui.fg_color = aep->ae_u.gui.fg_color;
8849 	taep->ae_u.gui.bg_color = aep->ae_u.gui.bg_color;
8850 	taep->ae_u.gui.sp_color = aep->ae_u.gui.sp_color;
8851 	taep->ae_u.gui.font = aep->ae_u.gui.font;
8852 # ifdef FEAT_XFONTSET
8853 	taep->ae_u.gui.fontset = aep->ae_u.gui.fontset;
8854 # endif
8855     }
8856 #endif
8857     if (table == &term_attr_table)
8858     {
8859 	if (aep->ae_u.term.start == NULL)
8860 	    taep->ae_u.term.start = NULL;
8861 	else
8862 	    taep->ae_u.term.start = vim_strsave(aep->ae_u.term.start);
8863 	if (aep->ae_u.term.stop == NULL)
8864 	    taep->ae_u.term.stop = NULL;
8865 	else
8866 	    taep->ae_u.term.stop = vim_strsave(aep->ae_u.term.stop);
8867     }
8868     else if (table == &cterm_attr_table)
8869     {
8870 	taep->ae_u.cterm.fg_color = aep->ae_u.cterm.fg_color;
8871 	taep->ae_u.cterm.bg_color = aep->ae_u.cterm.bg_color;
8872 #ifdef FEAT_TERMGUICOLORS
8873 	taep->ae_u.cterm.fg_rgb = aep->ae_u.cterm.fg_rgb;
8874 	taep->ae_u.cterm.bg_rgb = aep->ae_u.cterm.bg_rgb;
8875 #endif
8876     }
8877     ++table->ga_len;
8878     return (table->ga_len - 1 + ATTR_OFF);
8879 }
8880 
8881 #if defined(FEAT_TERMINAL) || defined(PROTO)
8882 /*
8883  * Get an attribute index for a cterm entry.
8884  * Uses an existing entry when possible or adds one when needed.
8885  */
8886     int
8887 get_cterm_attr_idx(int attr, int fg, int bg)
8888 {
8889     attrentry_T		at_en;
8890 
8891     vim_memset(&at_en, 0, sizeof(attrentry_T));
8892 #ifdef FEAT_TERMGUICOLORS
8893     at_en.ae_u.cterm.fg_rgb = INVALCOLOR;
8894     at_en.ae_u.cterm.bg_rgb = INVALCOLOR;
8895 #endif
8896     at_en.ae_attr = attr;
8897     at_en.ae_u.cterm.fg_color = fg;
8898     at_en.ae_u.cterm.bg_color = bg;
8899     return get_attr_entry(&cterm_attr_table, &at_en);
8900 }
8901 #endif
8902 
8903 #if (defined(FEAT_TERMINAL) && defined(FEAT_TERMGUICOLORS)) || defined(PROTO)
8904 /*
8905  * Get an attribute index for a 'termguicolors' entry.
8906  * Uses an existing entry when possible or adds one when needed.
8907  */
8908     int
8909 get_tgc_attr_idx(int attr, guicolor_T fg, guicolor_T bg)
8910 {
8911     attrentry_T		at_en;
8912 
8913     vim_memset(&at_en, 0, sizeof(attrentry_T));
8914     at_en.ae_attr = attr;
8915     if (fg == INVALCOLOR && bg == INVALCOLOR)
8916     {
8917 	/* If both GUI colors are not set fall back to the cterm colors.  Helps
8918 	 * if the GUI only has an attribute, such as undercurl. */
8919 	at_en.ae_u.cterm.fg_rgb = CTERMCOLOR;
8920 	at_en.ae_u.cterm.bg_rgb = CTERMCOLOR;
8921     }
8922     else
8923     {
8924 	at_en.ae_u.cterm.fg_rgb = fg;
8925 	at_en.ae_u.cterm.bg_rgb = bg;
8926     }
8927     return get_attr_entry(&cterm_attr_table, &at_en);
8928 }
8929 #endif
8930 
8931 #if (defined(FEAT_TERMINAL) && defined(FEAT_GUI)) || defined(PROTO)
8932 /*
8933  * Get an attribute index for a cterm entry.
8934  * Uses an existing entry when possible or adds one when needed.
8935  */
8936     int
8937 get_gui_attr_idx(int attr, guicolor_T fg, guicolor_T bg)
8938 {
8939     attrentry_T		at_en;
8940 
8941     vim_memset(&at_en, 0, sizeof(attrentry_T));
8942     at_en.ae_attr = attr;
8943     at_en.ae_u.gui.fg_color = fg;
8944     at_en.ae_u.gui.bg_color = bg;
8945     return get_attr_entry(&gui_attr_table, &at_en);
8946 }
8947 #endif
8948 
8949 /*
8950  * Clear all highlight tables.
8951  */
8952     void
8953 clear_hl_tables(void)
8954 {
8955     int		i;
8956     attrentry_T	*taep;
8957 
8958 #ifdef FEAT_GUI
8959     ga_clear(&gui_attr_table);
8960 #endif
8961     for (i = 0; i < term_attr_table.ga_len; ++i)
8962     {
8963 	taep = &(((attrentry_T *)term_attr_table.ga_data)[i]);
8964 	vim_free(taep->ae_u.term.start);
8965 	vim_free(taep->ae_u.term.stop);
8966     }
8967     ga_clear(&term_attr_table);
8968     ga_clear(&cterm_attr_table);
8969 }
8970 
8971 #if defined(FEAT_SYN_HL) || defined(FEAT_SPELL) || defined(PROTO)
8972 /*
8973  * Combine special attributes (e.g., for spelling) with other attributes
8974  * (e.g., for syntax highlighting).
8975  * "prim_attr" overrules "char_attr".
8976  * This creates a new group when required.
8977  * Since we expect there to be few spelling mistakes we don't cache the
8978  * result.
8979  * Return the resulting attributes.
8980  */
8981     int
8982 hl_combine_attr(int char_attr, int prim_attr)
8983 {
8984     attrentry_T *char_aep = NULL;
8985     attrentry_T *spell_aep;
8986     attrentry_T new_en;
8987 
8988     if (char_attr == 0)
8989 	return prim_attr;
8990     if (char_attr <= HL_ALL && prim_attr <= HL_ALL)
8991 	return ATTR_COMBINE(char_attr, prim_attr);
8992 #ifdef FEAT_GUI
8993     if (gui.in_use)
8994     {
8995 	if (char_attr > HL_ALL)
8996 	    char_aep = syn_gui_attr2entry(char_attr);
8997 	if (char_aep != NULL)
8998 	    new_en = *char_aep;
8999 	else
9000 	{
9001 	    vim_memset(&new_en, 0, sizeof(new_en));
9002 	    new_en.ae_u.gui.fg_color = INVALCOLOR;
9003 	    new_en.ae_u.gui.bg_color = INVALCOLOR;
9004 	    new_en.ae_u.gui.sp_color = INVALCOLOR;
9005 	    if (char_attr <= HL_ALL)
9006 		new_en.ae_attr = char_attr;
9007 	}
9008 
9009 	if (prim_attr <= HL_ALL)
9010 	    new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, prim_attr);
9011 	else
9012 	{
9013 	    spell_aep = syn_gui_attr2entry(prim_attr);
9014 	    if (spell_aep != NULL)
9015 	    {
9016 		new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr,
9017 							   spell_aep->ae_attr);
9018 		if (spell_aep->ae_u.gui.fg_color != INVALCOLOR)
9019 		    new_en.ae_u.gui.fg_color = spell_aep->ae_u.gui.fg_color;
9020 		if (spell_aep->ae_u.gui.bg_color != INVALCOLOR)
9021 		    new_en.ae_u.gui.bg_color = spell_aep->ae_u.gui.bg_color;
9022 		if (spell_aep->ae_u.gui.sp_color != INVALCOLOR)
9023 		    new_en.ae_u.gui.sp_color = spell_aep->ae_u.gui.sp_color;
9024 		if (spell_aep->ae_u.gui.font != NOFONT)
9025 		    new_en.ae_u.gui.font = spell_aep->ae_u.gui.font;
9026 # ifdef FEAT_XFONTSET
9027 		if (spell_aep->ae_u.gui.fontset != NOFONTSET)
9028 		    new_en.ae_u.gui.fontset = spell_aep->ae_u.gui.fontset;
9029 # endif
9030 	    }
9031 	}
9032 	return get_attr_entry(&gui_attr_table, &new_en);
9033     }
9034 #endif
9035 
9036     if (IS_CTERM)
9037     {
9038 	if (char_attr > HL_ALL)
9039 	    char_aep = syn_cterm_attr2entry(char_attr);
9040 	if (char_aep != NULL)
9041 	    new_en = *char_aep;
9042 	else
9043 	{
9044 	    vim_memset(&new_en, 0, sizeof(new_en));
9045 #ifdef FEAT_TERMGUICOLORS
9046 	    new_en.ae_u.cterm.bg_rgb = INVALCOLOR;
9047 	    new_en.ae_u.cterm.fg_rgb = INVALCOLOR;
9048 #endif
9049 	    if (char_attr <= HL_ALL)
9050 		new_en.ae_attr = char_attr;
9051 	}
9052 
9053 	if (prim_attr <= HL_ALL)
9054 		new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, prim_attr);
9055 	else
9056 	{
9057 	    spell_aep = syn_cterm_attr2entry(prim_attr);
9058 	    if (spell_aep != NULL)
9059 	    {
9060 		new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr,
9061 							   spell_aep->ae_attr);
9062 		if (spell_aep->ae_u.cterm.fg_color > 0)
9063 		    new_en.ae_u.cterm.fg_color = spell_aep->ae_u.cterm.fg_color;
9064 		if (spell_aep->ae_u.cterm.bg_color > 0)
9065 		    new_en.ae_u.cterm.bg_color = spell_aep->ae_u.cterm.bg_color;
9066 #ifdef FEAT_TERMGUICOLORS
9067 		/* If both fg and bg are not set fall back to cterm colors.
9068 		 * Helps for SpellBad which uses undercurl in the GUI. */
9069 		if (COLOR_INVALID(spell_aep->ae_u.cterm.fg_rgb)
9070 			&& COLOR_INVALID(spell_aep->ae_u.cterm.bg_rgb))
9071 		{
9072 		    if (spell_aep->ae_u.cterm.fg_color > 0)
9073 			new_en.ae_u.cterm.fg_rgb = CTERMCOLOR;
9074 		    if (spell_aep->ae_u.cterm.bg_color > 0)
9075 			new_en.ae_u.cterm.bg_rgb = CTERMCOLOR;
9076 		}
9077 		else
9078 		{
9079 		    if (spell_aep->ae_u.cterm.fg_rgb != INVALCOLOR)
9080 			new_en.ae_u.cterm.fg_rgb = spell_aep->ae_u.cterm.fg_rgb;
9081 		    if (spell_aep->ae_u.cterm.bg_rgb != INVALCOLOR)
9082 			new_en.ae_u.cterm.bg_rgb = spell_aep->ae_u.cterm.bg_rgb;
9083 		}
9084 #endif
9085 	    }
9086 	}
9087 	return get_attr_entry(&cterm_attr_table, &new_en);
9088     }
9089 
9090     if (char_attr > HL_ALL)
9091 	char_aep = syn_term_attr2entry(char_attr);
9092     if (char_aep != NULL)
9093 	new_en = *char_aep;
9094     else
9095     {
9096 	vim_memset(&new_en, 0, sizeof(new_en));
9097 	if (char_attr <= HL_ALL)
9098 	    new_en.ae_attr = char_attr;
9099     }
9100 
9101     if (prim_attr <= HL_ALL)
9102 	new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, prim_attr);
9103     else
9104     {
9105 	spell_aep = syn_term_attr2entry(prim_attr);
9106 	if (spell_aep != NULL)
9107 	{
9108 	    new_en.ae_attr = ATTR_COMBINE(new_en.ae_attr, spell_aep->ae_attr);
9109 	    if (spell_aep->ae_u.term.start != NULL)
9110 	    {
9111 		new_en.ae_u.term.start = spell_aep->ae_u.term.start;
9112 		new_en.ae_u.term.stop = spell_aep->ae_u.term.stop;
9113 	    }
9114 	}
9115     }
9116     return get_attr_entry(&term_attr_table, &new_en);
9117 }
9118 #endif
9119 
9120 #ifdef FEAT_GUI
9121 
9122     attrentry_T *
9123 syn_gui_attr2entry(int attr)
9124 {
9125     attr -= ATTR_OFF;
9126     if (attr >= gui_attr_table.ga_len)	    /* did ":syntax clear" */
9127 	return NULL;
9128     return &(GUI_ATTR_ENTRY(attr));
9129 }
9130 #endif /* FEAT_GUI */
9131 
9132 /*
9133  * Get the highlight attributes (HL_BOLD etc.) from an attribute nr.
9134  * Only to be used when "attr" > HL_ALL.
9135  */
9136     int
9137 syn_attr2attr(int attr)
9138 {
9139     attrentry_T	*aep;
9140 
9141 #ifdef FEAT_GUI
9142     if (gui.in_use)
9143 	aep = syn_gui_attr2entry(attr);
9144     else
9145 #endif
9146 	if (IS_CTERM)
9147 	    aep = syn_cterm_attr2entry(attr);
9148 	else
9149 	    aep = syn_term_attr2entry(attr);
9150 
9151     if (aep == NULL)	    /* highlighting not set */
9152 	return 0;
9153     return aep->ae_attr;
9154 }
9155 
9156 
9157     attrentry_T *
9158 syn_term_attr2entry(int attr)
9159 {
9160     attr -= ATTR_OFF;
9161     if (attr >= term_attr_table.ga_len)	    /* did ":syntax clear" */
9162 	return NULL;
9163     return &(TERM_ATTR_ENTRY(attr));
9164 }
9165 
9166     attrentry_T *
9167 syn_cterm_attr2entry(int attr)
9168 {
9169     attr -= ATTR_OFF;
9170     if (attr >= cterm_attr_table.ga_len)	/* did ":syntax clear" */
9171 	return NULL;
9172     return &(CTERM_ATTR_ENTRY(attr));
9173 }
9174 
9175 #define LIST_ATTR   1
9176 #define LIST_STRING 2
9177 #define LIST_INT    3
9178 
9179     static void
9180 highlight_list_one(int id)
9181 {
9182     struct hl_group	*sgp;
9183     int			didh = FALSE;
9184 
9185     sgp = &HL_TABLE()[id - 1];	    // index is ID minus one
9186 
9187     if (message_filtered(sgp->sg_name))
9188 	return;
9189 
9190     didh = highlight_list_arg(id, didh, LIST_ATTR,
9191 				    sgp->sg_term, NULL, "term");
9192     didh = highlight_list_arg(id, didh, LIST_STRING,
9193 				    0, sgp->sg_start, "start");
9194     didh = highlight_list_arg(id, didh, LIST_STRING,
9195 				    0, sgp->sg_stop, "stop");
9196 
9197     didh = highlight_list_arg(id, didh, LIST_ATTR,
9198 				    sgp->sg_cterm, NULL, "cterm");
9199     didh = highlight_list_arg(id, didh, LIST_INT,
9200 				    sgp->sg_cterm_fg, NULL, "ctermfg");
9201     didh = highlight_list_arg(id, didh, LIST_INT,
9202 				    sgp->sg_cterm_bg, NULL, "ctermbg");
9203 
9204 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
9205     didh = highlight_list_arg(id, didh, LIST_ATTR,
9206 				    sgp->sg_gui, NULL, "gui");
9207     didh = highlight_list_arg(id, didh, LIST_STRING,
9208 				    0, sgp->sg_gui_fg_name, "guifg");
9209     didh = highlight_list_arg(id, didh, LIST_STRING,
9210 				    0, sgp->sg_gui_bg_name, "guibg");
9211     didh = highlight_list_arg(id, didh, LIST_STRING,
9212 				    0, sgp->sg_gui_sp_name, "guisp");
9213 #endif
9214 #ifdef FEAT_GUI
9215     didh = highlight_list_arg(id, didh, LIST_STRING,
9216 				    0, sgp->sg_font_name, "font");
9217 #endif
9218 
9219     if (sgp->sg_link && !got_int)
9220     {
9221 	(void)syn_list_header(didh, 9999, id);
9222 	didh = TRUE;
9223 	msg_puts_attr("links to", HL_ATTR(HLF_D));
9224 	msg_putchar(' ');
9225 	msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name);
9226     }
9227 
9228     if (!didh)
9229 	highlight_list_arg(id, didh, LIST_STRING, 0, (char_u *)"cleared", "");
9230 #ifdef FEAT_EVAL
9231     if (p_verbose > 0)
9232 	last_set_msg(sgp->sg_script_ctx);
9233 #endif
9234 }
9235 
9236     static int
9237 highlight_list_arg(
9238     int		id,
9239     int		didh,
9240     int		type,
9241     int		iarg,
9242     char_u	*sarg,
9243     char	*name)
9244 {
9245     char_u	buf[100];
9246     char_u	*ts;
9247     int		i;
9248 
9249     if (got_int)
9250 	return FALSE;
9251     if (type == LIST_STRING ? (sarg != NULL) : (iarg != 0))
9252     {
9253 	ts = buf;
9254 	if (type == LIST_INT)
9255 	    sprintf((char *)buf, "%d", iarg - 1);
9256 	else if (type == LIST_STRING)
9257 	    ts = sarg;
9258 	else /* type == LIST_ATTR */
9259 	{
9260 	    buf[0] = NUL;
9261 	    for (i = 0; hl_attr_table[i] != 0; ++i)
9262 	    {
9263 		if (iarg & hl_attr_table[i])
9264 		{
9265 		    if (buf[0] != NUL)
9266 			vim_strcat(buf, (char_u *)",", 100);
9267 		    vim_strcat(buf, (char_u *)hl_name_table[i], 100);
9268 		    iarg &= ~hl_attr_table[i];	    /* don't want "inverse" */
9269 		}
9270 	    }
9271 	}
9272 
9273 	(void)syn_list_header(didh,
9274 			       (int)(vim_strsize(ts) + STRLEN(name) + 1), id);
9275 	didh = TRUE;
9276 	if (!got_int)
9277 	{
9278 	    if (*name != NUL)
9279 	    {
9280 		msg_puts_attr(name, HL_ATTR(HLF_D));
9281 		msg_puts_attr("=", HL_ATTR(HLF_D));
9282 	    }
9283 	    msg_outtrans(ts);
9284 	}
9285     }
9286     return didh;
9287 }
9288 
9289 #if (((defined(FEAT_EVAL) || defined(FEAT_PRINTER))) && defined(FEAT_SYN_HL)) || defined(PROTO)
9290 /*
9291  * Return "1" if highlight group "id" has attribute "flag".
9292  * Return NULL otherwise.
9293  */
9294     char_u *
9295 highlight_has_attr(
9296     int		id,
9297     int		flag,
9298     int		modec)	/* 'g' for GUI, 'c' for cterm, 't' for term */
9299 {
9300     int		attr;
9301 
9302     if (id <= 0 || id > highlight_ga.ga_len)
9303 	return NULL;
9304 
9305 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
9306     if (modec == 'g')
9307 	attr = HL_TABLE()[id - 1].sg_gui;
9308     else
9309 #endif
9310 	 if (modec == 'c')
9311 	attr = HL_TABLE()[id - 1].sg_cterm;
9312     else
9313 	attr = HL_TABLE()[id - 1].sg_term;
9314 
9315     if (attr & flag)
9316 	return (char_u *)"1";
9317     return NULL;
9318 }
9319 #endif
9320 
9321 #if (defined(FEAT_SYN_HL) && defined(FEAT_EVAL)) || defined(PROTO)
9322 /*
9323  * Return color name of highlight group "id".
9324  */
9325     char_u *
9326 highlight_color(
9327     int		id,
9328     char_u	*what,	/* "font", "fg", "bg", "sp", "fg#", "bg#" or "sp#" */
9329     int		modec)	/* 'g' for GUI, 'c' for cterm, 't' for term */
9330 {
9331     static char_u	name[20];
9332     int			n;
9333     int			fg = FALSE;
9334     int			sp = FALSE;
9335     int			font = FALSE;
9336 
9337     if (id <= 0 || id > highlight_ga.ga_len)
9338 	return NULL;
9339 
9340     if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'g')
9341 	fg = TRUE;
9342     else if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'o'
9343 	     && TOLOWER_ASC(what[2]) == 'n' && TOLOWER_ASC(what[3]) == 't')
9344 	font = TRUE;
9345     else if (TOLOWER_ASC(what[0]) == 's' && TOLOWER_ASC(what[1]) == 'p')
9346 	sp = TRUE;
9347     else if (!(TOLOWER_ASC(what[0]) == 'b' && TOLOWER_ASC(what[1]) == 'g'))
9348 	return NULL;
9349     if (modec == 'g')
9350     {
9351 # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
9352 #  ifdef FEAT_GUI
9353 	/* return font name */
9354 	if (font)
9355 	    return HL_TABLE()[id - 1].sg_font_name;
9356 #  endif
9357 
9358 	/* return #RRGGBB form (only possible when GUI is running) */
9359 	if ((USE_24BIT) && what[2] == '#')
9360 	{
9361 	    guicolor_T		color;
9362 	    long_u		rgb;
9363 	    static char_u	buf[10];
9364 
9365 	    if (fg)
9366 		color = HL_TABLE()[id - 1].sg_gui_fg;
9367 	    else if (sp)
9368 #  ifdef FEAT_GUI
9369 		color = HL_TABLE()[id - 1].sg_gui_sp;
9370 #  else
9371 		color = INVALCOLOR;
9372 #  endif
9373 	    else
9374 		color = HL_TABLE()[id - 1].sg_gui_bg;
9375 	    if (color == INVALCOLOR)
9376 		return NULL;
9377 	    rgb = (long_u)GUI_MCH_GET_RGB(color);
9378 	    sprintf((char *)buf, "#%02x%02x%02x",
9379 				      (unsigned)(rgb >> 16),
9380 				      (unsigned)(rgb >> 8) & 255,
9381 				      (unsigned)rgb & 255);
9382 	    return buf;
9383 	}
9384 # endif
9385 	if (fg)
9386 	    return (HL_TABLE()[id - 1].sg_gui_fg_name);
9387 	if (sp)
9388 	    return (HL_TABLE()[id - 1].sg_gui_sp_name);
9389 	return (HL_TABLE()[id - 1].sg_gui_bg_name);
9390     }
9391     if (font || sp)
9392 	return NULL;
9393     if (modec == 'c')
9394     {
9395 	if (fg)
9396 	    n = HL_TABLE()[id - 1].sg_cterm_fg - 1;
9397 	else
9398 	    n = HL_TABLE()[id - 1].sg_cterm_bg - 1;
9399 	if (n < 0)
9400 	    return NULL;
9401 	sprintf((char *)name, "%d", n);
9402 	return name;
9403     }
9404     /* term doesn't have color */
9405     return NULL;
9406 }
9407 #endif
9408 
9409 #if (defined(FEAT_SYN_HL) \
9410 	    && (defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)) \
9411 	&& defined(FEAT_PRINTER)) || defined(PROTO)
9412 /*
9413  * Return color name of highlight group "id" as RGB value.
9414  */
9415     long_u
9416 highlight_gui_color_rgb(
9417     int		id,
9418     int		fg)	/* TRUE = fg, FALSE = bg */
9419 {
9420     guicolor_T	color;
9421 
9422     if (id <= 0 || id > highlight_ga.ga_len)
9423 	return 0L;
9424 
9425     if (fg)
9426 	color = HL_TABLE()[id - 1].sg_gui_fg;
9427     else
9428 	color = HL_TABLE()[id - 1].sg_gui_bg;
9429 
9430     if (color == INVALCOLOR)
9431 	return 0L;
9432 
9433     return GUI_MCH_GET_RGB(color);
9434 }
9435 #endif
9436 
9437 /*
9438  * Output the syntax list header.
9439  * Return TRUE when started a new line.
9440  */
9441     static int
9442 syn_list_header(
9443     int	    did_header,		/* did header already */
9444     int	    outlen,		/* length of string that comes */
9445     int	    id)			/* highlight group id */
9446 {
9447     int	    endcol = 19;
9448     int	    newline = TRUE;
9449 
9450     if (!did_header)
9451     {
9452 	msg_putchar('\n');
9453 	if (got_int)
9454 	    return TRUE;
9455 	msg_outtrans(HL_TABLE()[id - 1].sg_name);
9456 	endcol = 15;
9457     }
9458     else if (msg_col + outlen + 1 >= Columns)
9459     {
9460 	msg_putchar('\n');
9461 	if (got_int)
9462 	    return TRUE;
9463     }
9464     else
9465     {
9466 	if (msg_col >= endcol)	/* wrap around is like starting a new line */
9467 	    newline = FALSE;
9468     }
9469 
9470     if (msg_col >= endcol)	/* output at least one space */
9471 	endcol = msg_col + 1;
9472     if (Columns <= endcol)	/* avoid hang for tiny window */
9473 	endcol = Columns - 1;
9474 
9475     msg_advance(endcol);
9476 
9477     /* Show "xxx" with the attributes. */
9478     if (!did_header)
9479     {
9480 	msg_puts_attr("xxx", syn_id2attr(id));
9481 	msg_putchar(' ');
9482     }
9483 
9484     return newline;
9485 }
9486 
9487 /*
9488  * Set the attribute numbers for a highlight group.
9489  * Called after one of the attributes has changed.
9490  */
9491     static void
9492 set_hl_attr(
9493     int		idx)	    /* index in array */
9494 {
9495     attrentry_T		at_en;
9496     struct hl_group	*sgp = HL_TABLE() + idx;
9497 
9498     /* The "Normal" group doesn't need an attribute number */
9499     if (sgp->sg_name_u != NULL && STRCMP(sgp->sg_name_u, "NORMAL") == 0)
9500 	return;
9501 
9502 #ifdef FEAT_GUI
9503     /*
9504      * For the GUI mode: If there are other than "normal" highlighting
9505      * attributes, need to allocate an attr number.
9506      */
9507     if (sgp->sg_gui_fg == INVALCOLOR
9508 	    && sgp->sg_gui_bg == INVALCOLOR
9509 	    && sgp->sg_gui_sp == INVALCOLOR
9510 	    && sgp->sg_font == NOFONT
9511 # ifdef FEAT_XFONTSET
9512 	    && sgp->sg_fontset == NOFONTSET
9513 # endif
9514 	    )
9515     {
9516 	sgp->sg_gui_attr = sgp->sg_gui;
9517     }
9518     else
9519     {
9520 	at_en.ae_attr = sgp->sg_gui;
9521 	at_en.ae_u.gui.fg_color = sgp->sg_gui_fg;
9522 	at_en.ae_u.gui.bg_color = sgp->sg_gui_bg;
9523 	at_en.ae_u.gui.sp_color = sgp->sg_gui_sp;
9524 	at_en.ae_u.gui.font = sgp->sg_font;
9525 # ifdef FEAT_XFONTSET
9526 	at_en.ae_u.gui.fontset = sgp->sg_fontset;
9527 # endif
9528 	sgp->sg_gui_attr = get_attr_entry(&gui_attr_table, &at_en);
9529     }
9530 #endif
9531     /*
9532      * For the term mode: If there are other than "normal" highlighting
9533      * attributes, need to allocate an attr number.
9534      */
9535     if (sgp->sg_start == NULL && sgp->sg_stop == NULL)
9536 	sgp->sg_term_attr = sgp->sg_term;
9537     else
9538     {
9539 	at_en.ae_attr = sgp->sg_term;
9540 	at_en.ae_u.term.start = sgp->sg_start;
9541 	at_en.ae_u.term.stop = sgp->sg_stop;
9542 	sgp->sg_term_attr = get_attr_entry(&term_attr_table, &at_en);
9543     }
9544 
9545     /*
9546      * For the color term mode: If there are other than "normal"
9547      * highlighting attributes, need to allocate an attr number.
9548      */
9549     if (sgp->sg_cterm_fg == 0 && sgp->sg_cterm_bg == 0
9550 # ifdef FEAT_TERMGUICOLORS
9551 	    && sgp->sg_gui_fg == INVALCOLOR
9552 	    && sgp->sg_gui_bg == INVALCOLOR
9553 # endif
9554 	    )
9555 	sgp->sg_cterm_attr = sgp->sg_cterm;
9556     else
9557     {
9558 	at_en.ae_attr = sgp->sg_cterm;
9559 	at_en.ae_u.cterm.fg_color = sgp->sg_cterm_fg;
9560 	at_en.ae_u.cterm.bg_color = sgp->sg_cterm_bg;
9561 # ifdef FEAT_TERMGUICOLORS
9562 #  ifdef MSWIN
9563 	{
9564 	    int id;
9565 	    guicolor_T fg, bg;
9566 
9567 	    id = syn_name2id((char_u *)"Normal");
9568 	    if (id > 0)
9569 	    {
9570 		syn_id2colors(id, &fg, &bg);
9571 		if (sgp->sg_gui_fg == INVALCOLOR)
9572 		    sgp->sg_gui_fg = fg;
9573 		if (sgp->sg_gui_bg == INVALCOLOR)
9574 		    sgp->sg_gui_bg = bg;
9575 	    }
9576 
9577 	}
9578 #  endif
9579 	at_en.ae_u.cterm.fg_rgb = GUI_MCH_GET_RGB2(sgp->sg_gui_fg);
9580 	at_en.ae_u.cterm.bg_rgb = GUI_MCH_GET_RGB2(sgp->sg_gui_bg);
9581 	if (at_en.ae_u.cterm.fg_rgb == INVALCOLOR
9582 		&& at_en.ae_u.cterm.bg_rgb == INVALCOLOR)
9583 	{
9584 	    /* If both fg and bg are invalid fall back to the cterm colors.
9585 	     * Helps when the GUI only uses an attribute, e.g. undercurl. */
9586 	    at_en.ae_u.cterm.fg_rgb = CTERMCOLOR;
9587 	    at_en.ae_u.cterm.bg_rgb = CTERMCOLOR;
9588 	}
9589 # endif
9590 	sgp->sg_cterm_attr = get_attr_entry(&cterm_attr_table, &at_en);
9591     }
9592 }
9593 
9594 /*
9595  * Lookup a highlight group name and return its ID.
9596  * If it is not found, 0 is returned.
9597  */
9598     int
9599 syn_name2id(char_u *name)
9600 {
9601     int		i;
9602     char_u	name_u[200];
9603 
9604     /* Avoid using stricmp() too much, it's slow on some systems */
9605     /* Avoid alloc()/free(), these are slow too.  ID names over 200 chars
9606      * don't deserve to be found! */
9607     vim_strncpy(name_u, name, 199);
9608     vim_strup(name_u);
9609     for (i = highlight_ga.ga_len; --i >= 0; )
9610 	if (HL_TABLE()[i].sg_name_u != NULL
9611 		&& STRCMP(name_u, HL_TABLE()[i].sg_name_u) == 0)
9612 	    break;
9613     return i + 1;
9614 }
9615 
9616 /*
9617  * Lookup a highlight group name and return its attributes.
9618  * Return zero if not found.
9619  */
9620     int
9621 syn_name2attr(char_u *name)
9622 {
9623     int id = syn_name2id(name);
9624 
9625     if (id != 0)
9626 	return syn_id2attr(id);
9627     return 0;
9628 }
9629 
9630 #if defined(FEAT_EVAL) || defined(PROTO)
9631 /*
9632  * Return TRUE if highlight group "name" exists.
9633  */
9634     int
9635 highlight_exists(char_u *name)
9636 {
9637     return (syn_name2id(name) > 0);
9638 }
9639 
9640 # if defined(FEAT_SEARCH_EXTRA) || defined(PROTO)
9641 /*
9642  * Return the name of highlight group "id".
9643  * When not a valid ID return an empty string.
9644  */
9645     char_u *
9646 syn_id2name(int id)
9647 {
9648     if (id <= 0 || id > highlight_ga.ga_len)
9649 	return (char_u *)"";
9650     return HL_TABLE()[id - 1].sg_name;
9651 }
9652 # endif
9653 #endif
9654 
9655 /*
9656  * Like syn_name2id(), but take a pointer + length argument.
9657  */
9658     int
9659 syn_namen2id(char_u *linep, int len)
9660 {
9661     char_u  *name;
9662     int	    id = 0;
9663 
9664     name = vim_strnsave(linep, len);
9665     if (name != NULL)
9666     {
9667 	id = syn_name2id(name);
9668 	vim_free(name);
9669     }
9670     return id;
9671 }
9672 
9673 /*
9674  * Find highlight group name in the table and return its ID.
9675  * The argument is a pointer to the name and the length of the name.
9676  * If it doesn't exist yet, a new entry is created.
9677  * Return 0 for failure.
9678  */
9679     int
9680 syn_check_group(char_u *pp, int len)
9681 {
9682     int	    id;
9683     char_u  *name;
9684 
9685     name = vim_strnsave(pp, len);
9686     if (name == NULL)
9687 	return 0;
9688 
9689     id = syn_name2id(name);
9690     if (id == 0)			/* doesn't exist yet */
9691 	id = syn_add_group(name);
9692     else
9693 	vim_free(name);
9694     return id;
9695 }
9696 
9697 /*
9698  * Add new highlight group and return its ID.
9699  * "name" must be an allocated string, it will be consumed.
9700  * Return 0 for failure.
9701  */
9702     static int
9703 syn_add_group(char_u *name)
9704 {
9705     char_u	*p;
9706 
9707     /* Check that the name is ASCII letters, digits and underscore. */
9708     for (p = name; *p != NUL; ++p)
9709     {
9710 	if (!vim_isprintc(*p))
9711 	{
9712 	    emsg(_("E669: Unprintable character in group name"));
9713 	    vim_free(name);
9714 	    return 0;
9715 	}
9716 	else if (!ASCII_ISALNUM(*p) && *p != '_')
9717 	{
9718 	    /* This is an error, but since there previously was no check only
9719 	     * give a warning. */
9720 	    msg_source(HL_ATTR(HLF_W));
9721 	    msg(_("W18: Invalid character in group name"));
9722 	    break;
9723 	}
9724     }
9725 
9726     /*
9727      * First call for this growarray: init growing array.
9728      */
9729     if (highlight_ga.ga_data == NULL)
9730     {
9731 	highlight_ga.ga_itemsize = sizeof(struct hl_group);
9732 	highlight_ga.ga_growsize = 10;
9733     }
9734 
9735     if (highlight_ga.ga_len >= MAX_HL_ID)
9736     {
9737 	emsg(_("E849: Too many highlight and syntax groups"));
9738 	vim_free(name);
9739 	return 0;
9740     }
9741 
9742     /*
9743      * Make room for at least one other syntax_highlight entry.
9744      */
9745     if (ga_grow(&highlight_ga, 1) == FAIL)
9746     {
9747 	vim_free(name);
9748 	return 0;
9749     }
9750 
9751     vim_memset(&(HL_TABLE()[highlight_ga.ga_len]), 0, sizeof(struct hl_group));
9752     HL_TABLE()[highlight_ga.ga_len].sg_name = name;
9753     HL_TABLE()[highlight_ga.ga_len].sg_name_u = vim_strsave_up(name);
9754 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
9755     HL_TABLE()[highlight_ga.ga_len].sg_gui_bg = INVALCOLOR;
9756     HL_TABLE()[highlight_ga.ga_len].sg_gui_fg = INVALCOLOR;
9757 # ifdef FEAT_GUI
9758     HL_TABLE()[highlight_ga.ga_len].sg_gui_sp = INVALCOLOR;
9759 # endif
9760 #endif
9761     ++highlight_ga.ga_len;
9762 
9763     return highlight_ga.ga_len;		    /* ID is index plus one */
9764 }
9765 
9766 /*
9767  * When, just after calling syn_add_group(), an error is discovered, this
9768  * function deletes the new name.
9769  */
9770     static void
9771 syn_unadd_group(void)
9772 {
9773     --highlight_ga.ga_len;
9774     vim_free(HL_TABLE()[highlight_ga.ga_len].sg_name);
9775     vim_free(HL_TABLE()[highlight_ga.ga_len].sg_name_u);
9776 }
9777 
9778 /*
9779  * Translate a group ID to highlight attributes.
9780  */
9781     int
9782 syn_id2attr(int hl_id)
9783 {
9784     int			attr;
9785     struct hl_group	*sgp;
9786 
9787     hl_id = syn_get_final_id(hl_id);
9788     sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
9789 
9790 #ifdef FEAT_GUI
9791     /*
9792      * Only use GUI attr when the GUI is being used.
9793      */
9794     if (gui.in_use)
9795 	attr = sgp->sg_gui_attr;
9796     else
9797 #endif
9798 	if (IS_CTERM)
9799 	    attr = sgp->sg_cterm_attr;
9800 	else
9801 	    attr = sgp->sg_term_attr;
9802 
9803     return attr;
9804 }
9805 
9806 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
9807 /*
9808  * Get the GUI colors and attributes for a group ID.
9809  * NOTE: the colors will be INVALCOLOR when not set, the color otherwise.
9810  */
9811     int
9812 syn_id2colors(int hl_id, guicolor_T *fgp, guicolor_T *bgp)
9813 {
9814     struct hl_group	*sgp;
9815 
9816     hl_id = syn_get_final_id(hl_id);
9817     sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
9818 
9819     *fgp = sgp->sg_gui_fg;
9820     *bgp = sgp->sg_gui_bg;
9821     return sgp->sg_gui;
9822 }
9823 #endif
9824 
9825 #if (defined(MSWIN) \
9826 	&& !defined(FEAT_GUI_MSWIN) \
9827 	&& defined(FEAT_TERMGUICOLORS)) || defined(PROTO)
9828     void
9829 syn_id2cterm_bg(int hl_id, int *fgp, int *bgp)
9830 {
9831     struct hl_group	*sgp;
9832 
9833     hl_id = syn_get_final_id(hl_id);
9834     sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
9835     *fgp = sgp->sg_cterm_fg - 1;
9836     *bgp = sgp->sg_cterm_bg - 1;
9837 }
9838 #endif
9839 
9840 /*
9841  * Translate a group ID to the final group ID (following links).
9842  */
9843     int
9844 syn_get_final_id(int hl_id)
9845 {
9846     int			count;
9847     struct hl_group	*sgp;
9848 
9849     if (hl_id > highlight_ga.ga_len || hl_id < 1)
9850 	return 0;			/* Can be called from eval!! */
9851 
9852     /*
9853      * Follow links until there is no more.
9854      * Look out for loops!  Break after 100 links.
9855      */
9856     for (count = 100; --count >= 0; )
9857     {
9858 	sgp = &HL_TABLE()[hl_id - 1];	    /* index is ID minus one */
9859 	if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len)
9860 	    break;
9861 	hl_id = sgp->sg_link;
9862     }
9863 
9864     return hl_id;
9865 }
9866 
9867 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
9868 /*
9869  * Call this function just after the GUI has started.
9870  * Also called when 'termguicolors' was set, gui.in_use will be FALSE then.
9871  * It finds the font and color handles for the highlighting groups.
9872  */
9873     void
9874 highlight_gui_started(void)
9875 {
9876     int	    idx;
9877 
9878     /* First get the colors from the "Normal" and "Menu" group, if set */
9879     if (USE_24BIT)
9880 	set_normal_colors();
9881 
9882     for (idx = 0; idx < highlight_ga.ga_len; ++idx)
9883 	gui_do_one_color(idx, FALSE, FALSE);
9884 
9885     highlight_changed();
9886 }
9887 
9888     static void
9889 gui_do_one_color(
9890     int		idx,
9891     int		do_menu UNUSED,	   /* TRUE: might set the menu font */
9892     int		do_tooltip UNUSED) /* TRUE: might set the tooltip font */
9893 {
9894     int		didit = FALSE;
9895 
9896 # ifdef FEAT_GUI
9897 #  ifdef FEAT_TERMGUICOLORS
9898     if (gui.in_use)
9899 #  endif
9900 	if (HL_TABLE()[idx].sg_font_name != NULL)
9901 	{
9902 	    hl_do_font(idx, HL_TABLE()[idx].sg_font_name, FALSE, do_menu,
9903 							    do_tooltip, TRUE);
9904 	    didit = TRUE;
9905 	}
9906 # endif
9907     if (HL_TABLE()[idx].sg_gui_fg_name != NULL)
9908     {
9909 	HL_TABLE()[idx].sg_gui_fg =
9910 			    color_name2handle(HL_TABLE()[idx].sg_gui_fg_name);
9911 	didit = TRUE;
9912     }
9913     if (HL_TABLE()[idx].sg_gui_bg_name != NULL)
9914     {
9915 	HL_TABLE()[idx].sg_gui_bg =
9916 			    color_name2handle(HL_TABLE()[idx].sg_gui_bg_name);
9917 	didit = TRUE;
9918     }
9919 # ifdef FEAT_GUI
9920     if (HL_TABLE()[idx].sg_gui_sp_name != NULL)
9921     {
9922 	HL_TABLE()[idx].sg_gui_sp =
9923 			    color_name2handle(HL_TABLE()[idx].sg_gui_sp_name);
9924 	didit = TRUE;
9925     }
9926 # endif
9927     if (didit)	/* need to get a new attr number */
9928 	set_hl_attr(idx);
9929 }
9930 #endif
9931 
9932 #if defined(USER_HIGHLIGHT) && defined(FEAT_STL_OPT)
9933 /*
9934  * Apply difference between User[1-9] and HLF_S to HLF_SNC, HLF_ST or HLF_STNC.
9935  */
9936     static void
9937 combine_stl_hlt(
9938 	int id,
9939 	int id_S,
9940 	int id_alt,
9941 	int hlcnt,
9942 	int i,
9943 	int hlf,
9944 	int *table)
9945 {
9946     struct hl_group *hlt = HL_TABLE();
9947 
9948     if (id_alt == 0)
9949     {
9950 	vim_memset(&hlt[hlcnt + i], 0, sizeof(struct hl_group));
9951 	hlt[hlcnt + i].sg_term = highlight_attr[hlf];
9952 	hlt[hlcnt + i].sg_cterm = highlight_attr[hlf];
9953 #  if defined(FEAT_GUI) || defined(FEAT_EVAL)
9954 	hlt[hlcnt + i].sg_gui = highlight_attr[hlf];
9955 #  endif
9956     }
9957     else
9958 	mch_memmove(&hlt[hlcnt + i],
9959 		    &hlt[id_alt - 1],
9960 		    sizeof(struct hl_group));
9961     hlt[hlcnt + i].sg_link = 0;
9962 
9963     hlt[hlcnt + i].sg_term ^=
9964 	hlt[id - 1].sg_term ^ hlt[id_S - 1].sg_term;
9965     if (hlt[id - 1].sg_start != hlt[id_S - 1].sg_start)
9966 	hlt[hlcnt + i].sg_start = hlt[id - 1].sg_start;
9967     if (hlt[id - 1].sg_stop != hlt[id_S - 1].sg_stop)
9968 	hlt[hlcnt + i].sg_stop = hlt[id - 1].sg_stop;
9969     hlt[hlcnt + i].sg_cterm ^=
9970 	hlt[id - 1].sg_cterm ^ hlt[id_S - 1].sg_cterm;
9971     if (hlt[id - 1].sg_cterm_fg != hlt[id_S - 1].sg_cterm_fg)
9972 	hlt[hlcnt + i].sg_cterm_fg = hlt[id - 1].sg_cterm_fg;
9973     if (hlt[id - 1].sg_cterm_bg != hlt[id_S - 1].sg_cterm_bg)
9974 	hlt[hlcnt + i].sg_cterm_bg = hlt[id - 1].sg_cterm_bg;
9975 #  if defined(FEAT_GUI) || defined(FEAT_EVAL)
9976     hlt[hlcnt + i].sg_gui ^=
9977 	hlt[id - 1].sg_gui ^ hlt[id_S - 1].sg_gui;
9978 #  endif
9979 #  ifdef FEAT_GUI
9980     if (hlt[id - 1].sg_gui_fg != hlt[id_S - 1].sg_gui_fg)
9981 	hlt[hlcnt + i].sg_gui_fg = hlt[id - 1].sg_gui_fg;
9982     if (hlt[id - 1].sg_gui_bg != hlt[id_S - 1].sg_gui_bg)
9983 	hlt[hlcnt + i].sg_gui_bg = hlt[id - 1].sg_gui_bg;
9984     if (hlt[id - 1].sg_gui_sp != hlt[id_S - 1].sg_gui_sp)
9985 	hlt[hlcnt + i].sg_gui_sp = hlt[id - 1].sg_gui_sp;
9986     if (hlt[id - 1].sg_font != hlt[id_S - 1].sg_font)
9987 	hlt[hlcnt + i].sg_font = hlt[id - 1].sg_font;
9988 #   ifdef FEAT_XFONTSET
9989     if (hlt[id - 1].sg_fontset != hlt[id_S - 1].sg_fontset)
9990 	hlt[hlcnt + i].sg_fontset = hlt[id - 1].sg_fontset;
9991 #   endif
9992 #  endif
9993     highlight_ga.ga_len = hlcnt + i + 1;
9994     set_hl_attr(hlcnt + i);	/* At long last we can apply */
9995     table[i] = syn_id2attr(hlcnt + i + 1);
9996 }
9997 #endif
9998 
9999 /*
10000  * Translate the 'highlight' option into attributes in highlight_attr[] and
10001  * set up the user highlights User1..9.  If FEAT_STL_OPT is in use, a set of
10002  * corresponding highlights to use on top of HLF_SNC is computed.
10003  * Called only when the 'highlight' option has been changed and upon first
10004  * screen redraw after any :highlight command.
10005  * Return FAIL when an invalid flag is found in 'highlight'.  OK otherwise.
10006  */
10007     int
10008 highlight_changed(void)
10009 {
10010     int		hlf;
10011     int		i;
10012     char_u	*p;
10013     int		attr;
10014     char_u	*end;
10015     int		id;
10016 #ifdef USER_HIGHLIGHT
10017     char_u      userhl[10];
10018 # ifdef FEAT_STL_OPT
10019     int		id_S = -1;
10020     int		id_SNC = 0;
10021 #  ifdef FEAT_TERMINAL
10022     int		id_ST = 0;
10023     int		id_STNC = 0;
10024 #  endif
10025     int		hlcnt;
10026 # endif
10027 #endif
10028     static int	hl_flags[HLF_COUNT] = HL_FLAGS;
10029 
10030     need_highlight_changed = FALSE;
10031 
10032     /*
10033      * Clear all attributes.
10034      */
10035     for (hlf = 0; hlf < (int)HLF_COUNT; ++hlf)
10036 	highlight_attr[hlf] = 0;
10037 
10038     /*
10039      * First set all attributes to their default value.
10040      * Then use the attributes from the 'highlight' option.
10041      */
10042     for (i = 0; i < 2; ++i)
10043     {
10044 	if (i)
10045 	    p = p_hl;
10046 	else
10047 	    p = get_highlight_default();
10048 	if (p == NULL)	    /* just in case */
10049 	    continue;
10050 
10051 	while (*p)
10052 	{
10053 	    for (hlf = 0; hlf < (int)HLF_COUNT; ++hlf)
10054 		if (hl_flags[hlf] == *p)
10055 		    break;
10056 	    ++p;
10057 	    if (hlf == (int)HLF_COUNT || *p == NUL)
10058 		return FAIL;
10059 
10060 	    /*
10061 	     * Allow several hl_flags to be combined, like "bu" for
10062 	     * bold-underlined.
10063 	     */
10064 	    attr = 0;
10065 	    for ( ; *p && *p != ','; ++p)	    /* parse upto comma */
10066 	    {
10067 		if (VIM_ISWHITE(*p))		    /* ignore white space */
10068 		    continue;
10069 
10070 		if (attr > HL_ALL)  /* Combination with ':' is not allowed. */
10071 		    return FAIL;
10072 
10073 		switch (*p)
10074 		{
10075 		    case 'b':	attr |= HL_BOLD;
10076 				break;
10077 		    case 'i':	attr |= HL_ITALIC;
10078 				break;
10079 		    case '-':
10080 		    case 'n':			    /* no highlighting */
10081 				break;
10082 		    case 'r':	attr |= HL_INVERSE;
10083 				break;
10084 		    case 's':	attr |= HL_STANDOUT;
10085 				break;
10086 		    case 'u':	attr |= HL_UNDERLINE;
10087 				break;
10088 		    case 'c':	attr |= HL_UNDERCURL;
10089 				break;
10090 		    case 't':	attr |= HL_STRIKETHROUGH;
10091 				break;
10092 		    case ':':	++p;		    /* highlight group name */
10093 				if (attr || *p == NUL)	 /* no combinations */
10094 				    return FAIL;
10095 				end = vim_strchr(p, ',');
10096 				if (end == NULL)
10097 				    end = p + STRLEN(p);
10098 				id = syn_check_group(p, (int)(end - p));
10099 				if (id == 0)
10100 				    return FAIL;
10101 				attr = syn_id2attr(id);
10102 				p = end - 1;
10103 #if defined(FEAT_STL_OPT) && defined(USER_HIGHLIGHT)
10104 				if (hlf == (int)HLF_SNC)
10105 				    id_SNC = syn_get_final_id(id);
10106 # ifdef FEAT_TERMINAL
10107 				else if (hlf == (int)HLF_ST)
10108 				    id_ST = syn_get_final_id(id);
10109 				else if (hlf == (int)HLF_STNC)
10110 				    id_STNC = syn_get_final_id(id);
10111 # endif
10112 				else if (hlf == (int)HLF_S)
10113 				    id_S = syn_get_final_id(id);
10114 #endif
10115 				break;
10116 		    default:	return FAIL;
10117 		}
10118 	    }
10119 	    highlight_attr[hlf] = attr;
10120 
10121 	    p = skip_to_option_part(p);	    /* skip comma and spaces */
10122 	}
10123     }
10124 
10125 #ifdef USER_HIGHLIGHT
10126     /* Setup the user highlights
10127      *
10128      * Temporarily utilize 28 more hl entries:
10129      * 9 for User1-User9 combined with StatusLineNC
10130      * 9 for User1-User9 combined with StatusLineTerm
10131      * 9 for User1-User9 combined with StatusLineTermNC
10132      * 1 for StatusLine default
10133      * Have to be in there simultaneously in case of table overflows in
10134      * get_attr_entry()
10135      */
10136 # ifdef FEAT_STL_OPT
10137     if (ga_grow(&highlight_ga, 28) == FAIL)
10138 	return FAIL;
10139     hlcnt = highlight_ga.ga_len;
10140     if (id_S == -1)
10141     {
10142 	/* Make sure id_S is always valid to simplify code below. Use the last
10143 	 * entry. */
10144 	vim_memset(&HL_TABLE()[hlcnt + 27], 0, sizeof(struct hl_group));
10145 	HL_TABLE()[hlcnt + 18].sg_term = highlight_attr[HLF_S];
10146 	id_S = hlcnt + 19;
10147     }
10148 # endif
10149     for (i = 0; i < 9; i++)
10150     {
10151 	sprintf((char *)userhl, "User%d", i + 1);
10152 	id = syn_name2id(userhl);
10153 	if (id == 0)
10154 	{
10155 	    highlight_user[i] = 0;
10156 # ifdef FEAT_STL_OPT
10157 	    highlight_stlnc[i] = 0;
10158 #  ifdef FEAT_TERMINAL
10159 	    highlight_stlterm[i] = 0;
10160 	    highlight_stltermnc[i] = 0;
10161 #  endif
10162 # endif
10163 	}
10164 	else
10165 	{
10166 	    highlight_user[i] = syn_id2attr(id);
10167 # ifdef FEAT_STL_OPT
10168 	    combine_stl_hlt(id, id_S, id_SNC, hlcnt, i,
10169 						     HLF_SNC, highlight_stlnc);
10170 #  ifdef FEAT_TERMINAL
10171 	    combine_stl_hlt(id, id_S, id_ST, hlcnt + 9, i,
10172 						    HLF_ST, highlight_stlterm);
10173 	    combine_stl_hlt(id, id_S, id_STNC, hlcnt + 18, i,
10174 						HLF_STNC, highlight_stltermnc);
10175 #  endif
10176 # endif
10177 	}
10178     }
10179 # ifdef FEAT_STL_OPT
10180     highlight_ga.ga_len = hlcnt;
10181 # endif
10182 
10183 #endif /* USER_HIGHLIGHT */
10184 
10185     return OK;
10186 }
10187 
10188 #if defined(FEAT_CMDL_COMPL) || defined(PROTO)
10189 
10190 static void highlight_list(void);
10191 static void highlight_list_two(int cnt, int attr);
10192 
10193 /*
10194  * Handle command line completion for :highlight command.
10195  */
10196     void
10197 set_context_in_highlight_cmd(expand_T *xp, char_u *arg)
10198 {
10199     char_u	*p;
10200 
10201     /* Default: expand group names */
10202     xp->xp_context = EXPAND_HIGHLIGHT;
10203     xp->xp_pattern = arg;
10204     include_link = 2;
10205     include_default = 1;
10206 
10207     /* (part of) subcommand already typed */
10208     if (*arg != NUL)
10209     {
10210 	p = skiptowhite(arg);
10211 	if (*p != NUL)			/* past "default" or group name */
10212 	{
10213 	    include_default = 0;
10214 	    if (STRNCMP("default", arg, p - arg) == 0)
10215 	    {
10216 		arg = skipwhite(p);
10217 		xp->xp_pattern = arg;
10218 		p = skiptowhite(arg);
10219 	    }
10220 	    if (*p != NUL)			/* past group name */
10221 	    {
10222 		include_link = 0;
10223 		if (arg[1] == 'i' && arg[0] == 'N')
10224 		    highlight_list();
10225 		if (STRNCMP("link", arg, p - arg) == 0
10226 			|| STRNCMP("clear", arg, p - arg) == 0)
10227 		{
10228 		    xp->xp_pattern = skipwhite(p);
10229 		    p = skiptowhite(xp->xp_pattern);
10230 		    if (*p != NUL)		/* past first group name */
10231 		    {
10232 			xp->xp_pattern = skipwhite(p);
10233 			p = skiptowhite(xp->xp_pattern);
10234 		    }
10235 		}
10236 		if (*p != NUL)			/* past group name(s) */
10237 		    xp->xp_context = EXPAND_NOTHING;
10238 	    }
10239 	}
10240     }
10241 }
10242 
10243 /*
10244  * List highlighting matches in a nice way.
10245  */
10246     static void
10247 highlight_list(void)
10248 {
10249     int		i;
10250 
10251     for (i = 10; --i >= 0; )
10252 	highlight_list_two(i, HL_ATTR(HLF_D));
10253     for (i = 40; --i >= 0; )
10254 	highlight_list_two(99, 0);
10255 }
10256 
10257     static void
10258 highlight_list_two(int cnt, int attr)
10259 {
10260     msg_puts_attr(&("N \bI \b!  \b"[cnt / 11]), attr);
10261     msg_clr_eos();
10262     out_flush();
10263     ui_delay(cnt == 99 ? 40L : (long)cnt * 50L, FALSE);
10264 }
10265 
10266 #endif /* FEAT_CMDL_COMPL */
10267 
10268 #if defined(FEAT_CMDL_COMPL) || (defined(FEAT_SYN_HL) && defined(FEAT_EVAL)) \
10269     || defined(FEAT_SIGNS) || defined(PROTO)
10270 /*
10271  * Function given to ExpandGeneric() to obtain the list of group names.
10272  */
10273     char_u *
10274 get_highlight_name(expand_T *xp UNUSED, int idx)
10275 {
10276     return get_highlight_name_ext(xp, idx, TRUE);
10277 }
10278 
10279 /*
10280  * Obtain a highlight group name.
10281  * When "skip_cleared" is TRUE don't return a cleared entry.
10282  */
10283     char_u *
10284 get_highlight_name_ext(expand_T *xp UNUSED, int idx, int skip_cleared)
10285 {
10286     if (idx < 0)
10287 	return NULL;
10288 
10289     /* Items are never removed from the table, skip the ones that were
10290      * cleared. */
10291     if (skip_cleared && idx < highlight_ga.ga_len && HL_TABLE()[idx].sg_cleared)
10292 	return (char_u *)"";
10293 
10294 #ifdef FEAT_CMDL_COMPL
10295     if (idx == highlight_ga.ga_len && include_none != 0)
10296 	return (char_u *)"none";
10297     if (idx == highlight_ga.ga_len + include_none && include_default != 0)
10298 	return (char_u *)"default";
10299     if (idx == highlight_ga.ga_len + include_none + include_default
10300 							 && include_link != 0)
10301 	return (char_u *)"link";
10302     if (idx == highlight_ga.ga_len + include_none + include_default + 1
10303 							 && include_link != 0)
10304 	return (char_u *)"clear";
10305 #endif
10306     if (idx >= highlight_ga.ga_len)
10307 	return NULL;
10308     return HL_TABLE()[idx].sg_name;
10309 }
10310 #endif
10311 
10312 #if defined(FEAT_GUI) || defined(PROTO)
10313 /*
10314  * Free all the highlight group fonts.
10315  * Used when quitting for systems which need it.
10316  */
10317     void
10318 free_highlight_fonts(void)
10319 {
10320     int	    idx;
10321 
10322     for (idx = 0; idx < highlight_ga.ga_len; ++idx)
10323     {
10324 	gui_mch_free_font(HL_TABLE()[idx].sg_font);
10325 	HL_TABLE()[idx].sg_font = NOFONT;
10326 # ifdef FEAT_XFONTSET
10327 	gui_mch_free_fontset(HL_TABLE()[idx].sg_fontset);
10328 	HL_TABLE()[idx].sg_fontset = NOFONTSET;
10329 # endif
10330     }
10331 
10332     gui_mch_free_font(gui.norm_font);
10333 # ifdef FEAT_XFONTSET
10334     gui_mch_free_fontset(gui.fontset);
10335 # endif
10336 # ifndef FEAT_GUI_GTK
10337     gui_mch_free_font(gui.bold_font);
10338     gui_mch_free_font(gui.ital_font);
10339     gui_mch_free_font(gui.boldital_font);
10340 # endif
10341 }
10342 #endif
10343 
10344 /**************************************
10345  *  End of Highlighting stuff	      *
10346  **************************************/
10347