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 | // highlight.c: low level code for UI and syntax highlighting |
5 | |
6 | #include "nvim/vim.h" |
7 | #include "nvim/highlight.h" |
8 | #include "nvim/highlight_defs.h" |
9 | #include "nvim/map.h" |
10 | #include "nvim/message.h" |
11 | #include "nvim/popupmnu.h" |
12 | #include "nvim/screen.h" |
13 | #include "nvim/syntax.h" |
14 | #include "nvim/ui.h" |
15 | #include "nvim/api/private/defs.h" |
16 | #include "nvim/api/private/helpers.h" |
17 | |
18 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
19 | # include "highlight.c.generated.h" |
20 | #endif |
21 | |
22 | static bool hlstate_active = false; |
23 | |
24 | static kvec_t(HlEntry) attr_entries = KV_INITIAL_VALUE; |
25 | |
26 | static Map(HlEntry, int) *attr_entry_ids; |
27 | static Map(int, int) *combine_attr_entries; |
28 | static Map(int, int) *blend_attr_entries; |
29 | static Map(int, int) *blendthrough_attr_entries; |
30 | |
31 | void highlight_init(void) |
32 | { |
33 | attr_entry_ids = map_new(HlEntry, int)(); |
34 | combine_attr_entries = map_new(int, int)(); |
35 | blend_attr_entries = map_new(int, int)(); |
36 | blendthrough_attr_entries = map_new(int, int)(); |
37 | |
38 | // index 0 is no attribute, add dummy entry: |
39 | kv_push(attr_entries, ((HlEntry){ .attr = HLATTRS_INIT, .kind = kHlUnknown, |
40 | .id1 = 0, .id2 = 0 })); |
41 | } |
42 | |
43 | /// @return TRUE if hl table was reset |
44 | bool highlight_use_hlstate(void) |
45 | { |
46 | if (hlstate_active) { |
47 | return false; |
48 | } |
49 | hlstate_active = true; |
50 | // hl tables must now be rebuilt. |
51 | clear_hl_tables(true); |
52 | return true; |
53 | } |
54 | |
55 | /// Return the attr number for a set of colors and font, and optionally |
56 | /// a semantic description (see ext_hlstate documentation). |
57 | /// Add a new entry to the attr_entries array if the combination is new. |
58 | /// @return 0 for error. |
59 | static int get_attr_entry(HlEntry entry) |
60 | { |
61 | if (!hlstate_active) { |
62 | // This information will not be used, erase it and reduce the table size. |
63 | entry.kind = kHlUnknown; |
64 | entry.id1 = 0; |
65 | entry.id2 = 0; |
66 | } |
67 | |
68 | int id = map_get(HlEntry, int)(attr_entry_ids, entry); |
69 | if (id > 0) { |
70 | return id; |
71 | } |
72 | |
73 | static bool recursive = false; |
74 | if (kv_size(attr_entries) > MAX_TYPENR) { |
75 | // Running out of attribute entries! remove all attributes, and |
76 | // compute new ones for all groups. |
77 | // When called recursively, we are really out of numbers. |
78 | if (recursive) { |
79 | EMSG(_("E424: Too many different highlighting attributes in use" )); |
80 | return 0; |
81 | } |
82 | recursive = true; |
83 | |
84 | clear_hl_tables(true); |
85 | |
86 | recursive = false; |
87 | if (entry.kind == kHlCombine) { |
88 | // This entry is now invalid, don't put it |
89 | return 0; |
90 | } |
91 | } |
92 | |
93 | id = (int)kv_size(attr_entries); |
94 | kv_push(attr_entries, entry); |
95 | |
96 | map_put(HlEntry, int)(attr_entry_ids, entry, id); |
97 | |
98 | Array inspect = hl_inspect(id); |
99 | |
100 | // Note: internally we don't distinguish between cterm and rgb attributes, |
101 | // remote_ui_hl_attr_define will however. |
102 | ui_call_hl_attr_define(id, entry.attr, entry.attr, inspect); |
103 | api_free_array(inspect); |
104 | return id; |
105 | } |
106 | |
107 | /// When a UI connects, we need to send it the table of highlights used so far. |
108 | void ui_send_all_hls(UI *ui) |
109 | { |
110 | if (ui->hl_attr_define) { |
111 | for (size_t i = 1; i < kv_size(attr_entries); i++) { |
112 | Array inspect = hl_inspect((int)i); |
113 | ui->hl_attr_define(ui, (Integer)i, kv_A(attr_entries, i).attr, |
114 | kv_A(attr_entries, i).attr, inspect); |
115 | api_free_array(inspect); |
116 | } |
117 | } |
118 | if (ui->hl_group_set) { |
119 | for (size_t hlf = 0; hlf < HLF_COUNT; hlf++) { |
120 | ui->hl_group_set(ui, cstr_as_string((char *)hlf_names[hlf]), |
121 | highlight_attr[hlf]); |
122 | } |
123 | } |
124 | } |
125 | |
126 | /// Get attribute code for a syntax group. |
127 | int hl_get_syn_attr(int idx, HlAttrs at_en) |
128 | { |
129 | // TODO(bfredl): should we do this unconditionally |
130 | if (at_en.cterm_fg_color != 0 || at_en.cterm_bg_color != 0 |
131 | || at_en.rgb_fg_color != -1 || at_en.rgb_bg_color != -1 |
132 | || at_en.rgb_sp_color != -1 || at_en.cterm_ae_attr != 0 |
133 | || at_en.rgb_ae_attr != 0) { |
134 | return get_attr_entry((HlEntry){ .attr = at_en, .kind = kHlSyntax, |
135 | .id1 = idx, .id2 = 0 }); |
136 | } else { |
137 | // If all the fields are cleared, clear the attr field back to default value |
138 | return 0; |
139 | } |
140 | } |
141 | |
142 | /// Get attribute code for a builtin highlight group. |
143 | /// |
144 | /// The final syntax group could be modified by hi-link or 'winhighlight'. |
145 | int hl_get_ui_attr(int idx, int final_id, bool optional) |
146 | { |
147 | HlAttrs attrs = HLATTRS_INIT; |
148 | bool available = false; |
149 | |
150 | if (final_id > 0) { |
151 | int syn_attr = syn_id2attr(final_id); |
152 | if (syn_attr != 0) { |
153 | attrs = syn_attr2entry(syn_attr); |
154 | available = true; |
155 | } |
156 | } |
157 | |
158 | if (HLF_PNI <= idx && idx <= HLF_PST) { |
159 | if (attrs.hl_blend == -1 && p_pb > 0) { |
160 | attrs.hl_blend = (int)p_pb; |
161 | } |
162 | if (pum_drawn()) { |
163 | must_redraw_pum = true; |
164 | } |
165 | } else if (idx == HLF_MSG) { |
166 | msg_grid.blending = attrs.hl_blend > -1; |
167 | } |
168 | |
169 | if (optional && !available) { |
170 | return 0; |
171 | } |
172 | return get_attr_entry((HlEntry){ .attr = attrs, .kind = kHlUI, |
173 | .id1 = idx, .id2 = final_id }); |
174 | } |
175 | |
176 | void update_window_hl(win_T *wp, bool invalid) |
177 | { |
178 | if (!wp->w_hl_needs_update && !invalid) { |
179 | return; |
180 | } |
181 | wp->w_hl_needs_update = false; |
182 | |
183 | // If a floating window is blending it always have a named |
184 | // wp->w_hl_attr_normal group. HL_ATTR(HLF_NFLOAT) is always named. |
185 | bool has_blend = wp->w_floating && wp->w_p_winbl != 0; |
186 | |
187 | // determine window specific background set in 'winhighlight' |
188 | bool float_win = wp->w_floating && !wp->w_float_config.external; |
189 | if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] != 0) { |
190 | wp->w_hl_attr_normal = hl_get_ui_attr(HLF_INACTIVE, |
191 | wp->w_hl_ids[HLF_INACTIVE], |
192 | !has_blend); |
193 | } else if (float_win && wp->w_hl_ids[HLF_NFLOAT] != 0) { |
194 | wp->w_hl_attr_normal = hl_get_ui_attr(HLF_NFLOAT, |
195 | wp->w_hl_ids[HLF_NFLOAT], !has_blend); |
196 | } else if (wp->w_hl_id_normal != 0) { |
197 | wp->w_hl_attr_normal = hl_get_ui_attr(-1, wp->w_hl_id_normal, !has_blend); |
198 | } else { |
199 | wp->w_hl_attr_normal = float_win ? HL_ATTR(HLF_NFLOAT) : 0; |
200 | } |
201 | |
202 | // if blend= attribute is not set, 'winblend' value overrides it. |
203 | if (wp->w_floating && wp->w_p_winbl > 0) { |
204 | HlEntry entry = kv_A(attr_entries, wp->w_hl_attr_normal); |
205 | if (entry.attr.hl_blend == -1) { |
206 | entry.attr.hl_blend = (int)wp->w_p_winbl; |
207 | wp->w_hl_attr_normal = get_attr_entry(entry); |
208 | } |
209 | } |
210 | |
211 | if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] == 0) { |
212 | wp->w_hl_attr_normal = hl_combine_attr(HL_ATTR(HLF_INACTIVE), |
213 | wp->w_hl_attr_normal); |
214 | } |
215 | |
216 | for (int hlf = 0; hlf < (int)HLF_COUNT; hlf++) { |
217 | int attr; |
218 | if (wp->w_hl_ids[hlf] != 0) { |
219 | attr = hl_get_ui_attr(hlf, wp->w_hl_ids[hlf], false); |
220 | } else { |
221 | attr = HL_ATTR(hlf); |
222 | } |
223 | wp->w_hl_attrs[hlf] = attr; |
224 | } |
225 | } |
226 | |
227 | /// Gets HL_UNDERLINE highlight. |
228 | int hl_get_underline(void) |
229 | { |
230 | return get_attr_entry((HlEntry){ |
231 | .attr = (HlAttrs){ |
232 | .cterm_ae_attr = (int16_t)HL_UNDERLINE, |
233 | .cterm_fg_color = 0, |
234 | .cterm_bg_color = 0, |
235 | .rgb_ae_attr = (int16_t)HL_UNDERLINE, |
236 | .rgb_fg_color = -1, |
237 | .rgb_bg_color = -1, |
238 | .rgb_sp_color = -1, |
239 | .hl_blend = -1, |
240 | }, |
241 | .kind = kHlUI, |
242 | .id1 = 0, |
243 | .id2 = 0, |
244 | }); |
245 | } |
246 | |
247 | /// Get attribute code for forwarded :terminal highlights. |
248 | int hl_get_term_attr(HlAttrs *aep) |
249 | { |
250 | return get_attr_entry((HlEntry){ .attr= *aep, .kind = kHlTerminal, |
251 | .id1 = 0, .id2 = 0 }); |
252 | } |
253 | |
254 | /// Clear all highlight tables. |
255 | void clear_hl_tables(bool reinit) |
256 | { |
257 | if (reinit) { |
258 | kv_size(attr_entries) = 1; |
259 | map_clear(HlEntry, int)(attr_entry_ids); |
260 | map_clear(int, int)(combine_attr_entries); |
261 | map_clear(int, int)(blend_attr_entries); |
262 | map_clear(int, int)(blendthrough_attr_entries); |
263 | memset(highlight_attr_last, -1, sizeof(highlight_attr_last)); |
264 | highlight_attr_set_all(); |
265 | highlight_changed(); |
266 | screen_invalidate_highlights(); |
267 | } else { |
268 | kv_destroy(attr_entries); |
269 | map_free(HlEntry, int)(attr_entry_ids); |
270 | map_free(int, int)(combine_attr_entries); |
271 | map_free(int, int)(blend_attr_entries); |
272 | map_free(int, int)(blendthrough_attr_entries); |
273 | } |
274 | } |
275 | |
276 | void hl_invalidate_blends(void) |
277 | { |
278 | map_clear(int, int)(blend_attr_entries); |
279 | map_clear(int, int)(blendthrough_attr_entries); |
280 | highlight_changed(); |
281 | update_window_hl(curwin, true); |
282 | } |
283 | |
284 | // Combine special attributes (e.g., for spelling) with other attributes |
285 | // (e.g., for syntax highlighting). |
286 | // "prim_attr" overrules "char_attr". |
287 | // This creates a new group when required. |
288 | // Since we expect there to be a lot of spelling mistakes we cache the result. |
289 | // Return the resulting attributes. |
290 | int hl_combine_attr(int char_attr, int prim_attr) |
291 | { |
292 | if (char_attr == 0) { |
293 | return prim_attr; |
294 | } else if (prim_attr == 0) { |
295 | return char_attr; |
296 | } |
297 | |
298 | // TODO(bfredl): could use a struct for clearer intent. |
299 | int combine_tag = (char_attr << 16) + prim_attr; |
300 | int id = map_get(int, int)(combine_attr_entries, combine_tag); |
301 | if (id > 0) { |
302 | return id; |
303 | } |
304 | |
305 | HlAttrs char_aep = syn_attr2entry(char_attr); |
306 | HlAttrs spell_aep = syn_attr2entry(prim_attr); |
307 | |
308 | // start with low-priority attribute, and override colors if present below. |
309 | HlAttrs new_en = char_aep; |
310 | |
311 | new_en.cterm_ae_attr |= spell_aep.cterm_ae_attr; |
312 | new_en.rgb_ae_attr |= spell_aep.rgb_ae_attr; |
313 | |
314 | if (spell_aep.cterm_fg_color > 0) { |
315 | new_en.cterm_fg_color = spell_aep.cterm_fg_color; |
316 | } |
317 | |
318 | if (spell_aep.cterm_bg_color > 0) { |
319 | new_en.cterm_bg_color = spell_aep.cterm_bg_color; |
320 | } |
321 | |
322 | if (spell_aep.rgb_fg_color >= 0) { |
323 | new_en.rgb_fg_color = spell_aep.rgb_fg_color; |
324 | } |
325 | |
326 | if (spell_aep.rgb_bg_color >= 0) { |
327 | new_en.rgb_bg_color = spell_aep.rgb_bg_color; |
328 | } |
329 | |
330 | if (spell_aep.rgb_sp_color >= 0) { |
331 | new_en.rgb_sp_color = spell_aep.rgb_sp_color; |
332 | } |
333 | |
334 | if (spell_aep.hl_blend >= 0) { |
335 | new_en.hl_blend = spell_aep.hl_blend; |
336 | } |
337 | |
338 | id = get_attr_entry((HlEntry){ .attr = new_en, .kind = kHlCombine, |
339 | .id1 = char_attr, .id2 = prim_attr }); |
340 | if (id > 0) { |
341 | map_put(int, int)(combine_attr_entries, combine_tag, id); |
342 | } |
343 | |
344 | return id; |
345 | } |
346 | |
347 | /// Get the used rgb colors for an attr group. |
348 | /// |
349 | /// If colors are unset, use builtin default colors. Never returns -1 |
350 | /// Cterm colors are unchanged. |
351 | static HlAttrs get_colors_force(int attr) |
352 | { |
353 | HlAttrs attrs = syn_attr2entry(attr); |
354 | if (attrs.rgb_bg_color == -1) { |
355 | attrs.rgb_bg_color = normal_bg; |
356 | } |
357 | if (attrs.rgb_fg_color == -1) { |
358 | attrs.rgb_fg_color = normal_fg; |
359 | } |
360 | if (attrs.rgb_sp_color == -1) { |
361 | attrs.rgb_sp_color = normal_sp; |
362 | } |
363 | HL_SET_DEFAULT_COLORS(attrs.rgb_fg_color, attrs.rgb_bg_color, |
364 | attrs.rgb_sp_color); |
365 | |
366 | if (attrs.rgb_ae_attr & HL_INVERSE) { |
367 | int temp = attrs.rgb_bg_color; |
368 | attrs.rgb_bg_color = attrs.rgb_fg_color; |
369 | attrs.rgb_fg_color = temp; |
370 | attrs.rgb_ae_attr &= ~HL_INVERSE; |
371 | } |
372 | |
373 | return attrs; |
374 | } |
375 | |
376 | /// Blend overlay attributes (for popupmenu) with other attributes |
377 | /// |
378 | /// This creates a new group when required. |
379 | /// This is called per-cell, so cache the result. |
380 | /// |
381 | /// @return the resulting attributes. |
382 | int hl_blend_attrs(int back_attr, int front_attr, bool *through) |
383 | { |
384 | HlAttrs fattrs = get_colors_force(front_attr); |
385 | int ratio = fattrs.hl_blend; |
386 | if (ratio <= 0) { |
387 | *through = false; |
388 | return front_attr; |
389 | } |
390 | |
391 | int combine_tag = (back_attr << 16) + front_attr; |
392 | Map(int, int) *map = (*through |
393 | ? blendthrough_attr_entries |
394 | : blend_attr_entries); |
395 | int id = map_get(int, int)(map, combine_tag); |
396 | if (id > 0) { |
397 | return id; |
398 | } |
399 | |
400 | HlAttrs battrs = get_colors_force(back_attr); |
401 | HlAttrs cattrs; |
402 | |
403 | if (*through) { |
404 | cattrs = battrs; |
405 | cattrs.rgb_fg_color = rgb_blend(ratio, battrs.rgb_fg_color, |
406 | fattrs.rgb_bg_color); |
407 | if (cattrs.rgb_ae_attr & (HL_UNDERLINE|HL_UNDERCURL)) { |
408 | cattrs.rgb_sp_color = rgb_blend(ratio, battrs.rgb_sp_color, |
409 | fattrs.rgb_bg_color); |
410 | } else { |
411 | cattrs.rgb_sp_color = -1; |
412 | } |
413 | |
414 | cattrs.cterm_bg_color = fattrs.cterm_bg_color; |
415 | cattrs.cterm_fg_color = cterm_blend(ratio, battrs.cterm_fg_color, |
416 | fattrs.cterm_bg_color); |
417 | } else { |
418 | cattrs = fattrs; |
419 | if (ratio >= 50) { |
420 | cattrs.rgb_ae_attr |= battrs.rgb_ae_attr; |
421 | } |
422 | cattrs.rgb_fg_color = rgb_blend(ratio/2, battrs.rgb_fg_color, |
423 | fattrs.rgb_fg_color); |
424 | if (cattrs.rgb_ae_attr & (HL_UNDERLINE|HL_UNDERCURL)) { |
425 | cattrs.rgb_sp_color = rgb_blend(ratio/2, battrs.rgb_bg_color, |
426 | fattrs.rgb_sp_color); |
427 | } else { |
428 | cattrs.rgb_sp_color = -1; |
429 | } |
430 | } |
431 | cattrs.rgb_bg_color = rgb_blend(ratio, battrs.rgb_bg_color, |
432 | fattrs.rgb_bg_color); |
433 | |
434 | cattrs.hl_blend = -1; // blend property was consumed |
435 | |
436 | HlKind kind = *through ? kHlBlendThrough : kHlBlend; |
437 | id = get_attr_entry((HlEntry){ .attr = cattrs, .kind = kind, |
438 | .id1 = back_attr, .id2 = front_attr }); |
439 | if (id > 0) { |
440 | map_put(int, int)(map, combine_tag, id); |
441 | } |
442 | return id; |
443 | } |
444 | |
445 | static int rgb_blend(int ratio, int rgb1, int rgb2) |
446 | { |
447 | int a = ratio, b = 100-ratio; |
448 | int r1 = (rgb1 & 0xFF0000) >> 16; |
449 | int g1 = (rgb1 & 0x00FF00) >> 8; |
450 | int b1 = (rgb1 & 0x0000FF) >> 0; |
451 | int r2 = (rgb2 & 0xFF0000) >> 16; |
452 | int g2 = (rgb2 & 0x00FF00) >> 8; |
453 | int b2 = (rgb2 & 0x0000FF) >> 0; |
454 | int mr = (a * r1 + b * r2)/100; |
455 | int mg = (a * g1 + b * g2)/100; |
456 | int mb = (a * b1 + b * b2)/100; |
457 | return (mr << 16) + (mg << 8) + mb; |
458 | } |
459 | |
460 | static int cterm_blend(int ratio, int c1, int c2) |
461 | { |
462 | // 1. Convert cterm color numbers to RGB. |
463 | // 2. Blend the RGB colors. |
464 | // 3. Convert the RGB result to a cterm color. |
465 | int rgb1 = hl_cterm2rgb_color(c1); |
466 | int rgb2 = hl_cterm2rgb_color(c2); |
467 | int rgb_blended = rgb_blend(ratio, rgb1, rgb2); |
468 | return hl_rgb2cterm_color(rgb_blended); |
469 | } |
470 | |
471 | /// Converts RGB color to 8-bit color (0-255). |
472 | static int hl_rgb2cterm_color(int rgb) |
473 | { |
474 | int r = (rgb & 0xFF0000) >> 16; |
475 | int g = (rgb & 0x00FF00) >> 8; |
476 | int b = (rgb & 0x0000FF) >> 0; |
477 | |
478 | return (r * 6 / 256) * 36 + (g * 6 / 256) * 6 + (b * 6 / 256); |
479 | } |
480 | |
481 | /// Converts 8-bit color (0-255) to RGB color. |
482 | /// This is compatible with xterm. |
483 | static int hl_cterm2rgb_color(int nr) |
484 | { |
485 | static int cube_value[] = { |
486 | 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF |
487 | }; |
488 | static int grey_ramp[] = { |
489 | 0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A, 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76, |
490 | 0x80, 0x8A, 0x94, 0x9E, 0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE |
491 | }; |
492 | static char_u ansi_table[16][4] = { |
493 | // R G B idx |
494 | { 0, 0, 0, 1 } , // black |
495 | { 224, 0, 0, 2 } , // dark red |
496 | { 0, 224, 0, 3 } , // dark green |
497 | { 224, 224, 0, 4 } , // dark yellow / brown |
498 | { 0, 0, 224, 5 } , // dark blue |
499 | { 224, 0, 224, 6 } , // dark magenta |
500 | { 0, 224, 224, 7 } , // dark cyan |
501 | { 224, 224, 224, 8 } , // light grey |
502 | |
503 | { 128, 128, 128, 9 } , // dark grey |
504 | { 255, 64, 64, 10 } , // light red |
505 | { 64, 255, 64, 11 } , // light green |
506 | { 255, 255, 64, 12 } , // yellow |
507 | { 64, 64, 255, 13 } , // light blue |
508 | { 255, 64, 255, 14 } , // light magenta |
509 | { 64, 255, 255, 15 } , // light cyan |
510 | { 255, 255, 255, 16 } , // white |
511 | }; |
512 | |
513 | int r = 0; |
514 | int g = 0; |
515 | int b = 0; |
516 | int idx; |
517 | // *ansi_idx = 0; |
518 | |
519 | if (nr < 16) { |
520 | r = ansi_table[nr][0]; |
521 | g = ansi_table[nr][1]; |
522 | b = ansi_table[nr][2]; |
523 | // *ansi_idx = ansi_table[nr][3]; |
524 | } else if (nr < 232) { // 216 color-cube |
525 | idx = nr - 16; |
526 | r = cube_value[idx / 36 % 6]; |
527 | g = cube_value[idx / 6 % 6]; |
528 | b = cube_value[idx % 6]; |
529 | // *ansi_idx = -1; |
530 | } else if (nr < 256) { // 24 greyscale ramp |
531 | idx = nr - 232; |
532 | r = grey_ramp[idx]; |
533 | g = grey_ramp[idx]; |
534 | b = grey_ramp[idx]; |
535 | // *ansi_idx = -1; |
536 | } |
537 | return (r << 16) + (g << 8) + b; |
538 | } |
539 | |
540 | /// Get highlight attributes for a attribute code |
541 | HlAttrs syn_attr2entry(int attr) |
542 | { |
543 | if (attr <= 0 || attr >= (int)kv_size(attr_entries)) { |
544 | // invalid attribute code, or the tables were cleared |
545 | return HLATTRS_INIT; |
546 | } |
547 | return kv_A(attr_entries, attr).attr; |
548 | } |
549 | |
550 | /// Gets highlight description for id `attr_id` as a map. |
551 | Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Error *err) |
552 | { |
553 | Dictionary dic = ARRAY_DICT_INIT; |
554 | |
555 | if (attr_id == 0) { |
556 | return dic; |
557 | } |
558 | |
559 | if (attr_id <= 0 || attr_id >= (int)kv_size(attr_entries)) { |
560 | api_set_error(err, kErrorTypeException, |
561 | "Invalid attribute id: %" PRId64, attr_id); |
562 | return dic; |
563 | } |
564 | |
565 | return hlattrs2dict(syn_attr2entry((int)attr_id), rgb); |
566 | } |
567 | |
568 | /// Converts an HlAttrs into Dictionary |
569 | /// |
570 | /// @param[in] aep data to convert |
571 | /// @param use_rgb use 'gui*' settings if true, else resorts to 'cterm*' |
572 | Dictionary hlattrs2dict(HlAttrs ae, bool use_rgb) |
573 | { |
574 | Dictionary hl = ARRAY_DICT_INIT; |
575 | int mask = use_rgb ? ae.rgb_ae_attr : ae.cterm_ae_attr; |
576 | |
577 | if (mask & HL_BOLD) { |
578 | PUT(hl, "bold" , BOOLEAN_OBJ(true)); |
579 | } |
580 | |
581 | if (mask & HL_STANDOUT) { |
582 | PUT(hl, "standout" , BOOLEAN_OBJ(true)); |
583 | } |
584 | |
585 | if (mask & HL_UNDERLINE) { |
586 | PUT(hl, "underline" , BOOLEAN_OBJ(true)); |
587 | } |
588 | |
589 | if (mask & HL_UNDERCURL) { |
590 | PUT(hl, "undercurl" , BOOLEAN_OBJ(true)); |
591 | } |
592 | |
593 | if (mask & HL_ITALIC) { |
594 | PUT(hl, "italic" , BOOLEAN_OBJ(true)); |
595 | } |
596 | |
597 | if (mask & HL_INVERSE) { |
598 | PUT(hl, "reverse" , BOOLEAN_OBJ(true)); |
599 | } |
600 | |
601 | if (mask & HL_STRIKETHROUGH) { |
602 | PUT(hl, "strikethrough" , BOOLEAN_OBJ(true)); |
603 | } |
604 | |
605 | if (use_rgb) { |
606 | if (ae.rgb_fg_color != -1) { |
607 | PUT(hl, "foreground" , INTEGER_OBJ(ae.rgb_fg_color)); |
608 | } |
609 | |
610 | if (ae.rgb_bg_color != -1) { |
611 | PUT(hl, "background" , INTEGER_OBJ(ae.rgb_bg_color)); |
612 | } |
613 | |
614 | if (ae.rgb_sp_color != -1) { |
615 | PUT(hl, "special" , INTEGER_OBJ(ae.rgb_sp_color)); |
616 | } |
617 | } else { |
618 | if (cterm_normal_fg_color != ae.cterm_fg_color) { |
619 | PUT(hl, "foreground" , INTEGER_OBJ(ae.cterm_fg_color - 1)); |
620 | } |
621 | |
622 | if (cterm_normal_bg_color != ae.cterm_bg_color) { |
623 | PUT(hl, "background" , INTEGER_OBJ(ae.cterm_bg_color - 1)); |
624 | } |
625 | } |
626 | |
627 | if (ae.hl_blend > -1) { |
628 | PUT(hl, "blend" , INTEGER_OBJ(ae.hl_blend)); |
629 | } |
630 | |
631 | return hl; |
632 | } |
633 | |
634 | Array hl_inspect(int attr) |
635 | { |
636 | Array ret = ARRAY_DICT_INIT; |
637 | if (hlstate_active) { |
638 | hl_inspect_impl(&ret, attr); |
639 | } |
640 | return ret; |
641 | } |
642 | |
643 | static void hl_inspect_impl(Array *arr, int attr) |
644 | { |
645 | Dictionary item = ARRAY_DICT_INIT; |
646 | if (attr <= 0 || attr >= (int)kv_size(attr_entries)) { |
647 | return; |
648 | } |
649 | |
650 | HlEntry e = kv_A(attr_entries, attr); |
651 | switch (e.kind) { |
652 | case kHlSyntax: |
653 | PUT(item, "kind" , STRING_OBJ(cstr_to_string("syntax" ))); |
654 | PUT(item, "hi_name" , |
655 | STRING_OBJ(cstr_to_string((char *)syn_id2name(e.id1)))); |
656 | break; |
657 | |
658 | case kHlUI: |
659 | PUT(item, "kind" , STRING_OBJ(cstr_to_string("ui" ))); |
660 | const char *ui_name = (e.id1 == -1) ? "Normal" : hlf_names[e.id1]; |
661 | PUT(item, "ui_name" , STRING_OBJ(cstr_to_string(ui_name))); |
662 | PUT(item, "hi_name" , |
663 | STRING_OBJ(cstr_to_string((char *)syn_id2name(e.id2)))); |
664 | break; |
665 | |
666 | case kHlTerminal: |
667 | PUT(item, "kind" , STRING_OBJ(cstr_to_string("term" ))); |
668 | break; |
669 | |
670 | case kHlCombine: |
671 | case kHlBlend: |
672 | case kHlBlendThrough: |
673 | // attribute combination is associative, so flatten to an array |
674 | hl_inspect_impl(arr, e.id1); |
675 | hl_inspect_impl(arr, e.id2); |
676 | return; |
677 | |
678 | case kHlUnknown: |
679 | return; |
680 | } |
681 | PUT(item, "id" , INTEGER_OBJ(attr)); |
682 | ADD(*arr, DICTIONARY_OBJ(item)); |
683 | } |
684 | |