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