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