1// This is an open source non-commercial project. Dear PVS-Studio, please check
2// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3
4/*
5 * syntax.c: code for syntax highlighting
6 */
7
8#include <assert.h>
9#include <ctype.h>
10#include <inttypes.h>
11#include <stdbool.h>
12#include <string.h>
13#include <stdlib.h>
14
15#include "nvim/vim.h"
16#include "nvim/ascii.h"
17#include "nvim/api/private/helpers.h"
18#include "nvim/syntax.h"
19#include "nvim/charset.h"
20#include "nvim/cursor_shape.h"
21#include "nvim/eval.h"
22#include "nvim/ex_cmds2.h"
23#include "nvim/ex_docmd.h"
24#include "nvim/fileio.h"
25#include "nvim/fold.h"
26#include "nvim/hashtab.h"
27#include "nvim/highlight.h"
28#include "nvim/indent_c.h"
29#include "nvim/mbyte.h"
30#include "nvim/memline.h"
31#include "nvim/memory.h"
32#include "nvim/message.h"
33#include "nvim/misc1.h"
34#include "nvim/keymap.h"
35#include "nvim/garray.h"
36#include "nvim/option.h"
37#include "nvim/os_unix.h"
38#include "nvim/path.h"
39#include "nvim/macros.h"
40#include "nvim/regexp.h"
41#include "nvim/screen.h"
42#include "nvim/sign.h"
43#include "nvim/strings.h"
44#include "nvim/syntax_defs.h"
45#include "nvim/terminal.h"
46#include "nvim/ui.h"
47#include "nvim/os/os.h"
48#include "nvim/os/time.h"
49#include "nvim/buffer.h"
50
51static bool did_syntax_onoff = false;
52
53/// Structure that stores information about a highlight group.
54/// The ID of a highlight group is also called group ID. It is the index in
55/// the highlight_ga array PLUS ONE.
56struct hl_group {
57 char_u *sg_name; ///< highlight group name
58 char_u *sg_name_u; ///< uppercase of sg_name
59 bool sg_cleared; ///< "hi clear" was used
60 int sg_attr; ///< Screen attr @see ATTR_ENTRY
61 int sg_link; ///< link to this highlight group ID
62 int sg_set; ///< combination of flags in \ref SG_SET
63 sctx_T sg_script_ctx; ///< script in which the group was last set
64 // for terminal UIs
65 int sg_cterm; ///< "cterm=" highlighting attr
66 ///< (combination of \ref HlAttrFlags)
67 int sg_cterm_fg; ///< terminal fg color number + 1
68 int sg_cterm_bg; ///< terminal bg color number + 1
69 bool sg_cterm_bold; ///< bold attr was set for light color
70 // for RGB UIs
71 int sg_gui; ///< "gui=" highlighting attributes
72 ///< (combination of \ref HlAttrFlags)
73 RgbValue sg_rgb_fg; ///< RGB foreground color
74 RgbValue sg_rgb_bg; ///< RGB background color
75 RgbValue sg_rgb_sp; ///< RGB special color
76 uint8_t *sg_rgb_fg_name; ///< RGB foreground color name
77 uint8_t *sg_rgb_bg_name; ///< RGB background color name
78 uint8_t *sg_rgb_sp_name; ///< RGB special color name
79
80 int sg_blend; ///< blend level (0-100 inclusive), -1 if unset
81};
82
83/// \addtogroup SG_SET
84/// @{
85#define SG_CTERM 2 // cterm has been set
86#define SG_GUI 4 // gui has been set
87#define SG_LINK 8 // link has been set
88/// @}
89
90// builtin |highlight-groups|
91static garray_T highlight_ga = GA_EMPTY_INIT_VALUE;
92
93static inline struct hl_group * HL_TABLE(void)
94{
95 return ((struct hl_group *)((highlight_ga.ga_data)));
96}
97
98#define MAX_HL_ID 20000 /* maximum value for a highlight ID. */
99
100/* different types of offsets that are possible */
101#define SPO_MS_OFF 0 /* match start offset */
102#define SPO_ME_OFF 1 /* match end offset */
103#define SPO_HS_OFF 2 /* highl. start offset */
104#define SPO_HE_OFF 3 /* highl. end offset */
105#define SPO_RS_OFF 4 /* region start offset */
106#define SPO_RE_OFF 5 /* region end offset */
107#define SPO_LC_OFF 6 /* leading context offset */
108#define SPO_COUNT 7
109
110/* Flags to indicate an additional string for highlight name completion. */
111static int include_none = 0; /* when 1 include "nvim/None" */
112static int include_default = 0; /* when 1 include "nvim/default" */
113static int include_link = 0; /* when 2 include "nvim/link" and "clear" */
114
115/// The "term", "cterm" and "gui" arguments can be any combination of the
116/// following names, separated by commas (but no spaces!).
117static char *(hl_name_table[]) =
118{ "bold", "standout", "underline", "undercurl",
119 "italic", "reverse", "inverse", "strikethrough", "NONE" };
120static int hl_attr_table[] =
121{ HL_BOLD, HL_STANDOUT, HL_UNDERLINE, HL_UNDERCURL, HL_ITALIC, HL_INVERSE,
122 HL_INVERSE, HL_STRIKETHROUGH, 0 };
123
124// The patterns that are being searched for are stored in a syn_pattern.
125// A match item consists of one pattern.
126// A start/end item consists of n start patterns and m end patterns.
127// A start/skip/end item consists of n start patterns, one skip pattern and m
128// end patterns.
129// For the latter two, the patterns are always consecutive: start-skip-end.
130//
131// A character offset can be given for the matched text (_m_start and _m_end)
132// and for the actually highlighted text (_h_start and _h_end).
133//
134// Note that ordering of members is optimized to reduce padding.
135typedef struct syn_pattern {
136 char sp_type; // see SPTYPE_ defines below
137 bool sp_syncing; // this item used for syncing
138 int16_t sp_syn_match_id; // highlight group ID of pattern
139 int16_t sp_off_flags; // see below
140 int sp_offsets[SPO_COUNT]; // offsets
141 int sp_flags; // see HL_ defines below
142 int sp_cchar; // conceal substitute character
143 int sp_ic; // ignore-case flag for sp_prog
144 int sp_sync_idx; // sync item index (syncing only)
145 int sp_line_id; // ID of last line where tried
146 int sp_startcol; // next match in sp_line_id line
147 int16_t *sp_cont_list; // cont. group IDs, if non-zero
148 int16_t *sp_next_list; // next group IDs, if non-zero
149 struct sp_syn sp_syn; // struct passed to in_id_list()
150 char_u *sp_pattern; // regexp to match, pattern
151 regprog_T *sp_prog; // regexp to match, program
152 syn_time_T sp_time;
153} synpat_T;
154
155
156typedef struct syn_cluster_S {
157 char_u *scl_name; // syntax cluster name
158 char_u *scl_name_u; // uppercase of scl_name
159 int16_t *scl_list; // IDs in this syntax cluster
160} syn_cluster_T;
161
162/*
163 * For the current state we need to remember more than just the idx.
164 * When si_m_endpos.lnum is 0, the items other than si_idx are unknown.
165 * (The end positions have the column number of the next char)
166 */
167typedef struct state_item {
168 int si_idx; // index of syntax pattern or
169 // KEYWORD_IDX
170 int si_id; // highlight group ID for keywords
171 int si_trans_id; // idem, transparency removed
172 int si_m_lnum; // lnum of the match
173 int si_m_startcol; // starting column of the match
174 lpos_T si_m_endpos; // just after end posn of the match
175 lpos_T si_h_startpos; // start position of the highlighting
176 lpos_T si_h_endpos; // end position of the highlighting
177 lpos_T si_eoe_pos; // end position of end pattern
178 int si_end_idx; // group ID for end pattern or zero
179 int si_ends; // if match ends before si_m_endpos
180 int si_attr; // attributes in this state
181 long si_flags; // HL_HAS_EOL flag in this state, and
182 // HL_SKIP* for si_next_list
183 int si_seqnr; // sequence number
184 int si_cchar; // substitution character for conceal
185 int16_t *si_cont_list; // list of contained groups
186 int16_t *si_next_list; // nextgroup IDs after this item ends
187 reg_extmatch_T *si_extmatch; // \z(...\) matches from start
188 // pattern
189} stateitem_T;
190
191/*
192 * Struct to reduce the number of arguments to get_syn_options(), it's used
193 * very often.
194 */
195typedef struct {
196 int flags; // flags for contained and transparent
197 bool keyword; // true for ":syn keyword"
198 int *sync_idx; // syntax item for "grouphere" argument, NULL
199 // if not allowed
200 bool has_cont_list; // true if "cont_list" can be used
201 int16_t *cont_list; // group IDs for "contains" argument
202 int16_t *cont_in_list; // group IDs for "containedin" argument
203 int16_t *next_list; // group IDs for "nextgroup" argument
204} syn_opt_arg_T;
205
206typedef struct {
207 proftime_T total;
208 int count;
209 int match;
210 proftime_T slowest;
211 proftime_T average;
212 int id;
213 char_u *pattern;
214} time_entry_T;
215
216struct name_list {
217 int flag;
218 char *name;
219};
220
221#ifdef INCLUDE_GENERATED_DECLARATIONS
222# include "syntax.c.generated.h"
223#endif
224
225static char *(spo_name_tab[SPO_COUNT]) =
226{"ms=", "me=", "hs=", "he=", "rs=", "re=", "lc="};
227
228/* The sp_off_flags are computed like this:
229 * offset from the start of the matched text: (1 << SPO_XX_OFF)
230 * offset from the end of the matched text: (1 << (SPO_XX_OFF + SPO_COUNT))
231 * When both are present, only one is used.
232 */
233
234#define SPTYPE_MATCH 1 /* match keyword with this group ID */
235#define SPTYPE_START 2 /* match a regexp, start of item */
236#define SPTYPE_END 3 /* match a regexp, end of item */
237#define SPTYPE_SKIP 4 /* match a regexp, skip within item */
238
239
240#define SYN_ITEMS(buf) ((synpat_T *)((buf)->b_syn_patterns.ga_data))
241
242#define NONE_IDX -2 /* value of sp_sync_idx for "NONE" */
243
244/*
245 * Flags for b_syn_sync_flags:
246 */
247#define SF_CCOMMENT 0x01 /* sync on a C-style comment */
248#define SF_MATCH 0x02 /* sync by matching a pattern */
249
250#define SYN_STATE_P(ssp) ((bufstate_T *)((ssp)->ga_data))
251
252#define MAXKEYWLEN 80 /* maximum length of a keyword */
253
254/*
255 * The attributes of the syntax item that has been recognized.
256 */
257static int current_attr = 0; /* attr of current syntax word */
258static int current_id = 0; /* ID of current char for syn_get_id() */
259static int current_trans_id = 0; /* idem, transparency removed */
260static int current_flags = 0;
261static int current_seqnr = 0;
262static int current_sub_char = 0;
263
264/*
265 * Methods of combining two clusters
266 */
267#define CLUSTER_REPLACE 1 /* replace first list with second */
268#define CLUSTER_ADD 2 /* add second list to first */
269#define CLUSTER_SUBTRACT 3 /* subtract second list from first */
270
271#define SYN_CLSTR(buf) ((syn_cluster_T *)((buf)->b_syn_clusters.ga_data))
272
273/*
274 * Syntax group IDs have different types:
275 * 0 - 19999 normal syntax groups
276 * 20000 - 20999 ALLBUT indicator (current_syn_inc_tag added)
277 * 21000 - 21999 TOP indicator (current_syn_inc_tag added)
278 * 22000 - 22999 CONTAINED indicator (current_syn_inc_tag added)
279 * 23000 - 32767 cluster IDs (subtract SYNID_CLUSTER for the cluster ID)
280 */
281#define SYNID_ALLBUT MAX_HL_ID /* syntax group ID for contains=ALLBUT */
282#define SYNID_TOP 21000 /* syntax group ID for contains=TOP */
283#define SYNID_CONTAINED 22000 /* syntax group ID for contains=CONTAINED */
284#define SYNID_CLUSTER 23000 /* first syntax group ID for clusters */
285
286#define MAX_SYN_INC_TAG 999 /* maximum before the above overflow */
287#define MAX_CLUSTER_ID (32767 - SYNID_CLUSTER)
288
289/*
290 * Annoying Hack(TM): ":syn include" needs this pointer to pass to
291 * expand_filename(). Most of the other syntax commands don't need it, so
292 * instead of passing it to them, we stow it here.
293 */
294static char_u **syn_cmdlinep;
295
296/*
297 * Another Annoying Hack(TM): To prevent rules from other ":syn include"'d
298 * files from leaking into ALLBUT lists, we assign a unique ID to the
299 * rules in each ":syn include"'d file.
300 */
301static int current_syn_inc_tag = 0;
302static int running_syn_inc_tag = 0;
303
304/*
305 * In a hashtable item "hi_key" points to "keyword" in a keyentry.
306 * This avoids adding a pointer to the hashtable item.
307 * KE2HIKEY() converts a var pointer to a hashitem key pointer.
308 * HIKEY2KE() converts a hashitem key pointer to a var pointer.
309 * HI2KE() converts a hashitem pointer to a var pointer.
310 */
311static keyentry_T dumkey;
312#define KE2HIKEY(kp) ((kp)->keyword)
313#define HIKEY2KE(p) ((keyentry_T *)((p) - (dumkey.keyword - (char_u *)&dumkey)))
314#define HI2KE(hi) HIKEY2KE((hi)->hi_key)
315
316// -V:HI2KE:782
317
318/*
319 * To reduce the time spent in keepend(), remember at which level in the state
320 * stack the first item with "keepend" is present. When "-1", there is no
321 * "keepend" on the stack.
322 */
323static int keepend_level = -1;
324
325static char msg_no_items[] = N_("No Syntax items defined for this buffer");
326
327// value of si_idx for keywords
328#define KEYWORD_IDX -1
329// valid of si_cont_list for containing all but contained groups
330#define ID_LIST_ALL (int16_t *)-1
331
332static int next_seqnr = 1; /* value to use for si_seqnr */
333
334/*
335 * The next possible match in the current line for any pattern is remembered,
336 * to avoid having to try for a match in each column.
337 * If next_match_idx == -1, not tried (in this line) yet.
338 * If next_match_col == MAXCOL, no match found in this line.
339 * (All end positions have the column of the char after the end)
340 */
341static int next_match_col; /* column for start of next match */
342static lpos_T next_match_m_endpos; /* position for end of next match */
343static lpos_T next_match_h_startpos; /* pos. for highl. start of next match */
344static lpos_T next_match_h_endpos; /* pos. for highl. end of next match */
345static int next_match_idx; /* index of matched item */
346static long next_match_flags; /* flags for next match */
347static lpos_T next_match_eos_pos; /* end of start pattn (start region) */
348static lpos_T next_match_eoe_pos; /* pos. for end of end pattern */
349static int next_match_end_idx; /* ID of group for end pattn or zero */
350static reg_extmatch_T *next_match_extmatch = NULL;
351
352/*
353 * A state stack is an array of integers or stateitem_T, stored in a
354 * garray_T. A state stack is invalid if its itemsize entry is zero.
355 */
356#define INVALID_STATE(ssp) ((ssp)->ga_itemsize == 0)
357#define VALID_STATE(ssp) ((ssp)->ga_itemsize != 0)
358
359/*
360 * The current state (within the line) of the recognition engine.
361 * When current_state.ga_itemsize is 0 the current state is invalid.
362 */
363static win_T *syn_win; // current window for highlighting
364static buf_T *syn_buf; // current buffer for highlighting
365static synblock_T *syn_block; // current buffer for highlighting
366static proftime_T *syn_tm; // timeout limit
367static linenr_T current_lnum = 0; // lnum of current state
368static colnr_T current_col = 0; // column of current state
369static int current_state_stored = 0; // TRUE if stored current state
370 // after setting current_finished
371static int current_finished = 0; // current line has been finished
372static garray_T current_state // current stack of state_items
373 = GA_EMPTY_INIT_VALUE;
374static int16_t *current_next_list = NULL; // when non-zero, nextgroup list
375static int current_next_flags = 0; // flags for current_next_list
376static int current_line_id = 0; // unique number for current line
377
378#define CUR_STATE(idx) ((stateitem_T *)(current_state.ga_data))[idx]
379
380static int syn_time_on = FALSE;
381# define IF_SYN_TIME(p) (p)
382
383// Set the timeout used for syntax highlighting.
384// Use NULL to reset, no timeout.
385void syn_set_timeout(proftime_T *tm)
386{
387 syn_tm = tm;
388}
389
390/*
391 * Start the syntax recognition for a line. This function is normally called
392 * from the screen updating, once for each displayed line.
393 * The buffer is remembered in syn_buf, because get_syntax_attr() doesn't get
394 * it. Careful: curbuf and curwin are likely to point to another buffer and
395 * window.
396 */
397void syntax_start(win_T *wp, linenr_T lnum)
398{
399 synstate_T *p;
400 synstate_T *last_valid = NULL;
401 synstate_T *last_min_valid = NULL;
402 synstate_T *sp, *prev = NULL;
403 linenr_T parsed_lnum;
404 linenr_T first_stored;
405 int dist;
406 static int changedtick = 0; /* remember the last change ID */
407
408 current_sub_char = NUL;
409
410 /*
411 * After switching buffers, invalidate current_state.
412 * Also do this when a change was made, the current state may be invalid
413 * then.
414 */
415 if (syn_block != wp->w_s
416 || syn_buf != wp->w_buffer
417 || changedtick != buf_get_changedtick(syn_buf)) {
418 invalidate_current_state();
419 syn_buf = wp->w_buffer;
420 syn_block = wp->w_s;
421 }
422 changedtick = buf_get_changedtick(syn_buf);
423 syn_win = wp;
424
425 /*
426 * Allocate syntax stack when needed.
427 */
428 syn_stack_alloc();
429 if (syn_block->b_sst_array == NULL)
430 return; /* out of memory */
431 syn_block->b_sst_lasttick = display_tick;
432
433 /*
434 * If the state of the end of the previous line is useful, store it.
435 */
436 if (VALID_STATE(&current_state)
437 && current_lnum < lnum
438 && current_lnum < syn_buf->b_ml.ml_line_count) {
439 (void)syn_finish_line(false);
440 if (!current_state_stored) {
441 ++current_lnum;
442 (void)store_current_state();
443 }
444
445 /*
446 * If the current_lnum is now the same as "lnum", keep the current
447 * state (this happens very often!). Otherwise invalidate
448 * current_state and figure it out below.
449 */
450 if (current_lnum != lnum)
451 invalidate_current_state();
452 } else
453 invalidate_current_state();
454
455 /*
456 * Try to synchronize from a saved state in b_sst_array[].
457 * Only do this if lnum is not before and not to far beyond a saved state.
458 */
459 if (INVALID_STATE(&current_state) && syn_block->b_sst_array != NULL) {
460 /* Find last valid saved state before start_lnum. */
461 for (p = syn_block->b_sst_first; p != NULL; p = p->sst_next) {
462 if (p->sst_lnum > lnum) {
463 break;
464 }
465 if (p->sst_change_lnum == 0) {
466 last_valid = p;
467 if (p->sst_lnum >= lnum - syn_block->b_syn_sync_minlines)
468 last_min_valid = p;
469 }
470 }
471 if (last_min_valid != NULL)
472 load_current_state(last_min_valid);
473 }
474
475 /*
476 * If "lnum" is before or far beyond a line with a saved state, need to
477 * re-synchronize.
478 */
479 if (INVALID_STATE(&current_state)) {
480 syn_sync(wp, lnum, last_valid);
481 if (current_lnum == 1)
482 /* First line is always valid, no matter "minlines". */
483 first_stored = 1;
484 else
485 /* Need to parse "minlines" lines before state can be considered
486 * valid to store. */
487 first_stored = current_lnum + syn_block->b_syn_sync_minlines;
488 } else
489 first_stored = current_lnum;
490
491 /*
492 * Advance from the sync point or saved state until the current line.
493 * Save some entries for syncing with later on.
494 */
495 if (syn_block->b_sst_len <= Rows)
496 dist = 999999;
497 else
498 dist = syn_buf->b_ml.ml_line_count / (syn_block->b_sst_len - Rows) + 1;
499 while (current_lnum < lnum) {
500 syn_start_line();
501 (void)syn_finish_line(false);
502 current_lnum++;
503
504 /* If we parsed at least "minlines" lines or started at a valid
505 * state, the current state is considered valid. */
506 if (current_lnum >= first_stored) {
507 /* Check if the saved state entry is for the current line and is
508 * equal to the current state. If so, then validate all saved
509 * states that depended on a change before the parsed line. */
510 if (prev == NULL)
511 prev = syn_stack_find_entry(current_lnum - 1);
512 if (prev == NULL)
513 sp = syn_block->b_sst_first;
514 else
515 sp = prev;
516 while (sp != NULL && sp->sst_lnum < current_lnum)
517 sp = sp->sst_next;
518 if (sp != NULL
519 && sp->sst_lnum == current_lnum
520 && syn_stack_equal(sp)) {
521 parsed_lnum = current_lnum;
522 prev = sp;
523 while (sp != NULL && sp->sst_change_lnum <= parsed_lnum) {
524 if (sp->sst_lnum <= lnum)
525 /* valid state before desired line, use this one */
526 prev = sp;
527 else if (sp->sst_change_lnum == 0)
528 /* past saved states depending on change, break here. */
529 break;
530 sp->sst_change_lnum = 0;
531 sp = sp->sst_next;
532 }
533 load_current_state(prev);
534 }
535 /* Store the state at this line when it's the first one, the line
536 * where we start parsing, or some distance from the previously
537 * saved state. But only when parsed at least 'minlines'. */
538 else if (prev == NULL
539 || current_lnum == lnum
540 || current_lnum >= prev->sst_lnum + dist)
541 prev = store_current_state();
542 }
543
544 /* This can take a long time: break when CTRL-C pressed. The current
545 * state will be wrong then. */
546 line_breakcheck();
547 if (got_int) {
548 current_lnum = lnum;
549 break;
550 }
551 }
552
553 syn_start_line();
554}
555
556/*
557 * We cannot simply discard growarrays full of state_items or buf_states; we
558 * have to manually release their extmatch pointers first.
559 */
560static void clear_syn_state(synstate_T *p)
561{
562 if (p->sst_stacksize > SST_FIX_STATES) {
563# define UNREF_BUFSTATE_EXTMATCH(bs) unref_extmatch((bs)->bs_extmatch)
564 GA_DEEP_CLEAR(&(p->sst_union.sst_ga), bufstate_T, UNREF_BUFSTATE_EXTMATCH);
565 } else {
566 for (int i = 0; i < p->sst_stacksize; i++) {
567 unref_extmatch(p->sst_union.sst_stack[i].bs_extmatch);
568 }
569 }
570}
571
572/*
573 * Cleanup the current_state stack.
574 */
575static void clear_current_state(void)
576{
577# define UNREF_STATEITEM_EXTMATCH(si) unref_extmatch((si)->si_extmatch)
578 GA_DEEP_CLEAR(&current_state, stateitem_T, UNREF_STATEITEM_EXTMATCH);
579}
580
581/*
582 * Try to find a synchronisation point for line "lnum".
583 *
584 * This sets current_lnum and the current state. One of three methods is
585 * used:
586 * 1. Search backwards for the end of a C-comment.
587 * 2. Search backwards for given sync patterns.
588 * 3. Simply start on a given number of lines above "lnum".
589 */
590static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid)
591{
592 buf_T *curbuf_save;
593 win_T *curwin_save;
594 pos_T cursor_save;
595 int idx;
596 linenr_T lnum;
597 linenr_T end_lnum;
598 linenr_T break_lnum;
599 bool had_sync_point;
600 stateitem_T *cur_si;
601 synpat_T *spp;
602 char_u *line;
603 int found_flags = 0;
604 int found_match_idx = 0;
605 linenr_T found_current_lnum = 0;
606 int found_current_col= 0;
607 lpos_T found_m_endpos;
608 colnr_T prev_current_col;
609
610 /*
611 * Clear any current state that might be hanging around.
612 */
613 invalidate_current_state();
614
615 /*
616 * Start at least "minlines" back. Default starting point for parsing is
617 * there.
618 * Start further back, to avoid that scrolling backwards will result in
619 * resyncing for every line. Now it resyncs only one out of N lines,
620 * where N is minlines * 1.5, or minlines * 2 if minlines is small.
621 * Watch out for overflow when minlines is MAXLNUM.
622 */
623 if (syn_block->b_syn_sync_minlines > start_lnum)
624 start_lnum = 1;
625 else {
626 if (syn_block->b_syn_sync_minlines == 1)
627 lnum = 1;
628 else if (syn_block->b_syn_sync_minlines < 10)
629 lnum = syn_block->b_syn_sync_minlines * 2;
630 else
631 lnum = syn_block->b_syn_sync_minlines * 3 / 2;
632 if (syn_block->b_syn_sync_maxlines != 0
633 && lnum > syn_block->b_syn_sync_maxlines)
634 lnum = syn_block->b_syn_sync_maxlines;
635 if (lnum >= start_lnum)
636 start_lnum = 1;
637 else
638 start_lnum -= lnum;
639 }
640 current_lnum = start_lnum;
641
642 /*
643 * 1. Search backwards for the end of a C-style comment.
644 */
645 if (syn_block->b_syn_sync_flags & SF_CCOMMENT) {
646 /* Need to make syn_buf the current buffer for a moment, to be able to
647 * use find_start_comment(). */
648 curwin_save = curwin;
649 curwin = wp;
650 curbuf_save = curbuf;
651 curbuf = syn_buf;
652
653 /*
654 * Skip lines that end in a backslash.
655 */
656 for (; start_lnum > 1; --start_lnum) {
657 line = ml_get(start_lnum - 1);
658 if (*line == NUL || *(line + STRLEN(line) - 1) != '\\')
659 break;
660 }
661 current_lnum = start_lnum;
662
663 /* set cursor to start of search */
664 cursor_save = wp->w_cursor;
665 wp->w_cursor.lnum = start_lnum;
666 wp->w_cursor.col = 0;
667
668 /*
669 * If the line is inside a comment, need to find the syntax item that
670 * defines the comment.
671 * Restrict the search for the end of a comment to b_syn_sync_maxlines.
672 */
673 if (find_start_comment((int)syn_block->b_syn_sync_maxlines) != NULL) {
674 for (idx = syn_block->b_syn_patterns.ga_len; --idx >= 0; )
675 if (SYN_ITEMS(syn_block)[idx].sp_syn.id
676 == syn_block->b_syn_sync_id
677 && SYN_ITEMS(syn_block)[idx].sp_type == SPTYPE_START) {
678 validate_current_state();
679 push_current_state(idx);
680 update_si_attr(current_state.ga_len - 1);
681 break;
682 }
683 }
684
685 /* restore cursor and buffer */
686 wp->w_cursor = cursor_save;
687 curwin = curwin_save;
688 curbuf = curbuf_save;
689 }
690 /*
691 * 2. Search backwards for given sync patterns.
692 */
693 else if (syn_block->b_syn_sync_flags & SF_MATCH) {
694 if (syn_block->b_syn_sync_maxlines != 0
695 && start_lnum > syn_block->b_syn_sync_maxlines)
696 break_lnum = start_lnum - syn_block->b_syn_sync_maxlines;
697 else
698 break_lnum = 0;
699
700 found_m_endpos.lnum = 0;
701 found_m_endpos.col = 0;
702 end_lnum = start_lnum;
703 lnum = start_lnum;
704 while (--lnum > break_lnum) {
705 /* This can take a long time: break when CTRL-C pressed. */
706 line_breakcheck();
707 if (got_int) {
708 invalidate_current_state();
709 current_lnum = start_lnum;
710 break;
711 }
712
713 /* Check if we have run into a valid saved state stack now. */
714 if (last_valid != NULL && lnum == last_valid->sst_lnum) {
715 load_current_state(last_valid);
716 break;
717 }
718
719 /*
720 * Check if the previous line has the line-continuation pattern.
721 */
722 if (lnum > 1 && syn_match_linecont(lnum - 1))
723 continue;
724
725 /*
726 * Start with nothing on the state stack
727 */
728 validate_current_state();
729
730 for (current_lnum = lnum; current_lnum < end_lnum; ++current_lnum) {
731 syn_start_line();
732 for (;; ) {
733 had_sync_point = syn_finish_line(true);
734 // When a sync point has been found, remember where, and
735 // continue to look for another one, further on in the line.
736 if (had_sync_point && current_state.ga_len) {
737 cur_si = &CUR_STATE(current_state.ga_len - 1);
738 if (cur_si->si_m_endpos.lnum > start_lnum) {
739 /* ignore match that goes to after where started */
740 current_lnum = end_lnum;
741 break;
742 }
743 if (cur_si->si_idx < 0) {
744 /* Cannot happen? */
745 found_flags = 0;
746 found_match_idx = KEYWORD_IDX;
747 } else {
748 spp = &(SYN_ITEMS(syn_block)[cur_si->si_idx]);
749 found_flags = spp->sp_flags;
750 found_match_idx = spp->sp_sync_idx;
751 }
752 found_current_lnum = current_lnum;
753 found_current_col = current_col;
754 found_m_endpos = cur_si->si_m_endpos;
755 /*
756 * Continue after the match (be aware of a zero-length
757 * match).
758 */
759 if (found_m_endpos.lnum > current_lnum) {
760 current_lnum = found_m_endpos.lnum;
761 current_col = found_m_endpos.col;
762 if (current_lnum >= end_lnum)
763 break;
764 } else if (found_m_endpos.col > current_col)
765 current_col = found_m_endpos.col;
766 else
767 ++current_col;
768
769 /* syn_current_attr() will have skipped the check for
770 * an item that ends here, need to do that now. Be
771 * careful not to go past the NUL. */
772 prev_current_col = current_col;
773 if (syn_getcurline()[current_col] != NUL)
774 ++current_col;
775 check_state_ends();
776 current_col = prev_current_col;
777 } else
778 break;
779 }
780 }
781
782 /*
783 * If a sync point was encountered, break here.
784 */
785 if (found_flags) {
786 /*
787 * Put the item that was specified by the sync point on the
788 * state stack. If there was no item specified, make the
789 * state stack empty.
790 */
791 clear_current_state();
792 if (found_match_idx >= 0) {
793 push_current_state(found_match_idx);
794 update_si_attr(current_state.ga_len - 1);
795 }
796
797 /*
798 * When using "grouphere", continue from the sync point
799 * match, until the end of the line. Parsing starts at
800 * the next line.
801 * For "groupthere" the parsing starts at start_lnum.
802 */
803 if (found_flags & HL_SYNC_HERE) {
804 if (!GA_EMPTY(&current_state)) {
805 cur_si = &CUR_STATE(current_state.ga_len - 1);
806 cur_si->si_h_startpos.lnum = found_current_lnum;
807 cur_si->si_h_startpos.col = found_current_col;
808 update_si_end(cur_si, (int)current_col, TRUE);
809 check_keepend();
810 }
811 current_col = found_m_endpos.col;
812 current_lnum = found_m_endpos.lnum;
813 (void)syn_finish_line(false);
814 current_lnum++;
815 } else {
816 current_lnum = start_lnum;
817 }
818
819 break;
820 }
821
822 end_lnum = lnum;
823 invalidate_current_state();
824 }
825
826 /* Ran into start of the file or exceeded maximum number of lines */
827 if (lnum <= break_lnum) {
828 invalidate_current_state();
829 current_lnum = break_lnum + 1;
830 }
831 }
832
833 validate_current_state();
834}
835
836static void save_chartab(char_u *chartab)
837{
838 if (syn_block->b_syn_isk != empty_option) {
839 memmove(chartab, syn_buf->b_chartab, (size_t)32);
840 memmove(syn_buf->b_chartab, syn_win->w_s->b_syn_chartab, (size_t)32);
841 }
842}
843
844static void restore_chartab(char_u *chartab)
845{
846 if (syn_win->w_s->b_syn_isk != empty_option) {
847 memmove(syn_buf->b_chartab, chartab, (size_t)32);
848 }
849}
850
851/*
852 * Return TRUE if the line-continuation pattern matches in line "lnum".
853 */
854static int syn_match_linecont(linenr_T lnum)
855{
856 if (syn_block->b_syn_linecont_prog != NULL) {
857 regmmatch_T regmatch;
858 // chartab array for syn iskeyword
859 char_u buf_chartab[32];
860 save_chartab(buf_chartab);
861
862 regmatch.rmm_ic = syn_block->b_syn_linecont_ic;
863 regmatch.regprog = syn_block->b_syn_linecont_prog;
864 int r = syn_regexec(&regmatch, lnum, (colnr_T)0,
865 IF_SYN_TIME(&syn_block->b_syn_linecont_time));
866 syn_block->b_syn_linecont_prog = regmatch.regprog;
867
868 restore_chartab(buf_chartab);
869 return r;
870 }
871 return FALSE;
872}
873
874/*
875 * Prepare the current state for the start of a line.
876 */
877static void syn_start_line(void)
878{
879 current_finished = FALSE;
880 current_col = 0;
881
882 /*
883 * Need to update the end of a start/skip/end that continues from the
884 * previous line and regions that have "keepend".
885 */
886 if (!GA_EMPTY(&current_state)) {
887 syn_update_ends(TRUE);
888 check_state_ends();
889 }
890
891 next_match_idx = -1;
892 current_line_id++;
893 next_seqnr = 1;
894}
895
896/*
897 * Check for items in the stack that need their end updated.
898 * When "startofline" is TRUE the last item is always updated.
899 * When "startofline" is FALSE the item with "keepend" is forcefully updated.
900 */
901static void syn_update_ends(int startofline)
902{
903 stateitem_T *cur_si;
904 int seen_keepend;
905
906 if (startofline) {
907 /* Check for a match carried over from a previous line with a
908 * contained region. The match ends as soon as the region ends. */
909 for (int i = 0; i < current_state.ga_len; ++i) {
910 cur_si = &CUR_STATE(i);
911 if (cur_si->si_idx >= 0
912 && (SYN_ITEMS(syn_block)[cur_si->si_idx]).sp_type
913 == SPTYPE_MATCH
914 && cur_si->si_m_endpos.lnum < current_lnum) {
915 cur_si->si_flags |= HL_MATCHCONT;
916 cur_si->si_m_endpos.lnum = 0;
917 cur_si->si_m_endpos.col = 0;
918 cur_si->si_h_endpos = cur_si->si_m_endpos;
919 cur_si->si_ends = TRUE;
920 }
921 }
922 }
923
924 /*
925 * Need to update the end of a start/skip/end that continues from the
926 * previous line. And regions that have "keepend", because they may
927 * influence contained items. If we've just removed "extend"
928 * (startofline == 0) then we should update ends of normal regions
929 * contained inside "keepend" because "extend" could have extended
930 * these "keepend" regions as well as contained normal regions.
931 * Then check for items ending in column 0.
932 */
933 int i = current_state.ga_len - 1;
934 if (keepend_level >= 0)
935 for (; i > keepend_level; --i)
936 if (CUR_STATE(i).si_flags & HL_EXTEND)
937 break;
938
939 seen_keepend = FALSE;
940 for (; i < current_state.ga_len; ++i) {
941 cur_si = &CUR_STATE(i);
942 if ((cur_si->si_flags & HL_KEEPEND)
943 || (seen_keepend && !startofline)
944 || (i == current_state.ga_len - 1 && startofline)) {
945 cur_si->si_h_startpos.col = 0; /* start highl. in col 0 */
946 cur_si->si_h_startpos.lnum = current_lnum;
947
948 if (!(cur_si->si_flags & HL_MATCHCONT))
949 update_si_end(cur_si, (int)current_col, !startofline);
950
951 if (!startofline && (cur_si->si_flags & HL_KEEPEND))
952 seen_keepend = TRUE;
953 }
954 }
955 check_keepend();
956}
957
958/****************************************
959 * Handling of the state stack cache.
960 */
961
962/*
963 * EXPLANATION OF THE SYNTAX STATE STACK CACHE
964 *
965 * To speed up syntax highlighting, the state stack for the start of some
966 * lines is cached. These entries can be used to start parsing at that point.
967 *
968 * The stack is kept in b_sst_array[] for each buffer. There is a list of
969 * valid entries. b_sst_first points to the first one, then follow sst_next.
970 * The entries are sorted on line number. The first entry is often for line 2
971 * (line 1 always starts with an empty stack).
972 * There is also a list for free entries. This construction is used to avoid
973 * having to allocate and free memory blocks too often.
974 *
975 * When making changes to the buffer, this is logged in b_mod_*. When calling
976 * update_screen() to update the display, it will call
977 * syn_stack_apply_changes() for each displayed buffer to adjust the cached
978 * entries. The entries which are inside the changed area are removed,
979 * because they must be recomputed. Entries below the changed have their line
980 * number adjusted for deleted/inserted lines, and have their sst_change_lnum
981 * set to indicate that a check must be made if the changed lines would change
982 * the cached entry.
983 *
984 * When later displaying lines, an entry is stored for each line. Displayed
985 * lines are likely to be displayed again, in which case the state at the
986 * start of the line is needed.
987 * For not displayed lines, an entry is stored for every so many lines. These
988 * entries will be used e.g., when scrolling backwards. The distance between
989 * entries depends on the number of lines in the buffer. For small buffers
990 * the distance is fixed at SST_DIST, for large buffers there is a fixed
991 * number of entries SST_MAX_ENTRIES, and the distance is computed.
992 */
993
994static void syn_stack_free_block(synblock_T *block)
995{
996 synstate_T *p;
997
998 if (block->b_sst_array != NULL) {
999 for (p = block->b_sst_first; p != NULL; p = p->sst_next) {
1000 clear_syn_state(p);
1001 }
1002 XFREE_CLEAR(block->b_sst_array);
1003 block->b_sst_first = NULL;
1004 block->b_sst_len = 0;
1005 }
1006}
1007/*
1008 * Free b_sst_array[] for buffer "buf".
1009 * Used when syntax items changed to force resyncing everywhere.
1010 */
1011void syn_stack_free_all(synblock_T *block)
1012{
1013 syn_stack_free_block(block);
1014
1015 /* When using "syntax" fold method, must update all folds. */
1016 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
1017 if (wp->w_s == block && foldmethodIsSyntax(wp)) {
1018 foldUpdateAll(wp);
1019 }
1020 }
1021}
1022
1023/*
1024 * Allocate the syntax state stack for syn_buf when needed.
1025 * If the number of entries in b_sst_array[] is much too big or a bit too
1026 * small, reallocate it.
1027 * Also used to allocate b_sst_array[] for the first time.
1028 */
1029static void syn_stack_alloc(void)
1030{
1031 long len;
1032 synstate_T *to, *from;
1033 synstate_T *sstp;
1034
1035 len = syn_buf->b_ml.ml_line_count / SST_DIST + Rows * 2;
1036 if (len < SST_MIN_ENTRIES)
1037 len = SST_MIN_ENTRIES;
1038 else if (len > SST_MAX_ENTRIES)
1039 len = SST_MAX_ENTRIES;
1040 if (syn_block->b_sst_len > len * 2 || syn_block->b_sst_len < len) {
1041 /* Allocate 50% too much, to avoid reallocating too often. */
1042 len = syn_buf->b_ml.ml_line_count;
1043 len = (len + len / 2) / SST_DIST + Rows * 2;
1044 if (len < SST_MIN_ENTRIES)
1045 len = SST_MIN_ENTRIES;
1046 else if (len > SST_MAX_ENTRIES)
1047 len = SST_MAX_ENTRIES;
1048
1049 if (syn_block->b_sst_array != NULL) {
1050 /* When shrinking the array, cleanup the existing stack.
1051 * Make sure that all valid entries fit in the new array. */
1052 while (syn_block->b_sst_len - syn_block->b_sst_freecount + 2 > len
1053 && syn_stack_cleanup())
1054 ;
1055 if (len < syn_block->b_sst_len - syn_block->b_sst_freecount + 2)
1056 len = syn_block->b_sst_len - syn_block->b_sst_freecount + 2;
1057 }
1058
1059 assert(len >= 0);
1060 sstp = xcalloc(len, sizeof(synstate_T));
1061
1062 to = sstp - 1;
1063 if (syn_block->b_sst_array != NULL) {
1064 /* Move the states from the old array to the new one. */
1065 for (from = syn_block->b_sst_first; from != NULL;
1066 from = from->sst_next) {
1067 ++to;
1068 *to = *from;
1069 to->sst_next = to + 1;
1070 }
1071 }
1072 if (to != sstp - 1) {
1073 to->sst_next = NULL;
1074 syn_block->b_sst_first = sstp;
1075 syn_block->b_sst_freecount = len - (int)(to - sstp) - 1;
1076 } else {
1077 syn_block->b_sst_first = NULL;
1078 syn_block->b_sst_freecount = len;
1079 }
1080
1081 /* Create the list of free entries. */
1082 syn_block->b_sst_firstfree = to + 1;
1083 while (++to < sstp + len)
1084 to->sst_next = to + 1;
1085 (sstp + len - 1)->sst_next = NULL;
1086
1087 xfree(syn_block->b_sst_array);
1088 syn_block->b_sst_array = sstp;
1089 syn_block->b_sst_len = len;
1090 }
1091}
1092
1093/*
1094 * Check for changes in a buffer to affect stored syntax states. Uses the
1095 * b_mod_* fields.
1096 * Called from update_screen(), before screen is being updated, once for each
1097 * displayed buffer.
1098 */
1099void syn_stack_apply_changes(buf_T *buf)
1100{
1101 syn_stack_apply_changes_block(&buf->b_s, buf);
1102
1103 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
1104 if ((wp->w_buffer == buf) && (wp->w_s != &buf->b_s)) {
1105 syn_stack_apply_changes_block(wp->w_s, buf);
1106 }
1107 }
1108}
1109
1110static void syn_stack_apply_changes_block(synblock_T *block, buf_T *buf)
1111{
1112 synstate_T *p, *prev, *np;
1113 linenr_T n;
1114
1115 prev = NULL;
1116 for (p = block->b_sst_first; p != NULL; ) {
1117 if (p->sst_lnum + block->b_syn_sync_linebreaks > buf->b_mod_top) {
1118 n = p->sst_lnum + buf->b_mod_xlines;
1119 if (n <= buf->b_mod_bot) {
1120 /* this state is inside the changed area, remove it */
1121 np = p->sst_next;
1122 if (prev == NULL)
1123 block->b_sst_first = np;
1124 else
1125 prev->sst_next = np;
1126 syn_stack_free_entry(block, p);
1127 p = np;
1128 continue;
1129 }
1130 /* This state is below the changed area. Remember the line
1131 * that needs to be parsed before this entry can be made valid
1132 * again. */
1133 if (p->sst_change_lnum != 0 && p->sst_change_lnum > buf->b_mod_top) {
1134 if (p->sst_change_lnum + buf->b_mod_xlines > buf->b_mod_top)
1135 p->sst_change_lnum += buf->b_mod_xlines;
1136 else
1137 p->sst_change_lnum = buf->b_mod_top;
1138 }
1139 if (p->sst_change_lnum == 0
1140 || p->sst_change_lnum < buf->b_mod_bot)
1141 p->sst_change_lnum = buf->b_mod_bot;
1142
1143 p->sst_lnum = n;
1144 }
1145 prev = p;
1146 p = p->sst_next;
1147 }
1148}
1149
1150/*
1151 * Reduce the number of entries in the state stack for syn_buf.
1152 * Returns TRUE if at least one entry was freed.
1153 */
1154static int syn_stack_cleanup(void)
1155{
1156 synstate_T *p, *prev;
1157 disptick_T tick;
1158 int above;
1159 int dist;
1160 int retval = FALSE;
1161
1162 if (syn_block->b_sst_first == NULL) {
1163 return retval;
1164 }
1165
1166 /* Compute normal distance between non-displayed entries. */
1167 if (syn_block->b_sst_len <= Rows)
1168 dist = 999999;
1169 else
1170 dist = syn_buf->b_ml.ml_line_count / (syn_block->b_sst_len - Rows) + 1;
1171
1172 /*
1173 * Go through the list to find the "tick" for the oldest entry that can
1174 * be removed. Set "above" when the "tick" for the oldest entry is above
1175 * "b_sst_lasttick" (the display tick wraps around).
1176 */
1177 tick = syn_block->b_sst_lasttick;
1178 above = FALSE;
1179 prev = syn_block->b_sst_first;
1180 for (p = prev->sst_next; p != NULL; prev = p, p = p->sst_next) {
1181 if (prev->sst_lnum + dist > p->sst_lnum) {
1182 if (p->sst_tick > syn_block->b_sst_lasttick) {
1183 if (!above || p->sst_tick < tick)
1184 tick = p->sst_tick;
1185 above = TRUE;
1186 } else if (!above && p->sst_tick < tick)
1187 tick = p->sst_tick;
1188 }
1189 }
1190
1191 /*
1192 * Go through the list to make the entries for the oldest tick at an
1193 * interval of several lines.
1194 */
1195 prev = syn_block->b_sst_first;
1196 for (p = prev->sst_next; p != NULL; prev = p, p = p->sst_next) {
1197 if (p->sst_tick == tick && prev->sst_lnum + dist > p->sst_lnum) {
1198 /* Move this entry from used list to free list */
1199 prev->sst_next = p->sst_next;
1200 syn_stack_free_entry(syn_block, p);
1201 p = prev;
1202 retval = TRUE;
1203 }
1204 }
1205 return retval;
1206}
1207
1208/*
1209 * Free the allocated memory for a syn_state item.
1210 * Move the entry into the free list.
1211 */
1212static void syn_stack_free_entry(synblock_T *block, synstate_T *p)
1213{
1214 clear_syn_state(p);
1215 p->sst_next = block->b_sst_firstfree;
1216 block->b_sst_firstfree = p;
1217 ++block->b_sst_freecount;
1218}
1219
1220/*
1221 * Find an entry in the list of state stacks at or before "lnum".
1222 * Returns NULL when there is no entry or the first entry is after "lnum".
1223 */
1224static synstate_T *syn_stack_find_entry(linenr_T lnum)
1225{
1226 synstate_T *p, *prev;
1227
1228 prev = NULL;
1229 for (p = syn_block->b_sst_first; p != NULL; prev = p, p = p->sst_next) {
1230 if (p->sst_lnum == lnum)
1231 return p;
1232 if (p->sst_lnum > lnum)
1233 break;
1234 }
1235 return prev;
1236}
1237
1238/*
1239 * Try saving the current state in b_sst_array[].
1240 * The current state must be valid for the start of the current_lnum line!
1241 */
1242static synstate_T *store_current_state(void)
1243{
1244 int i;
1245 synstate_T *p;
1246 bufstate_T *bp;
1247 stateitem_T *cur_si;
1248 synstate_T *sp = syn_stack_find_entry(current_lnum);
1249
1250 /*
1251 * If the current state contains a start or end pattern that continues
1252 * from the previous line, we can't use it. Don't store it then.
1253 */
1254 for (i = current_state.ga_len - 1; i >= 0; --i) {
1255 cur_si = &CUR_STATE(i);
1256 if (cur_si->si_h_startpos.lnum >= current_lnum
1257 || cur_si->si_m_endpos.lnum >= current_lnum
1258 || cur_si->si_h_endpos.lnum >= current_lnum
1259 || (cur_si->si_end_idx
1260 && cur_si->si_eoe_pos.lnum >= current_lnum))
1261 break;
1262 }
1263 if (i >= 0) {
1264 if (sp != NULL) {
1265 /* find "sp" in the list and remove it */
1266 if (syn_block->b_sst_first == sp)
1267 /* it's the first entry */
1268 syn_block->b_sst_first = sp->sst_next;
1269 else {
1270 /* find the entry just before this one to adjust sst_next */
1271 for (p = syn_block->b_sst_first; p != NULL; p = p->sst_next)
1272 if (p->sst_next == sp)
1273 break;
1274 if (p != NULL) /* just in case */
1275 p->sst_next = sp->sst_next;
1276 }
1277 syn_stack_free_entry(syn_block, sp);
1278 sp = NULL;
1279 }
1280 } else if (sp == NULL || sp->sst_lnum != current_lnum) {
1281 /*
1282 * Add a new entry
1283 */
1284 /* If no free items, cleanup the array first. */
1285 if (syn_block->b_sst_freecount == 0) {
1286 (void)syn_stack_cleanup();
1287 /* "sp" may have been moved to the freelist now */
1288 sp = syn_stack_find_entry(current_lnum);
1289 }
1290 /* Still no free items? Must be a strange problem... */
1291 if (syn_block->b_sst_freecount == 0)
1292 sp = NULL;
1293 else {
1294 /* Take the first item from the free list and put it in the used
1295 * list, after *sp */
1296 p = syn_block->b_sst_firstfree;
1297 syn_block->b_sst_firstfree = p->sst_next;
1298 --syn_block->b_sst_freecount;
1299 if (sp == NULL) {
1300 /* Insert in front of the list */
1301 p->sst_next = syn_block->b_sst_first;
1302 syn_block->b_sst_first = p;
1303 } else {
1304 /* insert in list after *sp */
1305 p->sst_next = sp->sst_next;
1306 sp->sst_next = p;
1307 }
1308 sp = p;
1309 sp->sst_stacksize = 0;
1310 sp->sst_lnum = current_lnum;
1311 }
1312 }
1313 if (sp != NULL) {
1314 /* When overwriting an existing state stack, clear it first */
1315 clear_syn_state(sp);
1316 sp->sst_stacksize = current_state.ga_len;
1317 if (current_state.ga_len > SST_FIX_STATES) {
1318 /* Need to clear it, might be something remaining from when the
1319 * length was less than SST_FIX_STATES. */
1320 ga_init(&sp->sst_union.sst_ga, (int)sizeof(bufstate_T), 1);
1321 ga_grow(&sp->sst_union.sst_ga, current_state.ga_len);
1322 sp->sst_union.sst_ga.ga_len = current_state.ga_len;
1323 bp = SYN_STATE_P(&(sp->sst_union.sst_ga));
1324 } else
1325 bp = sp->sst_union.sst_stack;
1326 for (i = 0; i < sp->sst_stacksize; ++i) {
1327 bp[i].bs_idx = CUR_STATE(i).si_idx;
1328 bp[i].bs_flags = CUR_STATE(i).si_flags;
1329 bp[i].bs_seqnr = CUR_STATE(i).si_seqnr;
1330 bp[i].bs_cchar = CUR_STATE(i).si_cchar;
1331 bp[i].bs_extmatch = ref_extmatch(CUR_STATE(i).si_extmatch);
1332 }
1333 sp->sst_next_flags = current_next_flags;
1334 sp->sst_next_list = current_next_list;
1335 sp->sst_tick = display_tick;
1336 sp->sst_change_lnum = 0;
1337 }
1338 current_state_stored = TRUE;
1339 return sp;
1340}
1341
1342/*
1343 * Copy a state stack from "from" in b_sst_array[] to current_state;
1344 */
1345static void load_current_state(synstate_T *from)
1346{
1347 int i;
1348 bufstate_T *bp;
1349
1350 clear_current_state();
1351 validate_current_state();
1352 keepend_level = -1;
1353 if (from->sst_stacksize) {
1354 ga_grow(&current_state, from->sst_stacksize);
1355 if (from->sst_stacksize > SST_FIX_STATES)
1356 bp = SYN_STATE_P(&(from->sst_union.sst_ga));
1357 else
1358 bp = from->sst_union.sst_stack;
1359 for (i = 0; i < from->sst_stacksize; ++i) {
1360 CUR_STATE(i).si_idx = bp[i].bs_idx;
1361 CUR_STATE(i).si_flags = bp[i].bs_flags;
1362 CUR_STATE(i).si_seqnr = bp[i].bs_seqnr;
1363 CUR_STATE(i).si_cchar = bp[i].bs_cchar;
1364 CUR_STATE(i).si_extmatch = ref_extmatch(bp[i].bs_extmatch);
1365 if (keepend_level < 0 && (CUR_STATE(i).si_flags & HL_KEEPEND))
1366 keepend_level = i;
1367 CUR_STATE(i).si_ends = FALSE;
1368 CUR_STATE(i).si_m_lnum = 0;
1369 if (CUR_STATE(i).si_idx >= 0)
1370 CUR_STATE(i).si_next_list =
1371 (SYN_ITEMS(syn_block)[CUR_STATE(i).si_idx]).sp_next_list;
1372 else
1373 CUR_STATE(i).si_next_list = NULL;
1374 update_si_attr(i);
1375 }
1376 current_state.ga_len = from->sst_stacksize;
1377 }
1378 current_next_list = from->sst_next_list;
1379 current_next_flags = from->sst_next_flags;
1380 current_lnum = from->sst_lnum;
1381}
1382
1383/*
1384 * Compare saved state stack "*sp" with the current state.
1385 * Return TRUE when they are equal.
1386 */
1387static int syn_stack_equal(synstate_T *sp)
1388{
1389 bufstate_T *bp;
1390 reg_extmatch_T *six, *bsx;
1391
1392 /* First a quick check if the stacks have the same size end nextlist. */
1393 if (sp->sst_stacksize != current_state.ga_len
1394 || sp->sst_next_list != current_next_list) {
1395 return FALSE;
1396 }
1397
1398 /* Need to compare all states on both stacks. */
1399 if (sp->sst_stacksize > SST_FIX_STATES)
1400 bp = SYN_STATE_P(&(sp->sst_union.sst_ga));
1401 else
1402 bp = sp->sst_union.sst_stack;
1403
1404 int i;
1405 for (i = current_state.ga_len; --i >= 0; ) {
1406 /* If the item has another index the state is different. */
1407 if (bp[i].bs_idx != CUR_STATE(i).si_idx)
1408 break;
1409 if (bp[i].bs_extmatch == CUR_STATE(i).si_extmatch) {
1410 continue;
1411 }
1412 /* When the extmatch pointers are different, the strings in
1413 * them can still be the same. Check if the extmatch
1414 * references are equal. */
1415 bsx = bp[i].bs_extmatch;
1416 six = CUR_STATE(i).si_extmatch;
1417 /* If one of the extmatch pointers is NULL the states are
1418 * different. */
1419 if (bsx == NULL || six == NULL)
1420 break;
1421 int j;
1422 for (j = 0; j < NSUBEXP; ++j) {
1423 /* Check each referenced match string. They must all be
1424 * equal. */
1425 if (bsx->matches[j] != six->matches[j]) {
1426 /* If the pointer is different it can still be the
1427 * same text. Compare the strings, ignore case when
1428 * the start item has the sp_ic flag set. */
1429 if (bsx->matches[j] == NULL || six->matches[j] == NULL) {
1430 break;
1431 }
1432 if (mb_strcmp_ic((SYN_ITEMS(syn_block)[CUR_STATE(i).si_idx]).sp_ic,
1433 (const char *)bsx->matches[j],
1434 (const char *)six->matches[j]) != 0) {
1435 break;
1436 }
1437 }
1438 }
1439 if (j != NSUBEXP)
1440 break;
1441 }
1442 if (i < 0)
1443 return TRUE;
1444
1445 return FALSE;
1446}
1447
1448/*
1449 * We stop parsing syntax above line "lnum". If the stored state at or below
1450 * this line depended on a change before it, it now depends on the line below
1451 * the last parsed line.
1452 * The window looks like this:
1453 * line which changed
1454 * displayed line
1455 * displayed line
1456 * lnum -> line below window
1457 */
1458void syntax_end_parsing(linenr_T lnum)
1459{
1460 synstate_T *sp;
1461
1462 sp = syn_stack_find_entry(lnum);
1463 if (sp != NULL && sp->sst_lnum < lnum)
1464 sp = sp->sst_next;
1465
1466 if (sp != NULL && sp->sst_change_lnum != 0)
1467 sp->sst_change_lnum = lnum;
1468}
1469
1470/*
1471 * End of handling of the state stack.
1472 ****************************************/
1473
1474static void invalidate_current_state(void)
1475{
1476 clear_current_state();
1477 current_state.ga_itemsize = 0; /* mark current_state invalid */
1478 current_next_list = NULL;
1479 keepend_level = -1;
1480}
1481
1482static void validate_current_state(void)
1483{
1484 current_state.ga_itemsize = sizeof(stateitem_T);
1485 ga_set_growsize(&current_state, 3);
1486}
1487
1488/*
1489 * Return TRUE if the syntax at start of lnum changed since last time.
1490 * This will only be called just after get_syntax_attr() for the previous
1491 * line, to check if the next line needs to be redrawn too.
1492 */
1493int syntax_check_changed(linenr_T lnum)
1494{
1495 int retval = TRUE;
1496 synstate_T *sp;
1497
1498 /*
1499 * Check the state stack when:
1500 * - lnum is just below the previously syntaxed line.
1501 * - lnum is not before the lines with saved states.
1502 * - lnum is not past the lines with saved states.
1503 * - lnum is at or before the last changed line.
1504 */
1505 if (VALID_STATE(&current_state) && lnum == current_lnum + 1) {
1506 sp = syn_stack_find_entry(lnum);
1507 if (sp != NULL && sp->sst_lnum == lnum) {
1508 /*
1509 * finish the previous line (needed when not all of the line was
1510 * drawn)
1511 */
1512 (void)syn_finish_line(false);
1513
1514 /*
1515 * Compare the current state with the previously saved state of
1516 * the line.
1517 */
1518 if (syn_stack_equal(sp))
1519 retval = FALSE;
1520
1521 /*
1522 * Store the current state in b_sst_array[] for later use.
1523 */
1524 ++current_lnum;
1525 (void)store_current_state();
1526 }
1527 }
1528
1529 return retval;
1530}
1531
1532/*
1533 * Finish the current line.
1534 * This doesn't return any attributes, it only gets the state at the end of
1535 * the line. It can start anywhere in the line, as long as the current state
1536 * is valid.
1537 */
1538static bool
1539syn_finish_line(
1540 const bool syncing // called for syncing
1541)
1542{
1543 while (!current_finished) {
1544 (void)syn_current_attr(syncing, false, NULL, false);
1545
1546 // When syncing, and found some item, need to check the item.
1547 if (syncing && current_state.ga_len) {
1548 // Check for match with sync item.
1549 const stateitem_T *const cur_si = &CUR_STATE(current_state.ga_len - 1);
1550 if (cur_si->si_idx >= 0
1551 && (SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags
1552 & (HL_SYNC_HERE|HL_SYNC_THERE))) {
1553 return true;
1554 }
1555
1556 // syn_current_attr() will have skipped the check for an item
1557 // that ends here, need to do that now. Be careful not to go
1558 // past the NUL.
1559 const colnr_T prev_current_col = current_col;
1560 if (syn_getcurline()[current_col] != NUL) {
1561 current_col++;
1562 }
1563 check_state_ends();
1564 current_col = prev_current_col;
1565 }
1566 current_col++;
1567 }
1568 return false;
1569}
1570
1571/*
1572 * Return highlight attributes for next character.
1573 * Must first call syntax_start() once for the line.
1574 * "col" is normally 0 for the first use in a line, and increments by one each
1575 * time. It's allowed to skip characters and to stop before the end of the
1576 * line. But only a "col" after a previously used column is allowed.
1577 * When "can_spell" is not NULL set it to TRUE when spell-checking should be
1578 * done.
1579 */
1580int
1581get_syntax_attr(
1582 const colnr_T col,
1583 bool *const can_spell,
1584 const bool keep_state // keep state of char at "col"
1585)
1586{
1587 int attr = 0;
1588
1589 if (can_spell != NULL)
1590 /* Default: Only do spelling when there is no @Spell cluster or when
1591 * ":syn spell toplevel" was used. */
1592 *can_spell = syn_block->b_syn_spell == SYNSPL_DEFAULT
1593 ? (syn_block->b_spell_cluster_id == 0)
1594 : (syn_block->b_syn_spell == SYNSPL_TOP);
1595
1596 /* check for out of memory situation */
1597 if (syn_block->b_sst_array == NULL)
1598 return 0;
1599
1600 /* After 'synmaxcol' the attribute is always zero. */
1601 if (syn_buf->b_p_smc > 0 && col >= (colnr_T)syn_buf->b_p_smc) {
1602 clear_current_state();
1603 current_id = 0;
1604 current_trans_id = 0;
1605 current_flags = 0;
1606 current_seqnr = 0;
1607 return 0;
1608 }
1609
1610 /* Make sure current_state is valid */
1611 if (INVALID_STATE(&current_state))
1612 validate_current_state();
1613
1614 /*
1615 * Skip from the current column to "col", get the attributes for "col".
1616 */
1617 while (current_col <= col) {
1618 attr = syn_current_attr(false, true, can_spell,
1619 current_col == col ? keep_state : false);
1620 current_col++;
1621 }
1622
1623 return attr;
1624}
1625
1626/*
1627 * Get syntax attributes for current_lnum, current_col.
1628 */
1629static int syn_current_attr(
1630 const bool syncing, // When true: called for syncing
1631 const bool displaying, // result will be displayed
1632 bool *const can_spell, // return: do spell checking
1633 const bool keep_state // keep syntax stack afterwards
1634)
1635{
1636 lpos_T endpos; // was: char_u *endp;
1637 lpos_T hl_startpos; // was: int hl_startcol;
1638 lpos_T hl_endpos;
1639 lpos_T eos_pos; // end-of-start match (start region)
1640 lpos_T eoe_pos; // end-of-end pattern
1641 int end_idx; // group ID for end pattern
1642 stateitem_T *cur_si, *sip = NULL;
1643 int startcol;
1644 int endcol;
1645 long flags;
1646 int cchar;
1647 int16_t *next_list;
1648 bool found_match; // found usable match
1649 static bool try_next_column = false; // must try in next col
1650 regmmatch_T regmatch;
1651 lpos_T pos;
1652 reg_extmatch_T *cur_extmatch = NULL;
1653 char_u buf_chartab[32]; // chartab array for syn iskeyword
1654 char_u *line; // current line. NOTE: becomes invalid after
1655 // looking for a pattern match!
1656
1657 // variables for zero-width matches that have a "nextgroup" argument
1658 bool keep_next_list;
1659 bool zero_width_next_list = false;
1660 garray_T zero_width_next_ga;
1661
1662 /*
1663 * No character, no attributes! Past end of line?
1664 * Do try matching with an empty line (could be the start of a region).
1665 */
1666 line = syn_getcurline();
1667 if (line[current_col] == NUL && current_col != 0) {
1668 /*
1669 * If we found a match after the last column, use it.
1670 */
1671 if (next_match_idx >= 0 && next_match_col >= (int)current_col
1672 && next_match_col != MAXCOL) {
1673 (void)push_next_match();
1674 }
1675
1676 current_finished = TRUE;
1677 current_state_stored = FALSE;
1678 return 0;
1679 }
1680
1681 /* if the current or next character is NUL, we will finish the line now */
1682 if (line[current_col] == NUL || line[current_col + 1] == NUL) {
1683 current_finished = TRUE;
1684 current_state_stored = FALSE;
1685 }
1686
1687 /*
1688 * When in the previous column there was a match but it could not be used
1689 * (empty match or already matched in this column) need to try again in
1690 * the next column.
1691 */
1692 if (try_next_column) {
1693 next_match_idx = -1;
1694 try_next_column = false;
1695 }
1696
1697 // Only check for keywords when not syncing and there are some.
1698 const bool do_keywords = !syncing
1699 && (syn_block->b_keywtab.ht_used > 0
1700 || syn_block->b_keywtab_ic.ht_used > 0);
1701
1702 /* Init the list of zero-width matches with a nextlist. This is used to
1703 * avoid matching the same item in the same position twice. */
1704 ga_init(&zero_width_next_ga, (int)sizeof(int), 10);
1705
1706 // use syntax iskeyword option
1707 save_chartab(buf_chartab);
1708
1709 /*
1710 * Repeat matching keywords and patterns, to find contained items at the
1711 * same column. This stops when there are no extra matches at the current
1712 * column.
1713 */
1714 do {
1715 found_match = false;
1716 keep_next_list = false;
1717 int syn_id = 0;
1718
1719 /*
1720 * 1. Check for a current state.
1721 * Only when there is no current state, or if the current state may
1722 * contain other things, we need to check for keywords and patterns.
1723 * Always need to check for contained items if some item has the
1724 * "containedin" argument (takes extra time!).
1725 */
1726 if (current_state.ga_len)
1727 cur_si = &CUR_STATE(current_state.ga_len - 1);
1728 else
1729 cur_si = NULL;
1730
1731 if (syn_block->b_syn_containedin || cur_si == NULL
1732 || cur_si->si_cont_list != NULL) {
1733 /*
1734 * 2. Check for keywords, if on a keyword char after a non-keyword
1735 * char. Don't do this when syncing.
1736 */
1737 if (do_keywords) {
1738 line = syn_getcurline();
1739 const char_u *cur_pos = line + current_col;
1740 if (vim_iswordp_buf(cur_pos, syn_buf)
1741 && (current_col == 0 || !vim_iswordp_buf(
1742 cur_pos - 1 - utf_head_off(line, cur_pos - 1), syn_buf))) {
1743 syn_id = check_keyword_id(line, (int)current_col, &endcol, &flags,
1744 &next_list, cur_si, &cchar);
1745 if (syn_id != 0) {
1746 push_current_state(KEYWORD_IDX);
1747 {
1748 cur_si = &CUR_STATE(current_state.ga_len - 1);
1749 cur_si->si_m_startcol = current_col;
1750 cur_si->si_h_startpos.lnum = current_lnum;
1751 cur_si->si_h_startpos.col = 0; /* starts right away */
1752 cur_si->si_m_endpos.lnum = current_lnum;
1753 cur_si->si_m_endpos.col = endcol;
1754 cur_si->si_h_endpos.lnum = current_lnum;
1755 cur_si->si_h_endpos.col = endcol;
1756 cur_si->si_ends = TRUE;
1757 cur_si->si_end_idx = 0;
1758 cur_si->si_flags = flags;
1759 cur_si->si_seqnr = next_seqnr++;
1760 cur_si->si_cchar = cchar;
1761 if (current_state.ga_len > 1)
1762 cur_si->si_flags |=
1763 CUR_STATE(current_state.ga_len - 2).si_flags
1764 & HL_CONCEAL;
1765 cur_si->si_id = syn_id;
1766 cur_si->si_trans_id = syn_id;
1767 if (flags & HL_TRANSP) {
1768 if (current_state.ga_len < 2) {
1769 cur_si->si_attr = 0;
1770 cur_si->si_trans_id = 0;
1771 } else {
1772 cur_si->si_attr = CUR_STATE(
1773 current_state.ga_len - 2).si_attr;
1774 cur_si->si_trans_id = CUR_STATE(
1775 current_state.ga_len - 2).si_trans_id;
1776 }
1777 } else {
1778 cur_si->si_attr = syn_id2attr(syn_id);
1779 }
1780 cur_si->si_cont_list = NULL;
1781 cur_si->si_next_list = next_list;
1782 check_keepend();
1783 }
1784 }
1785 }
1786 }
1787
1788 /*
1789 * 3. Check for patterns (only if no keyword found).
1790 */
1791 if (syn_id == 0 && syn_block->b_syn_patterns.ga_len) {
1792 /*
1793 * If we didn't check for a match yet, or we are past it, check
1794 * for any match with a pattern.
1795 */
1796 if (next_match_idx < 0 || next_match_col < (int)current_col) {
1797 /*
1798 * Check all relevant patterns for a match at this
1799 * position. This is complicated, because matching with a
1800 * pattern takes quite a bit of time, thus we want to
1801 * avoid doing it when it's not needed.
1802 */
1803 next_match_idx = 0; /* no match in this line yet */
1804 next_match_col = MAXCOL;
1805 for (int idx = syn_block->b_syn_patterns.ga_len; --idx >= 0; ) {
1806 synpat_T *const spp = &(SYN_ITEMS(syn_block)[idx]);
1807 if ( spp->sp_syncing == syncing
1808 && (displaying || !(spp->sp_flags & HL_DISPLAY))
1809 && (spp->sp_type == SPTYPE_MATCH
1810 || spp->sp_type == SPTYPE_START)
1811 && (current_next_list != NULL
1812 ? in_id_list(NULL, current_next_list,
1813 &spp->sp_syn, 0)
1814 : (cur_si == NULL
1815 ? !(spp->sp_flags & HL_CONTAINED)
1816 : in_id_list(cur_si,
1817 cur_si->si_cont_list, &spp->sp_syn,
1818 spp->sp_flags & HL_CONTAINED)))) {
1819 /* If we already tried matching in this line, and
1820 * there isn't a match before next_match_col, skip
1821 * this item. */
1822 if (spp->sp_line_id == current_line_id
1823 && spp->sp_startcol >= next_match_col)
1824 continue;
1825 spp->sp_line_id = current_line_id;
1826
1827 colnr_T lc_col = current_col - spp->sp_offsets[SPO_LC_OFF];
1828 if (lc_col < 0) {
1829 lc_col = 0;
1830 }
1831
1832 regmatch.rmm_ic = spp->sp_ic;
1833 regmatch.regprog = spp->sp_prog;
1834 int r = syn_regexec(&regmatch, current_lnum, lc_col,
1835 IF_SYN_TIME(&spp->sp_time));
1836 spp->sp_prog = regmatch.regprog;
1837 if (!r) {
1838 /* no match in this line, try another one */
1839 spp->sp_startcol = MAXCOL;
1840 continue;
1841 }
1842
1843 /*
1844 * Compute the first column of the match.
1845 */
1846 syn_add_start_off(&pos, &regmatch,
1847 spp, SPO_MS_OFF, -1);
1848 if (pos.lnum > current_lnum) {
1849 /* must have used end of match in a next line,
1850 * we can't handle that */
1851 spp->sp_startcol = MAXCOL;
1852 continue;
1853 }
1854 startcol = pos.col;
1855
1856 /* remember the next column where this pattern
1857 * matches in the current line */
1858 spp->sp_startcol = startcol;
1859
1860 /*
1861 * If a previously found match starts at a lower
1862 * column number, don't use this one.
1863 */
1864 if (startcol >= next_match_col)
1865 continue;
1866
1867 /*
1868 * If we matched this pattern at this position
1869 * before, skip it. Must retry in the next
1870 * column, because it may match from there.
1871 */
1872 if (did_match_already(idx, &zero_width_next_ga)) {
1873 try_next_column = true;
1874 continue;
1875 }
1876
1877 endpos.lnum = regmatch.endpos[0].lnum;
1878 endpos.col = regmatch.endpos[0].col;
1879
1880 /* Compute the highlight start. */
1881 syn_add_start_off(&hl_startpos, &regmatch,
1882 spp, SPO_HS_OFF, -1);
1883
1884 /* Compute the region start. */
1885 /* Default is to use the end of the match. */
1886 syn_add_end_off(&eos_pos, &regmatch,
1887 spp, SPO_RS_OFF, 0);
1888
1889 /*
1890 * Grab the external submatches before they get
1891 * overwritten. Reference count doesn't change.
1892 */
1893 unref_extmatch(cur_extmatch);
1894 cur_extmatch = re_extmatch_out;
1895 re_extmatch_out = NULL;
1896
1897 flags = 0;
1898 eoe_pos.lnum = 0; /* avoid warning */
1899 eoe_pos.col = 0;
1900 end_idx = 0;
1901 hl_endpos.lnum = 0;
1902
1903 /*
1904 * For a "oneline" the end must be found in the
1905 * same line too. Search for it after the end of
1906 * the match with the start pattern. Set the
1907 * resulting end positions at the same time.
1908 */
1909 if (spp->sp_type == SPTYPE_START
1910 && (spp->sp_flags & HL_ONELINE)) {
1911 lpos_T startpos;
1912
1913 startpos = endpos;
1914 find_endpos(idx, &startpos, &endpos, &hl_endpos,
1915 &flags, &eoe_pos, &end_idx, cur_extmatch);
1916 if (endpos.lnum == 0)
1917 continue; /* not found */
1918 }
1919 /*
1920 * For a "match" the size must be > 0 after the
1921 * end offset needs has been added. Except when
1922 * syncing.
1923 */
1924 else if (spp->sp_type == SPTYPE_MATCH) {
1925 syn_add_end_off(&hl_endpos, &regmatch, spp,
1926 SPO_HE_OFF, 0);
1927 syn_add_end_off(&endpos, &regmatch, spp,
1928 SPO_ME_OFF, 0);
1929 if (endpos.lnum == current_lnum
1930 && (int)endpos.col + syncing < startcol) {
1931 /*
1932 * If an empty string is matched, may need
1933 * to try matching again at next column.
1934 */
1935 if (regmatch.startpos[0].col == regmatch.endpos[0].col) {
1936 try_next_column = true;
1937 }
1938 continue;
1939 }
1940 }
1941
1942 /*
1943 * keep the best match so far in next_match_*
1944 */
1945 /* Highlighting must start after startpos and end
1946 * before endpos. */
1947 if (hl_startpos.lnum == current_lnum
1948 && (int)hl_startpos.col < startcol)
1949 hl_startpos.col = startcol;
1950 limit_pos_zero(&hl_endpos, &endpos);
1951
1952 next_match_idx = idx;
1953 next_match_col = startcol;
1954 next_match_m_endpos = endpos;
1955 next_match_h_endpos = hl_endpos;
1956 next_match_h_startpos = hl_startpos;
1957 next_match_flags = flags;
1958 next_match_eos_pos = eos_pos;
1959 next_match_eoe_pos = eoe_pos;
1960 next_match_end_idx = end_idx;
1961 unref_extmatch(next_match_extmatch);
1962 next_match_extmatch = cur_extmatch;
1963 cur_extmatch = NULL;
1964 }
1965 }
1966 }
1967
1968 /*
1969 * If we found a match at the current column, use it.
1970 */
1971 if (next_match_idx >= 0 && next_match_col == (int)current_col) {
1972 synpat_T *lspp;
1973
1974 /* When a zero-width item matched which has a nextgroup,
1975 * don't push the item but set nextgroup. */
1976 lspp = &(SYN_ITEMS(syn_block)[next_match_idx]);
1977 if (next_match_m_endpos.lnum == current_lnum
1978 && next_match_m_endpos.col == current_col
1979 && lspp->sp_next_list != NULL) {
1980 current_next_list = lspp->sp_next_list;
1981 current_next_flags = lspp->sp_flags;
1982 keep_next_list = true;
1983 zero_width_next_list = true;
1984
1985 /* Add the index to a list, so that we can check
1986 * later that we don't match it again (and cause an
1987 * endless loop). */
1988 GA_APPEND(int, &zero_width_next_ga, next_match_idx);
1989 next_match_idx = -1;
1990 } else {
1991 cur_si = push_next_match();
1992 }
1993 found_match = true;
1994 }
1995 }
1996 }
1997
1998 /*
1999 * Handle searching for nextgroup match.
2000 */
2001 if (current_next_list != NULL && !keep_next_list) {
2002 /*
2003 * If a nextgroup was not found, continue looking for one if:
2004 * - this is an empty line and the "skipempty" option was given
2005 * - we are on white space and the "skipwhite" option was given
2006 */
2007 if (!found_match) {
2008 line = syn_getcurline();
2009 if (((current_next_flags & HL_SKIPWHITE)
2010 && ascii_iswhite(line[current_col]))
2011 || ((current_next_flags & HL_SKIPEMPTY)
2012 && *line == NUL))
2013 break;
2014 }
2015
2016 /*
2017 * If a nextgroup was found: Use it, and continue looking for
2018 * contained matches.
2019 * If a nextgroup was not found: Continue looking for a normal
2020 * match.
2021 * When did set current_next_list for a zero-width item and no
2022 * match was found don't loop (would get stuck).
2023 */
2024 current_next_list = NULL;
2025 next_match_idx = -1;
2026 if (!zero_width_next_list) {
2027 found_match = true;
2028 }
2029 }
2030
2031 } while (found_match);
2032
2033 restore_chartab(buf_chartab);
2034
2035 /*
2036 * Use attributes from the current state, if within its highlighting.
2037 * If not, use attributes from the current-but-one state, etc.
2038 */
2039 current_attr = 0;
2040 current_id = 0;
2041 current_trans_id = 0;
2042 current_flags = 0;
2043 current_seqnr = 0;
2044 if (cur_si != NULL) {
2045 for (int idx = current_state.ga_len - 1; idx >= 0; --idx) {
2046 sip = &CUR_STATE(idx);
2047 if ((current_lnum > sip->si_h_startpos.lnum
2048 || (current_lnum == sip->si_h_startpos.lnum
2049 && current_col >= sip->si_h_startpos.col))
2050 && (sip->si_h_endpos.lnum == 0
2051 || current_lnum < sip->si_h_endpos.lnum
2052 || (current_lnum == sip->si_h_endpos.lnum
2053 && current_col < sip->si_h_endpos.col))) {
2054 current_attr = sip->si_attr;
2055 current_id = sip->si_id;
2056 current_trans_id = sip->si_trans_id;
2057 current_flags = sip->si_flags;
2058 current_seqnr = sip->si_seqnr;
2059 current_sub_char = sip->si_cchar;
2060 break;
2061 }
2062 }
2063
2064 if (can_spell != NULL) {
2065 struct sp_syn sps;
2066
2067 /*
2068 * set "can_spell" to TRUE if spell checking is supposed to be
2069 * done in the current item.
2070 */
2071 if (syn_block->b_spell_cluster_id == 0) {
2072 /* There is no @Spell cluster: Do spelling for items without
2073 * @NoSpell cluster. */
2074 if (syn_block->b_nospell_cluster_id == 0
2075 || current_trans_id == 0)
2076 *can_spell = (syn_block->b_syn_spell != SYNSPL_NOTOP);
2077 else {
2078 sps.inc_tag = 0;
2079 sps.id = syn_block->b_nospell_cluster_id;
2080 sps.cont_in_list = NULL;
2081 *can_spell = !in_id_list(sip, sip->si_cont_list, &sps, 0);
2082 }
2083 } else {
2084 /* The @Spell cluster is defined: Do spelling in items with
2085 * the @Spell cluster. But not when @NoSpell is also there.
2086 * At the toplevel only spell check when ":syn spell toplevel"
2087 * was used. */
2088 if (current_trans_id == 0)
2089 *can_spell = (syn_block->b_syn_spell == SYNSPL_TOP);
2090 else {
2091 sps.inc_tag = 0;
2092 sps.id = syn_block->b_spell_cluster_id;
2093 sps.cont_in_list = NULL;
2094 *can_spell = in_id_list(sip, sip->si_cont_list, &sps, 0);
2095
2096 if (syn_block->b_nospell_cluster_id != 0) {
2097 sps.id = syn_block->b_nospell_cluster_id;
2098 if (in_id_list(sip, sip->si_cont_list, &sps, 0))
2099 *can_spell = false;
2100 }
2101 }
2102 }
2103 }
2104
2105
2106 /*
2107 * Check for end of current state (and the states before it) at the
2108 * next column. Don't do this for syncing, because we would miss a
2109 * single character match.
2110 * First check if the current state ends at the current column. It
2111 * may be for an empty match and a containing item might end in the
2112 * current column.
2113 */
2114 if (!syncing && !keep_state) {
2115 check_state_ends();
2116 if (!GA_EMPTY(&current_state)
2117 && syn_getcurline()[current_col] != NUL) {
2118 ++current_col;
2119 check_state_ends();
2120 --current_col;
2121 }
2122 }
2123 } else if (can_spell != NULL)
2124 /* Default: Only do spelling when there is no @Spell cluster or when
2125 * ":syn spell toplevel" was used. */
2126 *can_spell = syn_block->b_syn_spell == SYNSPL_DEFAULT
2127 ? (syn_block->b_spell_cluster_id == 0)
2128 : (syn_block->b_syn_spell == SYNSPL_TOP);
2129
2130 /* nextgroup ends at end of line, unless "skipnl" or "skipempty" present */
2131 if (current_next_list != NULL
2132 && (line = syn_getcurline())[current_col] != NUL
2133 && line[current_col + 1] == NUL
2134 && !(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY))) {
2135 current_next_list = NULL;
2136 }
2137
2138 if (!GA_EMPTY(&zero_width_next_ga))
2139 ga_clear(&zero_width_next_ga);
2140
2141 /* No longer need external matches. But keep next_match_extmatch. */
2142 unref_extmatch(re_extmatch_out);
2143 re_extmatch_out = NULL;
2144 unref_extmatch(cur_extmatch);
2145
2146 return current_attr;
2147}
2148
2149
2150/*
2151 * Check if we already matched pattern "idx" at the current column.
2152 */
2153static int did_match_already(int idx, garray_T *gap)
2154{
2155 for (int i = current_state.ga_len; --i >= 0; ) {
2156 if (CUR_STATE(i).si_m_startcol == (int)current_col
2157 && CUR_STATE(i).si_m_lnum == (int)current_lnum
2158 && CUR_STATE(i).si_idx == idx) {
2159 return TRUE;
2160 }
2161 }
2162
2163 /* Zero-width matches with a nextgroup argument are not put on the syntax
2164 * stack, and can only be matched once anyway. */
2165 for (int i = gap->ga_len; --i >= 0; ) {
2166 if (((int *)(gap->ga_data))[i] == idx) {
2167 return TRUE;
2168 }
2169 }
2170
2171 return FALSE;
2172}
2173
2174/*
2175 * Push the next match onto the stack.
2176 */
2177static stateitem_T *push_next_match(void)
2178{
2179 stateitem_T *cur_si;
2180 synpat_T *spp;
2181 int save_flags;
2182
2183 spp = &(SYN_ITEMS(syn_block)[next_match_idx]);
2184
2185 /*
2186 * Push the item in current_state stack;
2187 */
2188 push_current_state(next_match_idx);
2189 {
2190 /*
2191 * If it's a start-skip-end type that crosses lines, figure out how
2192 * much it continues in this line. Otherwise just fill in the length.
2193 */
2194 cur_si = &CUR_STATE(current_state.ga_len - 1);
2195 cur_si->si_h_startpos = next_match_h_startpos;
2196 cur_si->si_m_startcol = current_col;
2197 cur_si->si_m_lnum = current_lnum;
2198 cur_si->si_flags = spp->sp_flags;
2199 cur_si->si_seqnr = next_seqnr++;
2200 cur_si->si_cchar = spp->sp_cchar;
2201 if (current_state.ga_len > 1)
2202 cur_si->si_flags |=
2203 CUR_STATE(current_state.ga_len - 2).si_flags & HL_CONCEAL;
2204 cur_si->si_next_list = spp->sp_next_list;
2205 cur_si->si_extmatch = ref_extmatch(next_match_extmatch);
2206 if (spp->sp_type == SPTYPE_START && !(spp->sp_flags & HL_ONELINE)) {
2207 /* Try to find the end pattern in the current line */
2208 update_si_end(cur_si, (int)(next_match_m_endpos.col), TRUE);
2209 check_keepend();
2210 } else {
2211 cur_si->si_m_endpos = next_match_m_endpos;
2212 cur_si->si_h_endpos = next_match_h_endpos;
2213 cur_si->si_ends = TRUE;
2214 cur_si->si_flags |= next_match_flags;
2215 cur_si->si_eoe_pos = next_match_eoe_pos;
2216 cur_si->si_end_idx = next_match_end_idx;
2217 }
2218 if (keepend_level < 0 && (cur_si->si_flags & HL_KEEPEND))
2219 keepend_level = current_state.ga_len - 1;
2220 check_keepend();
2221 update_si_attr(current_state.ga_len - 1);
2222
2223 save_flags = cur_si->si_flags & (HL_CONCEAL | HL_CONCEALENDS);
2224 /*
2225 * If the start pattern has another highlight group, push another item
2226 * on the stack for the start pattern.
2227 */
2228 if (spp->sp_type == SPTYPE_START && spp->sp_syn_match_id != 0) {
2229 push_current_state(next_match_idx);
2230 cur_si = &CUR_STATE(current_state.ga_len - 1);
2231 cur_si->si_h_startpos = next_match_h_startpos;
2232 cur_si->si_m_startcol = current_col;
2233 cur_si->si_m_lnum = current_lnum;
2234 cur_si->si_m_endpos = next_match_eos_pos;
2235 cur_si->si_h_endpos = next_match_eos_pos;
2236 cur_si->si_ends = TRUE;
2237 cur_si->si_end_idx = 0;
2238 cur_si->si_flags = HL_MATCH;
2239 cur_si->si_seqnr = next_seqnr++;
2240 cur_si->si_flags |= save_flags;
2241 if (cur_si->si_flags & HL_CONCEALENDS)
2242 cur_si->si_flags |= HL_CONCEAL;
2243 cur_si->si_next_list = NULL;
2244 check_keepend();
2245 update_si_attr(current_state.ga_len - 1);
2246 }
2247 }
2248
2249 next_match_idx = -1; /* try other match next time */
2250
2251 return cur_si;
2252}
2253
2254/*
2255 * Check for end of current state (and the states before it).
2256 */
2257static void check_state_ends(void)
2258{
2259 stateitem_T *cur_si;
2260 int had_extend;
2261
2262 cur_si = &CUR_STATE(current_state.ga_len - 1);
2263 for (;; ) {
2264 if (cur_si->si_ends
2265 && (cur_si->si_m_endpos.lnum < current_lnum
2266 || (cur_si->si_m_endpos.lnum == current_lnum
2267 && cur_si->si_m_endpos.col <= current_col))) {
2268 /*
2269 * If there is an end pattern group ID, highlight the end pattern
2270 * now. No need to pop the current item from the stack.
2271 * Only do this if the end pattern continues beyond the current
2272 * position.
2273 */
2274 if (cur_si->si_end_idx
2275 && (cur_si->si_eoe_pos.lnum > current_lnum
2276 || (cur_si->si_eoe_pos.lnum == current_lnum
2277 && cur_si->si_eoe_pos.col > current_col))) {
2278 cur_si->si_idx = cur_si->si_end_idx;
2279 cur_si->si_end_idx = 0;
2280 cur_si->si_m_endpos = cur_si->si_eoe_pos;
2281 cur_si->si_h_endpos = cur_si->si_eoe_pos;
2282 cur_si->si_flags |= HL_MATCH;
2283 cur_si->si_seqnr = next_seqnr++;
2284 if (cur_si->si_flags & HL_CONCEALENDS)
2285 cur_si->si_flags |= HL_CONCEAL;
2286 update_si_attr(current_state.ga_len - 1);
2287
2288 /* nextgroup= should not match in the end pattern */
2289 current_next_list = NULL;
2290
2291 /* what matches next may be different now, clear it */
2292 next_match_idx = 0;
2293 next_match_col = MAXCOL;
2294 break;
2295 } else {
2296 /* handle next_list, unless at end of line and no "skipnl" or
2297 * "skipempty" */
2298 current_next_list = cur_si->si_next_list;
2299 current_next_flags = cur_si->si_flags;
2300 if (!(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY))
2301 && syn_getcurline()[current_col] == NUL)
2302 current_next_list = NULL;
2303
2304 /* When the ended item has "extend", another item with
2305 * "keepend" now needs to check for its end. */
2306 had_extend = (cur_si->si_flags & HL_EXTEND);
2307
2308 pop_current_state();
2309
2310 if (GA_EMPTY(&current_state))
2311 break;
2312
2313 if (had_extend && keepend_level >= 0) {
2314 syn_update_ends(FALSE);
2315 if (GA_EMPTY(&current_state))
2316 break;
2317 }
2318
2319 cur_si = &CUR_STATE(current_state.ga_len - 1);
2320
2321 /*
2322 * Only for a region the search for the end continues after
2323 * the end of the contained item. If the contained match
2324 * included the end-of-line, break here, the region continues.
2325 * Don't do this when:
2326 * - "keepend" is used for the contained item
2327 * - not at the end of the line (could be end="x$"me=e-1).
2328 * - "excludenl" is used (HL_HAS_EOL won't be set)
2329 */
2330 if (cur_si->si_idx >= 0
2331 && SYN_ITEMS(syn_block)[cur_si->si_idx].sp_type
2332 == SPTYPE_START
2333 && !(cur_si->si_flags & (HL_MATCH | HL_KEEPEND))) {
2334 update_si_end(cur_si, (int)current_col, TRUE);
2335 check_keepend();
2336 if ((current_next_flags & HL_HAS_EOL)
2337 && keepend_level < 0
2338 && syn_getcurline()[current_col] == NUL)
2339 break;
2340 }
2341 }
2342 } else
2343 break;
2344 }
2345}
2346
2347/*
2348 * Update an entry in the current_state stack for a match or region. This
2349 * fills in si_attr, si_next_list and si_cont_list.
2350 */
2351static void update_si_attr(int idx)
2352{
2353 stateitem_T *sip = &CUR_STATE(idx);
2354 synpat_T *spp;
2355
2356 /* This should not happen... */
2357 if (sip->si_idx < 0)
2358 return;
2359
2360 spp = &(SYN_ITEMS(syn_block)[sip->si_idx]);
2361 if (sip->si_flags & HL_MATCH)
2362 sip->si_id = spp->sp_syn_match_id;
2363 else
2364 sip->si_id = spp->sp_syn.id;
2365 sip->si_attr = syn_id2attr(sip->si_id);
2366 sip->si_trans_id = sip->si_id;
2367 if (sip->si_flags & HL_MATCH)
2368 sip->si_cont_list = NULL;
2369 else
2370 sip->si_cont_list = spp->sp_cont_list;
2371
2372 /*
2373 * For transparent items, take attr from outer item.
2374 * Also take cont_list, if there is none.
2375 * Don't do this for the matchgroup of a start or end pattern.
2376 */
2377 if ((spp->sp_flags & HL_TRANSP) && !(sip->si_flags & HL_MATCH)) {
2378 if (idx == 0) {
2379 sip->si_attr = 0;
2380 sip->si_trans_id = 0;
2381 if (sip->si_cont_list == NULL)
2382 sip->si_cont_list = ID_LIST_ALL;
2383 } else {
2384 sip->si_attr = CUR_STATE(idx - 1).si_attr;
2385 sip->si_trans_id = CUR_STATE(idx - 1).si_trans_id;
2386 sip->si_h_startpos = CUR_STATE(idx - 1).si_h_startpos;
2387 sip->si_h_endpos = CUR_STATE(idx - 1).si_h_endpos;
2388 if (sip->si_cont_list == NULL) {
2389 sip->si_flags |= HL_TRANS_CONT;
2390 sip->si_cont_list = CUR_STATE(idx - 1).si_cont_list;
2391 }
2392 }
2393 }
2394}
2395
2396/*
2397 * Check the current stack for patterns with "keepend" flag.
2398 * Propagate the match-end to contained items, until a "skipend" item is found.
2399 */
2400static void check_keepend(void)
2401{
2402 int i;
2403 lpos_T maxpos;
2404 lpos_T maxpos_h;
2405 stateitem_T *sip;
2406
2407 /*
2408 * This check can consume a lot of time; only do it from the level where
2409 * there really is a keepend.
2410 */
2411 if (keepend_level < 0)
2412 return;
2413
2414 /*
2415 * Find the last index of an "extend" item. "keepend" items before that
2416 * won't do anything. If there is no "extend" item "i" will be
2417 * "keepend_level" and all "keepend" items will work normally.
2418 */
2419 for (i = current_state.ga_len - 1; i > keepend_level; --i)
2420 if (CUR_STATE(i).si_flags & HL_EXTEND)
2421 break;
2422
2423 maxpos.lnum = 0;
2424 maxpos.col = 0;
2425 maxpos_h.lnum = 0;
2426 maxpos_h.col = 0;
2427 for (; i < current_state.ga_len; ++i) {
2428 sip = &CUR_STATE(i);
2429 if (maxpos.lnum != 0) {
2430 limit_pos_zero(&sip->si_m_endpos, &maxpos);
2431 limit_pos_zero(&sip->si_h_endpos, &maxpos_h);
2432 limit_pos_zero(&sip->si_eoe_pos, &maxpos);
2433 sip->si_ends = TRUE;
2434 }
2435 if (sip->si_ends && (sip->si_flags & HL_KEEPEND)) {
2436 if (maxpos.lnum == 0
2437 || maxpos.lnum > sip->si_m_endpos.lnum
2438 || (maxpos.lnum == sip->si_m_endpos.lnum
2439 && maxpos.col > sip->si_m_endpos.col))
2440 maxpos = sip->si_m_endpos;
2441 if (maxpos_h.lnum == 0
2442 || maxpos_h.lnum > sip->si_h_endpos.lnum
2443 || (maxpos_h.lnum == sip->si_h_endpos.lnum
2444 && maxpos_h.col > sip->si_h_endpos.col))
2445 maxpos_h = sip->si_h_endpos;
2446 }
2447 }
2448}
2449
2450/*
2451 * Update an entry in the current_state stack for a start-skip-end pattern.
2452 * This finds the end of the current item, if it's in the current line.
2453 *
2454 * Return the flags for the matched END.
2455 */
2456static void
2457update_si_end(
2458 stateitem_T *sip,
2459 int startcol, /* where to start searching for the end */
2460 int force /* when TRUE overrule a previous end */
2461)
2462{
2463 lpos_T startpos;
2464 lpos_T endpos;
2465 lpos_T hl_endpos;
2466 lpos_T end_endpos;
2467 int end_idx;
2468
2469 /* return quickly for a keyword */
2470 if (sip->si_idx < 0)
2471 return;
2472
2473 /* Don't update when it's already done. Can be a match of an end pattern
2474 * that started in a previous line. Watch out: can also be a "keepend"
2475 * from a containing item. */
2476 if (!force && sip->si_m_endpos.lnum >= current_lnum)
2477 return;
2478
2479 /*
2480 * We need to find the end of the region. It may continue in the next
2481 * line.
2482 */
2483 end_idx = 0;
2484 startpos.lnum = current_lnum;
2485 startpos.col = startcol;
2486 find_endpos(sip->si_idx, &startpos, &endpos, &hl_endpos,
2487 &(sip->si_flags), &end_endpos, &end_idx, sip->si_extmatch);
2488
2489 if (endpos.lnum == 0) {
2490 /* No end pattern matched. */
2491 if (SYN_ITEMS(syn_block)[sip->si_idx].sp_flags & HL_ONELINE) {
2492 /* a "oneline" never continues in the next line */
2493 sip->si_ends = TRUE;
2494 sip->si_m_endpos.lnum = current_lnum;
2495 sip->si_m_endpos.col = (colnr_T)STRLEN(syn_getcurline());
2496 } else {
2497 /* continues in the next line */
2498 sip->si_ends = FALSE;
2499 sip->si_m_endpos.lnum = 0;
2500 }
2501 sip->si_h_endpos = sip->si_m_endpos;
2502 } else {
2503 /* match within this line */
2504 sip->si_m_endpos = endpos;
2505 sip->si_h_endpos = hl_endpos;
2506 sip->si_eoe_pos = end_endpos;
2507 sip->si_ends = TRUE;
2508 sip->si_end_idx = end_idx;
2509 }
2510}
2511
2512/*
2513 * Add a new state to the current state stack.
2514 * It is cleared and the index set to "idx".
2515 */
2516static void push_current_state(int idx)
2517{
2518 stateitem_T *p = GA_APPEND_VIA_PTR(stateitem_T, &current_state);
2519 memset(p, 0, sizeof(*p));
2520 p->si_idx = idx;
2521}
2522
2523/*
2524 * Remove a state from the current_state stack.
2525 */
2526static void pop_current_state(void)
2527{
2528 if (!GA_EMPTY(&current_state)) {
2529 unref_extmatch(CUR_STATE(current_state.ga_len - 1).si_extmatch);
2530 --current_state.ga_len;
2531 }
2532 /* after the end of a pattern, try matching a keyword or pattern */
2533 next_match_idx = -1;
2534
2535 /* if first state with "keepend" is popped, reset keepend_level */
2536 if (keepend_level >= current_state.ga_len)
2537 keepend_level = -1;
2538}
2539
2540/*
2541 * Find the end of a start/skip/end syntax region after "startpos".
2542 * Only checks one line.
2543 * Also handles a match item that continued from a previous line.
2544 * If not found, the syntax item continues in the next line. m_endpos->lnum
2545 * will be 0.
2546 * If found, the end of the region and the end of the highlighting is
2547 * computed.
2548 */
2549static void
2550find_endpos(
2551 int idx, // index of the pattern
2552 lpos_T *startpos, // where to start looking for an END match
2553 lpos_T *m_endpos, // return: end of match
2554 lpos_T *hl_endpos, // return: end of highlighting
2555 long *flagsp, // return: flags of matching END
2556 lpos_T *end_endpos, // return: end of end pattern match
2557 int *end_idx, // return: group ID for end pat. match, or 0
2558 reg_extmatch_T *start_ext // submatches from the start pattern
2559)
2560{
2561 colnr_T matchcol;
2562 synpat_T *spp, *spp_skip;
2563 int start_idx;
2564 int best_idx;
2565 regmmatch_T regmatch;
2566 regmmatch_T best_regmatch; /* startpos/endpos of best match */
2567 lpos_T pos;
2568 char_u *line;
2569 int had_match = false;
2570 char_u buf_chartab[32]; // chartab array for syn option iskeyword
2571
2572 /* just in case we are invoked for a keyword */
2573 if (idx < 0)
2574 return;
2575
2576 /*
2577 * Check for being called with a START pattern.
2578 * Can happen with a match that continues to the next line, because it
2579 * contained a region.
2580 */
2581 spp = &(SYN_ITEMS(syn_block)[idx]);
2582 if (spp->sp_type != SPTYPE_START) {
2583 *hl_endpos = *startpos;
2584 return;
2585 }
2586
2587 /*
2588 * Find the SKIP or first END pattern after the last START pattern.
2589 */
2590 for (;; ) {
2591 spp = &(SYN_ITEMS(syn_block)[idx]);
2592 if (spp->sp_type != SPTYPE_START)
2593 break;
2594 ++idx;
2595 }
2596
2597 /*
2598 * Lookup the SKIP pattern (if present)
2599 */
2600 if (spp->sp_type == SPTYPE_SKIP) {
2601 spp_skip = spp;
2602 ++idx;
2603 } else
2604 spp_skip = NULL;
2605
2606 /* Setup external matches for syn_regexec(). */
2607 unref_extmatch(re_extmatch_in);
2608 re_extmatch_in = ref_extmatch(start_ext);
2609
2610 matchcol = startpos->col; // start looking for a match at sstart
2611 start_idx = idx; // remember the first END pattern.
2612 best_regmatch.startpos[0].col = 0; // avoid compiler warning
2613
2614 // use syntax iskeyword option
2615 save_chartab(buf_chartab);
2616
2617 for (;; ) {
2618 /*
2619 * Find end pattern that matches first after "matchcol".
2620 */
2621 best_idx = -1;
2622 for (idx = start_idx; idx < syn_block->b_syn_patterns.ga_len; ++idx) {
2623 int lc_col = matchcol;
2624
2625 spp = &(SYN_ITEMS(syn_block)[idx]);
2626 if (spp->sp_type != SPTYPE_END) /* past last END pattern */
2627 break;
2628 lc_col -= spp->sp_offsets[SPO_LC_OFF];
2629 if (lc_col < 0)
2630 lc_col = 0;
2631
2632 regmatch.rmm_ic = spp->sp_ic;
2633 regmatch.regprog = spp->sp_prog;
2634 int r = syn_regexec(&regmatch, startpos->lnum, lc_col,
2635 IF_SYN_TIME(&spp->sp_time));
2636 spp->sp_prog = regmatch.regprog;
2637 if (r) {
2638 if (best_idx == -1 || regmatch.startpos[0].col
2639 < best_regmatch.startpos[0].col) {
2640 best_idx = idx;
2641 best_regmatch.startpos[0] = regmatch.startpos[0];
2642 best_regmatch.endpos[0] = regmatch.endpos[0];
2643 }
2644 }
2645 }
2646
2647 /*
2648 * If all end patterns have been tried, and there is no match, the
2649 * item continues until end-of-line.
2650 */
2651 if (best_idx == -1)
2652 break;
2653
2654 /*
2655 * If the skip pattern matches before the end pattern,
2656 * continue searching after the skip pattern.
2657 */
2658 if (spp_skip != NULL) {
2659 int lc_col = matchcol - spp_skip->sp_offsets[SPO_LC_OFF];
2660
2661 if (lc_col < 0)
2662 lc_col = 0;
2663 regmatch.rmm_ic = spp_skip->sp_ic;
2664 regmatch.regprog = spp_skip->sp_prog;
2665 int r = syn_regexec(&regmatch, startpos->lnum, lc_col,
2666 IF_SYN_TIME(&spp_skip->sp_time));
2667 spp_skip->sp_prog = regmatch.regprog;
2668 if (r && regmatch.startpos[0].col <= best_regmatch.startpos[0].col) {
2669 // Add offset to skip pattern match
2670 syn_add_end_off(&pos, &regmatch, spp_skip, SPO_ME_OFF, 1);
2671
2672 // If the skip pattern goes on to the next line, there is no
2673 // match with an end pattern in this line.
2674 if (pos.lnum > startpos->lnum) {
2675 break;
2676 }
2677
2678 line = ml_get_buf(syn_buf, startpos->lnum, false);
2679 int line_len = (int)STRLEN(line);
2680
2681 // take care of an empty match or negative offset
2682 if (pos.col <= matchcol) {
2683 matchcol++;
2684 } else if (pos.col <= regmatch.endpos[0].col) {
2685 matchcol = pos.col;
2686 } else {
2687 // Be careful not to jump over the NUL at the end-of-line
2688 for (matchcol = regmatch.endpos[0].col;
2689 matchcol < line_len && matchcol < pos.col;
2690 matchcol++) {
2691 }
2692 }
2693
2694 // if the skip pattern includes end-of-line, break here
2695 if (matchcol >= line_len) {
2696 break;
2697 }
2698
2699 continue; // start with first end pattern again
2700 }
2701 }
2702
2703 /*
2704 * Match from start pattern to end pattern.
2705 * Correct for match and highlight offset of end pattern.
2706 */
2707 spp = &(SYN_ITEMS(syn_block)[best_idx]);
2708 syn_add_end_off(m_endpos, &best_regmatch, spp, SPO_ME_OFF, 1);
2709 /* can't end before the start */
2710 if (m_endpos->lnum == startpos->lnum && m_endpos->col < startpos->col)
2711 m_endpos->col = startpos->col;
2712
2713 syn_add_end_off(end_endpos, &best_regmatch, spp, SPO_HE_OFF, 1);
2714 /* can't end before the start */
2715 if (end_endpos->lnum == startpos->lnum
2716 && end_endpos->col < startpos->col)
2717 end_endpos->col = startpos->col;
2718 /* can't end after the match */
2719 limit_pos(end_endpos, m_endpos);
2720
2721 /*
2722 * If the end group is highlighted differently, adjust the pointers.
2723 */
2724 if (spp->sp_syn_match_id != spp->sp_syn.id && spp->sp_syn_match_id != 0) {
2725 *end_idx = best_idx;
2726 if (spp->sp_off_flags & (1 << (SPO_RE_OFF + SPO_COUNT))) {
2727 hl_endpos->lnum = best_regmatch.endpos[0].lnum;
2728 hl_endpos->col = best_regmatch.endpos[0].col;
2729 } else {
2730 hl_endpos->lnum = best_regmatch.startpos[0].lnum;
2731 hl_endpos->col = best_regmatch.startpos[0].col;
2732 }
2733 hl_endpos->col += spp->sp_offsets[SPO_RE_OFF];
2734
2735 /* can't end before the start */
2736 if (hl_endpos->lnum == startpos->lnum
2737 && hl_endpos->col < startpos->col)
2738 hl_endpos->col = startpos->col;
2739 limit_pos(hl_endpos, m_endpos);
2740
2741 /* now the match ends where the highlighting ends, it is turned
2742 * into the matchgroup for the end */
2743 *m_endpos = *hl_endpos;
2744 } else {
2745 *end_idx = 0;
2746 *hl_endpos = *end_endpos;
2747 }
2748
2749 *flagsp = spp->sp_flags;
2750
2751 had_match = TRUE;
2752 break;
2753 }
2754
2755 /* no match for an END pattern in this line */
2756 if (!had_match)
2757 m_endpos->lnum = 0;
2758
2759 restore_chartab(buf_chartab);
2760
2761 /* Remove external matches. */
2762 unref_extmatch(re_extmatch_in);
2763 re_extmatch_in = NULL;
2764}
2765
2766/*
2767 * Limit "pos" not to be after "limit".
2768 */
2769static void limit_pos(lpos_T *pos, lpos_T *limit)
2770{
2771 if (pos->lnum > limit->lnum)
2772 *pos = *limit;
2773 else if (pos->lnum == limit->lnum && pos->col > limit->col)
2774 pos->col = limit->col;
2775}
2776
2777/*
2778 * Limit "pos" not to be after "limit", unless pos->lnum is zero.
2779 */
2780static void limit_pos_zero(lpos_T *pos, lpos_T *limit)
2781{
2782 if (pos->lnum == 0)
2783 *pos = *limit;
2784 else
2785 limit_pos(pos, limit);
2786}
2787
2788/*
2789 * Add offset to matched text for end of match or highlight.
2790 */
2791static void
2792syn_add_end_off(
2793 lpos_T *result, // returned position
2794 regmmatch_T *regmatch, // start/end of match
2795 synpat_T *spp, // matched pattern
2796 int idx, // index of offset
2797 int extra // extra chars for offset to start
2798)
2799{
2800 int col;
2801 int off;
2802 char_u *base;
2803 char_u *p;
2804
2805 if (spp->sp_off_flags & (1 << idx)) {
2806 result->lnum = regmatch->startpos[0].lnum;
2807 col = regmatch->startpos[0].col;
2808 off = spp->sp_offsets[idx] + extra;
2809 } else {
2810 result->lnum = regmatch->endpos[0].lnum;
2811 col = regmatch->endpos[0].col;
2812 off = spp->sp_offsets[idx];
2813 }
2814 /* Don't go past the end of the line. Matters for "rs=e+2" when there
2815 * is a matchgroup. Watch out for match with last NL in the buffer. */
2816 if (result->lnum > syn_buf->b_ml.ml_line_count)
2817 col = 0;
2818 else if (off != 0) {
2819 base = ml_get_buf(syn_buf, result->lnum, FALSE);
2820 p = base + col;
2821 if (off > 0) {
2822 while (off-- > 0 && *p != NUL) {
2823 MB_PTR_ADV(p);
2824 }
2825 } else {
2826 while (off++ < 0 && base < p) {
2827 MB_PTR_BACK(base, p);
2828 }
2829 }
2830 col = (int)(p - base);
2831 }
2832 result->col = col;
2833}
2834
2835/*
2836 * Add offset to matched text for start of match or highlight.
2837 * Avoid resulting column to become negative.
2838 */
2839static void
2840syn_add_start_off(
2841 lpos_T *result, // returned position
2842 regmmatch_T *regmatch, // start/end of match
2843 synpat_T *spp,
2844 int idx,
2845 int extra // extra chars for offset to end
2846)
2847{
2848 int col;
2849 int off;
2850 char_u *base;
2851 char_u *p;
2852
2853 if (spp->sp_off_flags & (1 << (idx + SPO_COUNT))) {
2854 result->lnum = regmatch->endpos[0].lnum;
2855 col = regmatch->endpos[0].col;
2856 off = spp->sp_offsets[idx] + extra;
2857 } else {
2858 result->lnum = regmatch->startpos[0].lnum;
2859 col = regmatch->startpos[0].col;
2860 off = spp->sp_offsets[idx];
2861 }
2862 if (result->lnum > syn_buf->b_ml.ml_line_count) {
2863 /* a "\n" at the end of the pattern may take us below the last line */
2864 result->lnum = syn_buf->b_ml.ml_line_count;
2865 col = (int)STRLEN(ml_get_buf(syn_buf, result->lnum, FALSE));
2866 }
2867 if (off != 0) {
2868 base = ml_get_buf(syn_buf, result->lnum, FALSE);
2869 p = base + col;
2870 if (off > 0) {
2871 while (off-- && *p != NUL) {
2872 MB_PTR_ADV(p);
2873 }
2874 } else {
2875 while (off++ && base < p) {
2876 MB_PTR_BACK(base, p);
2877 }
2878 }
2879 col = (int)(p - base);
2880 }
2881 result->col = col;
2882}
2883
2884/*
2885 * Get current line in syntax buffer.
2886 */
2887static char_u *syn_getcurline(void)
2888{
2889 return ml_get_buf(syn_buf, current_lnum, FALSE);
2890}
2891
2892/*
2893 * Call vim_regexec() to find a match with "rmp" in "syn_buf".
2894 * Returns TRUE when there is a match.
2895 */
2896static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T *st)
2897{
2898 int r;
2899 int timed_out = 0;
2900 proftime_T pt;
2901 const int l_syn_time_on = syn_time_on;
2902
2903 if (l_syn_time_on) {
2904 pt = profile_start();
2905 }
2906
2907 if (rmp->regprog == NULL) {
2908 // This can happen if a previous call to vim_regexec_multi() tried to
2909 // use the NFA engine, which resulted in NFA_TOO_EXPENSIVE, and
2910 // compiling the pattern with the other engine fails.
2911 return false;
2912 }
2913
2914 rmp->rmm_maxcol = syn_buf->b_p_smc;
2915 r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col,
2916 syn_tm, &timed_out);
2917
2918 if (l_syn_time_on) {
2919 pt = profile_end(pt);
2920 st->total = profile_add(st->total, pt);
2921 if (profile_cmp(pt, st->slowest) < 0) {
2922 st->slowest = pt;
2923 }
2924 ++st->count;
2925 if (r > 0)
2926 ++st->match;
2927 }
2928 if (timed_out && !syn_win->w_s->b_syn_slow) {
2929 syn_win->w_s->b_syn_slow = true;
2930 MSG(_("'redrawtime' exceeded, syntax highlighting disabled"));
2931 }
2932
2933 if (r > 0) {
2934 rmp->startpos[0].lnum += lnum;
2935 rmp->endpos[0].lnum += lnum;
2936 return TRUE;
2937 }
2938 return FALSE;
2939}
2940
2941/*
2942 * Check one position in a line for a matching keyword.
2943 * The caller must check if a keyword can start at startcol.
2944 * Return its ID if found, 0 otherwise.
2945 */
2946static int check_keyword_id(
2947 char_u *const line,
2948 const int startcol, // position in line to check for keyword
2949 int *const endcolp, // return: character after found keyword
2950 long *const flagsp, // return: flags of matching keyword
2951 int16_t **const next_listp, // return: next_list of matching keyword
2952 stateitem_T *const cur_si, // item at the top of the stack
2953 int *const ccharp // conceal substitution char
2954)
2955{
2956 // Find first character after the keyword. First character was already
2957 // checked.
2958 char_u *const kwp = line + startcol;
2959 int kwlen = 0;
2960 do {
2961 if (has_mbyte) {
2962 kwlen += (*mb_ptr2len)(kwp + kwlen);
2963 } else {
2964 kwlen++;
2965 }
2966 } while (vim_iswordp_buf(kwp + kwlen, syn_buf));
2967
2968 if (kwlen > MAXKEYWLEN) {
2969 return 0;
2970 }
2971
2972 // Must make a copy of the keyword, so we can add a NUL and make it
2973 // lowercase.
2974 char_u keyword[MAXKEYWLEN + 1]; // assume max. keyword len is 80
2975 STRLCPY(keyword, kwp, kwlen + 1);
2976
2977 keyentry_T *kp = NULL;
2978
2979 // matching case
2980 if (syn_block->b_keywtab.ht_used != 0) {
2981 kp = match_keyword(keyword, &syn_block->b_keywtab, cur_si);
2982 }
2983
2984 // ignoring case
2985 if (kp == NULL && syn_block->b_keywtab_ic.ht_used != 0) {
2986 str_foldcase(kwp, kwlen, keyword, MAXKEYWLEN + 1);
2987 kp = match_keyword(keyword, &syn_block->b_keywtab_ic, cur_si);
2988 }
2989
2990 if (kp != NULL) {
2991 *endcolp = startcol + kwlen;
2992 *flagsp = kp->flags;
2993 *next_listp = kp->next_list;
2994 *ccharp = kp->k_char;
2995 return kp->k_syn.id;
2996 }
2997
2998 return 0;
2999}
3000
3001/// Find keywords that match. There can be several with different
3002/// attributes.
3003/// When current_next_list is non-zero accept only that group, otherwise:
3004/// Accept a not-contained keyword at toplevel.
3005/// Accept a keyword at other levels only if it is in the contains list.
3006static keyentry_T *match_keyword(char_u *keyword, hashtab_T *ht,
3007 stateitem_T *cur_si)
3008{
3009 hashitem_T *hi = hash_find(ht, keyword);
3010 if (!HASHITEM_EMPTY(hi))
3011 for (keyentry_T *kp = HI2KE(hi); kp != NULL; kp = kp->ke_next) {
3012 if (current_next_list != 0
3013 ? in_id_list(NULL, current_next_list, &kp->k_syn, 0)
3014 : (cur_si == NULL
3015 ? !(kp->flags & HL_CONTAINED)
3016 : in_id_list(cur_si, cur_si->si_cont_list,
3017 &kp->k_syn, kp->flags & HL_CONTAINED))) {
3018 return kp;
3019 }
3020 }
3021 return NULL;
3022}
3023
3024/*
3025 * Handle ":syntax conceal" command.
3026 */
3027static void syn_cmd_conceal(exarg_T *eap, int syncing)
3028{
3029 char_u *arg = eap->arg;
3030 char_u *next;
3031
3032 eap->nextcmd = find_nextcmd(arg);
3033 if (eap->skip)
3034 return;
3035
3036 next = skiptowhite(arg);
3037 if (*arg == NUL) {
3038 if (curwin->w_s->b_syn_conceal) {
3039 MSG(_("syntax conceal on"));
3040 } else {
3041 MSG(_("syntax conceal off"));
3042 }
3043 } else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) {
3044 curwin->w_s->b_syn_conceal = true;
3045 } else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3) {
3046 curwin->w_s->b_syn_conceal = false;
3047 } else {
3048 EMSG2(_("E390: Illegal argument: %s"), arg);
3049 }
3050}
3051
3052/*
3053 * Handle ":syntax case" command.
3054 */
3055static void syn_cmd_case(exarg_T *eap, int syncing)
3056{
3057 char_u *arg = eap->arg;
3058 char_u *next;
3059
3060 eap->nextcmd = find_nextcmd(arg);
3061 if (eap->skip)
3062 return;
3063
3064 next = skiptowhite(arg);
3065 if (*arg == NUL) {
3066 if (curwin->w_s->b_syn_ic) {
3067 MSG(_("syntax case ignore"));
3068 } else {
3069 MSG(_("syntax case match"));
3070 }
3071 } else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) {
3072 curwin->w_s->b_syn_ic = false;
3073 } else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6) {
3074 curwin->w_s->b_syn_ic = true;
3075 } else {
3076 EMSG2(_("E390: Illegal argument: %s"), arg);
3077 }
3078}
3079
3080/*
3081 * Handle ":syntax spell" command.
3082 */
3083static void syn_cmd_spell(exarg_T *eap, int syncing)
3084{
3085 char_u *arg = eap->arg;
3086 char_u *next;
3087
3088 eap->nextcmd = find_nextcmd(arg);
3089 if (eap->skip)
3090 return;
3091
3092 next = skiptowhite(arg);
3093 if (*arg == NUL) {
3094 if (curwin->w_s->b_syn_spell == SYNSPL_TOP) {
3095 MSG(_("syntax spell toplevel"));
3096 } else if (curwin->w_s->b_syn_spell == SYNSPL_NOTOP) {
3097 MSG(_("syntax spell notoplevel"));
3098 } else {
3099 MSG(_("syntax spell default"));
3100 }
3101 } else if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8) {
3102 curwin->w_s->b_syn_spell = SYNSPL_TOP;
3103 } else if (STRNICMP(arg, "notoplevel", 10) == 0 && next - arg == 10) {
3104 curwin->w_s->b_syn_spell = SYNSPL_NOTOP;
3105 } else if (STRNICMP(arg, "default", 7) == 0 && next - arg == 7) {
3106 curwin->w_s->b_syn_spell = SYNSPL_DEFAULT;
3107 } else {
3108 EMSG2(_("E390: Illegal argument: %s"), arg);
3109 return;
3110 }
3111
3112 // assume spell checking changed, force a redraw
3113 redraw_win_later(curwin, NOT_VALID);
3114}
3115
3116/// Handle ":syntax iskeyword" command.
3117static void syn_cmd_iskeyword(exarg_T *eap, int syncing)
3118{
3119 char_u *arg = eap->arg;
3120 char_u save_chartab[32];
3121 char_u *save_isk;
3122
3123 if (eap->skip) {
3124 return;
3125 }
3126
3127 arg = skipwhite(arg);
3128 if (*arg == NUL) {
3129 MSG_PUTS("\n");
3130 if (curwin->w_s->b_syn_isk != empty_option) {
3131 MSG_PUTS(_("syntax iskeyword "));
3132 msg_outtrans(curwin->w_s->b_syn_isk);
3133 } else {
3134 msg_outtrans((char_u *)_("syntax iskeyword not set"));
3135 }
3136 } else {
3137 if (STRNICMP(arg, "clear", 5) == 0) {
3138 memmove(curwin->w_s->b_syn_chartab, curbuf->b_chartab, (size_t)32);
3139 clear_string_option(&curwin->w_s->b_syn_isk);
3140 } else {
3141 memmove(save_chartab, curbuf->b_chartab, (size_t)32);
3142 save_isk = curbuf->b_p_isk;
3143 curbuf->b_p_isk = vim_strsave(arg);
3144
3145 buf_init_chartab(curbuf, false);
3146 memmove(curwin->w_s->b_syn_chartab, curbuf->b_chartab, (size_t)32);
3147 memmove(curbuf->b_chartab, save_chartab, (size_t)32);
3148 clear_string_option(&curwin->w_s->b_syn_isk);
3149 curwin->w_s->b_syn_isk = curbuf->b_p_isk;
3150 curbuf->b_p_isk = save_isk;
3151 }
3152 }
3153 redraw_win_later(curwin, NOT_VALID);
3154}
3155
3156/*
3157 * Clear all syntax info for one buffer.
3158 */
3159void syntax_clear(synblock_T *block)
3160{
3161 block->b_syn_error = false; // clear previous error
3162 block->b_syn_slow = false; // clear previous timeout
3163 block->b_syn_ic = false; // Use case, by default
3164 block->b_syn_spell = SYNSPL_DEFAULT; // default spell checking
3165 block->b_syn_containedin = false;
3166 block->b_syn_conceal = false;
3167
3168 /* free the keywords */
3169 clear_keywtab(&block->b_keywtab);
3170 clear_keywtab(&block->b_keywtab_ic);
3171
3172 /* free the syntax patterns */
3173 for (int i = block->b_syn_patterns.ga_len; --i >= 0; ) {
3174 syn_clear_pattern(block, i);
3175 }
3176 ga_clear(&block->b_syn_patterns);
3177
3178 /* free the syntax clusters */
3179 for (int i = block->b_syn_clusters.ga_len; --i >= 0; ) {
3180 syn_clear_cluster(block, i);
3181 }
3182 ga_clear(&block->b_syn_clusters);
3183 block->b_spell_cluster_id = 0;
3184 block->b_nospell_cluster_id = 0;
3185
3186 block->b_syn_sync_flags = 0;
3187 block->b_syn_sync_minlines = 0;
3188 block->b_syn_sync_maxlines = 0;
3189 block->b_syn_sync_linebreaks = 0;
3190
3191 vim_regfree(block->b_syn_linecont_prog);
3192 block->b_syn_linecont_prog = NULL;
3193 XFREE_CLEAR(block->b_syn_linecont_pat);
3194 block->b_syn_folditems = 0;
3195 clear_string_option(&block->b_syn_isk);
3196
3197 /* free the stored states */
3198 syn_stack_free_all(block);
3199 invalidate_current_state();
3200
3201 /* Reset the counter for ":syn include" */
3202 running_syn_inc_tag = 0;
3203}
3204
3205/*
3206 * Get rid of ownsyntax for window "wp".
3207 */
3208void reset_synblock(win_T *wp)
3209{
3210 if (wp->w_s != &wp->w_buffer->b_s) {
3211 syntax_clear(wp->w_s);
3212 xfree(wp->w_s);
3213 wp->w_s = &wp->w_buffer->b_s;
3214 }
3215}
3216
3217/*
3218 * Clear syncing info for one buffer.
3219 */
3220static void syntax_sync_clear(void)
3221{
3222 /* free the syntax patterns */
3223 for (int i = curwin->w_s->b_syn_patterns.ga_len; --i >= 0; ) {
3224 if (SYN_ITEMS(curwin->w_s)[i].sp_syncing) {
3225 syn_remove_pattern(curwin->w_s, i);
3226 }
3227 }
3228
3229 curwin->w_s->b_syn_sync_flags = 0;
3230 curwin->w_s->b_syn_sync_minlines = 0;
3231 curwin->w_s->b_syn_sync_maxlines = 0;
3232 curwin->w_s->b_syn_sync_linebreaks = 0;
3233
3234 vim_regfree(curwin->w_s->b_syn_linecont_prog);
3235 curwin->w_s->b_syn_linecont_prog = NULL;
3236 XFREE_CLEAR(curwin->w_s->b_syn_linecont_pat);
3237 clear_string_option(&curwin->w_s->b_syn_isk);
3238
3239 syn_stack_free_all(curwin->w_s); /* Need to recompute all syntax. */
3240}
3241
3242/*
3243 * Remove one pattern from the buffer's pattern list.
3244 */
3245static void syn_remove_pattern(synblock_T *block, int idx)
3246{
3247 synpat_T *spp;
3248
3249 spp = &(SYN_ITEMS(block)[idx]);
3250 if (spp->sp_flags & HL_FOLD)
3251 --block->b_syn_folditems;
3252 syn_clear_pattern(block, idx);
3253 memmove(spp, spp + 1,
3254 sizeof(synpat_T) * (block->b_syn_patterns.ga_len - idx - 1));
3255 --block->b_syn_patterns.ga_len;
3256}
3257
3258/*
3259 * Clear and free one syntax pattern. When clearing all, must be called from
3260 * last to first!
3261 */
3262static void syn_clear_pattern(synblock_T *block, int i)
3263{
3264 xfree(SYN_ITEMS(block)[i].sp_pattern);
3265 vim_regfree(SYN_ITEMS(block)[i].sp_prog);
3266 /* Only free sp_cont_list and sp_next_list of first start pattern */
3267 if (i == 0 || SYN_ITEMS(block)[i - 1].sp_type != SPTYPE_START) {
3268 xfree(SYN_ITEMS(block)[i].sp_cont_list);
3269 xfree(SYN_ITEMS(block)[i].sp_next_list);
3270 xfree(SYN_ITEMS(block)[i].sp_syn.cont_in_list);
3271 }
3272}
3273
3274/*
3275 * Clear and free one syntax cluster.
3276 */
3277static void syn_clear_cluster(synblock_T *block, int i)
3278{
3279 xfree(SYN_CLSTR(block)[i].scl_name);
3280 xfree(SYN_CLSTR(block)[i].scl_name_u);
3281 xfree(SYN_CLSTR(block)[i].scl_list);
3282}
3283
3284/*
3285 * Handle ":syntax clear" command.
3286 */
3287static void syn_cmd_clear(exarg_T *eap, int syncing)
3288{
3289 char_u *arg = eap->arg;
3290 char_u *arg_end;
3291 int id;
3292
3293 eap->nextcmd = find_nextcmd(arg);
3294 if (eap->skip)
3295 return;
3296
3297 /*
3298 * We have to disable this within ":syn include @group filename",
3299 * because otherwise @group would get deleted.
3300 * Only required for Vim 5.x syntax files, 6.0 ones don't contain ":syn
3301 * clear".
3302 */
3303 if (curwin->w_s->b_syn_topgrp != 0)
3304 return;
3305
3306 if (ends_excmd(*arg)) {
3307 /*
3308 * No argument: Clear all syntax items.
3309 */
3310 if (syncing)
3311 syntax_sync_clear();
3312 else {
3313 syntax_clear(curwin->w_s);
3314 if (curwin->w_s == &curwin->w_buffer->b_s) {
3315 do_unlet(S_LEN("b:current_syntax"), true);
3316 }
3317 do_unlet(S_LEN("w:current_syntax"), true);
3318 }
3319 } else {
3320 /*
3321 * Clear the group IDs that are in the argument.
3322 */
3323 while (!ends_excmd(*arg)) {
3324 arg_end = skiptowhite(arg);
3325 if (*arg == '@') {
3326 id = syn_scl_namen2id(arg + 1, (int)(arg_end - arg - 1));
3327 if (id == 0) {
3328 EMSG2(_("E391: No such syntax cluster: %s"), arg);
3329 break;
3330 } else {
3331 // We can't physically delete a cluster without changing
3332 // the IDs of other clusters, so we do the next best thing
3333 // and make it empty.
3334 int scl_id = id - SYNID_CLUSTER;
3335
3336 XFREE_CLEAR(SYN_CLSTR(curwin->w_s)[scl_id].scl_list);
3337 }
3338 } else {
3339 id = syn_namen2id(arg, (int)(arg_end - arg));
3340 if (id == 0) {
3341 EMSG2(_(e_nogroup), arg);
3342 break;
3343 } else
3344 syn_clear_one(id, syncing);
3345 }
3346 arg = skipwhite(arg_end);
3347 }
3348 }
3349 redraw_curbuf_later(SOME_VALID);
3350 syn_stack_free_all(curwin->w_s); /* Need to recompute all syntax. */
3351}
3352
3353/*
3354 * Clear one syntax group for the current buffer.
3355 */
3356static void syn_clear_one(const int id, const bool syncing)
3357{
3358 synpat_T *spp;
3359
3360 /* Clear keywords only when not ":syn sync clear group-name" */
3361 if (!syncing) {
3362 syn_clear_keyword(id, &curwin->w_s->b_keywtab);
3363 syn_clear_keyword(id, &curwin->w_s->b_keywtab_ic);
3364 }
3365
3366 /* clear the patterns for "id" */
3367 for (int idx = curwin->w_s->b_syn_patterns.ga_len; --idx >= 0; ) {
3368 spp = &(SYN_ITEMS(curwin->w_s)[idx]);
3369 if (spp->sp_syn.id != id || spp->sp_syncing != syncing)
3370 continue;
3371 syn_remove_pattern(curwin->w_s, idx);
3372 }
3373}
3374
3375/*
3376 * Handle ":syntax on" command.
3377 */
3378static void syn_cmd_on(exarg_T *eap, int syncing)
3379{
3380 syn_cmd_onoff(eap, "syntax");
3381}
3382
3383/*
3384 * Handle ":syntax enable" command.
3385 */
3386static void syn_cmd_enable(exarg_T *eap, int syncing)
3387{
3388 set_internal_string_var((char_u *)"syntax_cmd", (char_u *)"enable");
3389 syn_cmd_onoff(eap, "syntax");
3390 do_unlet(S_LEN("g:syntax_cmd"), true);
3391}
3392
3393/*
3394 * Handle ":syntax reset" command.
3395 * It actually resets highlighting, not syntax.
3396 */
3397static void syn_cmd_reset(exarg_T *eap, int syncing)
3398{
3399 eap->nextcmd = check_nextcmd(eap->arg);
3400 if (!eap->skip) {
3401 set_internal_string_var((char_u *)"syntax_cmd", (char_u *)"reset");
3402 do_cmdline_cmd("runtime! syntax/syncolor.vim");
3403 do_unlet(S_LEN("g:syntax_cmd"), true);
3404 }
3405}
3406
3407/*
3408 * Handle ":syntax manual" command.
3409 */
3410static void syn_cmd_manual(exarg_T *eap, int syncing)
3411{
3412 syn_cmd_onoff(eap, "manual");
3413}
3414
3415/*
3416 * Handle ":syntax off" command.
3417 */
3418static void syn_cmd_off(exarg_T *eap, int syncing)
3419{
3420 syn_cmd_onoff(eap, "nosyntax");
3421}
3422
3423static void syn_cmd_onoff(exarg_T *eap, char *name)
3424 FUNC_ATTR_NONNULL_ALL
3425{
3426 eap->nextcmd = check_nextcmd(eap->arg);
3427 if (!eap->skip) {
3428 did_syntax_onoff = true;
3429 char buf[100];
3430 memcpy(buf, "so ", 4);
3431 vim_snprintf(buf + 3, sizeof(buf) - 3, SYNTAX_FNAME, name);
3432 do_cmdline_cmd(buf);
3433 }
3434}
3435
3436void syn_maybe_on(void)
3437{
3438 if (!did_syntax_onoff) {
3439 exarg_T ea;
3440 ea.arg = (char_u *)"";
3441 ea.skip = false;
3442 syn_cmd_onoff(&ea, "syntax");
3443 }
3444}
3445
3446/*
3447 * Handle ":syntax [list]" command: list current syntax words.
3448 */
3449static void
3450syn_cmd_list(
3451 exarg_T *eap,
3452 int syncing /* when TRUE: list syncing items */
3453)
3454{
3455 char_u *arg = eap->arg;
3456 char_u *arg_end;
3457
3458 eap->nextcmd = find_nextcmd(arg);
3459 if (eap->skip)
3460 return;
3461
3462 if (!syntax_present(curwin)) {
3463 MSG(_(msg_no_items));
3464 return;
3465 }
3466
3467 if (syncing) {
3468 if (curwin->w_s->b_syn_sync_flags & SF_CCOMMENT) {
3469 MSG_PUTS(_("syncing on C-style comments"));
3470 syn_lines_msg();
3471 syn_match_msg();
3472 return;
3473 } else if (!(curwin->w_s->b_syn_sync_flags & SF_MATCH)) {
3474 if (curwin->w_s->b_syn_sync_minlines == 0)
3475 MSG_PUTS(_("no syncing"));
3476 else {
3477 MSG_PUTS(_("syncing starts "));
3478 msg_outnum(curwin->w_s->b_syn_sync_minlines);
3479 MSG_PUTS(_(" lines before top line"));
3480 syn_match_msg();
3481 }
3482 return;
3483 }
3484 MSG_PUTS_TITLE(_("\n--- Syntax sync items ---"));
3485 if (curwin->w_s->b_syn_sync_minlines > 0
3486 || curwin->w_s->b_syn_sync_maxlines > 0
3487 || curwin->w_s->b_syn_sync_linebreaks > 0) {
3488 MSG_PUTS(_("\nsyncing on items"));
3489 syn_lines_msg();
3490 syn_match_msg();
3491 }
3492 } else
3493 MSG_PUTS_TITLE(_("\n--- Syntax items ---"));
3494 if (ends_excmd(*arg)) {
3495 /*
3496 * No argument: List all group IDs and all syntax clusters.
3497 */
3498 for (int id = 1; id <= highlight_ga.ga_len && !got_int; id++) {
3499 syn_list_one(id, syncing, false);
3500 }
3501 for (int id = 0; id < curwin->w_s->b_syn_clusters.ga_len && !got_int; ++id) {
3502 syn_list_cluster(id);
3503 }
3504 } else {
3505 /*
3506 * List the group IDs and syntax clusters that are in the argument.
3507 */
3508 while (!ends_excmd(*arg) && !got_int) {
3509 arg_end = skiptowhite(arg);
3510 if (*arg == '@') {
3511 int id = syn_scl_namen2id(arg + 1, (int)(arg_end - arg - 1));
3512 if (id == 0)
3513 EMSG2(_("E392: No such syntax cluster: %s"), arg);
3514 else
3515 syn_list_cluster(id - SYNID_CLUSTER);
3516 } else {
3517 int id = syn_namen2id(arg, (int)(arg_end - arg));
3518 if (id == 0) {
3519 EMSG2(_(e_nogroup), arg);
3520 } else {
3521 syn_list_one(id, syncing, true);
3522 }
3523 }
3524 arg = skipwhite(arg_end);
3525 }
3526 }
3527 eap->nextcmd = check_nextcmd(arg);
3528}
3529
3530static void syn_lines_msg(void)
3531{
3532 if (curwin->w_s->b_syn_sync_maxlines > 0
3533 || curwin->w_s->b_syn_sync_minlines > 0) {
3534 MSG_PUTS("; ");
3535 if (curwin->w_s->b_syn_sync_minlines > 0) {
3536 MSG_PUTS(_("minimal "));
3537 msg_outnum(curwin->w_s->b_syn_sync_minlines);
3538 if (curwin->w_s->b_syn_sync_maxlines)
3539 MSG_PUTS(", ");
3540 }
3541 if (curwin->w_s->b_syn_sync_maxlines > 0) {
3542 MSG_PUTS(_("maximal "));
3543 msg_outnum(curwin->w_s->b_syn_sync_maxlines);
3544 }
3545 MSG_PUTS(_(" lines before top line"));
3546 }
3547}
3548
3549static void syn_match_msg(void)
3550{
3551 if (curwin->w_s->b_syn_sync_linebreaks > 0) {
3552 MSG_PUTS(_("; match "));
3553 msg_outnum(curwin->w_s->b_syn_sync_linebreaks);
3554 MSG_PUTS(_(" line breaks"));
3555 }
3556}
3557
3558static int last_matchgroup;
3559
3560
3561/*
3562 * List one syntax item, for ":syntax" or "syntax list syntax_name".
3563 */
3564static void
3565syn_list_one(
3566 const int id,
3567 const bool syncing, // when true: list syncing items
3568 const bool link_only // when true; list link-only too
3569)
3570{
3571 bool did_header = false;
3572 static struct name_list namelist1[] =
3573 {
3574 {HL_DISPLAY, "display"},
3575 {HL_CONTAINED, "contained"},
3576 {HL_ONELINE, "oneline"},
3577 {HL_KEEPEND, "keepend"},
3578 {HL_EXTEND, "extend"},
3579 {HL_EXCLUDENL, "excludenl"},
3580 {HL_TRANSP, "transparent"},
3581 {HL_FOLD, "fold"},
3582 {HL_CONCEAL, "conceal"},
3583 {HL_CONCEALENDS, "concealends"},
3584 {0, NULL}
3585 };
3586 static struct name_list namelist2[] =
3587 {
3588 {HL_SKIPWHITE, "skipwhite"},
3589 {HL_SKIPNL, "skipnl"},
3590 {HL_SKIPEMPTY, "skipempty"},
3591 {0, NULL}
3592 };
3593
3594 const int attr = HL_ATTR(HLF_D); // highlight like directories
3595
3596 // list the keywords for "id"
3597 if (!syncing) {
3598 did_header = syn_list_keywords(id, &curwin->w_s->b_keywtab, false, attr);
3599 did_header = syn_list_keywords(id, &curwin->w_s->b_keywtab_ic,
3600 did_header, attr);
3601 }
3602
3603 // list the patterns for "id"
3604 for (int idx = 0;
3605 idx < curwin->w_s->b_syn_patterns.ga_len && !got_int;
3606 idx++) {
3607 const synpat_T *const spp = &(SYN_ITEMS(curwin->w_s)[idx]);
3608 if (spp->sp_syn.id != id || spp->sp_syncing != syncing) {
3609 continue;
3610 }
3611
3612 (void)syn_list_header(did_header, 0, id, true);
3613 did_header = true;
3614 last_matchgroup = 0;
3615 if (spp->sp_type == SPTYPE_MATCH) {
3616 put_pattern("match", ' ', spp, attr);
3617 msg_putchar(' ');
3618 } else if (spp->sp_type == SPTYPE_START) {
3619 while (SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_START)
3620 put_pattern("start", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr);
3621 if (SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_SKIP)
3622 put_pattern("skip", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr);
3623 while (idx < curwin->w_s->b_syn_patterns.ga_len
3624 && SYN_ITEMS(curwin->w_s)[idx].sp_type == SPTYPE_END)
3625 put_pattern("end", '=', &SYN_ITEMS(curwin->w_s)[idx++], attr);
3626 --idx;
3627 msg_putchar(' ');
3628 }
3629 syn_list_flags(namelist1, spp->sp_flags, attr);
3630
3631 if (spp->sp_cont_list != NULL) {
3632 put_id_list("contains", spp->sp_cont_list, attr);
3633 }
3634
3635 if (spp->sp_syn.cont_in_list != NULL) {
3636 put_id_list("containedin", spp->sp_syn.cont_in_list, attr);
3637 }
3638
3639 if (spp->sp_next_list != NULL) {
3640 put_id_list("nextgroup", spp->sp_next_list, attr);
3641 syn_list_flags(namelist2, spp->sp_flags, attr);
3642 }
3643 if (spp->sp_flags & (HL_SYNC_HERE|HL_SYNC_THERE)) {
3644 if (spp->sp_flags & HL_SYNC_HERE) {
3645 msg_puts_attr("grouphere", attr);
3646 } else {
3647 msg_puts_attr("groupthere", attr);
3648 }
3649 msg_putchar(' ');
3650 if (spp->sp_sync_idx >= 0)
3651 msg_outtrans(HL_TABLE()[SYN_ITEMS(curwin->w_s)
3652 [spp->sp_sync_idx].sp_syn.id - 1].sg_name);
3653 else
3654 MSG_PUTS("NONE");
3655 msg_putchar(' ');
3656 }
3657 }
3658
3659 /* list the link, if there is one */
3660 if (HL_TABLE()[id - 1].sg_link && (did_header || link_only) && !got_int) {
3661 (void)syn_list_header(did_header, 0, id, true);
3662 msg_puts_attr("links to", attr);
3663 msg_putchar(' ');
3664 msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name);
3665 }
3666}
3667
3668static void syn_list_flags(struct name_list *nlist, int flags, int attr)
3669{
3670 int i;
3671
3672 for (i = 0; nlist[i].flag != 0; ++i)
3673 if (flags & nlist[i].flag) {
3674 msg_puts_attr(nlist[i].name, attr);
3675 msg_putchar(' ');
3676 }
3677}
3678
3679/*
3680 * List one syntax cluster, for ":syntax" or "syntax list syntax_name".
3681 */
3682static void syn_list_cluster(int id)
3683{
3684 int endcol = 15;
3685
3686 /* slight hack: roughly duplicate the guts of syn_list_header() */
3687 msg_putchar('\n');
3688 msg_outtrans(SYN_CLSTR(curwin->w_s)[id].scl_name);
3689
3690 if (msg_col >= endcol) /* output at least one space */
3691 endcol = msg_col + 1;
3692 if (Columns <= endcol) /* avoid hang for tiny window */
3693 endcol = Columns - 1;
3694
3695 msg_advance(endcol);
3696 if (SYN_CLSTR(curwin->w_s)[id].scl_list != NULL) {
3697 put_id_list("cluster", SYN_CLSTR(curwin->w_s)[id].scl_list, HL_ATTR(HLF_D));
3698 } else {
3699 msg_puts_attr("cluster", HL_ATTR(HLF_D));
3700 msg_puts("=NONE");
3701 }
3702}
3703
3704static void put_id_list(const char *const name,
3705 const int16_t *const list,
3706 const int attr)
3707{
3708 msg_puts_attr(name, attr);
3709 msg_putchar('=');
3710 for (const int16_t *p = list; *p; p++) {
3711 if (*p >= SYNID_ALLBUT && *p < SYNID_TOP) {
3712 if (p[1]) {
3713 msg_puts("ALLBUT");
3714 } else {
3715 msg_puts("ALL");
3716 }
3717 } else if (*p >= SYNID_TOP && *p < SYNID_CONTAINED) {
3718 msg_puts("TOP");
3719 } else if (*p >= SYNID_CONTAINED && *p < SYNID_CLUSTER) {
3720 msg_puts("CONTAINED");
3721 } else if (*p >= SYNID_CLUSTER) {
3722 int scl_id = *p - SYNID_CLUSTER;
3723
3724 msg_putchar('@');
3725 msg_outtrans(SYN_CLSTR(curwin->w_s)[scl_id].scl_name);
3726 } else
3727 msg_outtrans(HL_TABLE()[*p - 1].sg_name);
3728 if (p[1])
3729 msg_putchar(',');
3730 }
3731 msg_putchar(' ');
3732}
3733
3734static void put_pattern(const char *const s, const int c,
3735 const synpat_T *const spp, const int attr)
3736{
3737 static const char *const sepchars = "/+=-#@\"|'^&";
3738 int i;
3739
3740 /* May have to write "matchgroup=group" */
3741 if (last_matchgroup != spp->sp_syn_match_id) {
3742 last_matchgroup = spp->sp_syn_match_id;
3743 msg_puts_attr("matchgroup", attr);
3744 msg_putchar('=');
3745 if (last_matchgroup == 0)
3746 msg_outtrans((char_u *)"NONE");
3747 else
3748 msg_outtrans(HL_TABLE()[last_matchgroup - 1].sg_name);
3749 msg_putchar(' ');
3750 }
3751
3752 // Output the name of the pattern and an '=' or ' '.
3753 msg_puts_attr(s, attr);
3754 msg_putchar(c);
3755
3756 /* output the pattern, in between a char that is not in the pattern */
3757 for (i = 0; vim_strchr(spp->sp_pattern, sepchars[i]) != NULL; )
3758 if (sepchars[++i] == NUL) {
3759 i = 0; /* no good char found, just use the first one */
3760 break;
3761 }
3762 msg_putchar(sepchars[i]);
3763 msg_outtrans(spp->sp_pattern);
3764 msg_putchar(sepchars[i]);
3765
3766 // output any pattern options
3767 bool first = true;
3768 for (i = 0; i < SPO_COUNT; i++) {
3769 const int mask = (1 << i);
3770 if (!(spp->sp_off_flags & (mask + (mask << SPO_COUNT)))) {
3771 continue;
3772 }
3773 if (!first) {
3774 msg_putchar(','); // Separate with commas.
3775 }
3776 msg_puts(spo_name_tab[i]);
3777 const long n = spp->sp_offsets[i];
3778 if (i != SPO_LC_OFF) {
3779 if (spp->sp_off_flags & mask)
3780 msg_putchar('s');
3781 else
3782 msg_putchar('e');
3783 if (n > 0)
3784 msg_putchar('+');
3785 }
3786 if (n || i == SPO_LC_OFF) {
3787 msg_outnum(n);
3788 }
3789 first = false;
3790 }
3791 msg_putchar(' ');
3792}
3793
3794// List or clear the keywords for one syntax group.
3795// Return true if the header has been printed.
3796static bool syn_list_keywords(
3797 const int id,
3798 const hashtab_T *const ht,
3799 bool did_header, // header has already been printed
3800 const int attr
3801)
3802{
3803 int prev_contained = 0;
3804 const int16_t *prev_next_list = NULL;
3805 const int16_t *prev_cont_in_list = NULL;
3806 int prev_skipnl = 0;
3807 int prev_skipwhite = 0;
3808 int prev_skipempty = 0;
3809
3810 // Unfortunately, this list of keywords is not sorted on alphabet but on
3811 // hash value...
3812 size_t todo = ht->ht_used;
3813 for (const hashitem_T *hi = ht->ht_array; todo > 0 && !got_int; hi++) {
3814 if (HASHITEM_EMPTY(hi)) {
3815 continue;
3816 }
3817 todo--;
3818 for (keyentry_T *kp = HI2KE(hi); kp != NULL && !got_int; kp = kp->ke_next) {
3819 if (kp->k_syn.id == id) {
3820 int outlen = 0;
3821 bool force_newline = false;
3822 if (prev_contained != (kp->flags & HL_CONTAINED)
3823 || prev_skipnl != (kp->flags & HL_SKIPNL)
3824 || prev_skipwhite != (kp->flags & HL_SKIPWHITE)
3825 || prev_skipempty != (kp->flags & HL_SKIPEMPTY)
3826 || prev_cont_in_list != kp->k_syn.cont_in_list
3827 || prev_next_list != kp->next_list) {
3828 force_newline = true;
3829 } else {
3830 outlen = (int)STRLEN(kp->keyword);
3831 }
3832 // output "contained" and "nextgroup" on each line
3833 if (syn_list_header(did_header, outlen, id, force_newline)) {
3834 prev_contained = 0;
3835 prev_next_list = NULL;
3836 prev_cont_in_list = NULL;
3837 prev_skipnl = 0;
3838 prev_skipwhite = 0;
3839 prev_skipempty = 0;
3840 }
3841 did_header = true;
3842 if (prev_contained != (kp->flags & HL_CONTAINED)) {
3843 msg_puts_attr("contained", attr);
3844 msg_putchar(' ');
3845 prev_contained = (kp->flags & HL_CONTAINED);
3846 }
3847 if (kp->k_syn.cont_in_list != prev_cont_in_list) {
3848 put_id_list("containedin", kp->k_syn.cont_in_list, attr);
3849 msg_putchar(' ');
3850 prev_cont_in_list = kp->k_syn.cont_in_list;
3851 }
3852 if (kp->next_list != prev_next_list) {
3853 put_id_list("nextgroup", kp->next_list, attr);
3854 msg_putchar(' ');
3855 prev_next_list = kp->next_list;
3856 if (kp->flags & HL_SKIPNL) {
3857 msg_puts_attr("skipnl", attr);
3858 msg_putchar(' ');
3859 prev_skipnl = (kp->flags & HL_SKIPNL);
3860 }
3861 if (kp->flags & HL_SKIPWHITE) {
3862 msg_puts_attr("skipwhite", attr);
3863 msg_putchar(' ');
3864 prev_skipwhite = (kp->flags & HL_SKIPWHITE);
3865 }
3866 if (kp->flags & HL_SKIPEMPTY) {
3867 msg_puts_attr("skipempty", attr);
3868 msg_putchar(' ');
3869 prev_skipempty = (kp->flags & HL_SKIPEMPTY);
3870 }
3871 }
3872 msg_outtrans(kp->keyword);
3873 }
3874 }
3875 }
3876
3877 return did_header;
3878}
3879
3880static void syn_clear_keyword(int id, hashtab_T *ht)
3881{
3882 hashitem_T *hi;
3883 keyentry_T *kp;
3884 keyentry_T *kp_prev;
3885 keyentry_T *kp_next;
3886 int todo;
3887
3888 hash_lock(ht);
3889 todo = (int)ht->ht_used;
3890 for (hi = ht->ht_array; todo > 0; ++hi) {
3891 if (HASHITEM_EMPTY(hi)) {
3892 continue;
3893 }
3894 --todo;
3895 kp_prev = NULL;
3896 for (kp = HI2KE(hi); kp != NULL; ) {
3897 if (kp->k_syn.id == id) {
3898 kp_next = kp->ke_next;
3899 if (kp_prev == NULL) {
3900 if (kp_next == NULL)
3901 hash_remove(ht, hi);
3902 else
3903 hi->hi_key = KE2HIKEY(kp_next);
3904 } else
3905 kp_prev->ke_next = kp_next;
3906 xfree(kp->next_list);
3907 xfree(kp->k_syn.cont_in_list);
3908 xfree(kp);
3909 kp = kp_next;
3910 } else {
3911 kp_prev = kp;
3912 kp = kp->ke_next;
3913 }
3914 }
3915 }
3916 hash_unlock(ht);
3917}
3918
3919/*
3920 * Clear a whole keyword table.
3921 */
3922static void clear_keywtab(hashtab_T *ht)
3923{
3924 hashitem_T *hi;
3925 int todo;
3926 keyentry_T *kp;
3927 keyentry_T *kp_next;
3928
3929 todo = (int)ht->ht_used;
3930 for (hi = ht->ht_array; todo > 0; ++hi) {
3931 if (!HASHITEM_EMPTY(hi)) {
3932 --todo;
3933 for (kp = HI2KE(hi); kp != NULL; kp = kp_next) {
3934 kp_next = kp->ke_next;
3935 xfree(kp->next_list);
3936 xfree(kp->k_syn.cont_in_list);
3937 xfree(kp);
3938 }
3939 }
3940 }
3941 hash_clear(ht);
3942 hash_init(ht);
3943}
3944
3945/// Add a keyword to the list of keywords.
3946///
3947/// @param name name of keyword
3948/// @param id group ID for this keyword
3949/// @param flags flags for this keyword
3950/// @param cont_in_list containedin for this keyword
3951/// @param next_list nextgroup for this keyword
3952static void add_keyword(char_u *const name,
3953 const int id,
3954 const int flags,
3955 int16_t *const cont_in_list,
3956 int16_t *const next_list,
3957 const int conceal_char)
3958{
3959 char_u name_folded[MAXKEYWLEN + 1];
3960 const char_u *const name_ic = (curwin->w_s->b_syn_ic)
3961 ? str_foldcase(name, (int)STRLEN(name), name_folded, sizeof(name_folded))
3962 : name;
3963
3964 keyentry_T *const kp = xmalloc(sizeof(keyentry_T) + STRLEN(name_ic));
3965 STRCPY(kp->keyword, name_ic);
3966 kp->k_syn.id = id;
3967 kp->k_syn.inc_tag = current_syn_inc_tag;
3968 kp->flags = flags;
3969 kp->k_char = conceal_char;
3970 kp->k_syn.cont_in_list = copy_id_list(cont_in_list);
3971 if (cont_in_list != NULL) {
3972 curwin->w_s->b_syn_containedin = TRUE;
3973 }
3974 kp->next_list = copy_id_list(next_list);
3975
3976 const hash_T hash = hash_hash(kp->keyword);
3977 hashtab_T *const ht = (curwin->w_s->b_syn_ic)
3978 ? &curwin->w_s->b_keywtab_ic
3979 : &curwin->w_s->b_keywtab;
3980 hashitem_T *const hi = hash_lookup(ht, (const char *)kp->keyword,
3981 STRLEN(kp->keyword), hash);
3982
3983 // even though it looks like only the kp->keyword member is
3984 // being used here, vim uses some pointer trickery to get the orignal
3985 // struct again later by using knowledge of the offset of the keyword
3986 // field in the struct. See the definition of the HI2KE macro.
3987 if (HASHITEM_EMPTY(hi)) {
3988 // new keyword, add to hashtable
3989 kp->ke_next = NULL;
3990 hash_add_item(ht, hi, kp->keyword, hash);
3991 } else {
3992 // keyword already exists, prepend to list
3993 kp->ke_next = HI2KE(hi);
3994 hi->hi_key = KE2HIKEY(kp);
3995 }
3996}
3997
3998/*
3999 * Get the start and end of the group name argument.
4000 * Return a pointer to the first argument.
4001 * Return NULL if the end of the command was found instead of further args.
4002 */
4003static char_u *
4004get_group_name (
4005 char_u *arg, /* start of the argument */
4006 char_u **name_end /* pointer to end of the name */
4007)
4008{
4009 char_u *rest;
4010
4011 *name_end = skiptowhite(arg);
4012 rest = skipwhite(*name_end);
4013
4014 /*
4015 * Check if there are enough arguments. The first argument may be a
4016 * pattern, where '|' is allowed, so only check for NUL.
4017 */
4018 if (ends_excmd(*arg) || *rest == NUL)
4019 return NULL;
4020 return rest;
4021}
4022
4023/*
4024 * Check for syntax command option arguments.
4025 * This can be called at any place in the list of arguments, and just picks
4026 * out the arguments that are known. Can be called several times in a row to
4027 * collect all options in between other arguments.
4028 * Return a pointer to the next argument (which isn't an option).
4029 * Return NULL for any error;
4030 */
4031static char_u *
4032get_syn_options(
4033 char_u *arg, // next argument to be checked
4034 syn_opt_arg_T *opt, // various things
4035 int *conceal_char,
4036 int skip // TRUE if skipping over command
4037)
4038{
4039 char_u *gname_start, *gname;
4040 int syn_id;
4041 int len = 0;
4042 char *p;
4043 int fidx;
4044 static const struct flag {
4045 char *name;
4046 int argtype;
4047 int flags;
4048 } flagtab[] = { {"cCoOnNtTaAiInNeEdD", 0, HL_CONTAINED},
4049 {"oOnNeElLiInNeE", 0, HL_ONELINE},
4050 {"kKeEeEpPeEnNdD", 0, HL_KEEPEND},
4051 {"eExXtTeEnNdD", 0, HL_EXTEND},
4052 {"eExXcClLuUdDeEnNlL", 0, HL_EXCLUDENL},
4053 {"tTrRaAnNsSpPaArReEnNtT", 0, HL_TRANSP},
4054 {"sSkKiIpPnNlL", 0, HL_SKIPNL},
4055 {"sSkKiIpPwWhHiItTeE", 0, HL_SKIPWHITE},
4056 {"sSkKiIpPeEmMpPtTyY", 0, HL_SKIPEMPTY},
4057 {"gGrRoOuUpPhHeErReE", 0, HL_SYNC_HERE},
4058 {"gGrRoOuUpPtThHeErReE", 0, HL_SYNC_THERE},
4059 {"dDiIsSpPlLaAyY", 0, HL_DISPLAY},
4060 {"fFoOlLdD", 0, HL_FOLD},
4061 {"cCoOnNcCeEaAlL", 0, HL_CONCEAL},
4062 {"cCoOnNcCeEaAlLeEnNdDsS", 0, HL_CONCEALENDS},
4063 {"cCcChHaArR", 11, 0},
4064 {"cCoOnNtTaAiInNsS", 1, 0},
4065 {"cCoOnNtTaAiInNeEdDiInN", 2, 0},
4066 {"nNeExXtTgGrRoOuUpP", 3, 0},};
4067 static const char *const first_letters = "cCoOkKeEtTsSgGdDfFnN";
4068
4069 if (arg == NULL) /* already detected error */
4070 return NULL;
4071
4072 if (curwin->w_s->b_syn_conceal)
4073 opt->flags |= HL_CONCEAL;
4074
4075 for (;; ) {
4076 /*
4077 * This is used very often when a large number of keywords is defined.
4078 * Need to skip quickly when no option name is found.
4079 * Also avoid tolower(), it's slow.
4080 */
4081 if (strchr(first_letters, *arg) == NULL)
4082 break;
4083
4084 for (fidx = ARRAY_SIZE(flagtab); --fidx >= 0; ) {
4085 p = flagtab[fidx].name;
4086 int i;
4087 for (i = 0, len = 0; p[i] != NUL; i += 2, ++len) {
4088 if (arg[len] != p[i] && arg[len] != p[i + 1])
4089 break;
4090 }
4091 if (p[i] == NUL && (ascii_iswhite(arg[len])
4092 || (flagtab[fidx].argtype > 0
4093 ? arg[len] == '='
4094 : ends_excmd(arg[len])))) {
4095 if (opt->keyword
4096 && (flagtab[fidx].flags == HL_DISPLAY
4097 || flagtab[fidx].flags == HL_FOLD
4098 || flagtab[fidx].flags == HL_EXTEND))
4099 /* treat "display", "fold" and "extend" as a keyword */
4100 fidx = -1;
4101 break;
4102 }
4103 }
4104 if (fidx < 0) /* no match found */
4105 break;
4106
4107 if (flagtab[fidx].argtype == 1) {
4108 if (!opt->has_cont_list) {
4109 EMSG(_("E395: contains argument not accepted here"));
4110 return NULL;
4111 }
4112 if (get_id_list(&arg, 8, &opt->cont_list, skip) == FAIL) {
4113 return NULL;
4114 }
4115 } else if (flagtab[fidx].argtype == 2) {
4116 if (get_id_list(&arg, 11, &opt->cont_in_list, skip) == FAIL) {
4117 return NULL;
4118 }
4119 } else if (flagtab[fidx].argtype == 3) {
4120 if (get_id_list(&arg, 9, &opt->next_list, skip) == FAIL) {
4121 return NULL;
4122 }
4123 } else if (flagtab[fidx].argtype == 11 && arg[5] == '=') {
4124 // cchar=?
4125 *conceal_char = utf_ptr2char(arg + 6);
4126 arg += mb_ptr2len(arg + 6) - 1;
4127 if (!vim_isprintc_strict(*conceal_char)) {
4128 EMSG(_("E844: invalid cchar value"));
4129 return NULL;
4130 }
4131 arg = skipwhite(arg + 7);
4132 } else {
4133 opt->flags |= flagtab[fidx].flags;
4134 arg = skipwhite(arg + len);
4135
4136 if (flagtab[fidx].flags == HL_SYNC_HERE
4137 || flagtab[fidx].flags == HL_SYNC_THERE) {
4138 if (opt->sync_idx == NULL) {
4139 EMSG(_("E393: group[t]here not accepted here"));
4140 return NULL;
4141 }
4142 gname_start = arg;
4143 arg = skiptowhite(arg);
4144 if (gname_start == arg)
4145 return NULL;
4146 gname = vim_strnsave(gname_start, (int)(arg - gname_start));
4147 if (STRCMP(gname, "NONE") == 0)
4148 *opt->sync_idx = NONE_IDX;
4149 else {
4150 syn_id = syn_name2id(gname);
4151 int i;
4152 for (i = curwin->w_s->b_syn_patterns.ga_len; --i >= 0; )
4153 if (SYN_ITEMS(curwin->w_s)[i].sp_syn.id == syn_id
4154 && SYN_ITEMS(curwin->w_s)[i].sp_type == SPTYPE_START) {
4155 *opt->sync_idx = i;
4156 break;
4157 }
4158 if (i < 0) {
4159 EMSG2(_("E394: Didn't find region item for %s"), gname);
4160 xfree(gname);
4161 return NULL;
4162 }
4163 }
4164
4165 xfree(gname);
4166 arg = skipwhite(arg);
4167 } else if (flagtab[fidx].flags == HL_FOLD
4168 && foldmethodIsSyntax(curwin))
4169 /* Need to update folds later. */
4170 foldUpdateAll(curwin);
4171 }
4172 }
4173
4174 return arg;
4175}
4176
4177/*
4178 * Adjustments to syntax item when declared in a ":syn include"'d file.
4179 * Set the contained flag, and if the item is not already contained, add it
4180 * to the specified top-level group, if any.
4181 */
4182static void syn_incl_toplevel(int id, int *flagsp)
4183{
4184 if ((*flagsp & HL_CONTAINED) || curwin->w_s->b_syn_topgrp == 0)
4185 return;
4186 *flagsp |= HL_CONTAINED;
4187 if (curwin->w_s->b_syn_topgrp >= SYNID_CLUSTER) {
4188 // We have to alloc this, because syn_combine_list() will free it.
4189 int16_t *grp_list = xmalloc(2 * sizeof(*grp_list));
4190 int tlg_id = curwin->w_s->b_syn_topgrp - SYNID_CLUSTER;
4191
4192 grp_list[0] = id;
4193 grp_list[1] = 0;
4194 syn_combine_list(&SYN_CLSTR(curwin->w_s)[tlg_id].scl_list, &grp_list,
4195 CLUSTER_ADD);
4196 }
4197}
4198
4199/*
4200 * Handle ":syntax include [@{group-name}] filename" command.
4201 */
4202static void syn_cmd_include(exarg_T *eap, int syncing)
4203{
4204 char_u *arg = eap->arg;
4205 int sgl_id = 1;
4206 char_u *group_name_end;
4207 char_u *rest;
4208 char_u *errormsg = NULL;
4209 int prev_toplvl_grp;
4210 int prev_syn_inc_tag;
4211 int source = FALSE;
4212
4213 eap->nextcmd = find_nextcmd(arg);
4214 if (eap->skip)
4215 return;
4216
4217 if (arg[0] == '@') {
4218 ++arg;
4219 rest = get_group_name(arg, &group_name_end);
4220 if (rest == NULL) {
4221 EMSG((char_u *)_("E397: Filename required"));
4222 return;
4223 }
4224 sgl_id = syn_check_cluster(arg, (int)(group_name_end - arg));
4225 if (sgl_id == 0)
4226 return;
4227 /* separate_nextcmd() and expand_filename() depend on this */
4228 eap->arg = rest;
4229 }
4230
4231 /*
4232 * Everything that's left, up to the next command, should be the
4233 * filename to include.
4234 */
4235 eap->argt |= (XFILE | NOSPC);
4236 separate_nextcmd(eap);
4237 if (*eap->arg == '<' || *eap->arg == '$' || path_is_absolute(eap->arg)) {
4238 // For an absolute path, "$VIM/..." or "<sfile>.." we ":source" the
4239 // file. Need to expand the file name first. In other cases
4240 // ":runtime!" is used.
4241 source = true;
4242 if (expand_filename(eap, syn_cmdlinep, &errormsg) == FAIL) {
4243 if (errormsg != NULL)
4244 EMSG(errormsg);
4245 return;
4246 }
4247 }
4248
4249 /*
4250 * Save and restore the existing top-level grouplist id and ":syn
4251 * include" tag around the actual inclusion.
4252 */
4253 if (running_syn_inc_tag >= MAX_SYN_INC_TAG) {
4254 EMSG((char_u *)_("E847: Too many syntax includes"));
4255 return;
4256 }
4257 prev_syn_inc_tag = current_syn_inc_tag;
4258 current_syn_inc_tag = ++running_syn_inc_tag;
4259 prev_toplvl_grp = curwin->w_s->b_syn_topgrp;
4260 curwin->w_s->b_syn_topgrp = sgl_id;
4261 if (source ? do_source(eap->arg, false, DOSO_NONE) == FAIL
4262 : source_runtime(eap->arg, DIP_ALL) == FAIL) {
4263 EMSG2(_(e_notopen), eap->arg);
4264 }
4265 curwin->w_s->b_syn_topgrp = prev_toplvl_grp;
4266 current_syn_inc_tag = prev_syn_inc_tag;
4267}
4268
4269/*
4270 * Handle ":syntax keyword {group-name} [{option}] keyword .." command.
4271 */
4272static void syn_cmd_keyword(exarg_T *eap, int syncing)
4273{
4274 char_u *arg = eap->arg;
4275 char_u *group_name_end;
4276 int syn_id;
4277 char_u *rest;
4278 char_u *keyword_copy = NULL;
4279 char_u *p;
4280 char_u *kw;
4281 syn_opt_arg_T syn_opt_arg;
4282 int cnt;
4283 int conceal_char = NUL;
4284
4285 rest = get_group_name(arg, &group_name_end);
4286
4287 if (rest != NULL) {
4288 if (eap->skip) {
4289 syn_id = -1;
4290 } else {
4291 syn_id = syn_check_group(arg, (int)(group_name_end - arg));
4292 }
4293 if (syn_id != 0) {
4294 // Allocate a buffer, for removing backslashes in the keyword.
4295 keyword_copy = xmalloc(STRLEN(rest) + 1);
4296 }
4297 if (keyword_copy != NULL) {
4298 syn_opt_arg.flags = 0;
4299 syn_opt_arg.keyword = true;
4300 syn_opt_arg.sync_idx = NULL;
4301 syn_opt_arg.has_cont_list = false;
4302 syn_opt_arg.cont_in_list = NULL;
4303 syn_opt_arg.next_list = NULL;
4304
4305 // The options given apply to ALL keywords, so all options must be
4306 // found before keywords can be created.
4307 // 1: collect the options and copy the keywords to keyword_copy.
4308 cnt = 0;
4309 p = keyword_copy;
4310 for (; rest != NULL && !ends_excmd(*rest); rest = skipwhite(rest)) {
4311 rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
4312 if (rest == NULL || ends_excmd(*rest)) {
4313 break;
4314 }
4315 // Copy the keyword, removing backslashes, and add a NUL.
4316 while (*rest != NUL && !ascii_iswhite(*rest)) {
4317 if (*rest == '\\' && rest[1] != NUL) {
4318 rest++;
4319 }
4320 *p++ = *rest++;
4321 }
4322 *p++ = NUL;
4323 cnt++;
4324 }
4325
4326 if (!eap->skip) {
4327 // Adjust flags for use of ":syn include".
4328 syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
4329
4330 // 2: Add an entry for each keyword.
4331 for (kw = keyword_copy; --cnt >= 0; kw += STRLEN(kw) + 1) {
4332 for (p = vim_strchr(kw, '[');; ) {
4333 if (p != NULL) {
4334 *p = NUL;
4335 }
4336 add_keyword(kw, syn_id, syn_opt_arg.flags,
4337 syn_opt_arg.cont_in_list,
4338 syn_opt_arg.next_list, conceal_char);
4339 if (p == NULL) {
4340 break;
4341 }
4342 if (p[1] == NUL) {
4343 emsgf(_("E789: Missing ']': %s"), kw);
4344 goto error;
4345 }
4346 if (p[1] == ']') {
4347 if (p[2] != NUL) {
4348 emsgf(_("E890: trailing char after ']': %s]%s"),
4349 kw, &p[2]);
4350 goto error;
4351 }
4352 kw = p + 1;
4353 break; // skip over the "]"
4354 }
4355 const int l = (*mb_ptr2len)(p + 1);
4356
4357 memmove(p, p + 1, l);
4358 p += l;
4359 }
4360 }
4361 }
4362
4363error:
4364 xfree(keyword_copy);
4365 xfree(syn_opt_arg.cont_in_list);
4366 xfree(syn_opt_arg.next_list);
4367 }
4368 }
4369
4370 if (rest != NULL)
4371 eap->nextcmd = check_nextcmd(rest);
4372 else
4373 EMSG2(_(e_invarg2), arg);
4374
4375 redraw_curbuf_later(SOME_VALID);
4376 syn_stack_free_all(curwin->w_s); /* Need to recompute all syntax. */
4377}
4378
4379/*
4380 * Handle ":syntax match {name} [{options}] {pattern} [{options}]".
4381 *
4382 * Also ":syntax sync match {name} [[grouphere | groupthere] {group-name}] .."
4383 */
4384static void
4385syn_cmd_match(
4386 exarg_T *eap,
4387 int syncing /* TRUE for ":syntax sync match .. " */
4388)
4389{
4390 char_u *arg = eap->arg;
4391 char_u *group_name_end;
4392 char_u *rest;
4393 synpat_T item; /* the item found in the line */
4394 int syn_id;
4395 syn_opt_arg_T syn_opt_arg;
4396 int sync_idx = 0;
4397 int conceal_char = NUL;
4398
4399 /* Isolate the group name, check for validity */
4400 rest = get_group_name(arg, &group_name_end);
4401
4402 /* Get options before the pattern */
4403 syn_opt_arg.flags = 0;
4404 syn_opt_arg.keyword = false;
4405 syn_opt_arg.sync_idx = syncing ? &sync_idx : NULL;
4406 syn_opt_arg.has_cont_list = true;
4407 syn_opt_arg.cont_list = NULL;
4408 syn_opt_arg.cont_in_list = NULL;
4409 syn_opt_arg.next_list = NULL;
4410 rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
4411
4412 /* get the pattern. */
4413 init_syn_patterns();
4414 memset(&item, 0, sizeof(item));
4415 rest = get_syn_pattern(rest, &item);
4416 if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL)) {
4417 syn_opt_arg.flags |= HL_HAS_EOL;
4418 }
4419
4420 // Get options after the pattern
4421 rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
4422
4423 if (rest != NULL) { /* all arguments are valid */
4424 /*
4425 * Check for trailing command and illegal trailing arguments.
4426 */
4427 eap->nextcmd = check_nextcmd(rest);
4428 if (!ends_excmd(*rest) || eap->skip)
4429 rest = NULL;
4430 else {
4431 if ((syn_id = syn_check_group(arg, (int)(group_name_end - arg))) != 0) {
4432 syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
4433 /*
4434 * Store the pattern in the syn_items list
4435 */
4436 synpat_T *spp = GA_APPEND_VIA_PTR(synpat_T,
4437 &curwin->w_s->b_syn_patterns);
4438 *spp = item;
4439 spp->sp_syncing = syncing;
4440 spp->sp_type = SPTYPE_MATCH;
4441 spp->sp_syn.id = syn_id;
4442 spp->sp_syn.inc_tag = current_syn_inc_tag;
4443 spp->sp_flags = syn_opt_arg.flags;
4444 spp->sp_sync_idx = sync_idx;
4445 spp->sp_cont_list = syn_opt_arg.cont_list;
4446 spp->sp_syn.cont_in_list = syn_opt_arg.cont_in_list;
4447 spp->sp_cchar = conceal_char;
4448 if (syn_opt_arg.cont_in_list != NULL)
4449 curwin->w_s->b_syn_containedin = TRUE;
4450 spp->sp_next_list = syn_opt_arg.next_list;
4451
4452 /* remember that we found a match for syncing on */
4453 if (syn_opt_arg.flags & (HL_SYNC_HERE|HL_SYNC_THERE))
4454 curwin->w_s->b_syn_sync_flags |= SF_MATCH;
4455 if (syn_opt_arg.flags & HL_FOLD)
4456 ++curwin->w_s->b_syn_folditems;
4457
4458 redraw_curbuf_later(SOME_VALID);
4459 syn_stack_free_all(curwin->w_s); /* Need to recompute all syntax. */
4460 return; /* don't free the progs and patterns now */
4461 }
4462 }
4463 }
4464
4465 /*
4466 * Something failed, free the allocated memory.
4467 */
4468 vim_regfree(item.sp_prog);
4469 xfree(item.sp_pattern);
4470 xfree(syn_opt_arg.cont_list);
4471 xfree(syn_opt_arg.cont_in_list);
4472 xfree(syn_opt_arg.next_list);
4473
4474 if (rest == NULL)
4475 EMSG2(_(e_invarg2), arg);
4476}
4477
4478/*
4479 * Handle ":syntax region {group-name} [matchgroup={group-name}]
4480 * start {start} .. [skip {skip}] end {end} .. [{options}]".
4481 */
4482static void
4483syn_cmd_region(
4484 exarg_T *eap,
4485 int syncing /* TRUE for ":syntax sync region .." */
4486)
4487{
4488 char_u *arg = eap->arg;
4489 char_u *group_name_end;
4490 char_u *rest; /* next arg, NULL on error */
4491 char_u *key_end;
4492 char_u *key = NULL;
4493 char_u *p;
4494 int item;
4495#define ITEM_START 0
4496#define ITEM_SKIP 1
4497#define ITEM_END 2
4498#define ITEM_MATCHGROUP 3
4499 struct pat_ptr {
4500 synpat_T *pp_synp; /* pointer to syn_pattern */
4501 int pp_matchgroup_id; /* matchgroup ID */
4502 struct pat_ptr *pp_next; /* pointer to next pat_ptr */
4503 } *(pat_ptrs[3]);
4504 /* patterns found in the line */
4505 struct pat_ptr *ppp;
4506 struct pat_ptr *ppp_next;
4507 int pat_count = 0; /* nr of syn_patterns found */
4508 int syn_id;
4509 int matchgroup_id = 0;
4510 int not_enough = FALSE; /* not enough arguments */
4511 int illegal = FALSE; /* illegal arguments */
4512 int success = FALSE;
4513 syn_opt_arg_T syn_opt_arg;
4514 int conceal_char = NUL;
4515
4516 /* Isolate the group name, check for validity */
4517 rest = get_group_name(arg, &group_name_end);
4518
4519 pat_ptrs[0] = NULL;
4520 pat_ptrs[1] = NULL;
4521 pat_ptrs[2] = NULL;
4522
4523 init_syn_patterns();
4524
4525 syn_opt_arg.flags = 0;
4526 syn_opt_arg.keyword = false;
4527 syn_opt_arg.sync_idx = NULL;
4528 syn_opt_arg.has_cont_list = true;
4529 syn_opt_arg.cont_list = NULL;
4530 syn_opt_arg.cont_in_list = NULL;
4531 syn_opt_arg.next_list = NULL;
4532
4533 // get the options, patterns and matchgroup.
4534 while (rest != NULL && !ends_excmd(*rest)) {
4535 // Check for option arguments
4536 rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip);
4537 if (rest == NULL || ends_excmd(*rest)) {
4538 break;
4539 }
4540
4541 /* must be a pattern or matchgroup then */
4542 key_end = rest;
4543 while (*key_end && !ascii_iswhite(*key_end) && *key_end != '=')
4544 ++key_end;
4545 xfree(key);
4546 key = vim_strnsave_up(rest, (int)(key_end - rest));
4547 if (STRCMP(key, "MATCHGROUP") == 0) {
4548 item = ITEM_MATCHGROUP;
4549 } else if (STRCMP(key, "START") == 0) {
4550 item = ITEM_START;
4551 } else if (STRCMP(key, "END") == 0) {
4552 item = ITEM_END;
4553 } else if (STRCMP(key, "SKIP") == 0) {
4554 if (pat_ptrs[ITEM_SKIP] != NULL) { // One skip pattern allowed.
4555 illegal = true;
4556 break;
4557 }
4558 item = ITEM_SKIP;
4559 } else {
4560 break;
4561 }
4562 rest = skipwhite(key_end);
4563 if (*rest != '=') {
4564 rest = NULL;
4565 EMSG2(_("E398: Missing '=': %s"), arg);
4566 break;
4567 }
4568 rest = skipwhite(rest + 1);
4569 if (*rest == NUL) {
4570 not_enough = TRUE;
4571 break;
4572 }
4573
4574 if (item == ITEM_MATCHGROUP) {
4575 p = skiptowhite(rest);
4576 if ((p - rest == 4 && STRNCMP(rest, "NONE", 4) == 0) || eap->skip)
4577 matchgroup_id = 0;
4578 else {
4579 matchgroup_id = syn_check_group(rest, (int)(p - rest));
4580 if (matchgroup_id == 0) {
4581 illegal = TRUE;
4582 break;
4583 }
4584 }
4585 rest = skipwhite(p);
4586 } else {
4587 /*
4588 * Allocate room for a syn_pattern, and link it in the list of
4589 * syn_patterns for this item, at the start (because the list is
4590 * used from end to start).
4591 */
4592 ppp = xmalloc(sizeof(struct pat_ptr));
4593 ppp->pp_next = pat_ptrs[item];
4594 pat_ptrs[item] = ppp;
4595 ppp->pp_synp = xcalloc(1, sizeof(synpat_T));
4596
4597 // Get the syntax pattern and the following offset(s).
4598
4599 // Enable the appropriate \z specials.
4600 if (item == ITEM_START) {
4601 reg_do_extmatch = REX_SET;
4602 } else {
4603 assert(item == ITEM_SKIP || item == ITEM_END);
4604 reg_do_extmatch = REX_USE;
4605 }
4606 rest = get_syn_pattern(rest, ppp->pp_synp);
4607 reg_do_extmatch = 0;
4608 if (item == ITEM_END && vim_regcomp_had_eol()
4609 && !(syn_opt_arg.flags & HL_EXCLUDENL)) {
4610 ppp->pp_synp->sp_flags |= HL_HAS_EOL;
4611 }
4612 ppp->pp_matchgroup_id = matchgroup_id;
4613 pat_count++;
4614 }
4615 }
4616 xfree(key);
4617 if (illegal || not_enough)
4618 rest = NULL;
4619
4620 // Must have a "start" and "end" pattern.
4621 if (rest != NULL && (pat_ptrs[ITEM_START] == NULL
4622 || pat_ptrs[ITEM_END] == NULL)) {
4623 not_enough = true;
4624 rest = NULL;
4625 }
4626
4627 if (rest != NULL) {
4628 /*
4629 * Check for trailing garbage or command.
4630 * If OK, add the item.
4631 */
4632 eap->nextcmd = check_nextcmd(rest);
4633 if (!ends_excmd(*rest) || eap->skip)
4634 rest = NULL;
4635 else {
4636 ga_grow(&(curwin->w_s->b_syn_patterns), pat_count);
4637 if ((syn_id = syn_check_group(arg, (int)(group_name_end - arg))) != 0) {
4638 syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
4639 /*
4640 * Store the start/skip/end in the syn_items list
4641 */
4642 int idx = curwin->w_s->b_syn_patterns.ga_len;
4643 for (item = ITEM_START; item <= ITEM_END; ++item) {
4644 for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp->pp_next) {
4645 SYN_ITEMS(curwin->w_s)[idx] = *(ppp->pp_synp);
4646 SYN_ITEMS(curwin->w_s)[idx].sp_syncing = syncing;
4647 SYN_ITEMS(curwin->w_s)[idx].sp_type =
4648 (item == ITEM_START) ? SPTYPE_START :
4649 (item == ITEM_SKIP) ? SPTYPE_SKIP : SPTYPE_END;
4650 SYN_ITEMS(curwin->w_s)[idx].sp_flags |= syn_opt_arg.flags;
4651 SYN_ITEMS(curwin->w_s)[idx].sp_syn.id = syn_id;
4652 SYN_ITEMS(curwin->w_s)[idx].sp_syn.inc_tag =
4653 current_syn_inc_tag;
4654 SYN_ITEMS(curwin->w_s)[idx].sp_syn_match_id =
4655 ppp->pp_matchgroup_id;
4656 SYN_ITEMS(curwin->w_s)[idx].sp_cchar = conceal_char;
4657 if (item == ITEM_START) {
4658 SYN_ITEMS(curwin->w_s)[idx].sp_cont_list =
4659 syn_opt_arg.cont_list;
4660 SYN_ITEMS(curwin->w_s)[idx].sp_syn.cont_in_list =
4661 syn_opt_arg.cont_in_list;
4662 if (syn_opt_arg.cont_in_list != NULL)
4663 curwin->w_s->b_syn_containedin = TRUE;
4664 SYN_ITEMS(curwin->w_s)[idx].sp_next_list =
4665 syn_opt_arg.next_list;
4666 }
4667 ++curwin->w_s->b_syn_patterns.ga_len;
4668 ++idx;
4669 if (syn_opt_arg.flags & HL_FOLD)
4670 ++curwin->w_s->b_syn_folditems;
4671 }
4672 }
4673
4674 redraw_curbuf_later(SOME_VALID);
4675 syn_stack_free_all(curwin->w_s); /* Need to recompute all syntax. */
4676 success = TRUE; /* don't free the progs and patterns now */
4677 }
4678 }
4679 }
4680
4681 /*
4682 * Free the allocated memory.
4683 */
4684 for (item = ITEM_START; item <= ITEM_END; ++item)
4685 for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp_next) {
4686 if (!success && ppp->pp_synp != NULL) {
4687 vim_regfree(ppp->pp_synp->sp_prog);
4688 xfree(ppp->pp_synp->sp_pattern);
4689 }
4690 xfree(ppp->pp_synp);
4691 ppp_next = ppp->pp_next;
4692 xfree(ppp);
4693 }
4694
4695 if (!success) {
4696 xfree(syn_opt_arg.cont_list);
4697 xfree(syn_opt_arg.cont_in_list);
4698 xfree(syn_opt_arg.next_list);
4699 if (not_enough)
4700 EMSG2(_("E399: Not enough arguments: syntax region %s"), arg);
4701 else if (illegal || rest == NULL)
4702 EMSG2(_(e_invarg2), arg);
4703 }
4704}
4705
4706// A simple syntax group ID comparison function suitable for use in qsort()
4707static int syn_compare_stub(const void *const v1, const void *const v2)
4708{
4709 const int16_t *const s1 = v1;
4710 const int16_t *const s2 = v2;
4711
4712 return *s1 > *s2 ? 1 : *s1 < *s2 ? -1 : 0;
4713}
4714
4715// Combines lists of syntax clusters.
4716// *clstr1 and *clstr2 must both be allocated memory; they will be consumed.
4717static void syn_combine_list(int16_t **const clstr1, int16_t **const clstr2,
4718 const int list_op)
4719{
4720 size_t count1 = 0;
4721 size_t count2 = 0;
4722 const int16_t *g1;
4723 const int16_t *g2;
4724 int16_t *clstr = NULL;
4725
4726 /*
4727 * Handle degenerate cases.
4728 */
4729 if (*clstr2 == NULL)
4730 return;
4731 if (*clstr1 == NULL || list_op == CLUSTER_REPLACE) {
4732 if (list_op == CLUSTER_REPLACE)
4733 xfree(*clstr1);
4734 if (list_op == CLUSTER_REPLACE || list_op == CLUSTER_ADD)
4735 *clstr1 = *clstr2;
4736 else
4737 xfree(*clstr2);
4738 return;
4739 }
4740
4741 for (g1 = *clstr1; *g1; g1++) {
4742 count1++;
4743 }
4744 for (g2 = *clstr2; *g2; g2++) {
4745 count2++;
4746 }
4747
4748 // For speed purposes, sort both lists.
4749 qsort(*clstr1, count1, sizeof(**clstr1), syn_compare_stub);
4750 qsort(*clstr2, count2, sizeof(**clstr2), syn_compare_stub);
4751
4752 // We proceed in two passes; in round 1, we count the elements to place
4753 // in the new list, and in round 2, we allocate and populate the new
4754 // list. For speed, we use a mergesort-like method, adding the smaller
4755 // of the current elements in each list to the new list.
4756 for (int round = 1; round <= 2; round++) {
4757 g1 = *clstr1;
4758 g2 = *clstr2;
4759 int count = 0;
4760
4761 /*
4762 * First, loop through the lists until one of them is empty.
4763 */
4764 while (*g1 && *g2) {
4765 /*
4766 * We always want to add from the first list.
4767 */
4768 if (*g1 < *g2) {
4769 if (round == 2)
4770 clstr[count] = *g1;
4771 count++;
4772 g1++;
4773 continue;
4774 }
4775 /*
4776 * We only want to add from the second list if we're adding the
4777 * lists.
4778 */
4779 if (list_op == CLUSTER_ADD) {
4780 if (round == 2)
4781 clstr[count] = *g2;
4782 count++;
4783 }
4784 if (*g1 == *g2)
4785 g1++;
4786 g2++;
4787 }
4788
4789 /*
4790 * Now add the leftovers from whichever list didn't get finished
4791 * first. As before, we only want to add from the second list if
4792 * we're adding the lists.
4793 */
4794 for (; *g1; g1++, count++)
4795 if (round == 2)
4796 clstr[count] = *g1;
4797 if (list_op == CLUSTER_ADD)
4798 for (; *g2; g2++, count++)
4799 if (round == 2)
4800 clstr[count] = *g2;
4801
4802 if (round == 1) {
4803 /*
4804 * If the group ended up empty, we don't need to allocate any
4805 * space for it.
4806 */
4807 if (count == 0) {
4808 clstr = NULL;
4809 break;
4810 }
4811 clstr = xmalloc((count + 1) * sizeof(*clstr));
4812 clstr[count] = 0;
4813 }
4814 }
4815
4816 /*
4817 * Finally, put the new list in place.
4818 */
4819 xfree(*clstr1);
4820 xfree(*clstr2);
4821 *clstr1 = clstr;
4822}
4823
4824// Lookup a syntax cluster name and return its ID.
4825// If it is not found, 0 is returned.
4826static int syn_scl_name2id(char_u *name)
4827{
4828 // Avoid using stricmp() too much, it's slow on some systems
4829 char_u *name_u = vim_strsave_up(name);
4830 int i;
4831 for (i = curwin->w_s->b_syn_clusters.ga_len; --i >= 0; ) {
4832 if (SYN_CLSTR(curwin->w_s)[i].scl_name_u != NULL
4833 && STRCMP(name_u, SYN_CLSTR(curwin->w_s)[i].scl_name_u) == 0) {
4834 break;
4835 }
4836 }
4837 xfree(name_u);
4838 return i < 0 ? 0 : i + SYNID_CLUSTER;
4839}
4840
4841/*
4842 * Like syn_scl_name2id(), but take a pointer + length argument.
4843 */
4844static int syn_scl_namen2id(char_u *linep, int len)
4845{
4846 char_u *name = vim_strnsave(linep, len);
4847 int id = syn_scl_name2id(name);
4848 xfree(name);
4849
4850 return id;
4851}
4852
4853// Find syntax cluster name in the table and return its ID.
4854// The argument is a pointer to the name and the length of the name.
4855// If it doesn't exist yet, a new entry is created.
4856// Return 0 for failure.
4857static int syn_check_cluster(char_u *pp, int len)
4858{
4859 int id;
4860 char_u *name;
4861
4862 name = vim_strnsave(pp, len);
4863
4864 id = syn_scl_name2id(name);
4865 if (id == 0) /* doesn't exist yet */
4866 id = syn_add_cluster(name);
4867 else
4868 xfree(name);
4869 return id;
4870}
4871
4872// Add new syntax cluster and return its ID.
4873// "name" must be an allocated string, it will be consumed.
4874// Return 0 for failure.
4875static int syn_add_cluster(char_u *name)
4876{
4877 /*
4878 * First call for this growarray: init growing array.
4879 */
4880 if (curwin->w_s->b_syn_clusters.ga_data == NULL) {
4881 curwin->w_s->b_syn_clusters.ga_itemsize = sizeof(syn_cluster_T);
4882 ga_set_growsize(&curwin->w_s->b_syn_clusters, 10);
4883 }
4884
4885 int len = curwin->w_s->b_syn_clusters.ga_len;
4886 if (len >= MAX_CLUSTER_ID) {
4887 EMSG((char_u *)_("E848: Too many syntax clusters"));
4888 xfree(name);
4889 return 0;
4890 }
4891
4892 syn_cluster_T *scp = GA_APPEND_VIA_PTR(syn_cluster_T,
4893 &curwin->w_s->b_syn_clusters);
4894 memset(scp, 0, sizeof(*scp));
4895 scp->scl_name = name;
4896 scp->scl_name_u = vim_strsave_up(name);
4897 scp->scl_list = NULL;
4898
4899 if (STRICMP(name, "Spell") == 0)
4900 curwin->w_s->b_spell_cluster_id = len + SYNID_CLUSTER;
4901 if (STRICMP(name, "NoSpell") == 0)
4902 curwin->w_s->b_nospell_cluster_id = len + SYNID_CLUSTER;
4903
4904 return len + SYNID_CLUSTER;
4905}
4906
4907/*
4908 * Handle ":syntax cluster {cluster-name} [contains={groupname},..]
4909 * [add={groupname},..] [remove={groupname},..]".
4910 */
4911static void syn_cmd_cluster(exarg_T *eap, int syncing)
4912{
4913 char_u *arg = eap->arg;
4914 char_u *group_name_end;
4915 char_u *rest;
4916 bool got_clstr = false;
4917 int opt_len;
4918 int list_op;
4919
4920 eap->nextcmd = find_nextcmd(arg);
4921 if (eap->skip)
4922 return;
4923
4924 rest = get_group_name(arg, &group_name_end);
4925
4926 if (rest != NULL) {
4927 int scl_id = syn_check_cluster(arg, (int)(group_name_end - arg));
4928 if (scl_id == 0) {
4929 return;
4930 }
4931 scl_id -= SYNID_CLUSTER;
4932
4933 for (;; ) {
4934 if (STRNICMP(rest, "add", 3) == 0
4935 && (ascii_iswhite(rest[3]) || rest[3] == '=')) {
4936 opt_len = 3;
4937 list_op = CLUSTER_ADD;
4938 } else if (STRNICMP(rest, "remove", 6) == 0
4939 && (ascii_iswhite(rest[6]) || rest[6] == '=')) {
4940 opt_len = 6;
4941 list_op = CLUSTER_SUBTRACT;
4942 } else if (STRNICMP(rest, "contains", 8) == 0
4943 && (ascii_iswhite(rest[8]) || rest[8] == '=')) {
4944 opt_len = 8;
4945 list_op = CLUSTER_REPLACE;
4946 } else
4947 break;
4948
4949 int16_t *clstr_list = NULL;
4950 if (get_id_list(&rest, opt_len, &clstr_list, eap->skip) == FAIL) {
4951 EMSG2(_(e_invarg2), rest);
4952 break;
4953 }
4954 if (scl_id >= 0) {
4955 syn_combine_list(&SYN_CLSTR(curwin->w_s)[scl_id].scl_list,
4956 &clstr_list, list_op);
4957 } else {
4958 xfree(clstr_list);
4959 }
4960 got_clstr = true;
4961 }
4962
4963 if (got_clstr) {
4964 redraw_curbuf_later(SOME_VALID);
4965 syn_stack_free_all(curwin->w_s); /* Need to recompute all. */
4966 }
4967 }
4968
4969 if (!got_clstr)
4970 EMSG(_("E400: No cluster specified"));
4971 if (rest == NULL || !ends_excmd(*rest))
4972 EMSG2(_(e_invarg2), arg);
4973}
4974
4975/*
4976 * On first call for current buffer: Init growing array.
4977 */
4978static void init_syn_patterns(void)
4979{
4980 curwin->w_s->b_syn_patterns.ga_itemsize = sizeof(synpat_T);
4981 ga_set_growsize(&curwin->w_s->b_syn_patterns, 10);
4982}
4983
4984/*
4985 * Get one pattern for a ":syntax match" or ":syntax region" command.
4986 * Stores the pattern and program in a synpat_T.
4987 * Returns a pointer to the next argument, or NULL in case of an error.
4988 */
4989static char_u *get_syn_pattern(char_u *arg, synpat_T *ci)
4990{
4991 char_u *end;
4992 int *p;
4993 int idx;
4994 char_u *cpo_save;
4995
4996 // need at least three chars
4997 if (arg == NULL || arg[0] == NUL || arg[1] == NUL || arg[2] == NUL) {
4998 return NULL;
4999 }
5000
5001 end = skip_regexp(arg + 1, *arg, TRUE, NULL);
5002 if (*end != *arg) { /* end delimiter not found */
5003 EMSG2(_("E401: Pattern delimiter not found: %s"), arg);
5004 return NULL;
5005 }
5006 /* store the pattern and compiled regexp program */
5007 ci->sp_pattern = vim_strnsave(arg + 1, (int)(end - arg - 1));
5008
5009 /* Make 'cpoptions' empty, to avoid the 'l' flag */
5010 cpo_save = p_cpo;
5011 p_cpo = (char_u *)"";
5012 ci->sp_prog = vim_regcomp(ci->sp_pattern, RE_MAGIC);
5013 p_cpo = cpo_save;
5014
5015 if (ci->sp_prog == NULL)
5016 return NULL;
5017 ci->sp_ic = curwin->w_s->b_syn_ic;
5018 syn_clear_time(&ci->sp_time);
5019
5020 /*
5021 * Check for a match, highlight or region offset.
5022 */
5023 ++end;
5024 do {
5025 for (idx = SPO_COUNT; --idx >= 0; )
5026 if (STRNCMP(end, spo_name_tab[idx], 3) == 0)
5027 break;
5028 if (idx >= 0) {
5029 p = &(ci->sp_offsets[idx]);
5030 if (idx != SPO_LC_OFF)
5031 switch (end[3]) {
5032 case 's': break;
5033 case 'b': break;
5034 case 'e': idx += SPO_COUNT; break;
5035 default: idx = -1; break;
5036 }
5037 if (idx >= 0) {
5038 ci->sp_off_flags |= (1 << idx);
5039 if (idx == SPO_LC_OFF) { /* lc=99 */
5040 end += 3;
5041 *p = getdigits_int(&end, true, 0);
5042
5043 /* "lc=" offset automatically sets "ms=" offset */
5044 if (!(ci->sp_off_flags & (1 << SPO_MS_OFF))) {
5045 ci->sp_off_flags |= (1 << SPO_MS_OFF);
5046 ci->sp_offsets[SPO_MS_OFF] = *p;
5047 }
5048 } else { /* yy=x+99 */
5049 end += 4;
5050 if (*end == '+') {
5051 end++;
5052 *p = getdigits_int(&end, true, 0); // positive offset
5053 } else if (*end == '-') {
5054 end++;
5055 *p = -getdigits_int(&end, true, 0); // negative offset
5056 }
5057 }
5058 if (*end != ',')
5059 break;
5060 ++end;
5061 }
5062 }
5063 } while (idx >= 0);
5064
5065 if (!ends_excmd(*end) && !ascii_iswhite(*end)) {
5066 EMSG2(_("E402: Garbage after pattern: %s"), arg);
5067 return NULL;
5068 }
5069 return skipwhite(end);
5070}
5071
5072/*
5073 * Handle ":syntax sync .." command.
5074 */
5075static void syn_cmd_sync(exarg_T *eap, int syncing)
5076{
5077 char_u *arg_start = eap->arg;
5078 char_u *arg_end;
5079 char_u *key = NULL;
5080 char_u *next_arg;
5081 int illegal = FALSE;
5082 int finished = FALSE;
5083 long n;
5084 char_u *cpo_save;
5085
5086 if (ends_excmd(*arg_start)) {
5087 syn_cmd_list(eap, TRUE);
5088 return;
5089 }
5090
5091 while (!ends_excmd(*arg_start)) {
5092 arg_end = skiptowhite(arg_start);
5093 next_arg = skipwhite(arg_end);
5094 xfree(key);
5095 key = vim_strnsave_up(arg_start, (int)(arg_end - arg_start));
5096 if (STRCMP(key, "CCOMMENT") == 0) {
5097 if (!eap->skip)
5098 curwin->w_s->b_syn_sync_flags |= SF_CCOMMENT;
5099 if (!ends_excmd(*next_arg)) {
5100 arg_end = skiptowhite(next_arg);
5101 if (!eap->skip)
5102 curwin->w_s->b_syn_sync_id = syn_check_group(next_arg,
5103 (int)(arg_end - next_arg));
5104 next_arg = skipwhite(arg_end);
5105 } else if (!eap->skip)
5106 curwin->w_s->b_syn_sync_id = syn_name2id((char_u *)"Comment");
5107 } else if ( STRNCMP(key, "LINES", 5) == 0
5108 || STRNCMP(key, "MINLINES", 8) == 0
5109 || STRNCMP(key, "MAXLINES", 8) == 0
5110 || STRNCMP(key, "LINEBREAKS", 10) == 0) {
5111 if (key[4] == 'S')
5112 arg_end = key + 6;
5113 else if (key[0] == 'L')
5114 arg_end = key + 11;
5115 else
5116 arg_end = key + 9;
5117 if (arg_end[-1] != '=' || !ascii_isdigit(*arg_end)) {
5118 illegal = TRUE;
5119 break;
5120 }
5121 n = getdigits_long(&arg_end, false, 0);
5122 if (!eap->skip) {
5123 if (key[4] == 'B')
5124 curwin->w_s->b_syn_sync_linebreaks = n;
5125 else if (key[1] == 'A')
5126 curwin->w_s->b_syn_sync_maxlines = n;
5127 else
5128 curwin->w_s->b_syn_sync_minlines = n;
5129 }
5130 } else if (STRCMP(key, "FROMSTART") == 0) {
5131 if (!eap->skip) {
5132 curwin->w_s->b_syn_sync_minlines = MAXLNUM;
5133 curwin->w_s->b_syn_sync_maxlines = 0;
5134 }
5135 } else if (STRCMP(key, "LINECONT") == 0) {
5136 if (*next_arg == NUL) { // missing pattern
5137 illegal = true;
5138 break;
5139 }
5140 if (curwin->w_s->b_syn_linecont_pat != NULL) {
5141 EMSG(_("E403: syntax sync: line continuations pattern specified twice"));
5142 finished = TRUE;
5143 break;
5144 }
5145 arg_end = skip_regexp(next_arg + 1, *next_arg, TRUE, NULL);
5146 if (*arg_end != *next_arg) { /* end delimiter not found */
5147 illegal = TRUE;
5148 break;
5149 }
5150
5151 if (!eap->skip) {
5152 /* store the pattern and compiled regexp program */
5153 curwin->w_s->b_syn_linecont_pat =
5154 vim_strnsave(next_arg + 1, (int)(arg_end - next_arg - 1));
5155 curwin->w_s->b_syn_linecont_ic = curwin->w_s->b_syn_ic;
5156
5157 /* Make 'cpoptions' empty, to avoid the 'l' flag */
5158 cpo_save = p_cpo;
5159 p_cpo = (char_u *)"";
5160 curwin->w_s->b_syn_linecont_prog =
5161 vim_regcomp(curwin->w_s->b_syn_linecont_pat, RE_MAGIC);
5162 p_cpo = cpo_save;
5163 syn_clear_time(&curwin->w_s->b_syn_linecont_time);
5164
5165 if (curwin->w_s->b_syn_linecont_prog == NULL) {
5166 XFREE_CLEAR(curwin->w_s->b_syn_linecont_pat);
5167 finished = true;
5168 break;
5169 }
5170 }
5171 next_arg = skipwhite(arg_end + 1);
5172 } else {
5173 eap->arg = next_arg;
5174 if (STRCMP(key, "MATCH") == 0)
5175 syn_cmd_match(eap, TRUE);
5176 else if (STRCMP(key, "REGION") == 0)
5177 syn_cmd_region(eap, TRUE);
5178 else if (STRCMP(key, "CLEAR") == 0)
5179 syn_cmd_clear(eap, TRUE);
5180 else
5181 illegal = TRUE;
5182 finished = TRUE;
5183 break;
5184 }
5185 arg_start = next_arg;
5186 }
5187 xfree(key);
5188 if (illegal)
5189 EMSG2(_("E404: Illegal arguments: %s"), arg_start);
5190 else if (!finished) {
5191 eap->nextcmd = check_nextcmd(arg_start);
5192 redraw_curbuf_later(SOME_VALID);
5193 syn_stack_free_all(curwin->w_s); /* Need to recompute all syntax. */
5194 }
5195}
5196
5197/*
5198 * Convert a line of highlight group names into a list of group ID numbers.
5199 * "arg" should point to the "contains" or "nextgroup" keyword.
5200 * "arg" is advanced to after the last group name.
5201 * Careful: the argument is modified (NULs added).
5202 * returns FAIL for some error, OK for success.
5203 */
5204static int
5205get_id_list(
5206 char_u **const arg,
5207 const int keylen, // length of keyword
5208 int16_t **const list, // where to store the resulting list, if not
5209 // NULL, the list is silently skipped!
5210 const bool skip
5211)
5212{
5213 char_u *p = NULL;
5214 char_u *end;
5215 int total_count = 0;
5216 int16_t *retval = NULL;
5217 regmatch_T regmatch;
5218 int id;
5219 bool failed = false;
5220
5221 // We parse the list twice:
5222 // round == 1: count the number of items, allocate the array.
5223 // round == 2: fill the array with the items.
5224 // In round 1 new groups may be added, causing the number of items to
5225 // grow when a regexp is used. In that case round 1 is done once again.
5226 for (int round = 1; round <= 2; round++) {
5227 // skip "contains"
5228 p = skipwhite(*arg + keylen);
5229 if (*p != '=') {
5230 EMSG2(_("E405: Missing equal sign: %s"), *arg);
5231 break;
5232 }
5233 p = skipwhite(p + 1);
5234 if (ends_excmd(*p)) {
5235 EMSG2(_("E406: Empty argument: %s"), *arg);
5236 break;
5237 }
5238
5239 // parse the arguments after "contains"
5240 int count = 0;
5241 do {
5242 for (end = p; *end && !ascii_iswhite(*end) && *end != ','; end++) {
5243 }
5244 char_u *const name = xmalloc((int)(end - p + 3)); // leave room for "^$"
5245 STRLCPY(name + 1, p, end - p + 1);
5246 if ( STRCMP(name + 1, "ALLBUT") == 0
5247 || STRCMP(name + 1, "ALL") == 0
5248 || STRCMP(name + 1, "TOP") == 0
5249 || STRCMP(name + 1, "CONTAINED") == 0) {
5250 if (TOUPPER_ASC(**arg) != 'C') {
5251 EMSG2(_("E407: %s not allowed here"), name + 1);
5252 failed = true;
5253 xfree(name);
5254 break;
5255 }
5256 if (count != 0) {
5257 EMSG2(_("E408: %s must be first in contains list"),
5258 name + 1);
5259 failed = true;
5260 xfree(name);
5261 break;
5262 }
5263 if (name[1] == 'A')
5264 id = SYNID_ALLBUT;
5265 else if (name[1] == 'T')
5266 id = SYNID_TOP;
5267 else
5268 id = SYNID_CONTAINED;
5269 id += current_syn_inc_tag;
5270 } else if (name[1] == '@') {
5271 if (skip) {
5272 id = -1;
5273 } else {
5274 id = syn_check_cluster(name + 2, (int)(end - p - 1));
5275 }
5276 } else {
5277 /*
5278 * Handle full group name.
5279 */
5280 if (vim_strpbrk(name + 1, (char_u *)"\\.*^$~[") == NULL) {
5281 id = syn_check_group(name + 1, (int)(end - p));
5282 } else {
5283 // Handle match of regexp with group names.
5284 *name = '^';
5285 STRCAT(name, "$");
5286 regmatch.regprog = vim_regcomp(name, RE_MAGIC);
5287 if (regmatch.regprog == NULL) {
5288 failed = true;
5289 xfree(name);
5290 break;
5291 }
5292
5293 regmatch.rm_ic = TRUE;
5294 id = 0;
5295 for (int i = highlight_ga.ga_len; --i >= 0; ) {
5296 if (vim_regexec(&regmatch, HL_TABLE()[i].sg_name, (colnr_T)0)) {
5297 if (round == 2) {
5298 // Got more items than expected; can happen
5299 // when adding items that match:
5300 // "contains=a.*b,axb".
5301 // Go back to first round.
5302 if (count >= total_count) {
5303 xfree(retval);
5304 round = 1;
5305 } else {
5306 retval[count] = i + 1; // -V522
5307 }
5308 }
5309 count++;
5310 id = -1; // Remember that we found one.
5311 }
5312 }
5313 vim_regfree(regmatch.regprog);
5314 }
5315 }
5316 xfree(name);
5317 if (id == 0) {
5318 EMSG2(_("E409: Unknown group name: %s"), p);
5319 failed = true;
5320 break;
5321 }
5322 if (id > 0) {
5323 if (round == 2) {
5324 // Got more items than expected, go back to first round.
5325 if (count >= total_count) {
5326 xfree(retval);
5327 round = 1;
5328 } else {
5329 retval[count] = id;
5330 }
5331 }
5332 ++count;
5333 }
5334 p = skipwhite(end);
5335 if (*p != ',')
5336 break;
5337 p = skipwhite(p + 1); /* skip comma in between arguments */
5338 } while (!ends_excmd(*p));
5339 if (failed)
5340 break;
5341 if (round == 1) {
5342 retval = xmalloc((count + 1) * sizeof(*retval));
5343 retval[count] = 0; // zero means end of the list
5344 total_count = count;
5345 }
5346 }
5347
5348 *arg = p;
5349 if (failed || retval == NULL) {
5350 xfree(retval);
5351 return FAIL;
5352 }
5353
5354 if (*list == NULL)
5355 *list = retval;
5356 else
5357 xfree(retval); /* list already found, don't overwrite it */
5358
5359 return OK;
5360}
5361
5362/*
5363 * Make a copy of an ID list.
5364 */
5365static int16_t *copy_id_list(const int16_t *const list)
5366{
5367 if (list == NULL) {
5368 return NULL;
5369 }
5370
5371 int count;
5372 for (count = 0; list[count]; count++) {
5373 }
5374 const size_t len = (count + 1) * sizeof(int16_t);
5375 int16_t *const retval = xmalloc(len);
5376 memmove(retval, list, len);
5377
5378 return retval;
5379}
5380
5381/*
5382 * Check if syntax group "ssp" is in the ID list "list" of "cur_si".
5383 * "cur_si" can be NULL if not checking the "containedin" list.
5384 * Used to check if a syntax item is in the "contains" or "nextgroup" list of
5385 * the current item.
5386 * This function is called very often, keep it fast!!
5387 */
5388static int
5389in_id_list(
5390 stateitem_T *cur_si, // current item or NULL
5391 int16_t *list, // id list
5392 struct sp_syn *ssp, // group id and ":syn include" tag of group
5393 int contained // group id is contained
5394)
5395{
5396 int retval;
5397 int16_t *scl_list;
5398 int16_t item;
5399 int16_t id = ssp->id;
5400 static int depth = 0;
5401 int r;
5402
5403 /* If ssp has a "containedin" list and "cur_si" is in it, return TRUE. */
5404 if (cur_si != NULL && ssp->cont_in_list != NULL
5405 && !(cur_si->si_flags & HL_MATCH)) {
5406 /* Ignore transparent items without a contains argument. Double check
5407 * that we don't go back past the first one. */
5408 while ((cur_si->si_flags & HL_TRANS_CONT)
5409 && cur_si > (stateitem_T *)(current_state.ga_data))
5410 --cur_si;
5411 /* cur_si->si_idx is -1 for keywords, these never contain anything. */
5412 if (cur_si->si_idx >= 0 && in_id_list(NULL, ssp->cont_in_list,
5413 &(SYN_ITEMS(syn_block)[cur_si->si_idx].sp_syn),
5414 SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags & HL_CONTAINED))
5415 return TRUE;
5416 }
5417
5418 if (list == NULL)
5419 return FALSE;
5420
5421 /*
5422 * If list is ID_LIST_ALL, we are in a transparent item that isn't
5423 * inside anything. Only allow not-contained groups.
5424 */
5425 if (list == ID_LIST_ALL)
5426 return !contained;
5427
5428 /*
5429 * If the first item is "ALLBUT", return TRUE if "id" is NOT in the
5430 * contains list. We also require that "id" is at the same ":syn include"
5431 * level as the list.
5432 */
5433 item = *list;
5434 if (item >= SYNID_ALLBUT && item < SYNID_CLUSTER) {
5435 if (item < SYNID_TOP) {
5436 /* ALL or ALLBUT: accept all groups in the same file */
5437 if (item - SYNID_ALLBUT != ssp->inc_tag)
5438 return FALSE;
5439 } else if (item < SYNID_CONTAINED) {
5440 /* TOP: accept all not-contained groups in the same file */
5441 if (item - SYNID_TOP != ssp->inc_tag || contained)
5442 return FALSE;
5443 } else {
5444 /* CONTAINED: accept all contained groups in the same file */
5445 if (item - SYNID_CONTAINED != ssp->inc_tag || !contained)
5446 return FALSE;
5447 }
5448 item = *++list;
5449 retval = FALSE;
5450 } else
5451 retval = TRUE;
5452
5453 /*
5454 * Return "retval" if id is in the contains list.
5455 */
5456 while (item != 0) {
5457 if (item == id)
5458 return retval;
5459 if (item >= SYNID_CLUSTER) {
5460 scl_list = SYN_CLSTR(syn_block)[item - SYNID_CLUSTER].scl_list;
5461 /* restrict recursiveness to 30 to avoid an endless loop for a
5462 * cluster that includes itself (indirectly) */
5463 if (scl_list != NULL && depth < 30) {
5464 ++depth;
5465 r = in_id_list(NULL, scl_list, ssp, contained);
5466 --depth;
5467 if (r)
5468 return retval;
5469 }
5470 }
5471 item = *++list;
5472 }
5473 return !retval;
5474}
5475
5476struct subcommand {
5477 char *name; /* subcommand name */
5478 void (*func)(exarg_T *, int); /* function to call */
5479};
5480
5481static struct subcommand subcommands[] =
5482{
5483 { "case", syn_cmd_case },
5484 { "clear", syn_cmd_clear },
5485 { "cluster", syn_cmd_cluster },
5486 { "conceal", syn_cmd_conceal },
5487 { "enable", syn_cmd_enable },
5488 { "include", syn_cmd_include },
5489 { "iskeyword", syn_cmd_iskeyword },
5490 { "keyword", syn_cmd_keyword },
5491 { "list", syn_cmd_list },
5492 { "manual", syn_cmd_manual },
5493 { "match", syn_cmd_match },
5494 { "on", syn_cmd_on },
5495 { "off", syn_cmd_off },
5496 { "region", syn_cmd_region },
5497 { "reset", syn_cmd_reset },
5498 { "spell", syn_cmd_spell },
5499 { "sync", syn_cmd_sync },
5500 { "", syn_cmd_list },
5501 { NULL, NULL }
5502};
5503
5504/*
5505 * ":syntax".
5506 * This searches the subcommands[] table for the subcommand name, and calls a
5507 * syntax_subcommand() function to do the rest.
5508 */
5509void ex_syntax(exarg_T *eap)
5510{
5511 char_u *arg = eap->arg;
5512 char_u *subcmd_end;
5513 char_u *subcmd_name;
5514 int i;
5515
5516 syn_cmdlinep = eap->cmdlinep;
5517
5518 /* isolate subcommand name */
5519 for (subcmd_end = arg; ASCII_ISALPHA(*subcmd_end); ++subcmd_end)
5520 ;
5521 subcmd_name = vim_strnsave(arg, (int)(subcmd_end - arg));
5522 if (eap->skip) /* skip error messages for all subcommands */
5523 ++emsg_skip;
5524 for (i = 0;; ++i) {
5525 if (subcommands[i].name == NULL) {
5526 EMSG2(_("E410: Invalid :syntax subcommand: %s"), subcmd_name);
5527 break;
5528 }
5529 if (STRCMP(subcmd_name, (char_u *)subcommands[i].name) == 0) {
5530 eap->arg = skipwhite(subcmd_end);
5531 (subcommands[i].func)(eap, FALSE);
5532 break;
5533 }
5534 }
5535 xfree(subcmd_name);
5536 if (eap->skip)
5537 --emsg_skip;
5538}
5539
5540void ex_ownsyntax(exarg_T *eap)
5541{
5542 char_u *old_value;
5543 char_u *new_value;
5544
5545 if (curwin->w_s == &curwin->w_buffer->b_s) {
5546 curwin->w_s = xmalloc(sizeof(synblock_T));
5547 memset(curwin->w_s, 0, sizeof(synblock_T));
5548 hash_init(&curwin->w_s->b_keywtab);
5549 hash_init(&curwin->w_s->b_keywtab_ic);
5550 // TODO: Keep the spell checking as it was. NOLINT(readability/todo)
5551 curwin->w_p_spell = false; // No spell checking
5552 clear_string_option(&curwin->w_s->b_p_spc);
5553 clear_string_option(&curwin->w_s->b_p_spf);
5554 clear_string_option(&curwin->w_s->b_p_spl);
5555 clear_string_option(&curwin->w_s->b_syn_isk);
5556 }
5557
5558 // Save value of b:current_syntax.
5559 old_value = get_var_value("b:current_syntax");
5560 if (old_value != NULL) {
5561 old_value = vim_strsave(old_value);
5562 }
5563
5564 /* Apply the "syntax" autocommand event, this finds and loads the syntax
5565 * file. */
5566 apply_autocmds(EVENT_SYNTAX, eap->arg, curbuf->b_fname, TRUE, curbuf);
5567
5568 // Move value of b:current_syntax to w:current_syntax.
5569 new_value = get_var_value("b:current_syntax");
5570 if (new_value != NULL) {
5571 set_internal_string_var((char_u *)"w:current_syntax", new_value);
5572 }
5573
5574 // Restore value of b:current_syntax.
5575 if (old_value == NULL) {
5576 do_unlet(S_LEN("b:current_syntax"), true);
5577 } else {
5578 set_internal_string_var((char_u *)"b:current_syntax", old_value);
5579 xfree(old_value);
5580 }
5581}
5582
5583bool syntax_present(win_T *win)
5584{
5585 return win->w_s->b_syn_patterns.ga_len != 0
5586 || win->w_s->b_syn_clusters.ga_len != 0
5587 || win->w_s->b_keywtab.ht_used > 0
5588 || win->w_s->b_keywtab_ic.ht_used > 0;
5589}
5590
5591
5592static enum {
5593 EXP_SUBCMD, // expand ":syn" sub-commands
5594 EXP_CASE, // expand ":syn case" arguments
5595 EXP_SPELL, // expand ":syn spell" arguments
5596 EXP_SYNC // expand ":syn sync" arguments
5597} expand_what;
5598
5599/*
5600 * Reset include_link, include_default, include_none to 0.
5601 * Called when we are done expanding.
5602 */
5603void reset_expand_highlight(void)
5604{
5605 include_link = include_default = include_none = 0;
5606}
5607
5608/*
5609 * Handle command line completion for :match and :echohl command: Add "None"
5610 * as highlight group.
5611 */
5612void set_context_in_echohl_cmd(expand_T *xp, const char *arg)
5613{
5614 xp->xp_context = EXPAND_HIGHLIGHT;
5615 xp->xp_pattern = (char_u *)arg;
5616 include_none = 1;
5617}
5618
5619/*
5620 * Handle command line completion for :syntax command.
5621 */
5622void set_context_in_syntax_cmd(expand_T *xp, const char *arg)
5623{
5624 // Default: expand subcommands.
5625 xp->xp_context = EXPAND_SYNTAX;
5626 expand_what = EXP_SUBCMD;
5627 xp->xp_pattern = (char_u *)arg;
5628 include_link = 0;
5629 include_default = 0;
5630
5631 /* (part of) subcommand already typed */
5632 if (*arg != NUL) {
5633 const char *p = (const char *)skiptowhite((const char_u *)arg);
5634 if (*p != NUL) { // Past first word.
5635 xp->xp_pattern = skipwhite((const char_u *)p);
5636 if (*skiptowhite(xp->xp_pattern) != NUL) {
5637 xp->xp_context = EXPAND_NOTHING;
5638 } else if (STRNICMP(arg, "case", p - arg) == 0) {
5639 expand_what = EXP_CASE;
5640 } else if (STRNICMP(arg, "spell", p - arg) == 0) {
5641 expand_what = EXP_SPELL;
5642 } else if (STRNICMP(arg, "sync", p - arg) == 0) {
5643 expand_what = EXP_SYNC;
5644 } else if (STRNICMP(arg, "keyword", p - arg) == 0
5645 || STRNICMP(arg, "region", p - arg) == 0
5646 || STRNICMP(arg, "match", p - arg) == 0
5647 || STRNICMP(arg, "list", p - arg) == 0) {
5648 xp->xp_context = EXPAND_HIGHLIGHT;
5649 } else {
5650 xp->xp_context = EXPAND_NOTHING;
5651 }
5652 }
5653 }
5654}
5655
5656/*
5657 * Function given to ExpandGeneric() to obtain the list syntax names for
5658 * expansion.
5659 */
5660char_u *get_syntax_name(expand_T *xp, int idx)
5661{
5662 switch (expand_what) {
5663 case EXP_SUBCMD:
5664 return (char_u *)subcommands[idx].name;
5665 case EXP_CASE: {
5666 static char *case_args[] = { "match", "ignore", NULL };
5667 return (char_u *)case_args[idx];
5668 }
5669 case EXP_SPELL: {
5670 static char *spell_args[] =
5671 { "toplevel", "notoplevel", "default", NULL };
5672 return (char_u *)spell_args[idx];
5673 }
5674 case EXP_SYNC: {
5675 static char *sync_args[] =
5676 { "ccomment", "clear", "fromstart",
5677 "linebreaks=", "linecont", "lines=", "match",
5678 "maxlines=", "minlines=", "region", NULL };
5679 return (char_u *)sync_args[idx];
5680 }
5681 }
5682 return NULL;
5683}
5684
5685
5686// Function called for expression evaluation: get syntax ID at file position.
5687int syn_get_id(
5688 win_T *wp,
5689 long lnum,
5690 colnr_T col,
5691 int trans, // remove transparency
5692 bool *spellp, // return: can do spell checking
5693 int keep_state // keep state of char at "col"
5694)
5695{
5696 // When the position is not after the current position and in the same
5697 // line of the same buffer, need to restart parsing.
5698 if (wp->w_buffer != syn_buf || lnum != current_lnum || col < current_col) {
5699 syntax_start(wp, lnum);
5700 } else if (col > current_col) {
5701 // next_match may not be correct when moving around, e.g. with the
5702 // "skip" expression in searchpair()
5703 next_match_idx = -1;
5704 }
5705
5706 (void)get_syntax_attr(col, spellp, keep_state);
5707
5708 return trans ? current_trans_id : current_id;
5709}
5710
5711/*
5712 * Get extra information about the syntax item. Must be called right after
5713 * get_syntax_attr().
5714 * Stores the current item sequence nr in "*seqnrp".
5715 * Returns the current flags.
5716 */
5717int get_syntax_info(int *seqnrp)
5718{
5719 *seqnrp = current_seqnr;
5720 return current_flags;
5721}
5722
5723
5724/// Get the sequence number of the concealed file position.
5725///
5726/// @return seqnr if the file position is concealed, 0 otherwise.
5727int syn_get_concealed_id(win_T *wp, linenr_T lnum, colnr_T col)
5728{
5729 int seqnr;
5730 int syntax_flags;
5731
5732 (void)syn_get_id(wp, lnum, col, false, NULL, false);
5733 syntax_flags = get_syntax_info(&seqnr);
5734
5735 if (syntax_flags & HL_CONCEAL) {
5736 return seqnr;
5737 }
5738 return 0;
5739}
5740
5741/*
5742 * Return conceal substitution character
5743 */
5744int syn_get_sub_char(void)
5745{
5746 return current_sub_char;
5747}
5748
5749/*
5750 * Return the syntax ID at position "i" in the current stack.
5751 * The caller must have called syn_get_id() before to fill the stack.
5752 * Returns -1 when "i" is out of range.
5753 */
5754int syn_get_stack_item(int i)
5755{
5756 if (i >= current_state.ga_len) {
5757 /* Need to invalidate the state, because we didn't properly finish it
5758 * for the last character, "keep_state" was TRUE. */
5759 invalidate_current_state();
5760 current_col = MAXCOL;
5761 return -1;
5762 }
5763 return CUR_STATE(i).si_id;
5764}
5765
5766/*
5767 * Function called to get folding level for line "lnum" in window "wp".
5768 */
5769int syn_get_foldlevel(win_T *wp, long lnum)
5770{
5771 int level = 0;
5772
5773 // Return quickly when there are no fold items at all.
5774 if (wp->w_s->b_syn_folditems != 0
5775 && !wp->w_s->b_syn_error
5776 && !wp->w_s->b_syn_slow) {
5777 syntax_start(wp, lnum);
5778
5779 for (int i = 0; i < current_state.ga_len; ++i) {
5780 if (CUR_STATE(i).si_flags & HL_FOLD) {
5781 ++level;
5782 }
5783 }
5784 }
5785 if (level > wp->w_p_fdn) {
5786 level = wp->w_p_fdn;
5787 if (level < 0)
5788 level = 0;
5789 }
5790 return level;
5791}
5792
5793/*
5794 * ":syntime".
5795 */
5796void ex_syntime(exarg_T *eap)
5797{
5798 if (STRCMP(eap->arg, "on") == 0)
5799 syn_time_on = TRUE;
5800 else if (STRCMP(eap->arg, "off") == 0)
5801 syn_time_on = FALSE;
5802 else if (STRCMP(eap->arg, "clear") == 0)
5803 syntime_clear();
5804 else if (STRCMP(eap->arg, "report") == 0)
5805 syntime_report();
5806 else
5807 EMSG2(_(e_invarg2), eap->arg);
5808}
5809
5810static void syn_clear_time(syn_time_T *st)
5811{
5812 st->total = profile_zero();
5813 st->slowest = profile_zero();
5814 st->count = 0;
5815 st->match = 0;
5816}
5817
5818/*
5819 * Clear the syntax timing for the current buffer.
5820 */
5821static void syntime_clear(void)
5822{
5823 synpat_T *spp;
5824
5825 if (!syntax_present(curwin)) {
5826 MSG(_(msg_no_items));
5827 return;
5828 }
5829 for (int idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx) {
5830 spp = &(SYN_ITEMS(curwin->w_s)[idx]);
5831 syn_clear_time(&spp->sp_time);
5832 }
5833}
5834
5835/*
5836 * Function given to ExpandGeneric() to obtain the possible arguments of the
5837 * ":syntime {on,off,clear,report}" command.
5838 */
5839char_u *get_syntime_arg(expand_T *xp, int idx)
5840{
5841 switch (idx) {
5842 case 0: return (char_u *)"on";
5843 case 1: return (char_u *)"off";
5844 case 2: return (char_u *)"clear";
5845 case 3: return (char_u *)"report";
5846 }
5847 return NULL;
5848}
5849
5850static int syn_compare_syntime(const void *v1, const void *v2)
5851{
5852 const time_entry_T *s1 = v1;
5853 const time_entry_T *s2 = v2;
5854
5855 return profile_cmp(s1->total, s2->total);
5856}
5857
5858/*
5859 * Clear the syntax timing for the current buffer.
5860 */
5861static void syntime_report(void)
5862{
5863 if (!syntax_present(curwin)) {
5864 MSG(_(msg_no_items));
5865 return;
5866 }
5867
5868 garray_T ga;
5869 ga_init(&ga, sizeof(time_entry_T), 50);
5870
5871 proftime_T total_total = profile_zero();
5872 int total_count = 0;
5873 time_entry_T *p;
5874 for (int idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx) {
5875 synpat_T *spp = &(SYN_ITEMS(curwin->w_s)[idx]);
5876 if (spp->sp_time.count > 0) {
5877 p = GA_APPEND_VIA_PTR(time_entry_T, &ga);
5878 p->total = spp->sp_time.total;
5879 total_total = profile_add(total_total, spp->sp_time.total);
5880 p->count = spp->sp_time.count;
5881 p->match = spp->sp_time.match;
5882 total_count += spp->sp_time.count;
5883 p->slowest = spp->sp_time.slowest;
5884 proftime_T tm = profile_divide(spp->sp_time.total, spp->sp_time.count);
5885 p->average = tm;
5886 p->id = spp->sp_syn.id;
5887 p->pattern = spp->sp_pattern;
5888 }
5889 }
5890
5891 // Sort on total time. Skip if there are no items to avoid passing NULL
5892 // pointer to qsort().
5893 if (ga.ga_len > 1) {
5894 qsort(ga.ga_data, (size_t)ga.ga_len, sizeof(time_entry_T),
5895 syn_compare_syntime);
5896 }
5897
5898 MSG_PUTS_TITLE(_(
5899 " TOTAL COUNT MATCH SLOWEST AVERAGE NAME PATTERN"));
5900 MSG_PUTS("\n");
5901 for (int idx = 0; idx < ga.ga_len && !got_int; ++idx) {
5902 p = ((time_entry_T *)ga.ga_data) + idx;
5903
5904 MSG_PUTS(profile_msg(p->total));
5905 MSG_PUTS(" "); /* make sure there is always a separating space */
5906 msg_advance(13);
5907 msg_outnum(p->count);
5908 MSG_PUTS(" ");
5909 msg_advance(20);
5910 msg_outnum(p->match);
5911 MSG_PUTS(" ");
5912 msg_advance(26);
5913 MSG_PUTS(profile_msg(p->slowest));
5914 MSG_PUTS(" ");
5915 msg_advance(38);
5916 MSG_PUTS(profile_msg(p->average));
5917 MSG_PUTS(" ");
5918 msg_advance(50);
5919 msg_outtrans(HL_TABLE()[p->id - 1].sg_name);
5920 MSG_PUTS(" ");
5921
5922 msg_advance(69);
5923 int len;
5924 if (Columns < 80)
5925 len = 20; /* will wrap anyway */
5926 else
5927 len = Columns - 70;
5928 if (len > (int)STRLEN(p->pattern))
5929 len = (int)STRLEN(p->pattern);
5930 msg_outtrans_len(p->pattern, len);
5931 MSG_PUTS("\n");
5932 }
5933 ga_clear(&ga);
5934 if (!got_int) {
5935 MSG_PUTS("\n");
5936 MSG_PUTS(profile_msg(total_total));
5937 msg_advance(13);
5938 msg_outnum(total_count);
5939 MSG_PUTS("\n");
5940 }
5941}
5942
5943/**************************************
5944* Highlighting stuff *
5945**************************************/
5946
5947// The default highlight groups. These are compiled-in for fast startup and
5948// they still work when the runtime files can't be found.
5949//
5950// When making changes here, also change runtime/colors/default.vim!
5951
5952static const char *highlight_init_both[] = {
5953 "Conceal "
5954 "ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey",
5955 "Cursor guibg=fg guifg=bg",
5956 "lCursor guibg=fg guifg=bg",
5957 "DiffText cterm=bold ctermbg=Red gui=bold guibg=Red",
5958 "ErrorMsg ctermbg=DarkRed ctermfg=White guibg=Red guifg=White",
5959 "IncSearch cterm=reverse gui=reverse",
5960 "ModeMsg cterm=bold gui=bold",
5961 "NonText ctermfg=Blue gui=bold guifg=Blue",
5962 "PmenuSbar ctermbg=Grey guibg=Grey",
5963 "StatusLine cterm=reverse,bold gui=reverse,bold",
5964 "StatusLineNC cterm=reverse gui=reverse",
5965 "TabLineFill cterm=reverse gui=reverse",
5966 "TabLineSel cterm=bold gui=bold",
5967 "TermCursor cterm=reverse gui=reverse",
5968 "VertSplit cterm=reverse gui=reverse",
5969 "WildMenu ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black",
5970 "default link EndOfBuffer NonText",
5971 "default link QuickFixLine Search",
5972 "default link Substitute Search",
5973 "default link Whitespace NonText",
5974 "default link MsgSeparator StatusLine",
5975 "default link NormalFloat Pmenu",
5976 "RedrawDebugNormal cterm=reverse gui=reverse",
5977 "RedrawDebugClear ctermbg=Yellow guibg=Yellow",
5978 "RedrawDebugComposed ctermbg=Green guibg=Green",
5979 "RedrawDebugRecompose ctermbg=Red guibg=Red",
5980 NULL
5981};
5982
5983// Default colors only used with a light background.
5984static const char *highlight_init_light[] = {
5985 "ColorColumn ctermbg=LightRed guibg=LightRed",
5986 "CursorColumn ctermbg=LightGrey guibg=Grey90",
5987 "CursorLine cterm=underline guibg=Grey90",
5988 "CursorLineNr ctermfg=Brown gui=bold guifg=Brown",
5989 "DiffAdd ctermbg=LightBlue guibg=LightBlue",
5990 "DiffChange ctermbg=LightMagenta guibg=LightMagenta",
5991 "DiffDelete ctermfg=Blue ctermbg=LightCyan gui=bold guifg=Blue guibg=LightCyan",
5992 "Directory ctermfg=DarkBlue guifg=Blue",
5993 "FoldColumn ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue",
5994 "Folded ctermbg=Grey ctermfg=DarkBlue guibg=LightGrey guifg=DarkBlue",
5995 "LineNr ctermfg=Brown guifg=Brown",
5996 "MatchParen ctermbg=Cyan guibg=Cyan",
5997 "MoreMsg ctermfg=DarkGreen gui=bold guifg=SeaGreen",
5998 "Pmenu ctermbg=LightMagenta ctermfg=Black guibg=LightMagenta",
5999 "PmenuSel ctermbg=LightGrey ctermfg=Black guibg=Grey",
6000 "PmenuThumb ctermbg=Black guibg=Black",
6001 "Question ctermfg=DarkGreen gui=bold guifg=SeaGreen",
6002 "Search ctermbg=Yellow ctermfg=NONE guibg=Yellow guifg=NONE",
6003 "SignColumn ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue",
6004 "SpecialKey ctermfg=DarkBlue guifg=Blue",
6005 "SpellBad ctermbg=LightRed guisp=Red gui=undercurl",
6006 "SpellCap ctermbg=LightBlue guisp=Blue gui=undercurl",
6007 "SpellLocal ctermbg=Cyan guisp=DarkCyan gui=undercurl",
6008 "SpellRare ctermbg=LightMagenta guisp=Magenta gui=undercurl",
6009 "TabLine cterm=underline ctermfg=black ctermbg=LightGrey gui=underline guibg=LightGrey",
6010 "Title ctermfg=DarkMagenta gui=bold guifg=Magenta",
6011 "Visual guibg=LightGrey",
6012 "WarningMsg ctermfg=DarkRed guifg=Red",
6013 "Normal gui=NONE",
6014 NULL
6015};
6016
6017// Default colors only used with a dark background.
6018static const char *highlight_init_dark[] = {
6019 "ColorColumn ctermbg=DarkRed guibg=DarkRed",
6020 "CursorColumn ctermbg=DarkGrey guibg=Grey40",
6021 "CursorLine cterm=underline guibg=Grey40",
6022 "CursorLineNr ctermfg=Yellow gui=bold guifg=Yellow",
6023 "DiffAdd ctermbg=DarkBlue guibg=DarkBlue",
6024 "DiffChange ctermbg=DarkMagenta guibg=DarkMagenta",
6025 "DiffDelete ctermfg=Blue ctermbg=DarkCyan gui=bold guifg=Blue guibg=DarkCyan",
6026 "Directory ctermfg=LightCyan guifg=Cyan",
6027 "FoldColumn ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan",
6028 "Folded ctermbg=DarkGrey ctermfg=Cyan guibg=DarkGrey guifg=Cyan",
6029 "LineNr ctermfg=Yellow guifg=Yellow",
6030 "MatchParen ctermbg=DarkCyan guibg=DarkCyan",
6031 "MoreMsg ctermfg=LightGreen gui=bold guifg=SeaGreen",
6032 "Pmenu ctermbg=Magenta ctermfg=Black guibg=Magenta",
6033 "PmenuSel ctermbg=Black ctermfg=DarkGrey guibg=DarkGrey",
6034 "PmenuThumb ctermbg=White guibg=White",
6035 "Question ctermfg=LightGreen gui=bold guifg=Green",
6036 "Search ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black",
6037 "SignColumn ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan",
6038 "SpecialKey ctermfg=LightBlue guifg=Cyan",
6039 "SpellBad ctermbg=Red guisp=Red gui=undercurl",
6040 "SpellCap ctermbg=Blue guisp=Blue gui=undercurl",
6041 "SpellLocal ctermbg=Cyan guisp=Cyan gui=undercurl",
6042 "SpellRare ctermbg=Magenta guisp=Magenta gui=undercurl",
6043 "TabLine cterm=underline ctermfg=white ctermbg=DarkGrey gui=underline guibg=DarkGrey",
6044 "Title ctermfg=LightMagenta gui=bold guifg=Magenta",
6045 "Visual guibg=DarkGrey",
6046 "WarningMsg ctermfg=LightRed guifg=Red",
6047 "Normal gui=NONE",
6048 NULL
6049};
6050
6051const char *const highlight_init_cmdline[] = {
6052 // XXX When modifying a list modify it in both valid and invalid halfs.
6053 // TODO(ZyX-I): merge valid and invalid groups via a macros.
6054
6055 // NvimInternalError should appear only when highlighter has a bug.
6056 "NvimInternalError ctermfg=Red ctermbg=Red guifg=Red guibg=Red",
6057
6058 // Highlight groups (links) used by parser:
6059
6060 "default link NvimAssignment Operator",
6061 "default link NvimPlainAssignment NvimAssignment",
6062 "default link NvimAugmentedAssignment NvimAssignment",
6063 "default link NvimAssignmentWithAddition NvimAugmentedAssignment",
6064 "default link NvimAssignmentWithSubtraction NvimAugmentedAssignment",
6065 "default link NvimAssignmentWithConcatenation NvimAugmentedAssignment",
6066
6067 "default link NvimOperator Operator",
6068
6069 "default link NvimUnaryOperator NvimOperator",
6070 "default link NvimUnaryPlus NvimUnaryOperator",
6071 "default link NvimUnaryMinus NvimUnaryOperator",
6072 "default link NvimNot NvimUnaryOperator",
6073
6074 "default link NvimBinaryOperator NvimOperator",
6075 "default link NvimComparison NvimBinaryOperator",
6076 "default link NvimComparisonModifier NvimComparison",
6077 "default link NvimBinaryPlus NvimBinaryOperator",
6078 "default link NvimBinaryMinus NvimBinaryOperator",
6079 "default link NvimConcat NvimBinaryOperator",
6080 "default link NvimConcatOrSubscript NvimConcat",
6081 "default link NvimOr NvimBinaryOperator",
6082 "default link NvimAnd NvimBinaryOperator",
6083 "default link NvimMultiplication NvimBinaryOperator",
6084 "default link NvimDivision NvimBinaryOperator",
6085 "default link NvimMod NvimBinaryOperator",
6086
6087 "default link NvimTernary NvimOperator",
6088 "default link NvimTernaryColon NvimTernary",
6089
6090 "default link NvimParenthesis Delimiter",
6091 "default link NvimLambda NvimParenthesis",
6092 "default link NvimNestingParenthesis NvimParenthesis",
6093 "default link NvimCallingParenthesis NvimParenthesis",
6094
6095 "default link NvimSubscript NvimParenthesis",
6096 "default link NvimSubscriptBracket NvimSubscript",
6097 "default link NvimSubscriptColon NvimSubscript",
6098 "default link NvimCurly NvimSubscript",
6099
6100 "default link NvimContainer NvimParenthesis",
6101 "default link NvimDict NvimContainer",
6102 "default link NvimList NvimContainer",
6103
6104 "default link NvimIdentifier Identifier",
6105 "default link NvimIdentifierScope NvimIdentifier",
6106 "default link NvimIdentifierScopeDelimiter NvimIdentifier",
6107 "default link NvimIdentifierName NvimIdentifier",
6108 "default link NvimIdentifierKey NvimIdentifier",
6109
6110 "default link NvimColon Delimiter",
6111 "default link NvimComma Delimiter",
6112 "default link NvimArrow Delimiter",
6113
6114 "default link NvimRegister SpecialChar",
6115 "default link NvimNumber Number",
6116 "default link NvimFloat NvimNumber",
6117 "default link NvimNumberPrefix Type",
6118
6119 "default link NvimOptionSigil Type",
6120 "default link NvimOptionName NvimIdentifier",
6121 "default link NvimOptionScope NvimIdentifierScope",
6122 "default link NvimOptionScopeDelimiter NvimIdentifierScopeDelimiter",
6123
6124 "default link NvimEnvironmentSigil NvimOptionSigil",
6125 "default link NvimEnvironmentName NvimIdentifier",
6126
6127 "default link NvimString String",
6128 "default link NvimStringBody NvimString",
6129 "default link NvimStringQuote NvimString",
6130 "default link NvimStringSpecial SpecialChar",
6131
6132 "default link NvimSingleQuote NvimStringQuote",
6133 "default link NvimSingleQuotedBody NvimStringBody",
6134 "default link NvimSingleQuotedQuote NvimStringSpecial",
6135
6136 "default link NvimDoubleQuote NvimStringQuote",
6137 "default link NvimDoubleQuotedBody NvimStringBody",
6138 "default link NvimDoubleQuotedEscape NvimStringSpecial",
6139
6140 "default link NvimFigureBrace NvimInternalError",
6141 "default link NvimSingleQuotedUnknownEscape NvimInternalError",
6142
6143 "default link NvimSpacing Normal",
6144
6145 // NvimInvalid groups:
6146
6147 "default link NvimInvalidSingleQuotedUnknownEscape NvimInternalError",
6148
6149 "default link NvimInvalid Error",
6150
6151 "default link NvimInvalidAssignment NvimInvalid",
6152 "default link NvimInvalidPlainAssignment NvimInvalidAssignment",
6153 "default link NvimInvalidAugmentedAssignment NvimInvalidAssignment",
6154 "default link NvimInvalidAssignmentWithAddition "
6155 "NvimInvalidAugmentedAssignment",
6156 "default link NvimInvalidAssignmentWithSubtraction "
6157 "NvimInvalidAugmentedAssignment",
6158 "default link NvimInvalidAssignmentWithConcatenation "
6159 "NvimInvalidAugmentedAssignment",
6160
6161 "default link NvimInvalidOperator NvimInvalid",
6162
6163 "default link NvimInvalidUnaryOperator NvimInvalidOperator",
6164 "default link NvimInvalidUnaryPlus NvimInvalidUnaryOperator",
6165 "default link NvimInvalidUnaryMinus NvimInvalidUnaryOperator",
6166 "default link NvimInvalidNot NvimInvalidUnaryOperator",
6167
6168 "default link NvimInvalidBinaryOperator NvimInvalidOperator",
6169 "default link NvimInvalidComparison NvimInvalidBinaryOperator",
6170 "default link NvimInvalidComparisonModifier NvimInvalidComparison",
6171 "default link NvimInvalidBinaryPlus NvimInvalidBinaryOperator",
6172 "default link NvimInvalidBinaryMinus NvimInvalidBinaryOperator",
6173 "default link NvimInvalidConcat NvimInvalidBinaryOperator",
6174 "default link NvimInvalidConcatOrSubscript NvimInvalidConcat",
6175 "default link NvimInvalidOr NvimInvalidBinaryOperator",
6176 "default link NvimInvalidAnd NvimInvalidBinaryOperator",
6177 "default link NvimInvalidMultiplication NvimInvalidBinaryOperator",
6178 "default link NvimInvalidDivision NvimInvalidBinaryOperator",
6179 "default link NvimInvalidMod NvimInvalidBinaryOperator",
6180
6181 "default link NvimInvalidTernary NvimInvalidOperator",
6182 "default link NvimInvalidTernaryColon NvimInvalidTernary",
6183
6184 "default link NvimInvalidDelimiter NvimInvalid",
6185
6186 "default link NvimInvalidParenthesis NvimInvalidDelimiter",
6187 "default link NvimInvalidLambda NvimInvalidParenthesis",
6188 "default link NvimInvalidNestingParenthesis NvimInvalidParenthesis",
6189 "default link NvimInvalidCallingParenthesis NvimInvalidParenthesis",
6190
6191 "default link NvimInvalidSubscript NvimInvalidParenthesis",
6192 "default link NvimInvalidSubscriptBracket NvimInvalidSubscript",
6193 "default link NvimInvalidSubscriptColon NvimInvalidSubscript",
6194 "default link NvimInvalidCurly NvimInvalidSubscript",
6195
6196 "default link NvimInvalidContainer NvimInvalidParenthesis",
6197 "default link NvimInvalidDict NvimInvalidContainer",
6198 "default link NvimInvalidList NvimInvalidContainer",
6199
6200 "default link NvimInvalidValue NvimInvalid",
6201
6202 "default link NvimInvalidIdentifier NvimInvalidValue",
6203 "default link NvimInvalidIdentifierScope NvimInvalidIdentifier",
6204 "default link NvimInvalidIdentifierScopeDelimiter NvimInvalidIdentifier",
6205 "default link NvimInvalidIdentifierName NvimInvalidIdentifier",
6206 "default link NvimInvalidIdentifierKey NvimInvalidIdentifier",
6207
6208 "default link NvimInvalidColon NvimInvalidDelimiter",
6209 "default link NvimInvalidComma NvimInvalidDelimiter",
6210 "default link NvimInvalidArrow NvimInvalidDelimiter",
6211
6212 "default link NvimInvalidRegister NvimInvalidValue",
6213 "default link NvimInvalidNumber NvimInvalidValue",
6214 "default link NvimInvalidFloat NvimInvalidNumber",
6215 "default link NvimInvalidNumberPrefix NvimInvalidNumber",
6216
6217 "default link NvimInvalidOptionSigil NvimInvalidIdentifier",
6218 "default link NvimInvalidOptionName NvimInvalidIdentifier",
6219 "default link NvimInvalidOptionScope NvimInvalidIdentifierScope",
6220 "default link NvimInvalidOptionScopeDelimiter "
6221 "NvimInvalidIdentifierScopeDelimiter",
6222
6223 "default link NvimInvalidEnvironmentSigil NvimInvalidOptionSigil",
6224 "default link NvimInvalidEnvironmentName NvimInvalidIdentifier",
6225
6226 // Invalid string bodies and specials are still highlighted as valid ones to
6227 // minimize the red area.
6228 "default link NvimInvalidString NvimInvalidValue",
6229 "default link NvimInvalidStringBody NvimStringBody",
6230 "default link NvimInvalidStringQuote NvimInvalidString",
6231 "default link NvimInvalidStringSpecial NvimStringSpecial",
6232
6233 "default link NvimInvalidSingleQuote NvimInvalidStringQuote",
6234 "default link NvimInvalidSingleQuotedBody NvimInvalidStringBody",
6235 "default link NvimInvalidSingleQuotedQuote NvimInvalidStringSpecial",
6236
6237 "default link NvimInvalidDoubleQuote NvimInvalidStringQuote",
6238 "default link NvimInvalidDoubleQuotedBody NvimInvalidStringBody",
6239 "default link NvimInvalidDoubleQuotedEscape NvimInvalidStringSpecial",
6240 "default link NvimInvalidDoubleQuotedUnknownEscape NvimInvalidValue",
6241
6242 "default link NvimInvalidFigureBrace NvimInvalidDelimiter",
6243
6244 "default link NvimInvalidSpacing ErrorMsg",
6245
6246 // Not actually invalid, but we highlight user that he is doing something
6247 // wrong.
6248 "default link NvimDoubleQuotedUnknownEscape NvimInvalidValue",
6249 NULL,
6250};
6251
6252/// Create default links for Nvim* highlight groups used for cmdline coloring
6253void syn_init_cmdline_highlight(bool reset, bool init)
6254{
6255 for (size_t i = 0 ; highlight_init_cmdline[i] != NULL ; i++) {
6256 do_highlight(highlight_init_cmdline[i], reset, init);
6257 }
6258}
6259
6260/// Load colors from a file if "g:colors_name" is set, otherwise load builtin
6261/// colors
6262///
6263/// @param both include groups where 'bg' doesn't matter
6264/// @param reset clear groups first
6265void init_highlight(bool both, bool reset)
6266{
6267 static int had_both = false;
6268
6269 // Try finding the color scheme file. Used when a color file was loaded
6270 // and 'background' or 't_Co' is changed.
6271 char_u *p = get_var_value("g:colors_name");
6272 if (p != NULL) {
6273 // Value of g:colors_name could be freed in load_colors() and make
6274 // p invalid, so copy it.
6275 char_u *copy_p = vim_strsave(p);
6276 bool okay = load_colors(copy_p);
6277 xfree(copy_p);
6278 if (okay) {
6279 return;
6280 }
6281 }
6282
6283 /*
6284 * Didn't use a color file, use the compiled-in colors.
6285 */
6286 if (both) {
6287 had_both = true;
6288 const char *const *const pp = highlight_init_both;
6289 for (size_t i = 0; pp[i] != NULL; i++) {
6290 do_highlight(pp[i], reset, true);
6291 }
6292 } else if (!had_both) {
6293 // Don't do anything before the call with both == TRUE from main().
6294 // Not everything has been setup then, and that call will overrule
6295 // everything anyway.
6296 return;
6297 }
6298
6299 const char *const *const pp = ((*p_bg == 'l')
6300 ? highlight_init_light
6301 : highlight_init_dark);
6302 for (size_t i = 0; pp[i] != NULL; i++) {
6303 do_highlight(pp[i], reset, true);
6304 }
6305
6306 /* Reverse looks ugly, but grey may not work for 8 colors. Thus let it
6307 * depend on the number of colors available.
6308 * With 8 colors brown is equal to yellow, need to use black for Search fg
6309 * to avoid Statement highlighted text disappears.
6310 * Clear the attributes, needed when changing the t_Co value. */
6311 if (t_colors > 8) {
6312 do_highlight(
6313 (*p_bg == 'l'
6314 ? "Visual cterm=NONE ctermbg=LightGrey"
6315 : "Visual cterm=NONE ctermbg=DarkGrey"), false, true);
6316 } else {
6317 do_highlight("Visual cterm=reverse ctermbg=NONE", false, true);
6318 if (*p_bg == 'l') {
6319 do_highlight("Search ctermfg=black", false, true);
6320 }
6321 }
6322
6323 /*
6324 * If syntax highlighting is enabled load the highlighting for it.
6325 */
6326 if (get_var_value("g:syntax_on") != NULL) {
6327 static int recursive = 0;
6328
6329 if (recursive >= 5) {
6330 EMSG(_("E679: recursive loop loading syncolor.vim"));
6331 } else {
6332 recursive++;
6333 (void)source_runtime((char_u *)"syntax/syncolor.vim", DIP_ALL);
6334 recursive--;
6335 }
6336 }
6337 syn_init_cmdline_highlight(false, false);
6338}
6339
6340/*
6341 * Load color file "name".
6342 * Return OK for success, FAIL for failure.
6343 */
6344int load_colors(char_u *name)
6345{
6346 char_u *buf;
6347 int retval = FAIL;
6348 static int recursive = false;
6349
6350 // When being called recursively, this is probably because setting
6351 // 'background' caused the highlighting to be reloaded. This means it is
6352 // working, thus we should return OK.
6353 if (recursive) {
6354 return OK;
6355 }
6356
6357 recursive = true;
6358 size_t buflen = STRLEN(name) + 12;
6359 buf = xmalloc(buflen);
6360 apply_autocmds(EVENT_COLORSCHEMEPRE, name, curbuf->b_fname, false, curbuf);
6361 snprintf((char *)buf, buflen, "colors/%s.vim", name);
6362 retval = source_runtime(buf, DIP_START + DIP_OPT);
6363 xfree(buf);
6364 apply_autocmds(EVENT_COLORSCHEME, name, curbuf->b_fname, FALSE, curbuf);
6365
6366 recursive = false;
6367
6368 return retval;
6369}
6370
6371static char *(color_names[28]) = {
6372 "Black", "DarkBlue", "DarkGreen", "DarkCyan",
6373 "DarkRed", "DarkMagenta", "Brown", "DarkYellow",
6374 "Gray", "Grey", "LightGray", "LightGrey",
6375 "DarkGray", "DarkGrey",
6376 "Blue", "LightBlue", "Green", "LightGreen",
6377 "Cyan", "LightCyan", "Red", "LightRed", "Magenta",
6378 "LightMagenta", "Yellow", "LightYellow", "White", "NONE" };
6379 // indices:
6380 // 0, 1, 2, 3,
6381 // 4, 5, 6, 7,
6382 // 8, 9, 10, 11,
6383 // 12, 13,
6384 // 14, 15, 16, 17,
6385 // 18, 19, 20, 21, 22,
6386 // 23, 24, 25, 26, 27
6387static int color_numbers_16[28] = { 0, 1, 2, 3,
6388 4, 5, 6, 6,
6389 7, 7, 7, 7,
6390 8, 8,
6391 9, 9, 10, 10,
6392 11, 11, 12, 12, 13,
6393 13, 14, 14, 15, -1 };
6394// for xterm with 88 colors...
6395static int color_numbers_88[28] = { 0, 4, 2, 6,
6396 1, 5, 32, 72,
6397 84, 84, 7, 7,
6398 82, 82,
6399 12, 43, 10, 61,
6400 14, 63, 9, 74, 13,
6401 75, 11, 78, 15, -1 };
6402// for xterm with 256 colors...
6403static int color_numbers_256[28] = { 0, 4, 2, 6,
6404 1, 5, 130, 130,
6405 248, 248, 7, 7,
6406 242, 242,
6407 12, 81, 10, 121,
6408 14, 159, 9, 224, 13,
6409 225, 11, 229, 15, -1 };
6410// for terminals with less than 16 colors...
6411static int color_numbers_8[28] = { 0, 4, 2, 6,
6412 1, 5, 3, 3,
6413 7, 7, 7, 7,
6414 0+8, 0+8,
6415 4+8, 4+8, 2+8, 2+8,
6416 6+8, 6+8, 1+8, 1+8, 5+8,
6417 5+8, 3+8, 3+8, 7+8, -1 };
6418
6419// Lookup the "cterm" value to be used for color with index "idx" in
6420// color_names[].
6421// "boldp" will be set to TRUE or FALSE for a foreground color when using 8
6422// colors, otherwise it will be unchanged.
6423int lookup_color(const int idx, const bool foreground, TriState *const boldp)
6424{
6425 int color = color_numbers_16[idx];
6426
6427 // Use the _16 table to check if it's a valid color name.
6428 if (color < 0) {
6429 return -1;
6430 }
6431
6432 if (t_colors == 8) {
6433 // t_Co is 8: use the 8 colors table
6434 color = color_numbers_8[idx];
6435 if (foreground) {
6436 // set/reset bold attribute to get light foreground
6437 // colors (on some terminals, e.g. "linux")
6438 if (color & 8) {
6439 *boldp = kTrue;
6440 } else {
6441 *boldp = kFalse;
6442 }
6443 }
6444 color &= 7; // truncate to 8 colors
6445 } else if (t_colors == 16) {
6446 color = color_numbers_8[idx];
6447 } else if (t_colors == 88) {
6448 color = color_numbers_88[idx];
6449 } else if (t_colors >= 256) {
6450 color = color_numbers_256[idx];
6451 }
6452 return color;
6453}
6454
6455
6456/// Handle ":highlight" command
6457///
6458/// When using ":highlight clear" this is called recursively for each group with
6459/// forceit and init being both true.
6460///
6461/// @param[in] line Command arguments.
6462/// @param[in] forceit True when bang is given, allows to link group even if
6463/// it has its own settings.
6464/// @param[in] init True when initializing.
6465void do_highlight(const char *line, const bool forceit, const bool init)
6466 FUNC_ATTR_NONNULL_ALL
6467{
6468 const char *name_end;
6469 const char *linep;
6470 const char *key_start;
6471 const char *arg_start;
6472 long i;
6473 int off;
6474 int len;
6475 int attr;
6476 int id;
6477 int idx;
6478 struct hl_group item_before;
6479 bool did_change = false;
6480 bool dodefault = false;
6481 bool doclear = false;
6482 bool dolink = false;
6483 bool error = false;
6484 int color;
6485 bool is_normal_group = false; // "Normal" group
6486 bool did_highlight_changed = false;
6487
6488 // If no argument, list current highlighting.
6489 if (ends_excmd((uint8_t)(*line))) {
6490 for (i = 1; i <= highlight_ga.ga_len && !got_int; i++) {
6491 // TODO(brammool): only call when the group has attributes set
6492 highlight_list_one(i);
6493 }
6494 return;
6495 }
6496
6497 // Isolate the name.
6498 name_end = (const char *)skiptowhite((const char_u *)line);
6499 linep = (const char *)skipwhite((const char_u *)name_end);
6500
6501 // Check for "default" argument.
6502 if (strncmp(line, "default", name_end - line) == 0) {
6503 dodefault = true;
6504 line = linep;
6505 name_end = (const char *)skiptowhite((const char_u *)line);
6506 linep = (const char *)skipwhite((const char_u *)name_end);
6507 }
6508
6509 // Check for "clear" or "link" argument.
6510 if (strncmp(line, "clear", name_end - line) == 0) {
6511 doclear = true;
6512 } else if (strncmp(line, "link", name_end - line) == 0) {
6513 dolink = true;
6514 }
6515
6516 // ":highlight {group-name}": list highlighting for one group.
6517 if (!doclear && !dolink && ends_excmd((uint8_t)(*linep))) {
6518 id = syn_namen2id((const char_u *)line, (int)(name_end - line));
6519 if (id == 0) {
6520 emsgf(_("E411: highlight group not found: %s"), line);
6521 } else {
6522 highlight_list_one(id);
6523 }
6524 return;
6525 }
6526
6527 // Handle ":highlight link {from} {to}" command.
6528 if (dolink) {
6529 const char *from_start = linep;
6530 const char *from_end;
6531 const char *to_start;
6532 const char *to_end;
6533 int from_id;
6534 int to_id;
6535
6536 from_end = (const char *)skiptowhite((const char_u *)from_start);
6537 to_start = (const char *)skipwhite((const char_u *)from_end);
6538 to_end = (const char *)skiptowhite((const char_u *)to_start);
6539
6540 if (ends_excmd((uint8_t)(*from_start))
6541 || ends_excmd((uint8_t)(*to_start))) {
6542 emsgf(_("E412: Not enough arguments: \":highlight link %s\""),
6543 from_start);
6544 return;
6545 }
6546
6547 if (!ends_excmd(*skipwhite((const char_u *)to_end))) {
6548 emsgf(_("E413: Too many arguments: \":highlight link %s\""), from_start);
6549 return;
6550 }
6551
6552 from_id = syn_check_group((const char_u *)from_start,
6553 (int)(from_end - from_start));
6554 if (strncmp(to_start, "NONE", 4) == 0) {
6555 to_id = 0;
6556 } else {
6557 to_id = syn_check_group((const char_u *)to_start,
6558 (int)(to_end - to_start));
6559 }
6560
6561 if (from_id > 0 && (!init || HL_TABLE()[from_id - 1].sg_set == 0)) {
6562 // Don't allow a link when there already is some highlighting
6563 // for the group, unless '!' is used
6564 if (to_id > 0 && !forceit && !init
6565 && hl_has_settings(from_id - 1, dodefault)) {
6566 if (sourcing_name == NULL && !dodefault) {
6567 EMSG(_("E414: group has settings, highlight link ignored"));
6568 }
6569 } else if (HL_TABLE()[from_id - 1].sg_link != to_id
6570 || HL_TABLE()[from_id - 1].sg_script_ctx.sc_sid
6571 != current_sctx.sc_sid
6572 || HL_TABLE()[from_id - 1].sg_cleared) {
6573 if (!init) {
6574 HL_TABLE()[from_id - 1].sg_set |= SG_LINK;
6575 }
6576 HL_TABLE()[from_id - 1].sg_link = to_id;
6577 HL_TABLE()[from_id - 1].sg_script_ctx = current_sctx;
6578 HL_TABLE()[from_id - 1].sg_script_ctx.sc_lnum += sourcing_lnum;
6579 HL_TABLE()[from_id - 1].sg_cleared = false;
6580 redraw_all_later(SOME_VALID);
6581
6582 // Only call highlight changed() once after multiple changes
6583 need_highlight_changed = true;
6584 }
6585 }
6586
6587 return;
6588 }
6589
6590 if (doclear) {
6591 // ":highlight clear [group]" command.
6592 line = linep;
6593 if (ends_excmd((uint8_t)(*line))) {
6594 do_unlet(S_LEN("colors_name"), true);
6595 restore_cterm_colors();
6596
6597 // Clear all default highlight groups and load the defaults.
6598 for (int j = 0; j < highlight_ga.ga_len; j++) {
6599 highlight_clear(j);
6600 }
6601 init_highlight(true, true);
6602 highlight_changed();
6603 redraw_all_later(NOT_VALID);
6604 return;
6605 }
6606 name_end = (const char *)skiptowhite((const char_u *)line);
6607 linep = (const char *)skipwhite((const char_u *)name_end);
6608 }
6609
6610 // Find the group name in the table. If it does not exist yet, add it.
6611 id = syn_check_group((const char_u *)line, (int)(name_end - line));
6612 if (id == 0) { // Failed (out of memory).
6613 return;
6614 }
6615 idx = id - 1; // Index is ID minus one.
6616
6617 // Return if "default" was used and the group already has settings
6618 if (dodefault && hl_has_settings(idx, true)) {
6619 return;
6620 }
6621
6622 // Make a copy so we can check if any attribute actually changed
6623 item_before = HL_TABLE()[idx];
6624 is_normal_group = (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0);
6625
6626 // Clear the highlighting for ":hi clear {group}" and ":hi clear".
6627 if (doclear || (forceit && init)) {
6628 highlight_clear(idx);
6629 if (!doclear) {
6630 HL_TABLE()[idx].sg_set = 0;
6631 }
6632 }
6633
6634 char *key = NULL;
6635 char *arg = NULL;
6636 if (!doclear) {
6637 while (!ends_excmd((uint8_t)(*linep))) {
6638 key_start = linep;
6639 if (*linep == '=') {
6640 emsgf(_("E415: unexpected equal sign: %s"), key_start);
6641 error = true;
6642 break;
6643 }
6644
6645 // Isolate the key ("term", "ctermfg", "ctermbg", "font", "guifg",
6646 // "guibg" or "guisp").
6647 while (*linep && !ascii_iswhite(*linep) && *linep != '=') {
6648 linep++;
6649 }
6650 xfree(key);
6651 key = (char *)vim_strnsave_up((const char_u *)key_start,
6652 (int)(linep - key_start));
6653 linep = (const char *)skipwhite((const char_u *)linep);
6654
6655 if (strcmp(key, "NONE") == 0) {
6656 if (!init || HL_TABLE()[idx].sg_set == 0) {
6657 if (!init) {
6658 HL_TABLE()[idx].sg_set |= SG_CTERM+SG_GUI;
6659 }
6660 highlight_clear(idx);
6661 }
6662 continue;
6663 }
6664
6665 // Check for the equal sign.
6666 if (*linep != '=') {
6667 emsgf(_("E416: missing equal sign: %s"), key_start);
6668 error = true;
6669 break;
6670 }
6671 linep++;
6672
6673 // Isolate the argument.
6674 linep = (const char *)skipwhite((const char_u *)linep);
6675 if (*linep == '\'') { // guifg='color name'
6676 arg_start = ++linep;
6677 linep = strchr(linep, '\'');
6678 if (linep == NULL) {
6679 emsgf(_(e_invarg2), key_start);
6680 error = true;
6681 break;
6682 }
6683 } else {
6684 arg_start = linep;
6685 linep = (const char *)skiptowhite((const char_u *)linep);
6686 }
6687 if (linep == arg_start) {
6688 emsgf(_("E417: missing argument: %s"), key_start);
6689 error = true;
6690 break;
6691 }
6692 xfree(arg);
6693 arg = xstrndup(arg_start, (size_t)(linep - arg_start));
6694
6695 if (*linep == '\'') {
6696 linep++;
6697 }
6698
6699 // Store the argument.
6700 if (strcmp(key, "TERM") == 0
6701 || strcmp(key, "CTERM") == 0
6702 || strcmp(key, "GUI") == 0) {
6703 attr = 0;
6704 off = 0;
6705 while (arg[off] != NUL) {
6706 for (i = ARRAY_SIZE(hl_attr_table); --i >= 0; ) {
6707 len = (int)STRLEN(hl_name_table[i]);
6708 if (STRNICMP(arg + off, hl_name_table[i], len) == 0) {
6709 attr |= hl_attr_table[i];
6710 off += len;
6711 break;
6712 }
6713 }
6714 if (i < 0) {
6715 emsgf(_("E418: Illegal value: %s"), arg);
6716 error = true;
6717 break;
6718 }
6719 if (arg[off] == ',') { // Another one follows.
6720 off++;
6721 }
6722 }
6723 if (error) {
6724 break;
6725 }
6726 if (*key == 'C') {
6727 if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM)) {
6728 if (!init) {
6729 HL_TABLE()[idx].sg_set |= SG_CTERM;
6730 }
6731 HL_TABLE()[idx].sg_cterm = attr;
6732 HL_TABLE()[idx].sg_cterm_bold = false;
6733 }
6734 } else if (*key == 'G') {
6735 if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) {
6736 if (!init) {
6737 HL_TABLE()[idx].sg_set |= SG_GUI;
6738 }
6739 HL_TABLE()[idx].sg_gui = attr;
6740 }
6741 }
6742 } else if (STRCMP(key, "FONT") == 0) {
6743 // in non-GUI fonts are simply ignored
6744 } else if (STRCMP(key, "CTERMFG") == 0 || STRCMP(key, "CTERMBG") == 0) {
6745 if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM)) {
6746 if (!init) {
6747 HL_TABLE()[idx].sg_set |= SG_CTERM;
6748 }
6749
6750 /* When setting the foreground color, and previously the "bold"
6751 * flag was set for a light color, reset it now */
6752 if (key[5] == 'F' && HL_TABLE()[idx].sg_cterm_bold) {
6753 HL_TABLE()[idx].sg_cterm &= ~HL_BOLD;
6754 HL_TABLE()[idx].sg_cterm_bold = false;
6755 }
6756
6757 if (ascii_isdigit(*arg)) {
6758 color = atoi((char *)arg);
6759 } else if (STRICMP(arg, "fg") == 0) {
6760 if (cterm_normal_fg_color) {
6761 color = cterm_normal_fg_color - 1;
6762 } else {
6763 EMSG(_("E419: FG color unknown"));
6764 error = true;
6765 break;
6766 }
6767 } else if (STRICMP(arg, "bg") == 0) {
6768 if (cterm_normal_bg_color > 0)
6769 color = cterm_normal_bg_color - 1;
6770 else {
6771 EMSG(_("E420: BG color unknown"));
6772 error = true;
6773 break;
6774 }
6775 } else {
6776 // Reduce calls to STRICMP a bit, it can be slow.
6777 off = TOUPPER_ASC(*arg);
6778 for (i = ARRAY_SIZE(color_names); --i >= 0; ) {
6779 if (off == color_names[i][0]
6780 && STRICMP(arg + 1, color_names[i] + 1) == 0) {
6781 break;
6782 }
6783 }
6784 if (i < 0) {
6785 emsgf(_("E421: Color name or number not recognized: %s"),
6786 key_start);
6787 error = true;
6788 break;
6789 }
6790
6791 TriState bold = kNone;
6792 color = lookup_color(i, key[5] == 'F', &bold);
6793
6794 // set/reset bold attribute to get light foreground
6795 // colors (on some terminals, e.g. "linux")
6796 if (bold == kTrue) {
6797 HL_TABLE()[idx].sg_cterm |= HL_BOLD;
6798 HL_TABLE()[idx].sg_cterm_bold = true;
6799 } else if (bold == kFalse) {
6800 HL_TABLE()[idx].sg_cterm &= ~HL_BOLD;
6801 }
6802 }
6803 // Add one to the argument, to avoid zero. Zero is used for
6804 // "NONE", then "color" is -1.
6805 if (key[5] == 'F') {
6806 HL_TABLE()[idx].sg_cterm_fg = color + 1;
6807 if (is_normal_group) {
6808 cterm_normal_fg_color = color + 1;
6809 }
6810 } else {
6811 HL_TABLE()[idx].sg_cterm_bg = color + 1;
6812 if (is_normal_group) {
6813 cterm_normal_bg_color = color + 1;
6814 if (!ui_rgb_attached()) {
6815 if (color >= 0) {
6816 int dark = -1;
6817
6818 if (t_colors < 16) {
6819 dark = (color == 0 || color == 4);
6820 } else if (color < 16) {
6821 // Limit the heuristic to the standard 16 colors
6822 dark = (color < 7 || color == 8);
6823 }
6824 // Set the 'background' option if the value is
6825 // wrong.
6826 if (dark != -1
6827 && dark != (*p_bg == 'd')
6828 && !option_was_set("bg")) {
6829 set_option_value("bg", 0L, (dark ? "dark" : "light"), 0);
6830 reset_option_was_set("bg");
6831 }
6832 }
6833 }
6834 }
6835 }
6836 }
6837 } else if (strcmp(key, "GUIFG") == 0) {
6838 char_u **const namep = &HL_TABLE()[idx].sg_rgb_fg_name;
6839
6840 if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) {
6841 if (!init) {
6842 HL_TABLE()[idx].sg_set |= SG_GUI;
6843 }
6844
6845 if (*namep == NULL || STRCMP(*namep, arg) != 0) {
6846 xfree(*namep);
6847 if (strcmp(arg, "NONE") != 0) {
6848 *namep = (char_u *)xstrdup(arg);
6849 HL_TABLE()[idx].sg_rgb_fg = name_to_color((char_u *)arg);
6850 } else {
6851 *namep = NULL;
6852 HL_TABLE()[idx].sg_rgb_fg = -1;
6853 }
6854 did_change = true;
6855 }
6856 }
6857
6858 if (is_normal_group) {
6859 normal_fg = HL_TABLE()[idx].sg_rgb_fg;
6860 }
6861 } else if (STRCMP(key, "GUIBG") == 0) {
6862 char_u **const namep = &HL_TABLE()[idx].sg_rgb_bg_name;
6863
6864 if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) {
6865 if (!init) {
6866 HL_TABLE()[idx].sg_set |= SG_GUI;
6867 }
6868
6869 if (*namep == NULL || STRCMP(*namep, arg) != 0) {
6870 xfree(*namep);
6871 if (STRCMP(arg, "NONE") != 0) {
6872 *namep = (char_u *)xstrdup(arg);
6873 HL_TABLE()[idx].sg_rgb_bg = name_to_color((char_u *)arg);
6874 } else {
6875 *namep = NULL;
6876 HL_TABLE()[idx].sg_rgb_bg = -1;
6877 }
6878 did_change = true;
6879 }
6880 }
6881
6882 if (is_normal_group) {
6883 normal_bg = HL_TABLE()[idx].sg_rgb_bg;
6884 }
6885 } else if (strcmp(key, "GUISP") == 0) {
6886 char_u **const namep = &HL_TABLE()[idx].sg_rgb_sp_name;
6887
6888 if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) {
6889 if (!init) {
6890 HL_TABLE()[idx].sg_set |= SG_GUI;
6891 }
6892
6893 if (*namep == NULL || STRCMP(*namep, arg) != 0) {
6894 xfree(*namep);
6895 if (strcmp(arg, "NONE") != 0) {
6896 *namep = (char_u *)xstrdup(arg);
6897 HL_TABLE()[idx].sg_rgb_sp = name_to_color((char_u *)arg);
6898 } else {
6899 *namep = NULL;
6900 HL_TABLE()[idx].sg_rgb_sp = -1;
6901 }
6902 did_change = true;
6903 }
6904 }
6905
6906 if (is_normal_group) {
6907 normal_sp = HL_TABLE()[idx].sg_rgb_sp;
6908 }
6909 } else if (strcmp(key, "START") == 0 || strcmp(key, "STOP") == 0) {
6910 // Ignored for now
6911 } else if (strcmp(key, "BLEND") == 0) {
6912 if (strcmp(arg, "NONE") != 0) {
6913 HL_TABLE()[idx].sg_blend = strtol(arg, NULL, 10);
6914 } else {
6915 HL_TABLE()[idx].sg_blend = -1;
6916 }
6917 } else {
6918 emsgf(_("E423: Illegal argument: %s"), key_start);
6919 error = true;
6920 break;
6921 }
6922 HL_TABLE()[idx].sg_cleared = false;
6923
6924 // When highlighting has been given for a group, don't link it.
6925 if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK)) {
6926 HL_TABLE()[idx].sg_link = 0;
6927 }
6928
6929 // Continue with next argument.
6930 linep = (const char *)skipwhite((const char_u *)linep);
6931 }
6932 }
6933
6934 // If there is an error, and it's a new entry, remove it from the table.
6935 if (error && idx == highlight_ga.ga_len) {
6936 syn_unadd_group();
6937 } else {
6938 if (!error && is_normal_group) {
6939 // Need to update all groups, because they might be using "bg" and/or
6940 // "fg", which have been changed now.
6941 highlight_attr_set_all();
6942
6943 if (!ui_has(kUILinegrid) && starting == 0) {
6944 // Older UIs assume that we clear the screen after normal group is
6945 // changed
6946 ui_refresh();
6947 } else {
6948 // TUI and newer UIs will repaint the screen themselves. NOT_VALID
6949 // redraw below will still handle usages of guibg=fg etc.
6950 ui_default_colors_set();
6951 }
6952 did_highlight_changed = true;
6953 redraw_all_later(NOT_VALID);
6954 } else {
6955 set_hl_attr(idx);
6956 }
6957 HL_TABLE()[idx].sg_script_ctx = current_sctx;
6958 HL_TABLE()[idx].sg_script_ctx.sc_lnum += sourcing_lnum;
6959 }
6960 xfree(key);
6961 xfree(arg);
6962
6963 // Only call highlight_changed() once, after a sequence of highlight
6964 // commands, and only if an attribute actually changed
6965 if ((did_change
6966 || memcmp(&HL_TABLE()[idx], &item_before, sizeof(item_before)) != 0)
6967 && !did_highlight_changed) {
6968 // Do not trigger a redraw when highlighting is changed while
6969 // redrawing. This may happen when evaluating 'statusline' changes the
6970 // StatusLine group.
6971 if (!updating_screen) {
6972 redraw_all_later(NOT_VALID);
6973 }
6974 need_highlight_changed = true;
6975 }
6976}
6977
6978#if defined(EXITFREE)
6979void free_highlight(void)
6980{
6981 for (int i = 0; i < highlight_ga.ga_len; ++i) {
6982 highlight_clear(i);
6983 xfree(HL_TABLE()[i].sg_name);
6984 xfree(HL_TABLE()[i].sg_name_u);
6985 }
6986 ga_clear(&highlight_ga);
6987}
6988
6989#endif
6990
6991/*
6992 * Reset the cterm colors to what they were before Vim was started, if
6993 * possible. Otherwise reset them to zero.
6994 */
6995void restore_cterm_colors(void)
6996{
6997 normal_fg = -1;
6998 normal_bg = -1;
6999 normal_sp = -1;
7000 cterm_normal_fg_color = 0;
7001 cterm_normal_bg_color = 0;
7002}
7003
7004/*
7005 * Return TRUE if highlight group "idx" has any settings.
7006 * When "check_link" is TRUE also check for an existing link.
7007 */
7008static int hl_has_settings(int idx, int check_link)
7009{
7010 return HL_TABLE()[idx].sg_attr != 0
7011 || HL_TABLE()[idx].sg_cterm_fg != 0
7012 || HL_TABLE()[idx].sg_cterm_bg != 0
7013 || HL_TABLE()[idx].sg_rgb_fg_name != NULL
7014 || HL_TABLE()[idx].sg_rgb_bg_name != NULL
7015 || HL_TABLE()[idx].sg_rgb_sp_name != NULL
7016 || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK));
7017}
7018
7019/*
7020 * Clear highlighting for one group.
7021 */
7022static void highlight_clear(int idx)
7023{
7024 HL_TABLE()[idx].sg_cleared = true;
7025
7026 HL_TABLE()[idx].sg_attr = 0;
7027 HL_TABLE()[idx].sg_cterm = 0;
7028 HL_TABLE()[idx].sg_cterm_bold = false;
7029 HL_TABLE()[idx].sg_cterm_fg = 0;
7030 HL_TABLE()[idx].sg_cterm_bg = 0;
7031 HL_TABLE()[idx].sg_gui = 0;
7032 HL_TABLE()[idx].sg_rgb_fg = -1;
7033 HL_TABLE()[idx].sg_rgb_bg = -1;
7034 HL_TABLE()[idx].sg_rgb_sp = -1;
7035 XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_fg_name);
7036 XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_bg_name);
7037 XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_sp_name);
7038 HL_TABLE()[idx].sg_blend = -1;
7039 // Clear the script ID only when there is no link, since that is not
7040 // cleared.
7041 if (HL_TABLE()[idx].sg_link == 0) {
7042 HL_TABLE()[idx].sg_script_ctx.sc_sid = 0;
7043 HL_TABLE()[idx].sg_script_ctx.sc_lnum = 0;
7044 }
7045}
7046
7047
7048/// \addtogroup LIST_XXX
7049/// @{
7050#define LIST_ATTR 1
7051#define LIST_STRING 2
7052#define LIST_INT 3
7053/// @}
7054
7055static void highlight_list_one(const int id)
7056{
7057 struct hl_group *const sgp = &HL_TABLE()[id - 1]; // index is ID minus one
7058 bool didh = false;
7059
7060 if (message_filtered(sgp->sg_name)) {
7061 return;
7062 }
7063
7064 didh = highlight_list_arg(id, didh, LIST_ATTR,
7065 sgp->sg_cterm, NULL, "cterm");
7066 didh = highlight_list_arg(id, didh, LIST_INT,
7067 sgp->sg_cterm_fg, NULL, "ctermfg");
7068 didh = highlight_list_arg(id, didh, LIST_INT,
7069 sgp->sg_cterm_bg, NULL, "ctermbg");
7070
7071 didh = highlight_list_arg(id, didh, LIST_ATTR,
7072 sgp->sg_gui, NULL, "gui");
7073 didh = highlight_list_arg(id, didh, LIST_STRING,
7074 0, sgp->sg_rgb_fg_name, "guifg");
7075 didh = highlight_list_arg(id, didh, LIST_STRING,
7076 0, sgp->sg_rgb_bg_name, "guibg");
7077 didh = highlight_list_arg(id, didh, LIST_STRING,
7078 0, sgp->sg_rgb_sp_name, "guisp");
7079
7080 didh = highlight_list_arg(id, didh, LIST_INT,
7081 sgp->sg_blend+1, NULL, "blend");
7082
7083 if (sgp->sg_link && !got_int) {
7084 (void)syn_list_header(didh, 0, id, true);
7085 didh = true;
7086 msg_puts_attr("links to", HL_ATTR(HLF_D));
7087 msg_putchar(' ');
7088 msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name);
7089 }
7090
7091 if (!didh)
7092 highlight_list_arg(id, didh, LIST_STRING, 0, (char_u *)"cleared", "");
7093 if (p_verbose > 0) {
7094 last_set_msg(sgp->sg_script_ctx);
7095 }
7096}
7097
7098/// Outputs a highlight when doing ":hi MyHighlight"
7099///
7100/// @param type one of \ref LIST_XXX
7101/// @param iarg integer argument used if \p type == LIST_INT
7102/// @param sarg string used if \p type == LIST_STRING
7103static bool highlight_list_arg(
7104 const int id, bool didh, const int type, int iarg,
7105 char_u *const sarg, const char *const name)
7106{
7107 char_u buf[100];
7108
7109 if (got_int) {
7110 return false;
7111 }
7112 if (type == LIST_STRING ? (sarg != NULL) : (iarg != 0)) {
7113 char_u *ts = buf;
7114 if (type == LIST_INT) {
7115 snprintf((char *)buf, sizeof(buf), "%d", iarg - 1);
7116 } else if (type == LIST_STRING) {
7117 ts = sarg;
7118 } else { // type == LIST_ATTR
7119 buf[0] = NUL;
7120 for (int i = 0; hl_attr_table[i] != 0; i++) {
7121 if (iarg & hl_attr_table[i]) {
7122 if (buf[0] != NUL)
7123 xstrlcat((char *)buf, ",", 100);
7124 xstrlcat((char *)buf, hl_name_table[i], 100);
7125 iarg &= ~hl_attr_table[i]; /* don't want "inverse" */
7126 }
7127 }
7128 }
7129
7130 (void)syn_list_header(didh, (int)(vim_strsize(ts) + STRLEN(name) + 1), id,
7131 false);
7132 didh = true;
7133 if (!got_int) {
7134 if (*name != NUL) {
7135 MSG_PUTS_ATTR(name, HL_ATTR(HLF_D));
7136 MSG_PUTS_ATTR("=", HL_ATTR(HLF_D));
7137 }
7138 msg_outtrans(ts);
7139 }
7140 }
7141 return didh;
7142}
7143
7144/// Check whether highlight group has attribute
7145///
7146/// @param[in] id Highlight group to check.
7147/// @param[in] flag Attribute to check.
7148/// @param[in] modec 'g' for GUI, 'c' for term.
7149///
7150/// @return "1" if highlight group has attribute, NULL otherwise.
7151const char *highlight_has_attr(const int id, const int flag, const int modec)
7152 FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
7153{
7154 int attr;
7155
7156 if (id <= 0 || id > highlight_ga.ga_len) {
7157 return NULL;
7158 }
7159
7160 if (modec == 'g') {
7161 attr = HL_TABLE()[id - 1].sg_gui;
7162 } else {
7163 attr = HL_TABLE()[id - 1].sg_cterm;
7164 }
7165
7166 return (attr & flag) ? "1" : NULL;
7167}
7168
7169/// Return color name of the given highlight group
7170///
7171/// @param[in] id Highlight group to work with.
7172/// @param[in] what What to return: one of "font", "fg", "bg", "sp", "fg#",
7173/// "bg#" or "sp#".
7174/// @param[in] modec 'g' for GUI, 'c' for cterm and 't' for term.
7175///
7176/// @return color name, possibly in a static buffer. Buffer will be overwritten
7177/// on next highlight_color() call. May return NULL.
7178const char *highlight_color(const int id, const char *const what,
7179 const int modec)
7180 FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
7181{
7182 static char name[20];
7183 int n;
7184 bool fg = false;
7185 bool sp = false;
7186 bool font = false;
7187
7188 if (id <= 0 || id > highlight_ga.ga_len) {
7189 return NULL;
7190 }
7191
7192 if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'g') {
7193 fg = true;
7194 } else if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'o'
7195 && TOLOWER_ASC(what[2]) == 'n' && TOLOWER_ASC(what[3]) == 't') {
7196 font = true;
7197 } else if (TOLOWER_ASC(what[0]) == 's' && TOLOWER_ASC(what[1]) == 'p') {
7198 sp = true;
7199 } else if (!(TOLOWER_ASC(what[0]) == 'b' && TOLOWER_ASC(what[1]) == 'g')) {
7200 return NULL;
7201 }
7202 if (modec == 'g') {
7203 if (what[2] == '#' && ui_rgb_attached()) {
7204 if (fg) {
7205 n = HL_TABLE()[id - 1].sg_rgb_fg;
7206 } else if (sp) {
7207 n = HL_TABLE()[id - 1].sg_rgb_sp;
7208 } else {
7209 n = HL_TABLE()[id - 1].sg_rgb_bg;
7210 }
7211 if (n < 0 || n > 0xffffff) {
7212 return NULL;
7213 }
7214 snprintf(name, sizeof(name), "#%06x", n);
7215 return name;
7216 }
7217 if (fg) {
7218 return (const char *)HL_TABLE()[id - 1].sg_rgb_fg_name;
7219 }
7220 if (sp) {
7221 return (const char *)HL_TABLE()[id - 1].sg_rgb_sp_name;
7222 }
7223 return (const char *)HL_TABLE()[id - 1].sg_rgb_bg_name;
7224 }
7225 if (font || sp) {
7226 return NULL;
7227 }
7228 if (modec == 'c') {
7229 if (fg) {
7230 n = HL_TABLE()[id - 1].sg_cterm_fg - 1;
7231 } else {
7232 n = HL_TABLE()[id - 1].sg_cterm_bg - 1;
7233 }
7234 if (n < 0) {
7235 return NULL;
7236 }
7237 snprintf(name, sizeof(name), "%d", n);
7238 return name;
7239 }
7240 // term doesn't have color.
7241 return NULL;
7242}
7243
7244/// Output the syntax list header.
7245///
7246/// @param did_header did header already
7247/// @param outlen length of string that comes
7248/// @param id highlight group id
7249/// @param force_newline always start a new line
7250/// @return true when started a new line.
7251static bool syn_list_header(const bool did_header, const int outlen,
7252 const int id, bool force_newline)
7253{
7254 int endcol = 19;
7255 bool newline = true;
7256 bool adjust = true;
7257
7258 if (!did_header) {
7259 msg_putchar('\n');
7260 if (got_int) {
7261 return true;
7262 }
7263 msg_outtrans(HL_TABLE()[id - 1].sg_name);
7264 endcol = 15;
7265 } else if ((ui_has(kUIMessages) || msg_silent) && !force_newline) {
7266 msg_putchar(' ');
7267 adjust = false;
7268 } else if (msg_col + outlen + 1 >= Columns || force_newline) {
7269 msg_putchar('\n');
7270 if (got_int) {
7271 return true;
7272 }
7273 } else {
7274 if (msg_col >= endcol) { // wrap around is like starting a new line
7275 newline = false;
7276 }
7277 }
7278
7279 if (adjust) {
7280 if (msg_col >= endcol) {
7281 // output at least one space
7282 endcol = msg_col + 1;
7283 }
7284
7285 msg_advance(endcol);
7286 }
7287
7288 /* Show "xxx" with the attributes. */
7289 if (!did_header) {
7290 msg_puts_attr("xxx", syn_id2attr(id));
7291 msg_putchar(' ');
7292 }
7293
7294 return newline;
7295}
7296
7297/// Set the attribute numbers for a highlight group.
7298/// Called after one of the attributes has changed.
7299/// @param idx corrected highlight index
7300static void set_hl_attr(int idx)
7301{
7302 HlAttrs at_en = HLATTRS_INIT;
7303 struct hl_group *sgp = HL_TABLE() + idx;
7304
7305 at_en.cterm_ae_attr = sgp->sg_cterm;
7306 at_en.cterm_fg_color = sgp->sg_cterm_fg;
7307 at_en.cterm_bg_color = sgp->sg_cterm_bg;
7308 at_en.rgb_ae_attr = sgp->sg_gui;
7309 // FIXME(tarruda): The "unset value" for rgb is -1, but since hlgroup is
7310 // initialized with 0(by garray functions), check for sg_rgb_{f,b}g_name
7311 // before setting attr_entry->{f,g}g_color to a other than -1
7312 at_en.rgb_fg_color = sgp->sg_rgb_fg_name ? sgp->sg_rgb_fg : -1;
7313 at_en.rgb_bg_color = sgp->sg_rgb_bg_name ? sgp->sg_rgb_bg : -1;
7314 at_en.rgb_sp_color = sgp->sg_rgb_sp_name ? sgp->sg_rgb_sp : -1;
7315 at_en.hl_blend = sgp->sg_blend;
7316
7317 sgp->sg_attr = hl_get_syn_attr(idx+1, at_en);
7318
7319 // a cursor style uses this syn_id, make sure its atribute is updated.
7320 if (cursor_mode_uses_syn_id(idx+1)) {
7321 ui_mode_info_set();
7322 }
7323}
7324
7325/// Lookup a highlight group name and return its ID.
7326///
7327/// @param highlight name e.g. 'Cursor', 'Normal'
7328/// @return the highlight id, else 0 if \p name does not exist
7329int syn_name2id(const char_u *name)
7330{
7331 int i;
7332 char_u name_u[200];
7333
7334 /* Avoid using stricmp() too much, it's slow on some systems */
7335 /* Avoid alloc()/free(), these are slow too. ID names over 200 chars
7336 * don't deserve to be found! */
7337 STRLCPY(name_u, name, 200);
7338 vim_strup(name_u);
7339 for (i = highlight_ga.ga_len; --i >= 0; )
7340 if (HL_TABLE()[i].sg_name_u != NULL
7341 && STRCMP(name_u, HL_TABLE()[i].sg_name_u) == 0)
7342 break;
7343 return i + 1;
7344}
7345
7346/// Lookup a highlight group name and return its attributes.
7347/// Return zero if not found.
7348int syn_name2attr(char_u *name)
7349{
7350 int id = syn_name2id(name);
7351
7352 if (id != 0) {
7353 return syn_id2attr(syn_get_final_id(id));
7354 }
7355 return 0;
7356}
7357
7358/*
7359 * Return TRUE if highlight group "name" exists.
7360 */
7361int highlight_exists(const char_u *name)
7362{
7363 return syn_name2id(name) > 0;
7364}
7365
7366/*
7367 * Return the name of highlight group "id".
7368 * When not a valid ID return an empty string.
7369 */
7370char_u *syn_id2name(int id)
7371{
7372 if (id <= 0 || id > highlight_ga.ga_len)
7373 return (char_u *)"";
7374 return HL_TABLE()[id - 1].sg_name;
7375}
7376
7377/*
7378 * Like syn_name2id(), but take a pointer + length argument.
7379 */
7380int syn_namen2id(const char_u *linep, int len)
7381{
7382 char_u *name = vim_strnsave(linep, len);
7383 int id = syn_name2id(name);
7384 xfree(name);
7385
7386 return id;
7387}
7388
7389/// Find highlight group name in the table and return its ID.
7390/// If it doesn't exist yet, a new entry is created.
7391///
7392/// @param pp Highlight group name
7393/// @param len length of \p pp
7394///
7395/// @return 0 for failure else the id of the group
7396int syn_check_group(const char_u *pp, int len)
7397{
7398 char_u *name = vim_strnsave(pp, len);
7399 int id = syn_name2id(name);
7400 if (id == 0) { // doesn't exist yet
7401 id = syn_add_group(name);
7402 } else {
7403 xfree(name);
7404 }
7405 return id;
7406}
7407
7408/// Add new highlight group and return its ID.
7409///
7410/// @param name must be an allocated string, it will be consumed.
7411/// @return 0 for failure, else the allocated group id
7412/// @see syn_check_group syn_unadd_group
7413static int syn_add_group(char_u *name)
7414{
7415 char_u *p;
7416
7417 /* Check that the name is ASCII letters, digits and underscore. */
7418 for (p = name; *p != NUL; ++p) {
7419 if (!vim_isprintc(*p)) {
7420 EMSG(_("E669: Unprintable character in group name"));
7421 xfree(name);
7422 return 0;
7423 } else if (!ASCII_ISALNUM(*p) && *p != '_') {
7424 /* This is an error, but since there previously was no check only
7425 * give a warning. */
7426 msg_source(HL_ATTR(HLF_W));
7427 MSG(_("W18: Invalid character in group name"));
7428 break;
7429 }
7430 }
7431
7432 /*
7433 * First call for this growarray: init growing array.
7434 */
7435 if (highlight_ga.ga_data == NULL) {
7436 highlight_ga.ga_itemsize = sizeof(struct hl_group);
7437 ga_set_growsize(&highlight_ga, 10);
7438 }
7439
7440 if (highlight_ga.ga_len >= MAX_HL_ID) {
7441 EMSG(_("E849: Too many highlight and syntax groups"));
7442 xfree(name);
7443 return 0;
7444 }
7445
7446 // Append another syntax_highlight entry.
7447 struct hl_group* hlgp = GA_APPEND_VIA_PTR(struct hl_group, &highlight_ga);
7448 memset(hlgp, 0, sizeof(*hlgp));
7449 hlgp->sg_name = name;
7450 hlgp->sg_rgb_bg = -1;
7451 hlgp->sg_rgb_fg = -1;
7452 hlgp->sg_rgb_sp = -1;
7453 hlgp->sg_blend = -1;
7454 hlgp->sg_name_u = vim_strsave_up(name);
7455
7456 return highlight_ga.ga_len; /* ID is index plus one */
7457}
7458
7459/// When, just after calling syn_add_group(), an error is discovered, this
7460/// function deletes the new name.
7461static void syn_unadd_group(void)
7462{
7463 highlight_ga.ga_len--;
7464 xfree(HL_TABLE()[highlight_ga.ga_len].sg_name);
7465 xfree(HL_TABLE()[highlight_ga.ga_len].sg_name_u);
7466}
7467
7468
7469/// Translate a group ID to highlight attributes.
7470/// @see syn_attr2entry
7471int syn_id2attr(int hl_id)
7472{
7473 struct hl_group *sgp;
7474
7475 hl_id = syn_get_final_id(hl_id);
7476 sgp = &HL_TABLE()[hl_id - 1]; /* index is ID minus one */
7477 return sgp->sg_attr;
7478}
7479
7480
7481/*
7482 * Translate a group ID to the final group ID (following links).
7483 */
7484int syn_get_final_id(int hl_id)
7485{
7486 int count;
7487 struct hl_group *sgp;
7488
7489 if (hl_id > highlight_ga.ga_len || hl_id < 1)
7490 return 0; /* Can be called from eval!! */
7491
7492 /*
7493 * Follow links until there is no more.
7494 * Look out for loops! Break after 100 links.
7495 */
7496 for (count = 100; --count >= 0; ) {
7497 sgp = &HL_TABLE()[hl_id - 1]; /* index is ID minus one */
7498 if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len)
7499 break;
7500 hl_id = sgp->sg_link;
7501 }
7502
7503 return hl_id;
7504}
7505
7506/// Refresh the color attributes of all highlight groups.
7507void highlight_attr_set_all(void)
7508{
7509 for (int idx = 0; idx < highlight_ga.ga_len; idx++) {
7510 struct hl_group *sgp = &HL_TABLE()[idx];
7511 if (sgp->sg_rgb_bg_name != NULL) {
7512 sgp->sg_rgb_bg = name_to_color(sgp->sg_rgb_bg_name);
7513 }
7514 if (sgp->sg_rgb_fg_name != NULL) {
7515 sgp->sg_rgb_fg = name_to_color(sgp->sg_rgb_fg_name);
7516 }
7517 if (sgp->sg_rgb_sp_name != NULL) {
7518 sgp->sg_rgb_sp = name_to_color(sgp->sg_rgb_sp_name);
7519 }
7520 set_hl_attr(idx);
7521 }
7522}
7523
7524// Apply difference between User[1-9] and HLF_S to HLF_SNC.
7525static void combine_stl_hlt(int id, int id_S, int id_alt, int hlcnt, int i,
7526 int hlf, int *table)
7527 FUNC_ATTR_NONNULL_ALL
7528{
7529 struct hl_group *const hlt = HL_TABLE();
7530
7531 if (id_alt == 0) {
7532 memset(&hlt[hlcnt + i], 0, sizeof(struct hl_group));
7533 hlt[hlcnt + i].sg_cterm = highlight_attr[hlf];
7534 hlt[hlcnt + i].sg_gui = highlight_attr[hlf];
7535 } else {
7536 memmove(&hlt[hlcnt + i], &hlt[id_alt - 1], sizeof(struct hl_group));
7537 }
7538 hlt[hlcnt + i].sg_link = 0;
7539
7540 hlt[hlcnt + i].sg_cterm ^= hlt[id - 1].sg_cterm ^ hlt[id_S - 1].sg_cterm;
7541 if (hlt[id - 1].sg_cterm_fg != hlt[id_S - 1].sg_cterm_fg) {
7542 hlt[hlcnt + i].sg_cterm_fg = hlt[id - 1].sg_cterm_fg;
7543 }
7544 if (hlt[id - 1].sg_cterm_bg != hlt[id_S - 1].sg_cterm_bg) {
7545 hlt[hlcnt + i].sg_cterm_bg = hlt[id - 1].sg_cterm_bg;
7546 }
7547 hlt[hlcnt + i].sg_gui ^= hlt[id - 1].sg_gui ^ hlt[id_S - 1].sg_gui;
7548 if (hlt[id - 1].sg_rgb_fg != hlt[id_S - 1].sg_rgb_fg) {
7549 hlt[hlcnt + i].sg_rgb_fg = hlt[id - 1].sg_rgb_fg;
7550 }
7551 if (hlt[id - 1].sg_rgb_bg != hlt[id_S - 1].sg_rgb_bg) {
7552 hlt[hlcnt + i].sg_rgb_bg = hlt[id - 1].sg_rgb_bg;
7553 }
7554 if (hlt[id - 1].sg_rgb_sp != hlt[id_S - 1].sg_rgb_sp) {
7555 hlt[hlcnt + i].sg_rgb_sp = hlt[id - 1].sg_rgb_sp;
7556 }
7557 highlight_ga.ga_len = hlcnt + i + 1;
7558 set_hl_attr(hlcnt + i); // At long last we can apply
7559 table[i] = syn_id2attr(hlcnt + i + 1);
7560}
7561
7562/// Translate highlight groups into attributes in highlight_attr[] and set up
7563/// the user highlights User1..9. A set of corresponding highlights to use on
7564/// top of HLF_SNC is computed. Called only when nvim starts and upon first
7565/// screen redraw after any :highlight command.
7566void highlight_changed(void)
7567{
7568 int id;
7569 char_u userhl[30]; // use 30 to avoid compiler warning
7570 int id_SNC = -1;
7571 int id_S = -1;
7572 int hlcnt;
7573
7574 need_highlight_changed = FALSE;
7575
7576 /// Translate builtin highlight groups into attributes for quick lookup.
7577 for (int hlf = 0; hlf < (int)HLF_COUNT; hlf++) {
7578 id = syn_check_group((char_u *)hlf_names[hlf], STRLEN(hlf_names[hlf]));
7579 if (id == 0) {
7580 abort();
7581 }
7582 int final_id = syn_get_final_id(id);
7583 if (hlf == (int)HLF_SNC) {
7584 id_SNC = final_id;
7585 } else if (hlf == (int)HLF_S) {
7586 id_S = final_id;
7587 }
7588
7589 highlight_attr[hlf] = hl_get_ui_attr(hlf, final_id,
7590 hlf == (int)HLF_INACTIVE);
7591
7592 if (highlight_attr[hlf] != highlight_attr_last[hlf]) {
7593 if (hlf == HLF_MSG) {
7594 clear_cmdline = true;
7595 }
7596 ui_call_hl_group_set(cstr_as_string((char *)hlf_names[hlf]),
7597 highlight_attr[hlf]);
7598 highlight_attr_last[hlf] = highlight_attr[hlf];
7599 }
7600 }
7601
7602 //
7603 // Setup the user highlights
7604 //
7605 // Temporarily utilize 10 more hl entries:
7606 // 9 for User1-User9 combined with StatusLineNC
7607 // 1 for StatusLine default
7608 // Must to be in there simultaneously in case of table overflows in
7609 // get_attr_entry()
7610 ga_grow(&highlight_ga, 10);
7611 hlcnt = highlight_ga.ga_len;
7612 if (id_S == -1) {
7613 // Make sure id_S is always valid to simplify code below. Use the last entry
7614 memset(&HL_TABLE()[hlcnt + 9], 0, sizeof(struct hl_group));
7615 id_S = hlcnt + 10;
7616 }
7617 for (int i = 0; i < 9; i++) {
7618 sprintf((char *)userhl, "User%d", i + 1);
7619 id = syn_name2id(userhl);
7620 if (id == 0) {
7621 highlight_user[i] = 0;
7622 highlight_stlnc[i] = 0;
7623 } else {
7624 highlight_user[i] = syn_id2attr(id);
7625 combine_stl_hlt(id, id_S, id_SNC, hlcnt, i, HLF_SNC, highlight_stlnc);
7626 }
7627 }
7628 highlight_ga.ga_len = hlcnt;
7629}
7630
7631
7632/*
7633 * Handle command line completion for :highlight command.
7634 */
7635void set_context_in_highlight_cmd(expand_T *xp, const char *arg)
7636{
7637 // Default: expand group names.
7638 xp->xp_context = EXPAND_HIGHLIGHT;
7639 xp->xp_pattern = (char_u *)arg;
7640 include_link = 2;
7641 include_default = 1;
7642
7643 /* (part of) subcommand already typed */
7644 if (*arg != NUL) {
7645 const char *p = (const char *)skiptowhite((const char_u *)arg);
7646 if (*p != NUL) { // Past "default" or group name.
7647 include_default = 0;
7648 if (strncmp("default", arg, p - arg) == 0) {
7649 arg = (const char *)skipwhite((const char_u *)p);
7650 xp->xp_pattern = (char_u *)arg;
7651 p = (const char *)skiptowhite((const char_u *)arg);
7652 }
7653 if (*p != NUL) { /* past group name */
7654 include_link = 0;
7655 if (arg[1] == 'i' && arg[0] == 'N') {
7656 highlight_list();
7657 }
7658 if (strncmp("link", arg, p - arg) == 0
7659 || strncmp("clear", arg, p - arg) == 0) {
7660 xp->xp_pattern = skipwhite((const char_u *)p);
7661 p = (const char *)skiptowhite(xp->xp_pattern);
7662 if (*p != NUL) { // Past first group name.
7663 xp->xp_pattern = skipwhite((const char_u *)p);
7664 p = (const char *)skiptowhite(xp->xp_pattern);
7665 }
7666 }
7667 if (*p != NUL) { // Past group name(s).
7668 xp->xp_context = EXPAND_NOTHING;
7669 }
7670 }
7671 }
7672 }
7673}
7674
7675/*
7676 * List highlighting matches in a nice way.
7677 */
7678static void highlight_list(void)
7679{
7680 int i;
7681
7682 for (i = 10; --i >= 0; ) {
7683 highlight_list_two(i, HL_ATTR(HLF_D));
7684 }
7685 for (i = 40; --i >= 0; ) {
7686 highlight_list_two(99, 0);
7687 }
7688}
7689
7690static void highlight_list_two(int cnt, int attr)
7691{
7692 msg_puts_attr(&("N \bI \b! \b"[cnt / 11]), attr);
7693 msg_clr_eos();
7694 ui_flush();
7695 os_delay(cnt == 99 ? 40L : (long)cnt * 50L, false);
7696}
7697
7698
7699/// Function given to ExpandGeneric() to obtain the list of group names.
7700const char *get_highlight_name(expand_T *const xp, int idx)
7701 FUNC_ATTR_WARN_UNUSED_RESULT
7702{
7703 return get_highlight_name_ext(xp, idx, true);
7704}
7705
7706
7707/// Obtain a highlight group name.
7708/// When "skip_cleared" is TRUE don't return a cleared entry.
7709const char *get_highlight_name_ext(expand_T *xp, int idx, int skip_cleared)
7710 FUNC_ATTR_WARN_UNUSED_RESULT
7711{
7712 if (idx < 0) {
7713 return NULL;
7714 }
7715
7716 // Items are never removed from the table, skip the ones that were cleared.
7717 if (skip_cleared && idx < highlight_ga.ga_len && HL_TABLE()[idx].sg_cleared) {
7718 return "";
7719 }
7720
7721 if (idx == highlight_ga.ga_len && include_none != 0) {
7722 return "none";
7723 } else if (idx == highlight_ga.ga_len + include_none
7724 && include_default != 0) {
7725 return "default";
7726 } else if (idx == highlight_ga.ga_len + include_none + include_default
7727 && include_link != 0) {
7728 return "link";
7729 } else if (idx == highlight_ga.ga_len + include_none + include_default + 1
7730 && include_link != 0) {
7731 return "clear";
7732 } else if (idx >= highlight_ga.ga_len) {
7733 return NULL;
7734 }
7735 return (const char *)HL_TABLE()[idx].sg_name;
7736}
7737
7738color_name_table_T color_name_table[] = {
7739 // Colors from rgb.txt
7740 { "AliceBlue", RGB_(0xf0, 0xf8, 0xff) },
7741 { "AntiqueWhite", RGB_(0xfa, 0xeb, 0xd7) },
7742 { "AntiqueWhite1", RGB_(0xff, 0xef, 0xdb) },
7743 { "AntiqueWhite2", RGB_(0xee, 0xdf, 0xcc) },
7744 { "AntiqueWhite3", RGB_(0xcd, 0xc0, 0xb0) },
7745 { "AntiqueWhite4", RGB_(0x8b, 0x83, 0x78) },
7746 { "Aqua", RGB_(0x00, 0xff, 0xff) },
7747 { "Aquamarine", RGB_(0x7f, 0xff, 0xd4) },
7748 { "Aquamarine1", RGB_(0x7f, 0xff, 0xd4) },
7749 { "Aquamarine2", RGB_(0x76, 0xee, 0xc6) },
7750 { "Aquamarine3", RGB_(0x66, 0xcd, 0xaa) },
7751 { "Aquamarine4", RGB_(0x45, 0x8b, 0x74) },
7752 { "Azure", RGB_(0xf0, 0xff, 0xff) },
7753 { "Azure1", RGB_(0xf0, 0xff, 0xff) },
7754 { "Azure2", RGB_(0xe0, 0xee, 0xee) },
7755 { "Azure3", RGB_(0xc1, 0xcd, 0xcd) },
7756 { "Azure4", RGB_(0x83, 0x8b, 0x8b) },
7757 { "Beige", RGB_(0xf5, 0xf5, 0xdc) },
7758 { "Bisque", RGB_(0xff, 0xe4, 0xc4) },
7759 { "Bisque1", RGB_(0xff, 0xe4, 0xc4) },
7760 { "Bisque2", RGB_(0xee, 0xd5, 0xb7) },
7761 { "Bisque3", RGB_(0xcd, 0xb7, 0x9e) },
7762 { "Bisque4", RGB_(0x8b, 0x7d, 0x6b) },
7763 { "Black", RGB_(0x00, 0x00, 0x00) },
7764 { "BlanchedAlmond", RGB_(0xff, 0xeb, 0xcd) },
7765 { "Blue", RGB_(0x00, 0x00, 0xff) },
7766 { "Blue1", RGB_(0x0, 0x0, 0xff) },
7767 { "Blue2", RGB_(0x0, 0x0, 0xee) },
7768 { "Blue3", RGB_(0x0, 0x0, 0xcd) },
7769 { "Blue4", RGB_(0x0, 0x0, 0x8b) },
7770 { "BlueViolet", RGB_(0x8a, 0x2b, 0xe2) },
7771 { "Brown", RGB_(0xa5, 0x2a, 0x2a) },
7772 { "Brown1", RGB_(0xff, 0x40, 0x40) },
7773 { "Brown2", RGB_(0xee, 0x3b, 0x3b) },
7774 { "Brown3", RGB_(0xcd, 0x33, 0x33) },
7775 { "Brown4", RGB_(0x8b, 0x23, 0x23) },
7776 { "BurlyWood", RGB_(0xde, 0xb8, 0x87) },
7777 { "Burlywood1", RGB_(0xff, 0xd3, 0x9b) },
7778 { "Burlywood2", RGB_(0xee, 0xc5, 0x91) },
7779 { "Burlywood3", RGB_(0xcd, 0xaa, 0x7d) },
7780 { "Burlywood4", RGB_(0x8b, 0x73, 0x55) },
7781 { "CadetBlue", RGB_(0x5f, 0x9e, 0xa0) },
7782 { "CadetBlue1", RGB_(0x98, 0xf5, 0xff) },
7783 { "CadetBlue2", RGB_(0x8e, 0xe5, 0xee) },
7784 { "CadetBlue3", RGB_(0x7a, 0xc5, 0xcd) },
7785 { "CadetBlue4", RGB_(0x53, 0x86, 0x8b) },
7786 { "ChartReuse", RGB_(0x7f, 0xff, 0x00) },
7787 { "Chartreuse1", RGB_(0x7f, 0xff, 0x0) },
7788 { "Chartreuse2", RGB_(0x76, 0xee, 0x0) },
7789 { "Chartreuse3", RGB_(0x66, 0xcd, 0x0) },
7790 { "Chartreuse4", RGB_(0x45, 0x8b, 0x0) },
7791 { "Chocolate", RGB_(0xd2, 0x69, 0x1e) },
7792 { "Chocolate1", RGB_(0xff, 0x7f, 0x24) },
7793 { "Chocolate2", RGB_(0xee, 0x76, 0x21) },
7794 { "Chocolate3", RGB_(0xcd, 0x66, 0x1d) },
7795 { "Chocolate4", RGB_(0x8b, 0x45, 0x13) },
7796 { "Coral", RGB_(0xff, 0x7f, 0x50) },
7797 { "Coral1", RGB_(0xff, 0x72, 0x56) },
7798 { "Coral2", RGB_(0xee, 0x6a, 0x50) },
7799 { "Coral3", RGB_(0xcd, 0x5b, 0x45) },
7800 { "Coral4", RGB_(0x8b, 0x3e, 0x2f) },
7801 { "CornFlowerBlue", RGB_(0x64, 0x95, 0xed) },
7802 { "Cornsilk", RGB_(0xff, 0xf8, 0xdc) },
7803 { "Cornsilk1", RGB_(0xff, 0xf8, 0xdc) },
7804 { "Cornsilk2", RGB_(0xee, 0xe8, 0xcd) },
7805 { "Cornsilk3", RGB_(0xcd, 0xc8, 0xb1) },
7806 { "Cornsilk4", RGB_(0x8b, 0x88, 0x78) },
7807 { "Crimson", RGB_(0xdc, 0x14, 0x3c) },
7808 { "Cyan", RGB_(0x00, 0xff, 0xff) },
7809 { "Cyan1", RGB_(0x0, 0xff, 0xff) },
7810 { "Cyan2", RGB_(0x0, 0xee, 0xee) },
7811 { "Cyan3", RGB_(0x0, 0xcd, 0xcd) },
7812 { "Cyan4", RGB_(0x0, 0x8b, 0x8b) },
7813 { "DarkBlue", RGB_(0x00, 0x00, 0x8b) },
7814 { "DarkCyan", RGB_(0x00, 0x8b, 0x8b) },
7815 { "DarkGoldenRod", RGB_(0xb8, 0x86, 0x0b) },
7816 { "DarkGoldenrod1", RGB_(0xff, 0xb9, 0xf) },
7817 { "DarkGoldenrod2", RGB_(0xee, 0xad, 0xe) },
7818 { "DarkGoldenrod3", RGB_(0xcd, 0x95, 0xc) },
7819 { "DarkGoldenrod4", RGB_(0x8b, 0x65, 0x8) },
7820 { "DarkGray", RGB_(0xa9, 0xa9, 0xa9) },
7821 { "DarkGreen", RGB_(0x00, 0x64, 0x00) },
7822 { "DarkGrey", RGB_(0xa9, 0xa9, 0xa9) },
7823 { "DarkKhaki", RGB_(0xbd, 0xb7, 0x6b) },
7824 { "DarkMagenta", RGB_(0x8b, 0x00, 0x8b) },
7825 { "DarkOliveGreen", RGB_(0x55, 0x6b, 0x2f) },
7826 { "DarkOliveGreen1", RGB_(0xca, 0xff, 0x70) },
7827 { "DarkOliveGreen2", RGB_(0xbc, 0xee, 0x68) },
7828 { "DarkOliveGreen3", RGB_(0xa2, 0xcd, 0x5a) },
7829 { "DarkOliveGreen4", RGB_(0x6e, 0x8b, 0x3d) },
7830 { "DarkOrange", RGB_(0xff, 0x8c, 0x00) },
7831 { "DarkOrange1", RGB_(0xff, 0x7f, 0x0) },
7832 { "DarkOrange2", RGB_(0xee, 0x76, 0x0) },
7833 { "DarkOrange3", RGB_(0xcd, 0x66, 0x0) },
7834 { "DarkOrange4", RGB_(0x8b, 0x45, 0x0) },
7835 { "DarkOrchid", RGB_(0x99, 0x32, 0xcc) },
7836 { "DarkOrchid1", RGB_(0xbf, 0x3e, 0xff) },
7837 { "DarkOrchid2", RGB_(0xb2, 0x3a, 0xee) },
7838 { "DarkOrchid3", RGB_(0x9a, 0x32, 0xcd) },
7839 { "DarkOrchid4", RGB_(0x68, 0x22, 0x8b) },
7840 { "DarkRed", RGB_(0x8b, 0x00, 0x00) },
7841 { "DarkSalmon", RGB_(0xe9, 0x96, 0x7a) },
7842 { "DarkSeaGreen", RGB_(0x8f, 0xbc, 0x8f) },
7843 { "DarkSeaGreen1", RGB_(0xc1, 0xff, 0xc1) },
7844 { "DarkSeaGreen2", RGB_(0xb4, 0xee, 0xb4) },
7845 { "DarkSeaGreen3", RGB_(0x9b, 0xcd, 0x9b) },
7846 { "DarkSeaGreen4", RGB_(0x69, 0x8b, 0x69) },
7847 { "DarkSlateBlue", RGB_(0x48, 0x3d, 0x8b) },
7848 { "DarkSlateGray", RGB_(0x2f, 0x4f, 0x4f) },
7849 { "DarkSlateGray1", RGB_(0x97, 0xff, 0xff) },
7850 { "DarkSlateGray2", RGB_(0x8d, 0xee, 0xee) },
7851 { "DarkSlateGray3", RGB_(0x79, 0xcd, 0xcd) },
7852 { "DarkSlateGray4", RGB_(0x52, 0x8b, 0x8b) },
7853 { "DarkSlateGrey", RGB_(0x2f, 0x4f, 0x4f) },
7854 { "DarkTurquoise", RGB_(0x00, 0xce, 0xd1) },
7855 { "DarkViolet", RGB_(0x94, 0x00, 0xd3) },
7856 { "DarkYellow", RGB_(0xbb, 0xbb, 0x00) },
7857 { "DeepPink", RGB_(0xff, 0x14, 0x93) },
7858 { "DeepPink1", RGB_(0xff, 0x14, 0x93) },
7859 { "DeepPink2", RGB_(0xee, 0x12, 0x89) },
7860 { "DeepPink3", RGB_(0xcd, 0x10, 0x76) },
7861 { "DeepPink4", RGB_(0x8b, 0xa, 0x50) },
7862 { "DeepSkyBlue", RGB_(0x00, 0xbf, 0xff) },
7863 { "DeepSkyBlue1", RGB_(0x0, 0xbf, 0xff) },
7864 { "DeepSkyBlue2", RGB_(0x0, 0xb2, 0xee) },
7865 { "DeepSkyBlue3", RGB_(0x0, 0x9a, 0xcd) },
7866 { "DeepSkyBlue4", RGB_(0x0, 0x68, 0x8b) },
7867 { "DimGray", RGB_(0x69, 0x69, 0x69) },
7868 { "DimGrey", RGB_(0x69, 0x69, 0x69) },
7869 { "DodgerBlue", RGB_(0x1e, 0x90, 0xff) },
7870 { "DodgerBlue1", RGB_(0x1e, 0x90, 0xff) },
7871 { "DodgerBlue2", RGB_(0x1c, 0x86, 0xee) },
7872 { "DodgerBlue3", RGB_(0x18, 0x74, 0xcd) },
7873 { "DodgerBlue4", RGB_(0x10, 0x4e, 0x8b) },
7874 { "Firebrick", RGB_(0xb2, 0x22, 0x22) },
7875 { "Firebrick1", RGB_(0xff, 0x30, 0x30) },
7876 { "Firebrick2", RGB_(0xee, 0x2c, 0x2c) },
7877 { "Firebrick3", RGB_(0xcd, 0x26, 0x26) },
7878 { "Firebrick4", RGB_(0x8b, 0x1a, 0x1a) },
7879 { "FloralWhite", RGB_(0xff, 0xfa, 0xf0) },
7880 { "ForestGreen", RGB_(0x22, 0x8b, 0x22) },
7881 { "Fuchsia", RGB_(0xff, 0x00, 0xff) },
7882 { "Gainsboro", RGB_(0xdc, 0xdc, 0xdc) },
7883 { "GhostWhite", RGB_(0xf8, 0xf8, 0xff) },
7884 { "Gold", RGB_(0xff, 0xd7, 0x00) },
7885 { "Gold1", RGB_(0xff, 0xd7, 0x0) },
7886 { "Gold2", RGB_(0xee, 0xc9, 0x0) },
7887 { "Gold3", RGB_(0xcd, 0xad, 0x0) },
7888 { "Gold4", RGB_(0x8b, 0x75, 0x0) },
7889 { "GoldenRod", RGB_(0xda, 0xa5, 0x20) },
7890 { "Goldenrod1", RGB_(0xff, 0xc1, 0x25) },
7891 { "Goldenrod2", RGB_(0xee, 0xb4, 0x22) },
7892 { "Goldenrod3", RGB_(0xcd, 0x9b, 0x1d) },
7893 { "Goldenrod4", RGB_(0x8b, 0x69, 0x14) },
7894 { "Gray", RGB_(0x80, 0x80, 0x80) },
7895 { "Gray0", RGB_(0x0, 0x0, 0x0) },
7896 { "Gray1", RGB_(0x3, 0x3, 0x3) },
7897 { "Gray10", RGB_(0x1a, 0x1a, 0x1a) },
7898 { "Gray100", RGB_(0xff, 0xff, 0xff) },
7899 { "Gray11", RGB_(0x1c, 0x1c, 0x1c) },
7900 { "Gray12", RGB_(0x1f, 0x1f, 0x1f) },
7901 { "Gray13", RGB_(0x21, 0x21, 0x21) },
7902 { "Gray14", RGB_(0x24, 0x24, 0x24) },
7903 { "Gray15", RGB_(0x26, 0x26, 0x26) },
7904 { "Gray16", RGB_(0x29, 0x29, 0x29) },
7905 { "Gray17", RGB_(0x2b, 0x2b, 0x2b) },
7906 { "Gray18", RGB_(0x2e, 0x2e, 0x2e) },
7907 { "Gray19", RGB_(0x30, 0x30, 0x30) },
7908 { "Gray2", RGB_(0x5, 0x5, 0x5) },
7909 { "Gray20", RGB_(0x33, 0x33, 0x33) },
7910 { "Gray21", RGB_(0x36, 0x36, 0x36) },
7911 { "Gray22", RGB_(0x38, 0x38, 0x38) },
7912 { "Gray23", RGB_(0x3b, 0x3b, 0x3b) },
7913 { "Gray24", RGB_(0x3d, 0x3d, 0x3d) },
7914 { "Gray25", RGB_(0x40, 0x40, 0x40) },
7915 { "Gray26", RGB_(0x42, 0x42, 0x42) },
7916 { "Gray27", RGB_(0x45, 0x45, 0x45) },
7917 { "Gray28", RGB_(0x47, 0x47, 0x47) },
7918 { "Gray29", RGB_(0x4a, 0x4a, 0x4a) },
7919 { "Gray3", RGB_(0x8, 0x8, 0x8) },
7920 { "Gray30", RGB_(0x4d, 0x4d, 0x4d) },
7921 { "Gray31", RGB_(0x4f, 0x4f, 0x4f) },
7922 { "Gray32", RGB_(0x52, 0x52, 0x52) },
7923 { "Gray33", RGB_(0x54, 0x54, 0x54) },
7924 { "Gray34", RGB_(0x57, 0x57, 0x57) },
7925 { "Gray35", RGB_(0x59, 0x59, 0x59) },
7926 { "Gray36", RGB_(0x5c, 0x5c, 0x5c) },
7927 { "Gray37", RGB_(0x5e, 0x5e, 0x5e) },
7928 { "Gray38", RGB_(0x61, 0x61, 0x61) },
7929 { "Gray39", RGB_(0x63, 0x63, 0x63) },
7930 { "Gray4", RGB_(0xa, 0xa, 0xa) },
7931 { "Gray40", RGB_(0x66, 0x66, 0x66) },
7932 { "Gray41", RGB_(0x69, 0x69, 0x69) },
7933 { "Gray42", RGB_(0x6b, 0x6b, 0x6b) },
7934 { "Gray43", RGB_(0x6e, 0x6e, 0x6e) },
7935 { "Gray44", RGB_(0x70, 0x70, 0x70) },
7936 { "Gray45", RGB_(0x73, 0x73, 0x73) },
7937 { "Gray46", RGB_(0x75, 0x75, 0x75) },
7938 { "Gray47", RGB_(0x78, 0x78, 0x78) },
7939 { "Gray48", RGB_(0x7a, 0x7a, 0x7a) },
7940 { "Gray49", RGB_(0x7d, 0x7d, 0x7d) },
7941 { "Gray5", RGB_(0xd, 0xd, 0xd) },
7942 { "Gray50", RGB_(0x7f, 0x7f, 0x7f) },
7943 { "Gray51", RGB_(0x82, 0x82, 0x82) },
7944 { "Gray52", RGB_(0x85, 0x85, 0x85) },
7945 { "Gray53", RGB_(0x87, 0x87, 0x87) },
7946 { "Gray54", RGB_(0x8a, 0x8a, 0x8a) },
7947 { "Gray55", RGB_(0x8c, 0x8c, 0x8c) },
7948 { "Gray56", RGB_(0x8f, 0x8f, 0x8f) },
7949 { "Gray57", RGB_(0x91, 0x91, 0x91) },
7950 { "Gray58", RGB_(0x94, 0x94, 0x94) },
7951 { "Gray59", RGB_(0x96, 0x96, 0x96) },
7952 { "Gray6", RGB_(0xf, 0xf, 0xf) },
7953 { "Gray60", RGB_(0x99, 0x99, 0x99) },
7954 { "Gray61", RGB_(0x9c, 0x9c, 0x9c) },
7955 { "Gray62", RGB_(0x9e, 0x9e, 0x9e) },
7956 { "Gray63", RGB_(0xa1, 0xa1, 0xa1) },
7957 { "Gray64", RGB_(0xa3, 0xa3, 0xa3) },
7958 { "Gray65", RGB_(0xa6, 0xa6, 0xa6) },
7959 { "Gray66", RGB_(0xa8, 0xa8, 0xa8) },
7960 { "Gray67", RGB_(0xab, 0xab, 0xab) },
7961 { "Gray68", RGB_(0xad, 0xad, 0xad) },
7962 { "Gray69", RGB_(0xb0, 0xb0, 0xb0) },
7963 { "Gray7", RGB_(0x12, 0x12, 0x12) },
7964 { "Gray70", RGB_(0xb3, 0xb3, 0xb3) },
7965 { "Gray71", RGB_(0xb5, 0xb5, 0xb5) },
7966 { "Gray72", RGB_(0xb8, 0xb8, 0xb8) },
7967 { "Gray73", RGB_(0xba, 0xba, 0xba) },
7968 { "Gray74", RGB_(0xbd, 0xbd, 0xbd) },
7969 { "Gray75", RGB_(0xbf, 0xbf, 0xbf) },
7970 { "Gray76", RGB_(0xc2, 0xc2, 0xc2) },
7971 { "Gray77", RGB_(0xc4, 0xc4, 0xc4) },
7972 { "Gray78", RGB_(0xc7, 0xc7, 0xc7) },
7973 { "Gray79", RGB_(0xc9, 0xc9, 0xc9) },
7974 { "Gray8", RGB_(0x14, 0x14, 0x14) },
7975 { "Gray80", RGB_(0xcc, 0xcc, 0xcc) },
7976 { "Gray81", RGB_(0xcf, 0xcf, 0xcf) },
7977 { "Gray82", RGB_(0xd1, 0xd1, 0xd1) },
7978 { "Gray83", RGB_(0xd4, 0xd4, 0xd4) },
7979 { "Gray84", RGB_(0xd6, 0xd6, 0xd6) },
7980 { "Gray85", RGB_(0xd9, 0xd9, 0xd9) },
7981 { "Gray86", RGB_(0xdb, 0xdb, 0xdb) },
7982 { "Gray87", RGB_(0xde, 0xde, 0xde) },
7983 { "Gray88", RGB_(0xe0, 0xe0, 0xe0) },
7984 { "Gray89", RGB_(0xe3, 0xe3, 0xe3) },
7985 { "Gray9", RGB_(0x17, 0x17, 0x17) },
7986 { "Gray90", RGB_(0xe5, 0xe5, 0xe5) },
7987 { "Gray91", RGB_(0xe8, 0xe8, 0xe8) },
7988 { "Gray92", RGB_(0xeb, 0xeb, 0xeb) },
7989 { "Gray93", RGB_(0xed, 0xed, 0xed) },
7990 { "Gray94", RGB_(0xf0, 0xf0, 0xf0) },
7991 { "Gray95", RGB_(0xf2, 0xf2, 0xf2) },
7992 { "Gray96", RGB_(0xf5, 0xf5, 0xf5) },
7993 { "Gray97", RGB_(0xf7, 0xf7, 0xf7) },
7994 { "Gray98", RGB_(0xfa, 0xfa, 0xfa) },
7995 { "Gray99", RGB_(0xfc, 0xfc, 0xfc) },
7996 { "Green", RGB_(0x00, 0x80, 0x00) },
7997 { "Green1", RGB_(0x0, 0xff, 0x0) },
7998 { "Green2", RGB_(0x0, 0xee, 0x0) },
7999 { "Green3", RGB_(0x0, 0xcd, 0x0) },
8000 { "Green4", RGB_(0x0, 0x8b, 0x0) },
8001 { "GreenYellow", RGB_(0xad, 0xff, 0x2f) },
8002 { "Grey", RGB_(0x80, 0x80, 0x80) },
8003 { "Grey0", RGB_(0x0, 0x0, 0x0) },
8004 { "Grey1", RGB_(0x3, 0x3, 0x3) },
8005 { "Grey10", RGB_(0x1a, 0x1a, 0x1a) },
8006 { "Grey100", RGB_(0xff, 0xff, 0xff) },
8007 { "Grey11", RGB_(0x1c, 0x1c, 0x1c) },
8008 { "Grey12", RGB_(0x1f, 0x1f, 0x1f) },
8009 { "Grey13", RGB_(0x21, 0x21, 0x21) },
8010 { "Grey14", RGB_(0x24, 0x24, 0x24) },
8011 { "Grey15", RGB_(0x26, 0x26, 0x26) },
8012 { "Grey16", RGB_(0x29, 0x29, 0x29) },
8013 { "Grey17", RGB_(0x2b, 0x2b, 0x2b) },
8014 { "Grey18", RGB_(0x2e, 0x2e, 0x2e) },
8015 { "Grey19", RGB_(0x30, 0x30, 0x30) },
8016 { "Grey2", RGB_(0x5, 0x5, 0x5) },
8017 { "Grey20", RGB_(0x33, 0x33, 0x33) },
8018 { "Grey21", RGB_(0x36, 0x36, 0x36) },
8019 { "Grey22", RGB_(0x38, 0x38, 0x38) },
8020 { "Grey23", RGB_(0x3b, 0x3b, 0x3b) },
8021 { "Grey24", RGB_(0x3d, 0x3d, 0x3d) },
8022 { "Grey25", RGB_(0x40, 0x40, 0x40) },
8023 { "Grey26", RGB_(0x42, 0x42, 0x42) },
8024 { "Grey27", RGB_(0x45, 0x45, 0x45) },
8025 { "Grey28", RGB_(0x47, 0x47, 0x47) },
8026 { "Grey29", RGB_(0x4a, 0x4a, 0x4a) },
8027 { "Grey3", RGB_(0x8, 0x8, 0x8) },
8028 { "Grey30", RGB_(0x4d, 0x4d, 0x4d) },
8029 { "Grey31", RGB_(0x4f, 0x4f, 0x4f) },
8030 { "Grey32", RGB_(0x52, 0x52, 0x52) },
8031 { "Grey33", RGB_(0x54, 0x54, 0x54) },
8032 { "Grey34", RGB_(0x57, 0x57, 0x57) },
8033 { "Grey35", RGB_(0x59, 0x59, 0x59) },
8034 { "Grey36", RGB_(0x5c, 0x5c, 0x5c) },
8035 { "Grey37", RGB_(0x5e, 0x5e, 0x5e) },
8036 { "Grey38", RGB_(0x61, 0x61, 0x61) },
8037 { "Grey39", RGB_(0x63, 0x63, 0x63) },
8038 { "Grey4", RGB_(0xa, 0xa, 0xa) },
8039 { "Grey40", RGB_(0x66, 0x66, 0x66) },
8040 { "Grey41", RGB_(0x69, 0x69, 0x69) },
8041 { "Grey42", RGB_(0x6b, 0x6b, 0x6b) },
8042 { "Grey43", RGB_(0x6e, 0x6e, 0x6e) },
8043 { "Grey44", RGB_(0x70, 0x70, 0x70) },
8044 { "Grey45", RGB_(0x73, 0x73, 0x73) },
8045 { "Grey46", RGB_(0x75, 0x75, 0x75) },
8046 { "Grey47", RGB_(0x78, 0x78, 0x78) },
8047 { "Grey48", RGB_(0x7a, 0x7a, 0x7a) },
8048 { "Grey49", RGB_(0x7d, 0x7d, 0x7d) },
8049 { "Grey5", RGB_(0xd, 0xd, 0xd) },
8050 { "Grey50", RGB_(0x7f, 0x7f, 0x7f) },
8051 { "Grey51", RGB_(0x82, 0x82, 0x82) },
8052 { "Grey52", RGB_(0x85, 0x85, 0x85) },
8053 { "Grey53", RGB_(0x87, 0x87, 0x87) },
8054 { "Grey54", RGB_(0x8a, 0x8a, 0x8a) },
8055 { "Grey55", RGB_(0x8c, 0x8c, 0x8c) },
8056 { "Grey56", RGB_(0x8f, 0x8f, 0x8f) },
8057 { "Grey57", RGB_(0x91, 0x91, 0x91) },
8058 { "Grey58", RGB_(0x94, 0x94, 0x94) },
8059 { "Grey59", RGB_(0x96, 0x96, 0x96) },
8060 { "Grey6", RGB_(0xf, 0xf, 0xf) },
8061 { "Grey60", RGB_(0x99, 0x99, 0x99) },
8062 { "Grey61", RGB_(0x9c, 0x9c, 0x9c) },
8063 { "Grey62", RGB_(0x9e, 0x9e, 0x9e) },
8064 { "Grey63", RGB_(0xa1, 0xa1, 0xa1) },
8065 { "Grey64", RGB_(0xa3, 0xa3, 0xa3) },
8066 { "Grey65", RGB_(0xa6, 0xa6, 0xa6) },
8067 { "Grey66", RGB_(0xa8, 0xa8, 0xa8) },
8068 { "Grey67", RGB_(0xab, 0xab, 0xab) },
8069 { "Grey68", RGB_(0xad, 0xad, 0xad) },
8070 { "Grey69", RGB_(0xb0, 0xb0, 0xb0) },
8071 { "Grey7", RGB_(0x12, 0x12, 0x12) },
8072 { "Grey70", RGB_(0xb3, 0xb3, 0xb3) },
8073 { "Grey71", RGB_(0xb5, 0xb5, 0xb5) },
8074 { "Grey72", RGB_(0xb8, 0xb8, 0xb8) },
8075 { "Grey73", RGB_(0xba, 0xba, 0xba) },
8076 { "Grey74", RGB_(0xbd, 0xbd, 0xbd) },
8077 { "Grey75", RGB_(0xbf, 0xbf, 0xbf) },
8078 { "Grey76", RGB_(0xc2, 0xc2, 0xc2) },
8079 { "Grey77", RGB_(0xc4, 0xc4, 0xc4) },
8080 { "Grey78", RGB_(0xc7, 0xc7, 0xc7) },
8081 { "Grey79", RGB_(0xc9, 0xc9, 0xc9) },
8082 { "Grey8", RGB_(0x14, 0x14, 0x14) },
8083 { "Grey80", RGB_(0xcc, 0xcc, 0xcc) },
8084 { "Grey81", RGB_(0xcf, 0xcf, 0xcf) },
8085 { "Grey82", RGB_(0xd1, 0xd1, 0xd1) },
8086 { "Grey83", RGB_(0xd4, 0xd4, 0xd4) },
8087 { "Grey84", RGB_(0xd6, 0xd6, 0xd6) },
8088 { "Grey85", RGB_(0xd9, 0xd9, 0xd9) },
8089 { "Grey86", RGB_(0xdb, 0xdb, 0xdb) },
8090 { "Grey87", RGB_(0xde, 0xde, 0xde) },
8091 { "Grey88", RGB_(0xe0, 0xe0, 0xe0) },
8092 { "Grey89", RGB_(0xe3, 0xe3, 0xe3) },
8093 { "Grey9", RGB_(0x17, 0x17, 0x17) },
8094 { "Grey90", RGB_(0xe5, 0xe5, 0xe5) },
8095 { "Grey91", RGB_(0xe8, 0xe8, 0xe8) },
8096 { "Grey92", RGB_(0xeb, 0xeb, 0xeb) },
8097 { "Grey93", RGB_(0xed, 0xed, 0xed) },
8098 { "Grey94", RGB_(0xf0, 0xf0, 0xf0) },
8099 { "Grey95", RGB_(0xf2, 0xf2, 0xf2) },
8100 { "Grey96", RGB_(0xf5, 0xf5, 0xf5) },
8101 { "Grey97", RGB_(0xf7, 0xf7, 0xf7) },
8102 { "Grey98", RGB_(0xfa, 0xfa, 0xfa) },
8103 { "Grey99", RGB_(0xfc, 0xfc, 0xfc) },
8104 { "Honeydew", RGB_(0xf0, 0xff, 0xf0) },
8105 { "Honeydew1", RGB_(0xf0, 0xff, 0xf0) },
8106 { "Honeydew2", RGB_(0xe0, 0xee, 0xe0) },
8107 { "Honeydew3", RGB_(0xc1, 0xcd, 0xc1) },
8108 { "Honeydew4", RGB_(0x83, 0x8b, 0x83) },
8109 { "HotPink", RGB_(0xff, 0x69, 0xb4) },
8110 { "HotPink1", RGB_(0xff, 0x6e, 0xb4) },
8111 { "HotPink2", RGB_(0xee, 0x6a, 0xa7) },
8112 { "HotPink3", RGB_(0xcd, 0x60, 0x90) },
8113 { "HotPink4", RGB_(0x8b, 0x3a, 0x62) },
8114 { "IndianRed", RGB_(0xcd, 0x5c, 0x5c) },
8115 { "IndianRed1", RGB_(0xff, 0x6a, 0x6a) },
8116 { "IndianRed2", RGB_(0xee, 0x63, 0x63) },
8117 { "IndianRed3", RGB_(0xcd, 0x55, 0x55) },
8118 { "IndianRed4", RGB_(0x8b, 0x3a, 0x3a) },
8119 { "Indigo", RGB_(0x4b, 0x00, 0x82) },
8120 { "Ivory", RGB_(0xff, 0xff, 0xf0) },
8121 { "Ivory1", RGB_(0xff, 0xff, 0xf0) },
8122 { "Ivory2", RGB_(0xee, 0xee, 0xe0) },
8123 { "Ivory3", RGB_(0xcd, 0xcd, 0xc1) },
8124 { "Ivory4", RGB_(0x8b, 0x8b, 0x83) },
8125 { "Khaki", RGB_(0xf0, 0xe6, 0x8c) },
8126 { "Khaki1", RGB_(0xff, 0xf6, 0x8f) },
8127 { "Khaki2", RGB_(0xee, 0xe6, 0x85) },
8128 { "Khaki3", RGB_(0xcd, 0xc6, 0x73) },
8129 { "Khaki4", RGB_(0x8b, 0x86, 0x4e) },
8130 { "Lavender", RGB_(0xe6, 0xe6, 0xfa) },
8131 { "LavenderBlush", RGB_(0xff, 0xf0, 0xf5) },
8132 { "LavenderBlush1", RGB_(0xff, 0xf0, 0xf5) },
8133 { "LavenderBlush2", RGB_(0xee, 0xe0, 0xe5) },
8134 { "LavenderBlush3", RGB_(0xcd, 0xc1, 0xc5) },
8135 { "LavenderBlush4", RGB_(0x8b, 0x83, 0x86) },
8136 { "LawnGreen", RGB_(0x7c, 0xfc, 0x00) },
8137 { "LemonChiffon", RGB_(0xff, 0xfa, 0xcd) },
8138 { "LemonChiffon1", RGB_(0xff, 0xfa, 0xcd) },
8139 { "LemonChiffon2", RGB_(0xee, 0xe9, 0xbf) },
8140 { "LemonChiffon3", RGB_(0xcd, 0xc9, 0xa5) },
8141 { "LemonChiffon4", RGB_(0x8b, 0x89, 0x70) },
8142 { "LightBlue", RGB_(0xad, 0xd8, 0xe6) },
8143 { "LightBlue1", RGB_(0xbf, 0xef, 0xff) },
8144 { "LightBlue2", RGB_(0xb2, 0xdf, 0xee) },
8145 { "LightBlue3", RGB_(0x9a, 0xc0, 0xcd) },
8146 { "LightBlue4", RGB_(0x68, 0x83, 0x8b) },
8147 { "LightCoral", RGB_(0xf0, 0x80, 0x80) },
8148 { "LightCyan", RGB_(0xe0, 0xff, 0xff) },
8149 { "LightCyan1", RGB_(0xe0, 0xff, 0xff) },
8150 { "LightCyan2", RGB_(0xd1, 0xee, 0xee) },
8151 { "LightCyan3", RGB_(0xb4, 0xcd, 0xcd) },
8152 { "LightCyan4", RGB_(0x7a, 0x8b, 0x8b) },
8153 { "LightGoldenrod", RGB_(0xee, 0xdd, 0x82) },
8154 { "LightGoldenrod1", RGB_(0xff, 0xec, 0x8b) },
8155 { "LightGoldenrod2", RGB_(0xee, 0xdc, 0x82) },
8156 { "LightGoldenrod3", RGB_(0xcd, 0xbe, 0x70) },
8157 { "LightGoldenrod4", RGB_(0x8b, 0x81, 0x4c) },
8158 { "LightGoldenRodYellow", RGB_(0xfa, 0xfa, 0xd2) },
8159 { "LightGray", RGB_(0xd3, 0xd3, 0xd3) },
8160 { "LightGreen", RGB_(0x90, 0xee, 0x90) },
8161 { "LightGrey", RGB_(0xd3, 0xd3, 0xd3) },
8162 { "LightMagenta", RGB_(0xff, 0xbb, 0xff) },
8163 { "LightPink", RGB_(0xff, 0xb6, 0xc1) },
8164 { "LightPink1", RGB_(0xff, 0xae, 0xb9) },
8165 { "LightPink2", RGB_(0xee, 0xa2, 0xad) },
8166 { "LightPink3", RGB_(0xcd, 0x8c, 0x95) },
8167 { "LightPink4", RGB_(0x8b, 0x5f, 0x65) },
8168 { "LightRed", RGB_(0xff, 0xbb, 0xbb) },
8169 { "LightSalmon", RGB_(0xff, 0xa0, 0x7a) },
8170 { "LightSalmon1", RGB_(0xff, 0xa0, 0x7a) },
8171 { "LightSalmon2", RGB_(0xee, 0x95, 0x72) },
8172 { "LightSalmon3", RGB_(0xcd, 0x81, 0x62) },
8173 { "LightSalmon4", RGB_(0x8b, 0x57, 0x42) },
8174 { "LightSeaGreen", RGB_(0x20, 0xb2, 0xaa) },
8175 { "LightSkyBlue", RGB_(0x87, 0xce, 0xfa) },
8176 { "LightSkyBlue1", RGB_(0xb0, 0xe2, 0xff) },
8177 { "LightSkyBlue2", RGB_(0xa4, 0xd3, 0xee) },
8178 { "LightSkyBlue3", RGB_(0x8d, 0xb6, 0xcd) },
8179 { "LightSkyBlue4", RGB_(0x60, 0x7b, 0x8b) },
8180 { "LightSlateBlue", RGB_(0x84, 0x70, 0xff) },
8181 { "LightSlateGray", RGB_(0x77, 0x88, 0x99) },
8182 { "LightSlateGrey", RGB_(0x77, 0x88, 0x99) },
8183 { "LightSteelBlue", RGB_(0xb0, 0xc4, 0xde) },
8184 { "LightSteelBlue1", RGB_(0xca, 0xe1, 0xff) },
8185 { "LightSteelBlue2", RGB_(0xbc, 0xd2, 0xee) },
8186 { "LightSteelBlue3", RGB_(0xa2, 0xb5, 0xcd) },
8187 { "LightSteelBlue4", RGB_(0x6e, 0x7b, 0x8b) },
8188 { "LightYellow", RGB_(0xff, 0xff, 0xe0) },
8189 { "LightYellow1", RGB_(0xff, 0xff, 0xe0) },
8190 { "LightYellow2", RGB_(0xee, 0xee, 0xd1) },
8191 { "LightYellow3", RGB_(0xcd, 0xcd, 0xb4) },
8192 { "LightYellow4", RGB_(0x8b, 0x8b, 0x7a) },
8193 { "Lime", RGB_(0x00, 0xff, 0x00) },
8194 { "LimeGreen", RGB_(0x32, 0xcd, 0x32) },
8195 { "Linen", RGB_(0xfa, 0xf0, 0xe6) },
8196 { "Magenta", RGB_(0xff, 0x00, 0xff) },
8197 { "Magenta1", RGB_(0xff, 0x0, 0xff) },
8198 { "Magenta2", RGB_(0xee, 0x0, 0xee) },
8199 { "Magenta3", RGB_(0xcd, 0x0, 0xcd) },
8200 { "Magenta4", RGB_(0x8b, 0x0, 0x8b) },
8201 { "Maroon", RGB_(0x80, 0x00, 0x00) },
8202 { "Maroon1", RGB_(0xff, 0x34, 0xb3) },
8203 { "Maroon2", RGB_(0xee, 0x30, 0xa7) },
8204 { "Maroon3", RGB_(0xcd, 0x29, 0x90) },
8205 { "Maroon4", RGB_(0x8b, 0x1c, 0x62) },
8206 { "MediumAquamarine", RGB_(0x66, 0xcd, 0xaa) },
8207 { "MediumBlue", RGB_(0x00, 0x00, 0xcd) },
8208 { "MediumOrchid", RGB_(0xba, 0x55, 0xd3) },
8209 { "MediumOrchid1", RGB_(0xe0, 0x66, 0xff) },
8210 { "MediumOrchid2", RGB_(0xd1, 0x5f, 0xee) },
8211 { "MediumOrchid3", RGB_(0xb4, 0x52, 0xcd) },
8212 { "MediumOrchid4", RGB_(0x7a, 0x37, 0x8b) },
8213 { "MediumPurple", RGB_(0x93, 0x70, 0xdb) },
8214 { "MediumPurple1", RGB_(0xab, 0x82, 0xff) },
8215 { "MediumPurple2", RGB_(0x9f, 0x79, 0xee) },
8216 { "MediumPurple3", RGB_(0x89, 0x68, 0xcd) },
8217 { "MediumPurple4", RGB_(0x5d, 0x47, 0x8b) },
8218 { "MediumSeaGreen", RGB_(0x3c, 0xb3, 0x71) },
8219 { "MediumSlateBlue", RGB_(0x7b, 0x68, 0xee) },
8220 { "MediumSpringGreen", RGB_(0x00, 0xfa, 0x9a) },
8221 { "MediumTurquoise", RGB_(0x48, 0xd1, 0xcc) },
8222 { "MediumVioletRed", RGB_(0xc7, 0x15, 0x85) },
8223 { "MidnightBlue", RGB_(0x19, 0x19, 0x70) },
8224 { "MintCream", RGB_(0xf5, 0xff, 0xfa) },
8225 { "MistyRose", RGB_(0xff, 0xe4, 0xe1) },
8226 { "MistyRose1", RGB_(0xff, 0xe4, 0xe1) },
8227 { "MistyRose2", RGB_(0xee, 0xd5, 0xd2) },
8228 { "MistyRose3", RGB_(0xcd, 0xb7, 0xb5) },
8229 { "MistyRose4", RGB_(0x8b, 0x7d, 0x7b) },
8230 { "Moccasin", RGB_(0xff, 0xe4, 0xb5) },
8231 { "NavajoWhite", RGB_(0xff, 0xde, 0xad) },
8232 { "NavajoWhite1", RGB_(0xff, 0xde, 0xad) },
8233 { "NavajoWhite2", RGB_(0xee, 0xcf, 0xa1) },
8234 { "NavajoWhite3", RGB_(0xcd, 0xb3, 0x8b) },
8235 { "NavajoWhite4", RGB_(0x8b, 0x79, 0x5e) },
8236 { "Navy", RGB_(0x00, 0x00, 0x80) },
8237 { "NavyBlue", RGB_(0x0, 0x0, 0x80) },
8238 { "OldLace", RGB_(0xfd, 0xf5, 0xe6) },
8239 { "Olive", RGB_(0x80, 0x80, 0x00) },
8240 { "OliveDrab", RGB_(0x6b, 0x8e, 0x23) },
8241 { "OliveDrab1", RGB_(0xc0, 0xff, 0x3e) },
8242 { "OliveDrab2", RGB_(0xb3, 0xee, 0x3a) },
8243 { "OliveDrab3", RGB_(0x9a, 0xcd, 0x32) },
8244 { "OliveDrab4", RGB_(0x69, 0x8b, 0x22) },
8245 { "Orange", RGB_(0xff, 0xa5, 0x00) },
8246 { "Orange1", RGB_(0xff, 0xa5, 0x0) },
8247 { "Orange2", RGB_(0xee, 0x9a, 0x0) },
8248 { "Orange3", RGB_(0xcd, 0x85, 0x0) },
8249 { "Orange4", RGB_(0x8b, 0x5a, 0x0) },
8250 { "OrangeRed", RGB_(0xff, 0x45, 0x00) },
8251 { "OrangeRed1", RGB_(0xff, 0x45, 0x0) },
8252 { "OrangeRed2", RGB_(0xee, 0x40, 0x0) },
8253 { "OrangeRed3", RGB_(0xcd, 0x37, 0x0) },
8254 { "OrangeRed4", RGB_(0x8b, 0x25, 0x0) },
8255 { "Orchid", RGB_(0xda, 0x70, 0xd6) },
8256 { "Orchid1", RGB_(0xff, 0x83, 0xfa) },
8257 { "Orchid2", RGB_(0xee, 0x7a, 0xe9) },
8258 { "Orchid3", RGB_(0xcd, 0x69, 0xc9) },
8259 { "Orchid4", RGB_(0x8b, 0x47, 0x89) },
8260 { "PaleGoldenRod", RGB_(0xee, 0xe8, 0xaa) },
8261 { "PaleGreen", RGB_(0x98, 0xfb, 0x98) },
8262 { "PaleGreen1", RGB_(0x9a, 0xff, 0x9a) },
8263 { "PaleGreen2", RGB_(0x90, 0xee, 0x90) },
8264 { "PaleGreen3", RGB_(0x7c, 0xcd, 0x7c) },
8265 { "PaleGreen4", RGB_(0x54, 0x8b, 0x54) },
8266 { "PaleTurquoise", RGB_(0xaf, 0xee, 0xee) },
8267 { "PaleTurquoise1", RGB_(0xbb, 0xff, 0xff) },
8268 { "PaleTurquoise2", RGB_(0xae, 0xee, 0xee) },
8269 { "PaleTurquoise3", RGB_(0x96, 0xcd, 0xcd) },
8270 { "PaleTurquoise4", RGB_(0x66, 0x8b, 0x8b) },
8271 { "PaleVioletRed", RGB_(0xdb, 0x70, 0x93) },
8272 { "PaleVioletRed1", RGB_(0xff, 0x82, 0xab) },
8273 { "PaleVioletRed2", RGB_(0xee, 0x79, 0x9f) },
8274 { "PaleVioletRed3", RGB_(0xcd, 0x68, 0x89) },
8275 { "PaleVioletRed4", RGB_(0x8b, 0x47, 0x5d) },
8276 { "PapayaWhip", RGB_(0xff, 0xef, 0xd5) },
8277 { "PeachPuff", RGB_(0xff, 0xda, 0xb9) },
8278 { "PeachPuff1", RGB_(0xff, 0xda, 0xb9) },
8279 { "PeachPuff2", RGB_(0xee, 0xcb, 0xad) },
8280 { "PeachPuff3", RGB_(0xcd, 0xaf, 0x95) },
8281 { "PeachPuff4", RGB_(0x8b, 0x77, 0x65) },
8282 { "Peru", RGB_(0xcd, 0x85, 0x3f) },
8283 { "Pink", RGB_(0xff, 0xc0, 0xcb) },
8284 { "Pink1", RGB_(0xff, 0xb5, 0xc5) },
8285 { "Pink2", RGB_(0xee, 0xa9, 0xb8) },
8286 { "Pink3", RGB_(0xcd, 0x91, 0x9e) },
8287 { "Pink4", RGB_(0x8b, 0x63, 0x6c) },
8288 { "Plum", RGB_(0xdd, 0xa0, 0xdd) },
8289 { "Plum1", RGB_(0xff, 0xbb, 0xff) },
8290 { "Plum2", RGB_(0xee, 0xae, 0xee) },
8291 { "Plum3", RGB_(0xcd, 0x96, 0xcd) },
8292 { "Plum4", RGB_(0x8b, 0x66, 0x8b) },
8293 { "PowderBlue", RGB_(0xb0, 0xe0, 0xe6) },
8294 { "Purple", RGB_(0x80, 0x00, 0x80) },
8295 { "Purple1", RGB_(0x9b, 0x30, 0xff) },
8296 { "Purple2", RGB_(0x91, 0x2c, 0xee) },
8297 { "Purple3", RGB_(0x7d, 0x26, 0xcd) },
8298 { "Purple4", RGB_(0x55, 0x1a, 0x8b) },
8299 { "RebeccaPurple", RGB_(0x66, 0x33, 0x99) },
8300 { "Red", RGB_(0xff, 0x00, 0x00) },
8301 { "Red1", RGB_(0xff, 0x0, 0x0) },
8302 { "Red2", RGB_(0xee, 0x0, 0x0) },
8303 { "Red3", RGB_(0xcd, 0x0, 0x0) },
8304 { "Red4", RGB_(0x8b, 0x0, 0x0) },
8305 { "RosyBrown", RGB_(0xbc, 0x8f, 0x8f) },
8306 { "RosyBrown1", RGB_(0xff, 0xc1, 0xc1) },
8307 { "RosyBrown2", RGB_(0xee, 0xb4, 0xb4) },
8308 { "RosyBrown3", RGB_(0xcd, 0x9b, 0x9b) },
8309 { "RosyBrown4", RGB_(0x8b, 0x69, 0x69) },
8310 { "RoyalBlue", RGB_(0x41, 0x69, 0xe1) },
8311 { "RoyalBlue1", RGB_(0x48, 0x76, 0xff) },
8312 { "RoyalBlue2", RGB_(0x43, 0x6e, 0xee) },
8313 { "RoyalBlue3", RGB_(0x3a, 0x5f, 0xcd) },
8314 { "RoyalBlue4", RGB_(0x27, 0x40, 0x8b) },
8315 { "SaddleBrown", RGB_(0x8b, 0x45, 0x13) },
8316 { "Salmon", RGB_(0xfa, 0x80, 0x72) },
8317 { "Salmon1", RGB_(0xff, 0x8c, 0x69) },
8318 { "Salmon2", RGB_(0xee, 0x82, 0x62) },
8319 { "Salmon3", RGB_(0xcd, 0x70, 0x54) },
8320 { "Salmon4", RGB_(0x8b, 0x4c, 0x39) },
8321 { "SandyBrown", RGB_(0xf4, 0xa4, 0x60) },
8322 { "SeaGreen", RGB_(0x2e, 0x8b, 0x57) },
8323 { "SeaGreen1", RGB_(0x54, 0xff, 0x9f) },
8324 { "SeaGreen2", RGB_(0x4e, 0xee, 0x94) },
8325 { "SeaGreen3", RGB_(0x43, 0xcd, 0x80) },
8326 { "SeaGreen4", RGB_(0x2e, 0x8b, 0x57) },
8327 { "SeaShell", RGB_(0xff, 0xf5, 0xee) },
8328 { "Seashell1", RGB_(0xff, 0xf5, 0xee) },
8329 { "Seashell2", RGB_(0xee, 0xe5, 0xde) },
8330 { "Seashell3", RGB_(0xcd, 0xc5, 0xbf) },
8331 { "Seashell4", RGB_(0x8b, 0x86, 0x82) },
8332 { "Sienna", RGB_(0xa0, 0x52, 0x2d) },
8333 { "Sienna1", RGB_(0xff, 0x82, 0x47) },
8334 { "Sienna2", RGB_(0xee, 0x79, 0x42) },
8335 { "Sienna3", RGB_(0xcd, 0x68, 0x39) },
8336 { "Sienna4", RGB_(0x8b, 0x47, 0x26) },
8337 { "Silver", RGB_(0xc0, 0xc0, 0xc0) },
8338 { "SkyBlue", RGB_(0x87, 0xce, 0xeb) },
8339 { "SkyBlue1", RGB_(0x87, 0xce, 0xff) },
8340 { "SkyBlue2", RGB_(0x7e, 0xc0, 0xee) },
8341 { "SkyBlue3", RGB_(0x6c, 0xa6, 0xcd) },
8342 { "SkyBlue4", RGB_(0x4a, 0x70, 0x8b) },
8343 { "SlateBlue", RGB_(0x6a, 0x5a, 0xcd) },
8344 { "SlateBlue1", RGB_(0x83, 0x6f, 0xff) },
8345 { "SlateBlue2", RGB_(0x7a, 0x67, 0xee) },
8346 { "SlateBlue3", RGB_(0x69, 0x59, 0xcd) },
8347 { "SlateBlue4", RGB_(0x47, 0x3c, 0x8b) },
8348 { "SlateGray", RGB_(0x70, 0x80, 0x90) },
8349 { "SlateGray1", RGB_(0xc6, 0xe2, 0xff) },
8350 { "SlateGray2", RGB_(0xb9, 0xd3, 0xee) },
8351 { "SlateGray3", RGB_(0x9f, 0xb6, 0xcd) },
8352 { "SlateGray4", RGB_(0x6c, 0x7b, 0x8b) },
8353 { "SlateGrey", RGB_(0x70, 0x80, 0x90) },
8354 { "Snow", RGB_(0xff, 0xfa, 0xfa) },
8355 { "Snow1", RGB_(0xff, 0xfa, 0xfa) },
8356 { "Snow2", RGB_(0xee, 0xe9, 0xe9) },
8357 { "Snow3", RGB_(0xcd, 0xc9, 0xc9) },
8358 { "Snow4", RGB_(0x8b, 0x89, 0x89) },
8359 { "SpringGreen", RGB_(0x00, 0xff, 0x7f) },
8360 { "SpringGreen1", RGB_(0x0, 0xff, 0x7f) },
8361 { "SpringGreen2", RGB_(0x0, 0xee, 0x76) },
8362 { "SpringGreen3", RGB_(0x0, 0xcd, 0x66) },
8363 { "SpringGreen4", RGB_(0x0, 0x8b, 0x45) },
8364 { "SteelBlue", RGB_(0x46, 0x82, 0xb4) },
8365 { "SteelBlue1", RGB_(0x63, 0xb8, 0xff) },
8366 { "SteelBlue2", RGB_(0x5c, 0xac, 0xee) },
8367 { "SteelBlue3", RGB_(0x4f, 0x94, 0xcd) },
8368 { "SteelBlue4", RGB_(0x36, 0x64, 0x8b) },
8369 { "Tan", RGB_(0xd2, 0xb4, 0x8c) },
8370 { "Tan1", RGB_(0xff, 0xa5, 0x4f) },
8371 { "Tan2", RGB_(0xee, 0x9a, 0x49) },
8372 { "Tan3", RGB_(0xcd, 0x85, 0x3f) },
8373 { "Tan4", RGB_(0x8b, 0x5a, 0x2b) },
8374 { "Teal", RGB_(0x00, 0x80, 0x80) },
8375 { "Thistle", RGB_(0xd8, 0xbf, 0xd8) },
8376 { "Thistle1", RGB_(0xff, 0xe1, 0xff) },
8377 { "Thistle2", RGB_(0xee, 0xd2, 0xee) },
8378 { "Thistle3", RGB_(0xcd, 0xb5, 0xcd) },
8379 { "Thistle4", RGB_(0x8b, 0x7b, 0x8b) },
8380 { "Tomato", RGB_(0xff, 0x63, 0x47) },
8381 { "Tomato1", RGB_(0xff, 0x63, 0x47) },
8382 { "Tomato2", RGB_(0xee, 0x5c, 0x42) },
8383 { "Tomato3", RGB_(0xcd, 0x4f, 0x39) },
8384 { "Tomato4", RGB_(0x8b, 0x36, 0x26) },
8385 { "Turquoise", RGB_(0x40, 0xe0, 0xd0) },
8386 { "Turquoise1", RGB_(0x0, 0xf5, 0xff) },
8387 { "Turquoise2", RGB_(0x0, 0xe5, 0xee) },
8388 { "Turquoise3", RGB_(0x0, 0xc5, 0xcd) },
8389 { "Turquoise4", RGB_(0x0, 0x86, 0x8b) },
8390 { "Violet", RGB_(0xee, 0x82, 0xee) },
8391 { "VioletRed", RGB_(0xd0, 0x20, 0x90) },
8392 { "VioletRed1", RGB_(0xff, 0x3e, 0x96) },
8393 { "VioletRed2", RGB_(0xee, 0x3a, 0x8c) },
8394 { "VioletRed3", RGB_(0xcd, 0x32, 0x78) },
8395 { "VioletRed4", RGB_(0x8b, 0x22, 0x52) },
8396 { "WebGray", RGB_(0x80, 0x80, 0x80) },
8397 { "WebGreen", RGB_(0x0, 0x80, 0x0) },
8398 { "WebGrey", RGB_(0x80, 0x80, 0x80) },
8399 { "WebMaroon", RGB_(0x80, 0x0, 0x0) },
8400 { "WebPurple", RGB_(0x80, 0x0, 0x80) },
8401 { "Wheat", RGB_(0xf5, 0xde, 0xb3) },
8402 { "Wheat1", RGB_(0xff, 0xe7, 0xba) },
8403 { "Wheat2", RGB_(0xee, 0xd8, 0xae) },
8404 { "Wheat3", RGB_(0xcd, 0xba, 0x96) },
8405 { "Wheat4", RGB_(0x8b, 0x7e, 0x66) },
8406 { "White", RGB_(0xff, 0xff, 0xff) },
8407 { "WhiteSmoke", RGB_(0xf5, 0xf5, 0xf5) },
8408 { "X11Gray", RGB_(0xbe, 0xbe, 0xbe) },
8409 { "X11Green", RGB_(0x0, 0xff, 0x0) },
8410 { "X11Grey", RGB_(0xbe, 0xbe, 0xbe) },
8411 { "X11Maroon", RGB_(0xb0, 0x30, 0x60) },
8412 { "X11Purple", RGB_(0xa0, 0x20, 0xf0) },
8413 { "Yellow", RGB_(0xff, 0xff, 0x00) },
8414 { "Yellow1", RGB_(0xff, 0xff, 0x0) },
8415 { "Yellow2", RGB_(0xee, 0xee, 0x0) },
8416 { "Yellow3", RGB_(0xcd, 0xcd, 0x0) },
8417 { "Yellow4", RGB_(0x8b, 0x8b, 0x0) },
8418 { "YellowGreen", RGB_(0x9a, 0xcd, 0x32) },
8419 { NULL, 0 },
8420};
8421
8422
8423/// Translate to RgbValue if \p name is an hex value (e.g. #XXXXXX),
8424/// else look into color_name_table to translate a color name to its
8425/// hex value
8426///
8427/// @param[in] name string value to convert to RGB
8428/// return the hex value or -1 if could not find a correct value
8429RgbValue name_to_color(const char_u *name)
8430{
8431
8432 if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2])
8433 && isxdigit(name[3]) && isxdigit(name[4]) && isxdigit(name[5])
8434 && isxdigit(name[6]) && name[7] == NUL) {
8435 // rgb hex string
8436 return strtol((char *)(name + 1), NULL, 16);
8437 } else if (!STRICMP(name, "bg") || !STRICMP(name, "background")) {
8438 return normal_bg;
8439 } else if (!STRICMP(name, "fg") || !STRICMP(name, "foreground")) {
8440 return normal_fg;
8441 }
8442
8443 for (int i = 0; color_name_table[i].name != NULL; i++) {
8444 if (!STRICMP(name, color_name_table[i].name)) {
8445 return color_name_table[i].color;
8446 }
8447 }
8448
8449 return -1;
8450}
8451
8452
8453/**************************************
8454* End of Highlighting stuff *
8455**************************************/
8456