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