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