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
22static bool hlstate_active = false;
23
24static kvec_t(HlEntry) attr_entries = KV_INITIAL_VALUE;
25
26static Map(HlEntry, int) *attr_entry_ids;
27static Map(int, int) *combine_attr_entries;
28static Map(int, int) *blend_attr_entries;
29static Map(int, int) *blendthrough_attr_entries;
30
31void 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
44bool 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.
59static 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.
108void 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.
127int 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'.
145int 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
176void 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.
228int 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.
248int 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.
255void 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
276void 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.
290int 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.
351static 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.
382int 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
445static 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
460static 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).
472static 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.
483static 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
541HlAttrs 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.
551Dictionary 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*'
572Dictionary 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
634Array 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
643static 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