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