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 | |
51 | static 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. |
56 | struct 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| |
91 | static garray_T highlight_ga = GA_EMPTY_INIT_VALUE; |
92 | |
93 | static 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. */ |
111 | static int include_none = 0; /* when 1 include "nvim/None" */ |
112 | static int include_default = 0; /* when 1 include "nvim/default" */ |
113 | static 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!). |
117 | static char *(hl_name_table[]) = |
118 | { "bold" , "standout" , "underline" , "undercurl" , |
119 | "italic" , "reverse" , "inverse" , "strikethrough" , "NONE" }; |
120 | static 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. |
135 | typedef 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 | |
156 | typedef 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 | */ |
167 | typedef 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 | */ |
195 | typedef 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 | |
206 | typedef 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 | |
216 | struct name_list { |
217 | int flag; |
218 | char *name; |
219 | }; |
220 | |
221 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
222 | # include "syntax.c.generated.h" |
223 | #endif |
224 | |
225 | static 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 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 | */ |
257 | static int current_attr = 0; /* attr of current syntax word */ |
258 | static int current_id = 0; /* ID of current char for syn_get_id() */ |
259 | static int current_trans_id = 0; /* idem, transparency removed */ |
260 | static int current_flags = 0; |
261 | static int current_seqnr = 0; |
262 | static 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 | */ |
294 | static 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 | */ |
301 | static int current_syn_inc_tag = 0; |
302 | static 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 | */ |
311 | static 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 | */ |
323 | static int keepend_level = -1; |
324 | |
325 | static 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 | |
332 | static 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 | */ |
341 | static int next_match_col; /* column for start of next match */ |
342 | static lpos_T next_match_m_endpos; /* position for end of next match */ |
343 | static lpos_T next_match_h_startpos; /* pos. for highl. start of next match */ |
344 | static lpos_T next_match_h_endpos; /* pos. for highl. end of next match */ |
345 | static int next_match_idx; /* index of matched item */ |
346 | static long next_match_flags; /* flags for next match */ |
347 | static lpos_T next_match_eos_pos; /* end of start pattn (start region) */ |
348 | static lpos_T next_match_eoe_pos; /* pos. for end of end pattern */ |
349 | static int next_match_end_idx; /* ID of group for end pattn or zero */ |
350 | static 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 | */ |
363 | static win_T *syn_win; // current window for highlighting |
364 | static buf_T *syn_buf; // current buffer for highlighting |
365 | static synblock_T *syn_block; // current buffer for highlighting |
366 | static proftime_T *syn_tm; // timeout limit |
367 | static linenr_T current_lnum = 0; // lnum of current state |
368 | static colnr_T current_col = 0; // column of current state |
369 | static int current_state_stored = 0; // TRUE if stored current state |
370 | // after setting current_finished |
371 | static int current_finished = 0; // current line has been finished |
372 | static garray_T current_state // current stack of state_items |
373 | = GA_EMPTY_INIT_VALUE; |
374 | static int16_t *current_next_list = NULL; // when non-zero, nextgroup list |
375 | static int current_next_flags = 0; // flags for current_next_list |
376 | static int current_line_id = 0; // unique number for current line |
377 | |
378 | #define CUR_STATE(idx) ((stateitem_T *)(current_state.ga_data))[idx] |
379 | |
380 | static 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. |
385 | void 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 | */ |
397 | void 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(¤t_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(¤t_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(¤t_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 | */ |
560 | static 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 | */ |
575 | static void clear_current_state(void) |
576 | { |
577 | # define UNREF_STATEITEM_EXTMATCH(si) unref_extmatch((si)->si_extmatch) |
578 | GA_DEEP_CLEAR(¤t_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 | */ |
590 | static 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(¤t_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 | |
836 | static 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 | |
844 | static 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 | */ |
854 | static 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(®match, 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 | */ |
877 | static 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(¤t_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 | */ |
901 | static 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 | |
994 | static 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 | */ |
1011 | void 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 | */ |
1029 | static 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 | */ |
1099 | void 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 | |
1110 | static 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 | */ |
1154 | static 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 | */ |
1212 | static 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 | */ |
1224 | static 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 | */ |
1242 | static 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 | */ |
1345 | static 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(¤t_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 | */ |
1387 | static 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 | */ |
1458 | void 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 | |
1474 | static 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 | |
1482 | static void validate_current_state(void) |
1483 | { |
1484 | current_state.ga_itemsize = sizeof(stateitem_T); |
1485 | ga_set_growsize(¤t_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 | */ |
1493 | int 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(¤t_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 | */ |
1538 | static bool |
1539 | syn_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 | */ |
1580 | int |
1581 | get_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(¤t_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 | */ |
1629 | static 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(®match, 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, ®match, |
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, ®match, |
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, ®match, |
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, ®match, spp, |
1926 | SPO_HE_OFF, 0); |
1927 | syn_add_end_off(&endpos, ®match, 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(¤t_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 | */ |
2153 | static 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 | */ |
2177 | static 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 | */ |
2257 | static 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(¤t_state)) |
2311 | break; |
2312 | |
2313 | if (had_extend && keepend_level >= 0) { |
2314 | syn_update_ends(FALSE); |
2315 | if (GA_EMPTY(¤t_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 | */ |
2351 | static 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 | */ |
2400 | static 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 | */ |
2456 | static void |
2457 | update_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 | */ |
2516 | static void push_current_state(int idx) |
2517 | { |
2518 | stateitem_T *p = GA_APPEND_VIA_PTR(stateitem_T, ¤t_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 | */ |
2526 | static void pop_current_state(void) |
2527 | { |
2528 | if (!GA_EMPTY(¤t_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 | */ |
2549 | static void |
2550 | find_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(®match, 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(®match, 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, ®match, 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 | */ |
2769 | static 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 | */ |
2780 | static 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 | */ |
2791 | static void |
2792 | syn_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 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 | */ |
2839 | static void |
2840 | syn_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 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 | */ |
2887 | static 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 | */ |
2896 | static 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 | */ |
2946 | static 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. |
3006 | static 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 | */ |
3027 | static 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 | */ |
3055 | static 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 | */ |
3083 | static 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. |
3117 | static 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 | */ |
3159 | void 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 | */ |
3208 | void 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 | */ |
3220 | static 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 | */ |
3245 | static 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 | */ |
3262 | static 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 | */ |
3277 | static 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 | */ |
3287 | static 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 | */ |
3356 | static 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 | */ |
3378 | static 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 | */ |
3386 | static 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 | */ |
3397 | static 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 | */ |
3410 | static 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 | */ |
3418 | static void syn_cmd_off(exarg_T *eap, int syncing) |
3419 | { |
3420 | syn_cmd_onoff(eap, "nosyntax" ); |
3421 | } |
3422 | |
3423 | static 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 | |
3436 | void 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 | */ |
3449 | static void |
3450 | syn_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 | |
3530 | static 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 | |
3549 | static 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 | |
3558 | static int last_matchgroup; |
3559 | |
3560 | |
3561 | /* |
3562 | * List one syntax item, for ":syntax" or "syntax list syntax_name". |
3563 | */ |
3564 | static void |
3565 | syn_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 = 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 | |
3668 | static 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 | */ |
3682 | static 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 | |
3704 | static 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 | |
3734 | static 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. |
3796 | static bool syn_list_keywords( |
3797 | const int id, |
3798 | const hashtab_T *const ht, |
3799 | bool , // 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 | |
3880 | static 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 | */ |
3922 | static 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 |
3952 | static 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 | */ |
4003 | static char_u * |
4004 | get_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 | */ |
4031 | static char_u * |
4032 | get_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 | */ |
4182 | static 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 | */ |
4202 | static 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 | */ |
4272 | static 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 | |
4363 | error: |
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 | */ |
4384 | static void |
4385 | syn_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 | */ |
4482 | static void |
4483 | syn_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() |
4707 | static 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. |
4717 | static 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. |
4826 | static 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 | */ |
4844 | static 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. |
4857 | static 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. |
4875 | static 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 | */ |
4911 | static 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 | */ |
4978 | static 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 | */ |
4989 | static 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 | */ |
5075 | static 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 | */ |
5204 | static int |
5205 | get_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(®match, 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 | */ |
5365 | static 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 | */ |
5388 | static int |
5389 | in_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 | |
5476 | struct subcommand { |
5477 | char *name; /* subcommand name */ |
5478 | void (*func)(exarg_T *, int); /* function to call */ |
5479 | }; |
5480 | |
5481 | static 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 | */ |
5509 | void 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 | |
5540 | void 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 | |
5583 | bool 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 | |
5592 | static 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 | */ |
5603 | void 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 | */ |
5612 | void 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 | */ |
5622 | void 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 | */ |
5660 | char_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. |
5687 | int 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 | */ |
5717 | int 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. |
5727 | int 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 | */ |
5744 | int 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 | */ |
5754 | int 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 | */ |
5769 | int 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 | */ |
5796 | void 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 | |
5810 | static 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 | */ |
5821 | static 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 | */ |
5839 | char_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 | |
5850 | static 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 | */ |
5861 | static 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 | |
5952 | static 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. |
5984 | static 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. |
6018 | static 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 | |
6051 | const 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 |
6253 | void 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 |
6265 | void 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 | */ |
6344 | int 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 | |
6371 | static 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 |
6387 | static 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... |
6395 | static 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... |
6403 | static 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... |
6411 | static 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. |
6423 | int 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. |
6465 | void 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) |
6979 | void 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 | */ |
6995 | void 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 | */ |
7008 | static 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 | */ |
7022 | static 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 | |
7055 | static 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 |
7103 | static 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. |
7151 | const 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. |
7178 | const 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. |
7251 | static bool (const bool , 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 |
7300 | static 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 |
7329 | int 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. |
7348 | int 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 | */ |
7361 | int 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 | */ |
7370 | char_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 | */ |
7380 | int 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 |
7396 | int 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 |
7413 | static 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. |
7461 | static 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 |
7471 | int 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 | */ |
7484 | int 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. |
7507 | void 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. |
7525 | static 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. |
7566 | void 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 | */ |
7635 | void 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 | */ |
7678 | static 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 | |
7690 | static 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. |
7700 | const 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. |
7709 | const 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 | |
7738 | color_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 |
8429 | RgbValue 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 | |