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