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/api/private/defs.h"
7 #include "nvim/api/private/helpers.h"
8 #include "nvim/highlight.h"
9 #include "nvim/highlight_defs.h"
10 #include "nvim/lua/executor.h"
11 #include "nvim/map.h"
12 #include "nvim/message.h"
13 #include "nvim/option.h"
14 #include "nvim/popupmnu.h"
15 #include "nvim/screen.h"
16 #include "nvim/syntax.h"
17 #include "nvim/ui.h"
18 #include "nvim/vim.h"
19 
20 #ifdef INCLUDE_GENERATED_DECLARATIONS
21 # include "highlight.c.generated.h"
22 #endif
23 
24 static bool hlstate_active = false;
25 
26 static kvec_t(HlEntry) attr_entries = KV_INITIAL_VALUE;
27 
28 static Map(HlEntry, int) attr_entry_ids = MAP_INIT;
29 static Map(int, int) combine_attr_entries = MAP_INIT;
30 static Map(int, int) blend_attr_entries = MAP_INIT;
31 static Map(int, int) blendthrough_attr_entries = MAP_INIT;
32 
33 /// highlight entries private to a namespace
34 static Map(ColorKey, ColorItem) ns_hl;
35 
highlight_init(void)36 void highlight_init(void)
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
highlight_use_hlstate(void)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.
get_attr_entry(HlEntry entry)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   size_t next_id = kv_size(attr_entries);
94   if (next_id > INT_MAX) {
95     ELOG("The index on attr_entries has overflowed");
96     return 0;
97   }
98   id = (int)next_id;
99   kv_push(attr_entries, entry);
100 
101   map_put(HlEntry, int)(&attr_entry_ids, entry, id);
102 
103   Array inspect = hl_inspect(id);
104 
105   // Note: internally we don't distinguish between cterm and rgb attributes,
106   // remote_ui_hl_attr_define will however.
107   ui_call_hl_attr_define(id, entry.attr, entry.attr, inspect);
108   api_free_array(inspect);
109   return id;
110 }
111 
112 /// When a UI connects, we need to send it the table of highlights used so far.
ui_send_all_hls(UI * ui)113 void ui_send_all_hls(UI *ui)
114 {
115   if (ui->hl_attr_define) {
116     for (size_t i = 1; i < kv_size(attr_entries); i++) {
117       Array inspect = hl_inspect((int)i);
118       ui->hl_attr_define(ui, (Integer)i, kv_A(attr_entries, i).attr,
119                          kv_A(attr_entries, i).attr, inspect);
120       api_free_array(inspect);
121     }
122   }
123   if (ui->hl_group_set) {
124     for (size_t hlf = 0; hlf < HLF_COUNT; hlf++) {
125       ui->hl_group_set(ui, cstr_as_string((char *)hlf_names[hlf]),
126                        highlight_attr[hlf]);
127     }
128   }
129 }
130 
131 /// Get attribute code for a syntax group.
hl_get_syn_attr(int ns_id,int idx,HlAttrs at_en)132 int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en)
133 {
134   // TODO(bfredl): should we do this unconditionally
135   if (at_en.cterm_fg_color != 0 || at_en.cterm_bg_color != 0
136       || at_en.rgb_fg_color != -1 || at_en.rgb_bg_color != -1
137       || at_en.rgb_sp_color != -1 || at_en.cterm_ae_attr != 0
138       || at_en.rgb_ae_attr != 0 || ns_id != 0) {
139     return get_attr_entry((HlEntry){ .attr = at_en, .kind = kHlSyntax,
140                                      .id1 = idx, .id2 = ns_id });
141   } else {
142     // If all the fields are cleared, clear the attr field back to default value
143     return 0;
144   }
145 }
146 
ns_hl_def(NS ns_id,int hl_id,HlAttrs attrs,int link_id)147 void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id)
148 {
149   DecorProvider *p = get_decor_provider(ns_id, true);
150   if ((attrs.rgb_ae_attr & HL_DEFAULT)
151       && map_has(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id))) {
152     return;
153   }
154   int attr_id = link_id > 0 ? -1 : hl_get_syn_attr(ns_id, hl_id, attrs);
155   ColorItem it = { .attr_id = attr_id,
156                    .link_id = link_id,
157                    .version = p->hl_valid,
158                    .is_default = (attrs.rgb_ae_attr & HL_DEFAULT) };
159   map_put(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id), it);
160 }
161 
ns_get_hl(NS ns_id,int hl_id,bool link,bool nodefault)162 int ns_get_hl(NS ns_id, int hl_id, bool link, bool nodefault)
163 {
164   static int recursive = 0;
165 
166   if (ns_id < 0) {
167     if (ns_hl_active <= 0) {
168       return -1;
169     }
170     ns_id = ns_hl_active;
171   }
172 
173   DecorProvider *p = get_decor_provider(ns_id, true);
174   ColorItem it = map_get(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id));
175   // TODO(bfredl): map_ref true even this?
176   bool valid_cache = it.version >= p->hl_valid;
177 
178   if (!valid_cache && p->hl_def != LUA_NOREF && !recursive) {
179     FIXED_TEMP_ARRAY(args, 3);
180     args.items[0] = INTEGER_OBJ((Integer)ns_id);
181     args.items[1] = STRING_OBJ(cstr_to_string((char *)syn_id2name(hl_id)));
182     args.items[2] = BOOLEAN_OBJ(link);
183     // TODO(bfredl): preload the "global" attr dict?
184 
185     Error err = ERROR_INIT;
186     recursive++;
187     Object ret = nlua_call_ref(p->hl_def, "hl_def", args, true, &err);
188     recursive--;
189 
190     // TODO(bfredl): or "inherit", combine with global value?
191     bool fallback = true;
192     int tmp = false;
193     HlAttrs attrs = HLATTRS_INIT;
194     if (ret.type == kObjectTypeDictionary) {
195       Dictionary dict = ret.data.dictionary;
196       fallback = false;
197       attrs = dict2hlattrs(dict, true, &it.link_id, &err);
198       for (size_t i = 0; i < dict.size; i++) {
199         char *key = dict.items[i].key.data;
200         Object val = dict.items[i].value;
201         bool truthy = api_object_to_bool(val, key, false, &err);
202 
203         if (strequal(key, "fallback")) {
204           fallback = truthy;
205         } else if (strequal(key, "temp")) {
206           tmp = truthy;
207         }
208       }
209       if (it.link_id >= 0) {
210         fallback = true;
211       }
212     }
213 
214     it.attr_id = fallback ? -1 : hl_get_syn_attr((int)ns_id, hl_id, attrs);
215     it.version = p->hl_valid-tmp;
216     it.is_default = attrs.rgb_ae_attr & HL_DEFAULT;
217     map_put(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id), it);
218   }
219 
220   if (it.is_default && nodefault) {
221     return -1;
222   }
223 
224   if (link) {
225     return it.attr_id >= 0 ? 0 : it.link_id;
226   } else {
227     return it.attr_id;
228   }
229 }
230 
231 
win_check_ns_hl(win_T * wp)232 bool win_check_ns_hl(win_T *wp)
233 {
234   if (ns_hl_changed) {
235     highlight_changed();
236     if (wp) {
237       update_window_hl(wp, true);
238     }
239     ns_hl_changed = false;
240     return true;
241   }
242   return false;
243 }
244 
245 /// Get attribute code for a builtin highlight group.
246 ///
247 /// The final syntax group could be modified by hi-link or 'winhighlight'.
hl_get_ui_attr(int idx,int final_id,bool optional)248 int hl_get_ui_attr(int idx, int final_id, bool optional)
249 {
250   HlAttrs attrs = HLATTRS_INIT;
251   bool available = false;
252 
253   if (final_id > 0) {
254     int syn_attr = syn_id2attr(final_id);
255     if (syn_attr != 0) {
256       attrs = syn_attr2entry(syn_attr);
257       available = true;
258     }
259   }
260 
261   if (HLF_PNI <= idx && idx <= HLF_PST) {
262     if (attrs.hl_blend == -1 && p_pb > 0) {
263       attrs.hl_blend = (int)p_pb;
264     }
265     if (pum_drawn()) {
266       must_redraw_pum = true;
267     }
268   } else if (idx == HLF_MSG) {
269     msg_grid.blending = attrs.hl_blend > -1;
270   }
271 
272   if (optional && !available) {
273     return 0;
274   }
275   return get_attr_entry((HlEntry){ .attr = attrs, .kind = kHlUI,
276                                    .id1 = idx, .id2 = final_id });
277 }
278 
update_window_hl(win_T * wp,bool invalid)279 void update_window_hl(win_T *wp, bool invalid)
280 {
281   if (!wp->w_hl_needs_update && !invalid) {
282     return;
283   }
284   wp->w_hl_needs_update = false;
285 
286   // If a floating window is blending it always have a named
287   // wp->w_hl_attr_normal group. HL_ATTR(HLF_NFLOAT) is always named.
288   bool has_blend = wp->w_floating && wp->w_p_winbl != 0;
289 
290   // determine window specific background set in 'winhighlight'
291   bool float_win = wp->w_floating && !wp->w_float_config.external;
292   if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] != 0) {
293     wp->w_hl_attr_normal = hl_get_ui_attr(HLF_INACTIVE,
294                                           wp->w_hl_ids[HLF_INACTIVE],
295                                           !has_blend);
296   } else if (float_win && wp->w_hl_ids[HLF_NFLOAT] != 0) {
297     wp->w_hl_attr_normal = hl_get_ui_attr(HLF_NFLOAT,
298                                           wp->w_hl_ids[HLF_NFLOAT], !has_blend);
299   } else if (wp->w_hl_id_normal != 0) {
300     wp->w_hl_attr_normal = hl_get_ui_attr(-1, wp->w_hl_id_normal, !has_blend);
301   } else {
302     wp->w_hl_attr_normal = float_win ? HL_ATTR(HLF_NFLOAT) : 0;
303   }
304 
305   // NOOOO! You cannot just pretend that "Normal" is just like any other
306   // syntax group! It needs at least 10 layers of special casing! Noooooo!
307   //
308   // haha, theme engine go brrr
309   int normality = syn_check_group(S_LEN("Normal"));
310   int ns_attr = ns_get_hl(-1, normality, false, false);
311   if (ns_attr > 0) {
312     // TODO(bfredl): hantera NormalNC and so on
313     wp->w_hl_attr_normal = ns_attr;
314   }
315 
316   // if blend= attribute is not set, 'winblend' value overrides it.
317   if (wp->w_floating && wp->w_p_winbl > 0) {
318     HlEntry entry = kv_A(attr_entries, wp->w_hl_attr_normal);
319     if (entry.attr.hl_blend == -1) {
320       entry.attr.hl_blend = (int)wp->w_p_winbl;
321       wp->w_hl_attr_normal = get_attr_entry(entry);
322     }
323   }
324 
325   if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] == 0) {
326     wp->w_hl_attr_normal = hl_combine_attr(HL_ATTR(HLF_INACTIVE),
327                                            wp->w_hl_attr_normal);
328   }
329 
330   for (int hlf = 0; hlf < (int)HLF_COUNT; hlf++) {
331     int attr;
332     if (wp->w_hl_ids[hlf] != 0) {
333       attr = hl_get_ui_attr(hlf, wp->w_hl_ids[hlf], false);
334     } else {
335       attr = HL_ATTR(hlf);
336     }
337     wp->w_hl_attrs[hlf] = attr;
338   }
339 
340   wp->w_float_config.shadow = false;
341   if (wp->w_floating && wp->w_float_config.border) {
342     for (int i = 0; i < 8; i++) {
343       int attr = wp->w_hl_attrs[HLF_BORDER];
344       if (wp->w_float_config.border_hl_ids[i]) {
345         attr = hl_get_ui_attr(HLF_BORDER, wp->w_float_config.border_hl_ids[i],
346                               false);
347         HlAttrs a = syn_attr2entry(attr);
348         if (a.hl_blend) {
349           wp->w_float_config.shadow = true;
350         }
351       }
352       wp->w_float_config.border_attr[i] = attr;
353     }
354   }
355 
356   // shadow might cause blending
357   check_blending(wp);
358 }
359 
360 /// Gets HL_UNDERLINE highlight.
hl_get_underline(void)361 int hl_get_underline(void)
362 {
363   return get_attr_entry((HlEntry){
364     .attr = (HlAttrs){
365       .cterm_ae_attr = (int16_t)HL_UNDERLINE,
366       .cterm_fg_color = 0,
367       .cterm_bg_color = 0,
368       .rgb_ae_attr = (int16_t)HL_UNDERLINE,
369       .rgb_fg_color = -1,
370       .rgb_bg_color = -1,
371       .rgb_sp_color = -1,
372       .hl_blend = -1,
373     },
374     .kind = kHlUI,
375     .id1 = 0,
376     .id2 = 0,
377   });
378 }
379 
380 /// Get attribute code for forwarded :terminal highlights.
hl_get_term_attr(HlAttrs * aep)381 int hl_get_term_attr(HlAttrs *aep)
382 {
383   return get_attr_entry((HlEntry){ .attr= *aep, .kind = kHlTerminal,
384                                    .id1 = 0, .id2 = 0 });
385 }
386 
387 /// Clear all highlight tables.
clear_hl_tables(bool reinit)388 void clear_hl_tables(bool reinit)
389 {
390   if (reinit) {
391     kv_size(attr_entries) = 1;
392     map_clear(HlEntry, int)(&attr_entry_ids);
393     map_clear(int, int)(&combine_attr_entries);
394     map_clear(int, int)(&blend_attr_entries);
395     map_clear(int, int)(&blendthrough_attr_entries);
396     memset(highlight_attr_last, -1, sizeof(highlight_attr_last));
397     highlight_attr_set_all();
398     highlight_changed();
399     screen_invalidate_highlights();
400   } else {
401     kv_destroy(attr_entries);
402     map_destroy(HlEntry, int)(&attr_entry_ids);
403     map_destroy(int, int)(&combine_attr_entries);
404     map_destroy(int, int)(&blend_attr_entries);
405     map_destroy(int, int)(&blendthrough_attr_entries);
406     map_destroy(ColorKey, ColorItem)(&ns_hl);
407   }
408 }
409 
hl_invalidate_blends(void)410 void hl_invalidate_blends(void)
411 {
412   map_clear(int, int)(&blend_attr_entries);
413   map_clear(int, int)(&blendthrough_attr_entries);
414   highlight_changed();
415   update_window_hl(curwin, true);
416 }
417 
418 // Combine special attributes (e.g., for spelling) with other attributes
419 // (e.g., for syntax highlighting).
420 // "prim_attr" overrules "char_attr".
421 // This creates a new group when required.
422 // Since we expect there to be a lot of spelling mistakes we cache the result.
423 // Return the resulting attributes.
hl_combine_attr(int char_attr,int prim_attr)424 int hl_combine_attr(int char_attr, int prim_attr)
425 {
426   if (char_attr == 0) {
427     return prim_attr;
428   } else if (prim_attr == 0) {
429     return char_attr;
430   }
431 
432   // TODO(bfredl): could use a struct for clearer intent.
433   int combine_tag = (char_attr << 16) + prim_attr;
434   int id = map_get(int, int)(&combine_attr_entries, combine_tag);
435   if (id > 0) {
436     return id;
437   }
438 
439   HlAttrs char_aep = syn_attr2entry(char_attr);
440   HlAttrs spell_aep = syn_attr2entry(prim_attr);
441 
442   // start with low-priority attribute, and override colors if present below.
443   HlAttrs new_en = char_aep;
444 
445   if (spell_aep.cterm_ae_attr & HL_NOCOMBINE) {
446     new_en.cterm_ae_attr = spell_aep.cterm_ae_attr;
447   } else {
448     new_en.cterm_ae_attr |= spell_aep.cterm_ae_attr;
449   }
450   if (spell_aep.rgb_ae_attr & HL_NOCOMBINE) {
451     new_en.rgb_ae_attr = spell_aep.rgb_ae_attr;
452   } else {
453     new_en.rgb_ae_attr |= spell_aep.rgb_ae_attr;
454   }
455 
456   if (spell_aep.cterm_fg_color > 0) {
457     new_en.cterm_fg_color = spell_aep.cterm_fg_color;
458     new_en.rgb_ae_attr &= ((~HL_FG_INDEXED)
459                            | (spell_aep.rgb_ae_attr & HL_FG_INDEXED));
460   }
461 
462   if (spell_aep.cterm_bg_color > 0) {
463     new_en.cterm_bg_color = spell_aep.cterm_bg_color;
464     new_en.rgb_ae_attr &= ((~HL_BG_INDEXED)
465                            | (spell_aep.rgb_ae_attr & HL_BG_INDEXED));
466   }
467 
468   if (spell_aep.rgb_fg_color >= 0) {
469     new_en.rgb_fg_color = spell_aep.rgb_fg_color;
470     new_en.rgb_ae_attr &= ((~HL_FG_INDEXED)
471                            | (spell_aep.rgb_ae_attr & HL_FG_INDEXED));
472   }
473 
474   if (spell_aep.rgb_bg_color >= 0) {
475     new_en.rgb_bg_color = spell_aep.rgb_bg_color;
476     new_en.rgb_ae_attr &= ((~HL_BG_INDEXED)
477                            | (spell_aep.rgb_ae_attr & HL_BG_INDEXED));
478   }
479 
480   if (spell_aep.rgb_sp_color >= 0) {
481     new_en.rgb_sp_color = spell_aep.rgb_sp_color;
482   }
483 
484   if (spell_aep.hl_blend >= 0) {
485     new_en.hl_blend = spell_aep.hl_blend;
486   }
487 
488   id = get_attr_entry((HlEntry){ .attr = new_en, .kind = kHlCombine,
489                                  .id1 = char_attr, .id2 = prim_attr });
490   if (id > 0) {
491     map_put(int, int)(&combine_attr_entries, combine_tag, id);
492   }
493 
494   return id;
495 }
496 
497 /// Get the used rgb colors for an attr group.
498 ///
499 /// If colors are unset, use builtin default colors. Never returns -1
500 /// Cterm colors are unchanged.
get_colors_force(int attr)501 static HlAttrs get_colors_force(int attr)
502 {
503   HlAttrs attrs = syn_attr2entry(attr);
504   if (attrs.rgb_bg_color == -1) {
505     attrs.rgb_bg_color = normal_bg;
506   }
507   if (attrs.rgb_fg_color == -1) {
508     attrs.rgb_fg_color = normal_fg;
509   }
510   if (attrs.rgb_sp_color == -1) {
511     attrs.rgb_sp_color = normal_sp;
512   }
513   HL_SET_DEFAULT_COLORS(attrs.rgb_fg_color, attrs.rgb_bg_color,
514                         attrs.rgb_sp_color);
515 
516   if (attrs.rgb_ae_attr & HL_INVERSE) {
517     int temp = attrs.rgb_bg_color;
518     attrs.rgb_bg_color = attrs.rgb_fg_color;
519     attrs.rgb_fg_color = temp;
520     attrs.rgb_ae_attr &= ~HL_INVERSE;
521   }
522 
523   return attrs;
524 }
525 
526 /// Blend overlay attributes (for popupmenu) with other attributes
527 ///
528 /// This creates a new group when required.
529 /// This is called per-cell, so cache the result.
530 ///
531 /// @return the resulting attributes.
hl_blend_attrs(int back_attr,int front_attr,bool * through)532 int hl_blend_attrs(int back_attr, int front_attr, bool *through)
533 {
534   if (front_attr < 0 || back_attr < 0) {
535     return -1;
536   }
537 
538   HlAttrs fattrs = get_colors_force(front_attr);
539   int ratio = fattrs.hl_blend;
540   if (ratio <= 0) {
541     *through = false;
542     return front_attr;
543   }
544 
545   int combine_tag = (back_attr << 16) + front_attr;
546   Map(int, int) *map = (*through
547                         ? &blendthrough_attr_entries
548                         : &blend_attr_entries);
549   int id = map_get(int, int)(map, combine_tag);
550   if (id > 0) {
551     return id;
552   }
553 
554   HlAttrs battrs = get_colors_force(back_attr);
555   HlAttrs cattrs;
556 
557   if (*through) {
558     cattrs = battrs;
559     cattrs.rgb_fg_color = rgb_blend(ratio, battrs.rgb_fg_color,
560                                     fattrs.rgb_bg_color);
561     if (cattrs.rgb_ae_attr & (HL_UNDERLINE|HL_UNDERCURL)) {
562       cattrs.rgb_sp_color = rgb_blend(ratio, battrs.rgb_sp_color,
563                                       fattrs.rgb_bg_color);
564     } else {
565       cattrs.rgb_sp_color = -1;
566     }
567 
568     cattrs.cterm_bg_color = fattrs.cterm_bg_color;
569     cattrs.cterm_fg_color = cterm_blend(ratio, battrs.cterm_fg_color,
570                                         fattrs.cterm_bg_color);
571     cattrs.rgb_ae_attr &= ~(HL_FG_INDEXED | HL_BG_INDEXED);
572   } else {
573     cattrs = fattrs;
574     if (ratio >= 50) {
575       cattrs.rgb_ae_attr |= battrs.rgb_ae_attr;
576     }
577     cattrs.rgb_fg_color = rgb_blend(ratio/2, battrs.rgb_fg_color,
578                                     fattrs.rgb_fg_color);
579     if (cattrs.rgb_ae_attr & (HL_UNDERLINE|HL_UNDERCURL)) {
580       cattrs.rgb_sp_color = rgb_blend(ratio/2, battrs.rgb_bg_color,
581                                       fattrs.rgb_sp_color);
582     } else {
583       cattrs.rgb_sp_color = -1;
584     }
585 
586     cattrs.rgb_ae_attr &= ~HL_BG_INDEXED;
587   }
588   cattrs.rgb_bg_color = rgb_blend(ratio, battrs.rgb_bg_color,
589                                   fattrs.rgb_bg_color);
590 
591   cattrs.hl_blend = -1;  // blend property was consumed
592 
593   HlKind kind = *through ? kHlBlendThrough : kHlBlend;
594   id = get_attr_entry((HlEntry){ .attr = cattrs, .kind = kind,
595                                  .id1 = back_attr, .id2 = front_attr });
596   if (id > 0) {
597     map_put(int, int)(map, combine_tag, id);
598   }
599   return id;
600 }
601 
rgb_blend(int ratio,int rgb1,int rgb2)602 static int rgb_blend(int ratio, int rgb1, int rgb2)
603 {
604   int a = ratio, b = 100-ratio;
605   int r1 = (rgb1 & 0xFF0000) >> 16;
606   int g1 = (rgb1 & 0x00FF00) >> 8;
607   int b1 = (rgb1 & 0x0000FF) >> 0;
608   int r2 = (rgb2 & 0xFF0000) >> 16;
609   int g2 = (rgb2 & 0x00FF00) >> 8;
610   int b2 = (rgb2 & 0x0000FF) >> 0;
611   int mr = (a * r1 + b * r2)/100;
612   int mg = (a * g1 + b * g2)/100;
613   int mb = (a * b1 + b * b2)/100;
614   return (mr << 16) + (mg << 8) + mb;
615 }
616 
cterm_blend(int ratio,int c1,int c2)617 static int cterm_blend(int ratio, int c1, int c2)
618 {
619   // 1. Convert cterm color numbers to RGB.
620   // 2. Blend the RGB colors.
621   // 3. Convert the RGB result to a cterm color.
622   int rgb1 = hl_cterm2rgb_color(c1);
623   int rgb2 = hl_cterm2rgb_color(c2);
624   int rgb_blended = rgb_blend(ratio, rgb1, rgb2);
625   return hl_rgb2cterm_color(rgb_blended);
626 }
627 
628 /// Converts RGB color to 8-bit color (0-255).
hl_rgb2cterm_color(int rgb)629 static int hl_rgb2cterm_color(int rgb)
630 {
631   int r = (rgb & 0xFF0000) >> 16;
632   int g = (rgb & 0x00FF00) >> 8;
633   int b = (rgb & 0x0000FF) >> 0;
634 
635   return (r * 6 / 256) * 36 + (g * 6 / 256) * 6 + (b * 6 / 256);
636 }
637 
638 /// Converts 8-bit color (0-255) to RGB color.
639 /// This is compatible with xterm.
hl_cterm2rgb_color(int nr)640 static int hl_cterm2rgb_color(int nr)
641 {
642   static int cube_value[] = {
643     0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF
644   };
645   static int grey_ramp[] = {
646     0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A, 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76,
647     0x80, 0x8A, 0x94, 0x9E, 0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE
648   };
649   static char_u ansi_table[16][4] = {
650     //  R    G    B   idx
651     {   0,   0,   0,  1 },  // black
652     { 224,   0,   0,  2 },  // dark red
653     {   0, 224,   0,  3 },  // dark green
654     { 224, 224,   0,  4 },  // dark yellow / brown
655     {   0,   0, 224,  5 },  // dark blue
656     { 224,   0, 224,  6 },  // dark magenta
657     {   0, 224, 224,  7 },  // dark cyan
658     { 224, 224, 224,  8 },  // light grey
659 
660     { 128, 128, 128,  9 },  // dark grey
661     { 255,  64,  64, 10 },  // light red
662     {  64, 255,  64, 11 },  // light green
663     { 255, 255,  64, 12 },  // yellow
664     {  64,  64, 255, 13 },  // light blue
665     { 255,  64, 255, 14 },  // light magenta
666     {  64, 255, 255, 15 },  // light cyan
667     { 255, 255, 255, 16 },  // white
668   };
669 
670   int r = 0;
671   int g = 0;
672   int b = 0;
673   int idx;
674   // *ansi_idx = 0;
675 
676   if (nr < 16) {
677     r = ansi_table[nr][0];
678     g = ansi_table[nr][1];
679     b = ansi_table[nr][2];
680     // *ansi_idx = ansi_table[nr][3];
681   } else if (nr < 232) {  // 216 color-cube
682     idx = nr - 16;
683     r = cube_value[idx / 36 % 6];
684     g = cube_value[idx / 6  % 6];
685     b = cube_value[idx      % 6];
686     // *ansi_idx = -1;
687   } else if (nr < 256) {  // 24 greyscale ramp
688     idx = nr - 232;
689     r = grey_ramp[idx];
690     g = grey_ramp[idx];
691     b = grey_ramp[idx];
692     // *ansi_idx = -1;
693   }
694   return (r << 16) + (g << 8) + b;
695 }
696 
697 /// Get highlight attributes for a attribute code
syn_attr2entry(int attr)698 HlAttrs syn_attr2entry(int attr)
699 {
700   if (attr <= 0 || attr >= (int)kv_size(attr_entries)) {
701     // invalid attribute code, or the tables were cleared
702     return HLATTRS_INIT;
703   }
704   return kv_A(attr_entries, attr).attr;
705 }
706 
707 /// Gets highlight description for id `attr_id` as a map.
hl_get_attr_by_id(Integer attr_id,Boolean rgb,Error * err)708 Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Error *err)
709 {
710   Dictionary dic = ARRAY_DICT_INIT;
711 
712   if (attr_id == 0) {
713     return dic;
714   }
715 
716   if (attr_id <= 0 || attr_id >= (int)kv_size(attr_entries)) {
717     api_set_error(err, kErrorTypeException,
718                   "Invalid attribute id: %" PRId64, attr_id);
719     return dic;
720   }
721 
722   return hlattrs2dict(syn_attr2entry((int)attr_id), rgb);
723 }
724 
725 /// Converts an HlAttrs into Dictionary
726 ///
727 /// @param[in] aep data to convert
728 /// @param use_rgb use 'gui*' settings if true, else resorts to 'cterm*'
hlattrs2dict(HlAttrs ae,bool use_rgb)729 Dictionary hlattrs2dict(HlAttrs ae, bool use_rgb)
730 {
731   Dictionary hl = ARRAY_DICT_INIT;
732   int mask  = use_rgb ? ae.rgb_ae_attr : ae.cterm_ae_attr;
733 
734   if (mask & HL_BOLD) {
735     PUT(hl, "bold", BOOLEAN_OBJ(true));
736   }
737 
738   if (mask & HL_STANDOUT) {
739     PUT(hl, "standout", BOOLEAN_OBJ(true));
740   }
741 
742   if (mask & HL_UNDERLINE) {
743     PUT(hl, "underline", BOOLEAN_OBJ(true));
744   }
745 
746   if (mask & HL_UNDERCURL) {
747     PUT(hl, "undercurl", BOOLEAN_OBJ(true));
748   }
749 
750   if (mask & HL_ITALIC) {
751     PUT(hl, "italic", BOOLEAN_OBJ(true));
752   }
753 
754   if (mask & HL_INVERSE) {
755     PUT(hl, "reverse", BOOLEAN_OBJ(true));
756   }
757 
758   if (mask & HL_STRIKETHROUGH) {
759     PUT(hl, "strikethrough", BOOLEAN_OBJ(true));
760   }
761 
762   if (use_rgb) {
763     if (mask & HL_FG_INDEXED) {
764       PUT(hl, "fg_indexed", BOOLEAN_OBJ(true));
765     }
766 
767     if (mask & HL_BG_INDEXED) {
768       PUT(hl, "bg_indexed", BOOLEAN_OBJ(true));
769     }
770 
771     if (ae.rgb_fg_color != -1) {
772       PUT(hl, "foreground", INTEGER_OBJ(ae.rgb_fg_color));
773     }
774 
775     if (ae.rgb_bg_color != -1) {
776       PUT(hl, "background", INTEGER_OBJ(ae.rgb_bg_color));
777     }
778 
779     if (ae.rgb_sp_color != -1) {
780       PUT(hl, "special", INTEGER_OBJ(ae.rgb_sp_color));
781     }
782   } else {
783     if (cterm_normal_fg_color != ae.cterm_fg_color && ae.cterm_fg_color != 0) {
784       PUT(hl, "foreground", INTEGER_OBJ(ae.cterm_fg_color - 1));
785     }
786 
787     if (cterm_normal_bg_color != ae.cterm_bg_color && ae.cterm_bg_color != 0) {
788       PUT(hl, "background", INTEGER_OBJ(ae.cterm_bg_color - 1));
789     }
790   }
791 
792   if (ae.hl_blend > -1) {
793     PUT(hl, "blend", INTEGER_OBJ(ae.hl_blend));
794   }
795 
796   return hl;
797 }
798 
dict2hlattrs(Dictionary dict,bool use_rgb,int * link_id,Error * err)799 HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err)
800 {
801   HlAttrs hlattrs = HLATTRS_INIT;
802 
803   int32_t fg = -1, bg = -1, ctermfg = -1, ctermbg = -1, sp = -1;
804   int16_t mask = 0;
805   int16_t cterm_mask = 0;
806   bool cterm_mask_provided = false;
807 
808   for (size_t i = 0; i < dict.size; i++) {
809     char *key = dict.items[i].key.data;
810     Object val = dict.items[i].value;
811 
812     struct {
813       const char *name;
814       int16_t flag;
815     } flags[] = {
816       { "bold", HL_BOLD },
817       { "standout", HL_STANDOUT },
818       { "underline", HL_UNDERLINE },
819       { "undercurl", HL_UNDERCURL },
820       { "italic", HL_ITALIC },
821       { "reverse", HL_INVERSE },
822       { "default", HL_DEFAULT },
823       { "global", HL_GLOBAL },
824       { NULL, 0 },
825     };
826 
827     int j;
828     for (j = 0; flags[j].name; j++) {
829       if (strequal(flags[j].name, key)) {
830         if (api_object_to_bool(val, key, false, err)) {
831           mask = mask | flags[j].flag;
832         }
833         break;
834       }
835     }
836 
837     // Handle cterm attrs
838     if (strequal(key, "cterm") && val.type == kObjectTypeDictionary) {
839       cterm_mask_provided = true;
840       Dictionary cterm_dict = val.data.dictionary;
841       for (size_t l = 0; l < cterm_dict.size; l++) {
842         char *cterm_dict_key = cterm_dict.items[l].key.data;
843         Object cterm_dict_val = cterm_dict.items[l].value;
844         for (int m = 0; flags[m].name; m++) {
845           if (strequal(flags[m].name, cterm_dict_key)) {
846             if (api_object_to_bool(cterm_dict_val, cterm_dict_key, false,
847                                    err)) {
848               cterm_mask |= flags[m].flag;
849             }
850             break;
851           }
852         }
853       }
854     }
855 
856     struct {
857       const char *name;
858       const char *shortname;
859       int *dest;
860     } colors[] = {
861       { "foreground", "fg", &fg },
862       { "background", "bg", &bg },
863       { "ctermfg", NULL, &ctermfg },
864       { "ctermbg", NULL, &ctermbg },
865       { "special", "sp", &sp },
866       { NULL, NULL, NULL },
867     };
868 
869     int k;
870     for (k = 0; (!flags[j].name) && colors[k].name; k++) {
871       if (strequal(colors[k].name, key) || strequal(colors[k].shortname, key)) {
872         if (val.type == kObjectTypeInteger) {
873           *colors[k].dest = (int)val.data.integer;
874         } else if (val.type == kObjectTypeString) {
875           String str = val.data.string;
876           // TODO(bfredl): be more fancy with "bg", "fg" etc
877           if (str.size) {
878             *colors[k].dest = name_to_color(str.data);
879           }
880         } else {
881           api_set_error(err, kErrorTypeValidation,
882                         "'%s' must be string or integer", key);
883         }
884         break;
885       }
886     }
887 
888     if (flags[j].name || colors[k].name) {
889       // handled above
890     } else if (link_id && strequal(key, "link")) {
891       if (val.type == kObjectTypeString) {
892         String str = val.data.string;
893         *link_id = syn_check_group(str.data, (int)str.size);
894       } else if (val.type == kObjectTypeInteger) {
895         // TODO(bfredl): validate range?
896         *link_id = (int)val.data.integer;
897       } else {
898         api_set_error(err, kErrorTypeValidation,
899                       "'link' must be string or integer");
900       }
901     }
902 
903     if (ERROR_SET(err)) {
904       return hlattrs;  // error set, caller should not use retval
905     }
906   }
907 
908   // apply gui mask as default for cterm mask
909   if (!cterm_mask_provided) {
910     cterm_mask = mask;
911   }
912   if (use_rgb) {
913     hlattrs.rgb_ae_attr = mask;
914     hlattrs.rgb_bg_color = bg;
915     hlattrs.rgb_fg_color = fg;
916     hlattrs.rgb_sp_color = sp;
917     hlattrs.cterm_bg_color =
918       ctermbg == -1 ? cterm_normal_bg_color : ctermbg + 1;
919     hlattrs.cterm_fg_color =
920       ctermfg == -1 ? cterm_normal_fg_color : ctermfg + 1;
921     hlattrs.cterm_ae_attr = cterm_mask;
922   } else {
923     hlattrs.cterm_ae_attr = cterm_mask;
924     hlattrs.cterm_bg_color = bg == -1 ? cterm_normal_bg_color : bg + 1;
925     hlattrs.cterm_fg_color = fg == -1 ? cterm_normal_fg_color : fg + 1;
926   }
927 
928   return hlattrs;
929 }
hl_inspect(int attr)930 Array hl_inspect(int attr)
931 {
932   Array ret = ARRAY_DICT_INIT;
933   if (hlstate_active) {
934     hl_inspect_impl(&ret, attr);
935   }
936   return ret;
937 }
938 
hl_inspect_impl(Array * arr,int attr)939 static void hl_inspect_impl(Array *arr, int attr)
940 {
941   Dictionary item = ARRAY_DICT_INIT;
942   if (attr <= 0 || attr >= (int)kv_size(attr_entries)) {
943     return;
944   }
945 
946   HlEntry e = kv_A(attr_entries, attr);
947   switch (e.kind) {
948   case kHlSyntax:
949     PUT(item, "kind", STRING_OBJ(cstr_to_string("syntax")));
950     PUT(item, "hi_name",
951         STRING_OBJ(cstr_to_string((char *)syn_id2name(e.id1))));
952     break;
953 
954   case kHlUI:
955     PUT(item, "kind", STRING_OBJ(cstr_to_string("ui")));
956     const char *ui_name = (e.id1 == -1) ? "Normal" : hlf_names[e.id1];
957     PUT(item, "ui_name", STRING_OBJ(cstr_to_string(ui_name)));
958     PUT(item, "hi_name",
959         STRING_OBJ(cstr_to_string((char *)syn_id2name(e.id2))));
960     break;
961 
962   case kHlTerminal:
963     PUT(item, "kind", STRING_OBJ(cstr_to_string("term")));
964     break;
965 
966   case kHlCombine:
967   case kHlBlend:
968   case kHlBlendThrough:
969     // attribute combination is associative, so flatten to an array
970     hl_inspect_impl(arr, e.id1);
971     hl_inspect_impl(arr, e.id2);
972     return;
973 
974   case kHlUnknown:
975     return;
976   }
977   PUT(item, "id", INTEGER_OBJ(attr));
978   ADD(*arr, DICTIONARY_OBJ(item));
979 }
980