highlight.c (36878B)
1 // highlight.c: low level code for UI and syntax highlighting 2 3 #include <assert.h> 4 #include <inttypes.h> 5 #include <lauxlib.h> 6 #include <string.h> 7 8 #include "nvim/api/keysets_defs.h" 9 #include "nvim/api/private/defs.h" 10 #include "nvim/api/private/dispatch.h" 11 #include "nvim/api/private/helpers.h" 12 #include "nvim/api/private/validate.h" 13 #include "nvim/api/ui.h" 14 #include "nvim/decoration_defs.h" 15 #include "nvim/decoration_provider.h" 16 #include "nvim/drawscreen.h" 17 #include "nvim/gettext_defs.h" 18 #include "nvim/globals.h" 19 #include "nvim/highlight.h" 20 #include "nvim/highlight_defs.h" 21 #include "nvim/highlight_group.h" 22 #include "nvim/lua/executor.h" 23 #include "nvim/macros_defs.h" 24 #include "nvim/map_defs.h" 25 #include "nvim/memory.h" 26 #include "nvim/memory_defs.h" 27 #include "nvim/message.h" 28 #include "nvim/option.h" 29 #include "nvim/popupmenu.h" 30 #include "nvim/strings.h" 31 #include "nvim/types_defs.h" 32 #include "nvim/ui.h" 33 #include "nvim/vim_defs.h" 34 35 #include "highlight.c.generated.h" 36 37 static bool hlstate_active = false; 38 39 static Set(HlEntry) attr_entries = SET_INIT; 40 static Map(uint64_t, int) combine_attr_entries = MAP_INIT; 41 static Map(uint64_t, int) blend_attr_entries = MAP_INIT; 42 static Map(uint64_t, int) blendthrough_attr_entries = MAP_INIT; 43 static Set(cstr_t) urls = SET_INIT; 44 45 #define attr_entry(i) attr_entries.keys[i] 46 47 /// highlight entries private to a namespace 48 static Map(ColorKey, ColorItem) ns_hls; 49 typedef int NSHlAttr[HLF_COUNT]; 50 static PMap(int) ns_hl_attr; 51 52 void highlight_init(void) 53 { 54 // index 0 is no attribute, add dummy entry: 55 set_put(HlEntry, &attr_entries, ((HlEntry){ .attr = HLATTRS_INIT, .kind = kHlInvalid, 56 .id1 = 0, .id2 = 0 })); 57 } 58 59 /// @return true if hl table was reset 60 bool highlight_use_hlstate(void) 61 { 62 if (hlstate_active) { 63 return false; 64 } 65 hlstate_active = true; 66 // hl tables must now be rebuilt. 67 clear_hl_tables(true); 68 return true; 69 } 70 71 /// Return the attr number for a set of colors and font, and optionally 72 /// a semantic description (see ext_hlstate documentation). 73 /// Add a new entry to the attr_entries array if the combination is new. 74 /// @return 0 for error. 75 static int get_attr_entry(HlEntry entry) 76 { 77 bool retried = false; 78 if (!hlstate_active) { 79 // This information will not be used, erase it and reduce the table size. 80 entry.kind = kHlUnknown; 81 entry.id1 = 0; 82 entry.id2 = 0; 83 } 84 85 retry: {} 86 MHPutStatus status; 87 uint32_t k = set_put_idx(HlEntry, &attr_entries, entry, &status); 88 if (status == kMHExisting) { 89 return (int)k; 90 } 91 92 static bool recursive = false; 93 if (set_size(&attr_entries) > MAX_TYPENR) { 94 // Running out of attribute entries! remove all attributes, and 95 // compute new ones for all groups. 96 // When called recursively, we are really out of numbers. 97 if (recursive || retried) { 98 emsg(_("E424: Too many different highlighting attributes in use")); 99 return 0; 100 } 101 recursive = true; 102 103 clear_hl_tables(true); 104 105 recursive = false; 106 if (entry.kind == kHlCombine) { 107 // This entry is now invalid, don't put it 108 return 0; 109 } 110 retried = true; 111 goto retry; 112 } 113 114 // new attr id, send event to remote ui:s 115 int id = (int)k; 116 117 Arena arena = ARENA_EMPTY; 118 Array inspect = hl_inspect(id, &arena); 119 120 // Note: internally we don't distinguish between cterm and rgb attributes, 121 // remote_ui_hl_attr_define will however. 122 ui_call_hl_attr_define(id, entry.attr, entry.attr, inspect); 123 arena_mem_free(arena_finish(&arena)); 124 return id; 125 } 126 127 /// When a UI connects, we need to send it the table of highlights used so far. 128 void ui_send_all_hls(RemoteUI *ui) 129 { 130 for (size_t i = 1; i < set_size(&attr_entries); i++) { 131 Arena arena = ARENA_EMPTY; 132 Array inspect = hl_inspect((int)i, &arena); 133 HlAttrs attr = attr_entry(i).attr; 134 remote_ui_hl_attr_define(ui, (Integer)i, attr, attr, inspect); 135 arena_mem_free(arena_finish(&arena)); 136 } 137 for (size_t hlf = 0; hlf < HLF_COUNT; hlf++) { 138 remote_ui_hl_group_set(ui, cstr_as_string(hlf_names[hlf]), 139 highlight_attr[hlf]); 140 } 141 } 142 143 /// Get attribute code for a syntax group. 144 int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en) 145 { 146 // TODO(bfredl): should we do this unconditionally 147 if (at_en.cterm_fg_color != 0 || at_en.cterm_bg_color != 0 148 || at_en.rgb_fg_color != -1 || at_en.rgb_bg_color != -1 149 || at_en.rgb_sp_color != -1 || at_en.cterm_ae_attr != 0 150 || at_en.rgb_ae_attr != 0 || ns_id != 0) { 151 return get_attr_entry((HlEntry){ .attr = at_en, .kind = kHlSyntax, 152 .id1 = idx, .id2 = ns_id }); 153 } 154 // If all the fields are cleared, clear the attr field back to default value 155 return 0; 156 } 157 158 void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id, Dict(highlight) *dict) 159 { 160 if (ns_id == 0) { 161 assert(dict); 162 // set in global (':highlight') namespace 163 set_hl_group(hl_id, attrs, dict, link_id); 164 return; 165 } 166 if ((attrs.rgb_ae_attr & HL_DEFAULT) 167 && map_has(ColorKey, &ns_hls, (ColorKey(ns_id, hl_id)))) { 168 return; 169 } 170 DecorProvider *p = get_decor_provider(ns_id, true); 171 int attr_id = link_id > 0 ? -1 : hl_get_syn_attr(ns_id, hl_id, attrs); 172 ColorItem it = { .attr_id = attr_id, 173 .link_id = link_id, 174 .version = p->hl_valid, 175 .is_default = (attrs.rgb_ae_attr & HL_DEFAULT), 176 .link_global = (attrs.rgb_ae_attr & HL_GLOBAL) }; 177 map_put(ColorKey, ColorItem)(&ns_hls, ColorKey(ns_id, hl_id), it); 178 p->hl_cached = false; 179 } 180 181 int ns_get_hl(NS *ns_hl, int hl_id, bool link, bool nodefault) 182 { 183 static int recursive = 0; 184 185 if (*ns_hl == 0) { 186 // ns=0 (the default namespace) does not have a provider so stop here 187 return -1; 188 } 189 190 if (*ns_hl < 0) { 191 if (ns_hl_active <= 0) { 192 return -1; 193 } 194 *ns_hl = ns_hl_active; 195 } 196 197 int ns_id = *ns_hl; 198 199 DecorProvider *p = get_decor_provider(ns_id, true); 200 ColorItem it = map_get(ColorKey, ColorItem)(&ns_hls, ColorKey(ns_id, hl_id)); 201 // TODO(bfredl): map_ref true even this? 202 bool valid_item = it.version >= p->hl_valid; 203 204 if (!valid_item && p->hl_def != LUA_NOREF && !recursive) { 205 MAXSIZE_TEMP_ARRAY(args, 3); 206 ADD_C(args, INTEGER_OBJ((Integer)ns_id)); 207 ADD_C(args, CSTR_AS_OBJ(syn_id2name(hl_id))); 208 ADD_C(args, BOOLEAN_OBJ(link)); 209 // TODO(bfredl): preload the "global" attr dict? 210 211 Error err = ERROR_INIT; 212 recursive++; 213 Object ret = nlua_call_ref(p->hl_def, "hl_def", args, kRetObject, NULL, &err); 214 recursive--; 215 216 // TODO(bfredl): or "inherit", combine with global value? 217 bool fallback = true; 218 int tmp = false; 219 HlAttrs attrs = HLATTRS_INIT; 220 if (ret.type == kObjectTypeDict) { 221 fallback = false; 222 Dict(highlight) dict = KEYDICT_INIT; 223 if (api_dict_to_keydict(&dict, KeyDict_highlight_get_field, ret.data.dict, &err)) { 224 attrs = dict2hlattrs(&dict, true, &it.link_id, &err); 225 fallback = GET_BOOL_OR_TRUE(&dict, highlight, fallback); 226 tmp = dict.fallback; // or false 227 if (it.link_id >= 0) { 228 fallback = true; 229 } 230 } 231 } 232 233 it.attr_id = fallback ? -1 : hl_get_syn_attr(ns_id, hl_id, attrs); 234 it.version = p->hl_valid - tmp; 235 it.is_default = attrs.rgb_ae_attr & HL_DEFAULT; 236 it.link_global = attrs.rgb_ae_attr & HL_GLOBAL; 237 map_put(ColorKey, ColorItem)(&ns_hls, ColorKey(ns_id, hl_id), it); 238 valid_item = true; 239 } 240 241 if ((it.is_default && nodefault) || !valid_item) { 242 return -1; 243 } 244 245 if (link) { 246 if (it.attr_id >= 0) { 247 return 0; 248 } 249 if (it.link_global) { 250 *ns_hl = 0; 251 } 252 return it.link_id; 253 } else { 254 return it.attr_id; 255 } 256 } 257 258 bool hl_check_ns(void) 259 { 260 int ns = 0; 261 if (ns_hl_fast > 0) { 262 ns = ns_hl_fast; 263 } else if (ns_hl_win >= 0) { 264 ns = ns_hl_win; 265 } else { 266 ns = ns_hl_global; 267 } 268 if (ns_hl_active == ns) { 269 return false; 270 } 271 272 ns_hl_active = ns; 273 hl_attr_active = highlight_attr; 274 if (ns > 0) { 275 update_ns_hl(ns); 276 NSHlAttr *hl_def = (NSHlAttr *)pmap_get(int)(&ns_hl_attr, ns); 277 if (hl_def) { 278 hl_attr_active = *hl_def; 279 } 280 } 281 need_highlight_changed = true; 282 return true; 283 } 284 285 /// prepare for drawing window `wp` or global elements if NULL 286 /// 287 /// Note: pum should be drawn in the context of the current window! 288 bool win_check_ns_hl(win_T *wp) 289 { 290 ns_hl_win = wp ? wp->w_ns_hl : -1; 291 return hl_check_ns(); 292 } 293 294 /// Get attribute code for a builtin highlight group. 295 /// 296 /// The final syntax group could be modified by hi-link or 'winhighlight'. 297 int hl_get_ui_attr(int ns_id, int idx, int final_id, bool optional) 298 { 299 HlAttrs attrs = HLATTRS_INIT; 300 bool available = false; 301 302 if (final_id > 0) { 303 int syn_attr = syn_ns_id2attr(ns_id, final_id, &optional); 304 if (syn_attr > 0) { 305 attrs = syn_attr2entry(syn_attr); 306 available = true; 307 } 308 } 309 310 if (HLF_PNI <= idx && idx <= HLF_PST) { 311 if (attrs.hl_blend == -1 && p_pb > 0) { 312 attrs.hl_blend = (int)p_pb; 313 } 314 if (pum_drawn()) { 315 must_redraw_pum = true; 316 } 317 } 318 319 if (optional && !available) { 320 return 0; 321 } 322 return get_attr_entry((HlEntry){ .attr = attrs, .kind = kHlUI, 323 .id1 = idx, .id2 = final_id }); 324 } 325 326 /// Apply 'winblend' to highlight attributes. 327 /// 328 /// @param winbl The 'winblend' value. 329 /// @param attr The original attribute code. 330 /// 331 /// @return The attribute code with 'winblend' applied. 332 int hl_apply_winblend(int winbl, int attr) 333 { 334 HlEntry entry = attr_entry(attr); 335 // if blend= attribute is not set, 'winblend' value overrides it. 336 if (entry.attr.hl_blend == -1 && winbl > 0) { 337 entry.attr.hl_blend = winbl; 338 attr = get_attr_entry(entry); 339 } 340 return attr; 341 } 342 343 void update_window_hl(win_T *wp, bool invalid) 344 { 345 int ns_id = wp->w_ns_hl; 346 347 update_ns_hl(ns_id); 348 if (ns_id != wp->w_ns_hl_active || wp->w_ns_hl_attr == NULL) { 349 wp->w_ns_hl_active = ns_id; 350 351 wp->w_ns_hl_attr = *(NSHlAttr *)pmap_get(int)(&ns_hl_attr, ns_id); 352 if (!wp->w_ns_hl_attr) { 353 // No specific highlights, use the defaults. 354 wp->w_ns_hl_attr = highlight_attr; 355 } 356 } 357 358 int *hl_def = wp->w_ns_hl_attr; 359 360 if (!wp->w_hl_needs_update && !invalid) { 361 return; 362 } 363 wp->w_hl_needs_update = false; 364 365 // If a floating window is blending it always have a named 366 // wp->w_hl_attr_normal group. HL_ATTR(HLF_NFLOAT) is always named. 367 368 // determine window specific background set in 'winhighlight' 369 bool float_win = wp->w_floating && !wp->w_config.external; 370 if (float_win && hl_def[HLF_NFLOAT] != 0 && ns_id > 0) { 371 wp->w_hl_attr_normal = hl_def[HLF_NFLOAT]; 372 } else if (hl_def[HLF_NONE] > 0) { 373 wp->w_hl_attr_normal = hl_def[HLF_NONE]; 374 } else if (float_win) { 375 wp->w_hl_attr_normal = HL_ATTR(HLF_NFLOAT) > 0 376 ? HL_ATTR(HLF_NFLOAT) : highlight_attr[HLF_NFLOAT]; 377 } else { 378 wp->w_hl_attr_normal = 0; 379 } 380 381 if (wp->w_floating) { 382 wp->w_hl_attr_normal = hl_apply_winblend((int)wp->w_p_winbl, wp->w_hl_attr_normal); 383 } 384 385 wp->w_config.shadow = false; 386 if (wp->w_floating && wp->w_config.border) { 387 for (int i = 0; i < 8; i++) { 388 int attr = hl_def[HLF_BORDER]; 389 if (wp->w_config.border_hl_ids[i]) { 390 attr = hl_get_ui_attr(ns_id, HLF_BORDER, 391 wp->w_config.border_hl_ids[i], false); 392 } 393 attr = hl_apply_winblend((int)wp->w_p_winbl, attr); 394 if (syn_attr2entry(attr).hl_blend > 0) { 395 wp->w_config.shadow = true; 396 } 397 wp->w_config.border_attr[i] = attr; 398 } 399 } 400 401 // shadow might cause blending 402 check_blending(wp); 403 404 // TODO(bfredl): this a bit ad-hoc. move it from highlight ns logic to 'winhl' 405 // implementation? 406 if (hl_def[HLF_INACTIVE] == 0) { 407 wp->w_hl_attr_normalnc = hl_combine_attr(HL_ATTR(HLF_INACTIVE), 408 wp->w_hl_attr_normal); 409 } else { 410 wp->w_hl_attr_normalnc = hl_def[HLF_INACTIVE]; 411 } 412 413 if (wp->w_floating) { 414 wp->w_hl_attr_normalnc = hl_apply_winblend((int)wp->w_p_winbl, wp->w_hl_attr_normalnc); 415 } 416 } 417 418 void update_ns_hl(int ns_id) 419 { 420 if (ns_id <= 0) { 421 return; 422 } 423 DecorProvider *p = get_decor_provider(ns_id, true); 424 if (p->hl_cached) { 425 return; 426 } 427 428 NSHlAttr **alloc = (NSHlAttr **)pmap_put_ref(int)(&ns_hl_attr, ns_id, NULL, NULL); 429 if (*alloc == NULL) { 430 *alloc = xmalloc(sizeof(**alloc)); 431 } 432 int *hl_attrs = **alloc; 433 434 for (int hlf = 1; hlf < HLF_COUNT; hlf++) { 435 int id = syn_check_group(hlf_names[hlf], strlen(hlf_names[hlf])); 436 bool optional = (hlf == HLF_INACTIVE || hlf == HLF_NFLOAT); 437 hl_attrs[hlf] = hl_get_ui_attr(ns_id, hlf, id, optional); 438 } 439 440 // NOOOO! You cannot just pretend that "Normal" is just like any other 441 // syntax group! It needs at least 10 layers of special casing! Noooooo! 442 // 443 // haha, tema engine go brrr 444 int normality = syn_check_group(S_LEN("Normal")); 445 hl_attrs[HLF_NONE] = hl_get_ui_attr(ns_id, -1, normality, true); 446 447 // hl_get_ui_attr might have invalidated the decor provider 448 p = get_decor_provider(ns_id, true); 449 p->hl_cached = true; 450 } 451 452 int win_bg_attr(win_T *wp) 453 { 454 if (ns_hl_fast < 0) { 455 int local = (wp == curwin) ? wp->w_hl_attr_normal : wp->w_hl_attr_normalnc; 456 if (local) { 457 return local; 458 } 459 } 460 461 if (wp == curwin || hl_attr_active[HLF_INACTIVE] == 0) { 462 return hl_attr_active[HLF_NONE]; 463 } else { 464 return hl_attr_active[HLF_INACTIVE]; 465 } 466 } 467 468 /// Gets HL_UNDERLINE highlight. 469 int hl_get_underline(void) 470 { 471 HlAttrs attrs = HLATTRS_INIT; 472 attrs.cterm_ae_attr = (int16_t)HL_UNDERLINE; 473 attrs.rgb_ae_attr = (int16_t)HL_UNDERLINE; 474 return get_attr_entry((HlEntry){ 475 .attr = attrs, 476 .kind = kHlUI, 477 .id1 = 0, 478 .id2 = 0, 479 }); 480 } 481 482 /// Augment an existing attribute with a URL. 483 /// 484 /// @param attr Existing attribute to combine with 485 /// @param url The URL to associate with the highlight attribute 486 /// @return Combined attribute 487 int hl_add_url(int attr, const char *url) 488 { 489 HlAttrs attrs = HLATTRS_INIT; 490 491 MHPutStatus status; 492 uint32_t k = set_put_idx(cstr_t, &urls, url, &status); 493 if (status != kMHExisting) { 494 urls.keys[k] = xstrdup(url); 495 } 496 497 attrs.url = (int32_t)k; 498 499 int new = get_attr_entry((HlEntry){ 500 .attr = attrs, 501 .kind = kHlUI, 502 .id1 = 0, 503 .id2 = 0, 504 }); 505 506 return hl_combine_attr(attr, new); 507 } 508 509 /// Get a URL by its index. 510 /// 511 /// @param index URL index 512 /// @return URL 513 const char *hl_get_url(uint32_t index) 514 { 515 assert(urls.keys); 516 return urls.keys[index]; 517 } 518 519 /// Get attribute code for forwarded :terminal highlights. 520 int hl_get_term_attr(HlAttrs *aep) 521 { 522 return get_attr_entry((HlEntry){ .attr = *aep, .kind = kHlTerminal, 523 .id1 = 0, .id2 = 0 }); 524 } 525 526 /// Clear all highlight tables. 527 void clear_hl_tables(bool reinit) 528 { 529 const char *url = NULL; 530 set_foreach(&urls, url, { 531 xfree((void *)url); 532 }); 533 534 if (reinit) { 535 set_clear(HlEntry, &attr_entries); 536 highlight_init(); 537 map_clear(uint64_t, &combine_attr_entries); 538 map_clear(uint64_t, &blend_attr_entries); 539 map_clear(uint64_t, &blendthrough_attr_entries); 540 set_clear(cstr_t, &urls); 541 memset(highlight_attr_last, -1, sizeof(highlight_attr_last)); 542 highlight_attr_set_all(); 543 highlight_changed(); 544 screen_invalidate_highlights(); 545 } else { 546 set_destroy(HlEntry, &attr_entries); 547 map_destroy(uint64_t, &combine_attr_entries); 548 map_destroy(uint64_t, &blend_attr_entries); 549 map_destroy(uint64_t, &blendthrough_attr_entries); 550 map_destroy(ColorKey, &ns_hls); 551 set_destroy(cstr_t, &urls); 552 } 553 } 554 555 void hl_invalidate_blends(void) 556 { 557 map_clear(uint64_t, &blend_attr_entries); 558 map_clear(uint64_t, &blendthrough_attr_entries); 559 highlight_changed(); 560 update_window_hl(curwin, true); 561 } 562 563 /// Combine HlAttrFlags. 564 /// The underline attribute in "prim_ae" overrules the one in "char_ae" if both are present. 565 static int32_t hl_combine_ae(int32_t char_ae, int32_t prim_ae) 566 { 567 int32_t char_ul = char_ae & HL_UNDERLINE_MASK; 568 int32_t prim_ul = prim_ae & HL_UNDERLINE_MASK; 569 int32_t new_ul = prim_ul ? prim_ul : char_ul; 570 return (char_ae & ~HL_UNDERLINE_MASK) | (prim_ae & ~HL_UNDERLINE_MASK) | new_ul; 571 } 572 573 // Combine special attributes (e.g., for spelling) with other attributes 574 // (e.g., for syntax highlighting). 575 // "prim_attr" overrules "char_attr". 576 // This creates a new group when required. 577 // Since we expect there to be a lot of spelling mistakes we cache the result. 578 // Return the resulting attributes. 579 int hl_combine_attr(int char_attr, int prim_attr) 580 { 581 if (char_attr == 0) { 582 return prim_attr; 583 } else if (prim_attr == 0) { 584 return char_attr; 585 } 586 587 uint64_t combine_tag = HlAttrKey(char_attr, prim_attr); 588 int id = map_get(uint64_t, int)(&combine_attr_entries, combine_tag); 589 if (id > 0) { 590 return id; 591 } 592 593 HlAttrs char_aep = syn_attr2entry(char_attr); 594 HlAttrs prim_aep = syn_attr2entry(prim_attr); 595 596 // start with low-priority attribute, and override colors if present below. 597 HlAttrs new_en = char_aep; 598 599 if (prim_aep.cterm_ae_attr & HL_NOCOMBINE) { 600 new_en.cterm_ae_attr = prim_aep.cterm_ae_attr; 601 } else { 602 new_en.cterm_ae_attr = hl_combine_ae(new_en.cterm_ae_attr, prim_aep.cterm_ae_attr); 603 } 604 if (prim_aep.rgb_ae_attr & HL_NOCOMBINE) { 605 new_en.rgb_ae_attr = prim_aep.rgb_ae_attr; 606 } else { 607 new_en.rgb_ae_attr = hl_combine_ae(new_en.rgb_ae_attr, prim_aep.rgb_ae_attr); 608 } 609 610 if (prim_aep.cterm_fg_color > 0) { 611 new_en.cterm_fg_color = prim_aep.cterm_fg_color; 612 new_en.rgb_ae_attr &= ((~HL_FG_INDEXED) 613 | (prim_aep.rgb_ae_attr & HL_FG_INDEXED)); 614 } 615 616 if (prim_aep.cterm_bg_color > 0) { 617 new_en.cterm_bg_color = prim_aep.cterm_bg_color; 618 new_en.rgb_ae_attr &= ((~HL_BG_INDEXED) 619 | (prim_aep.rgb_ae_attr & HL_BG_INDEXED)); 620 } 621 622 if (prim_aep.rgb_fg_color >= 0) { 623 new_en.rgb_fg_color = prim_aep.rgb_fg_color; 624 new_en.rgb_ae_attr &= ((~HL_FG_INDEXED) 625 | (prim_aep.rgb_ae_attr & HL_FG_INDEXED)); 626 } 627 628 if (prim_aep.rgb_bg_color >= 0) { 629 new_en.rgb_bg_color = prim_aep.rgb_bg_color; 630 new_en.rgb_ae_attr &= ((~HL_BG_INDEXED) 631 | (prim_aep.rgb_ae_attr & HL_BG_INDEXED)); 632 } 633 634 if (prim_aep.rgb_sp_color >= 0) { 635 new_en.rgb_sp_color = prim_aep.rgb_sp_color; 636 } 637 638 if (prim_aep.hl_blend >= 0) { 639 new_en.hl_blend = prim_aep.hl_blend; 640 } 641 642 if ((new_en.url == -1) && (prim_aep.url >= 0)) { 643 new_en.url = prim_aep.url; 644 } 645 646 id = get_attr_entry((HlEntry){ .attr = new_en, .kind = kHlCombine, 647 .id1 = char_attr, .id2 = prim_attr }); 648 if (id > 0) { 649 map_put(uint64_t, int)(&combine_attr_entries, combine_tag, id); 650 } 651 652 return id; 653 } 654 655 /// Get the used rgb colors for an attr group. 656 /// 657 /// If colors are unset, use builtin default colors. Never returns -1 658 /// Cterm colors are unchanged. 659 static HlAttrs get_colors_force(HlAttrs attrs) 660 { 661 if (attrs.rgb_bg_color == -1) { 662 attrs.rgb_bg_color = normal_bg; 663 } 664 if (attrs.rgb_fg_color == -1) { 665 attrs.rgb_fg_color = normal_fg; 666 } 667 if (attrs.rgb_sp_color == -1) { 668 attrs.rgb_sp_color = normal_sp; 669 } 670 HL_SET_DEFAULT_COLORS(attrs.rgb_fg_color, attrs.rgb_bg_color, 671 attrs.rgb_sp_color); 672 673 if (attrs.rgb_ae_attr & HL_INVERSE) { 674 int temp = attrs.rgb_bg_color; 675 attrs.rgb_bg_color = attrs.rgb_fg_color; 676 attrs.rgb_fg_color = temp; 677 attrs.rgb_ae_attr &= ~HL_INVERSE; 678 } 679 680 return attrs; 681 } 682 683 /// Blend overlay attributes (for popupmenu) with other attributes 684 /// 685 /// This creates a new group when required. 686 /// This is called per-cell, so cache the result. 687 /// 688 /// @return the resulting attributes. 689 int hl_blend_attrs(int back_attr, int front_attr, bool *through) 690 { 691 // Cannot blend uninitialized cells, use front_attr for uninitialized background cells. 692 if (front_attr < 0 || back_attr < 0) { 693 return front_attr; 694 } 695 696 HlAttrs fattrs_raw = syn_attr2entry(front_attr); 697 HlAttrs fattrs = get_colors_force(fattrs_raw); 698 int ratio = fattrs.hl_blend; 699 if (ratio <= 0) { 700 *through = false; 701 return front_attr; 702 } 703 704 uint64_t combine_tag = HlAttrKey(back_attr, front_attr); 705 Map(uint64_t, int) *map = (*through 706 ? &blendthrough_attr_entries 707 : &blend_attr_entries); 708 int id = map_get(uint64_t, int)(map, combine_tag); 709 if (id > 0) { 710 return id; 711 } 712 713 HlAttrs battrs_raw = syn_attr2entry(back_attr); 714 HlAttrs battrs = get_colors_force(battrs_raw); 715 HlAttrs cattrs; 716 717 if (*through) { 718 cattrs = battrs; 719 cattrs.rgb_fg_color = rgb_blend(ratio, battrs.rgb_fg_color, fattrs.rgb_bg_color); 720 // Only apply special colors when the foreground attribute has an underline style. 721 if (fattrs_raw.rgb_ae_attr & HL_UNDERLINE_MASK) { 722 cattrs.rgb_sp_color = rgb_blend(ratio, battrs.rgb_sp_color, fattrs.rgb_bg_color); 723 } else { 724 cattrs.rgb_sp_color = -1; 725 } 726 727 cattrs.cterm_bg_color = fattrs.cterm_bg_color; 728 cattrs.cterm_fg_color = (int16_t)cterm_blend(ratio, battrs.cterm_fg_color, 729 fattrs.cterm_bg_color); 730 cattrs.rgb_ae_attr &= ~(HL_FG_INDEXED | HL_BG_INDEXED); 731 } else { 732 cattrs = fattrs; 733 cattrs.rgb_fg_color = rgb_blend(ratio/2, battrs.rgb_fg_color, fattrs.rgb_fg_color); 734 if (cattrs.rgb_ae_attr & (HL_UNDERLINE_MASK)) { 735 cattrs.rgb_sp_color = rgb_blend(ratio/2, battrs.rgb_bg_color, fattrs.rgb_sp_color); 736 } else { 737 cattrs.rgb_sp_color = -1; 738 } 739 740 cattrs.rgb_ae_attr &= ~HL_BG_INDEXED; 741 } 742 743 // Check if we should preserve background transparency 744 // Special case for blend=100: preserve back layer background exactly (including bg=NONE) 745 if (ratio == 100 && battrs_raw.rgb_bg_color == -1) { 746 // For 100% blend with transparent background, preserve the transparency 747 cattrs.rgb_bg_color = -1; 748 } else { 749 // Use the raw attributes (before forcing colors) to check original transparency 750 cattrs.rgb_bg_color = (battrs_raw.rgb_bg_color == -1) && (fattrs_raw.rgb_bg_color == -1) 751 ? -1 752 : rgb_blend(ratio, battrs.rgb_bg_color, fattrs.rgb_bg_color); 753 } 754 cattrs.hl_blend = -1; // blend property was consumed 755 HlKind kind = *through ? kHlBlendThrough : kHlBlend; 756 id = get_attr_entry((HlEntry){ .attr = cattrs, .kind = kind, 757 .id1 = back_attr, .id2 = front_attr }); 758 if (id > 0) { 759 map_put(uint64_t, int)(map, combine_tag, id); 760 } 761 return id; 762 } 763 764 static int rgb_blend(int ratio, int rgb1, int rgb2) 765 { 766 int a = ratio; 767 int b = 100 - ratio; 768 int r1 = (rgb1 & 0xFF0000) >> 16; 769 int g1 = (rgb1 & 0x00FF00) >> 8; 770 int b1 = (rgb1 & 0x0000FF) >> 0; 771 int r2 = (rgb2 & 0xFF0000) >> 16; 772 int g2 = (rgb2 & 0x00FF00) >> 8; 773 int b2 = (rgb2 & 0x0000FF) >> 0; 774 int mr = (a * r1 + b * r2)/100; 775 int mg = (a * g1 + b * g2)/100; 776 int mb = (a * b1 + b * b2)/100; 777 return (mr << 16) + (mg << 8) + mb; 778 } 779 780 static int cterm_blend(int ratio, int16_t c1, int16_t c2) 781 { 782 // 1. Convert cterm color numbers to RGB. 783 // 2. Blend the RGB colors. 784 // 3. Convert the RGB result to a cterm color. 785 int rgb1 = hl_cterm2rgb_color(c1); 786 int rgb2 = hl_cterm2rgb_color(c2); 787 int rgb_blended = rgb_blend(ratio, rgb1, rgb2); 788 return hl_rgb2cterm_color(rgb_blended); 789 } 790 791 /// Converts RGB color to 8-bit color (0-255). 792 static int hl_rgb2cterm_color(int rgb) 793 { 794 int r = (rgb & 0xFF0000) >> 16; 795 int g = (rgb & 0x00FF00) >> 8; 796 int b = (rgb & 0x0000FF) >> 0; 797 798 return (r * 6 / 256) * 36 + (g * 6 / 256) * 6 + (b * 6 / 256); 799 } 800 801 /// Converts 8-bit color (0-255) to RGB color. 802 /// This is compatible with xterm. 803 static int hl_cterm2rgb_color(int nr) 804 { 805 static int cube_value[] = { 806 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF 807 }; 808 static int grey_ramp[] = { 809 0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A, 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76, 810 0x80, 0x8A, 0x94, 0x9E, 0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE 811 }; 812 static uint8_t ansi_table[16][4] = { 813 // R G B idx 814 { 0, 0, 0, 1 }, // black 815 { 224, 0, 0, 2 }, // dark red 816 { 0, 224, 0, 3 }, // dark green 817 { 224, 224, 0, 4 }, // dark yellow / brown 818 { 0, 0, 224, 5 }, // dark blue 819 { 224, 0, 224, 6 }, // dark magenta 820 { 0, 224, 224, 7 }, // dark cyan 821 { 224, 224, 224, 8 }, // light grey 822 823 { 128, 128, 128, 9 }, // dark grey 824 { 255, 64, 64, 10 }, // light red 825 { 64, 255, 64, 11 }, // light green 826 { 255, 255, 64, 12 }, // yellow 827 { 64, 64, 255, 13 }, // light blue 828 { 255, 64, 255, 14 }, // light magenta 829 { 64, 255, 255, 15 }, // light cyan 830 { 255, 255, 255, 16 }, // white 831 }; 832 833 int r = 0; 834 int g = 0; 835 int b = 0; 836 int idx; 837 // *ansi_idx = 0; 838 839 if (nr < 16) { 840 r = ansi_table[nr][0]; 841 g = ansi_table[nr][1]; 842 b = ansi_table[nr][2]; 843 // *ansi_idx = ansi_table[nr][3]; 844 } else if (nr < 232) { // 216 color-cube 845 idx = nr - 16; 846 r = cube_value[idx / 36 % 6]; 847 g = cube_value[idx / 6 % 6]; 848 b = cube_value[idx % 6]; 849 // *ansi_idx = -1; 850 } else if (nr < 256) { // 24 greyscale ramp 851 idx = nr - 232; 852 r = grey_ramp[idx]; 853 g = grey_ramp[idx]; 854 b = grey_ramp[idx]; 855 // *ansi_idx = -1; 856 } 857 return (r << 16) + (g << 8) + b; 858 } 859 860 /// Get highlight attributes for a attribute code 861 HlAttrs syn_attr2entry(int attr) 862 { 863 if (attr <= 0 || attr >= (int)set_size(&attr_entries)) { 864 // invalid attribute code, or the tables were cleared 865 return HLATTRS_INIT; 866 } 867 return attr_entry(attr).attr; 868 } 869 870 /// Gets highlight description for id `attr_id` as a map. 871 Dict hl_get_attr_by_id(Integer attr_id, Boolean rgb, Arena *arena, Error *err) 872 { 873 Dict dic = ARRAY_DICT_INIT; 874 875 if (attr_id == 0) { 876 return dic; 877 } 878 879 if (attr_id <= 0 || attr_id >= (int)set_size(&attr_entries)) { 880 api_set_error(err, kErrorTypeException, 881 "Invalid attribute id: %" PRId64, attr_id); 882 return dic; 883 } 884 Dict retval = arena_dict(arena, HLATTRS_DICT_SIZE); 885 hlattrs2dict(&retval, NULL, syn_attr2entry((int)attr_id), rgb, false); 886 return retval; 887 } 888 889 /// Converts an HlAttrs into Dict 890 /// 891 /// @param[in/out] hl Dict with pre-allocated space for HLATTRS_DICT_SIZE elements 892 /// @param[in] aep data to convert 893 /// @param use_rgb use 'gui*' settings if true, else resorts to 'cterm*' 894 /// @param short_keys change (foreground, background, special) to (fg, bg, sp) for 'gui*' settings 895 /// (foreground, background) to (ctermfg, ctermbg) for 'cterm*' settings 896 void hlattrs2dict(Dict *hl, Dict *hl_attrs, HlAttrs ae, bool use_rgb, bool short_keys) 897 { 898 hl_attrs = hl_attrs ? hl_attrs : hl; 899 assert(hl->capacity >= HLATTRS_DICT_SIZE); // at most 16 items 900 assert(hl_attrs->capacity >= HLATTRS_DICT_SIZE); // at most 16 items 901 int mask = use_rgb ? ae.rgb_ae_attr : ae.cterm_ae_attr; 902 903 if (mask & HL_INVERSE) { 904 PUT_C(*hl_attrs, "reverse", BOOLEAN_OBJ(true)); 905 } 906 907 if (mask & HL_BOLD) { 908 PUT_C(*hl_attrs, "bold", BOOLEAN_OBJ(true)); 909 } 910 911 if (mask & HL_ITALIC) { 912 PUT_C(*hl_attrs, "italic", BOOLEAN_OBJ(true)); 913 } 914 915 switch (mask & HL_UNDERLINE_MASK) { 916 case HL_UNDERLINE: 917 PUT_C(*hl_attrs, "underline", BOOLEAN_OBJ(true)); 918 break; 919 920 case HL_UNDERCURL: 921 PUT_C(*hl_attrs, "undercurl", BOOLEAN_OBJ(true)); 922 break; 923 924 case HL_UNDERDOUBLE: 925 PUT_C(*hl_attrs, "underdouble", BOOLEAN_OBJ(true)); 926 break; 927 928 case HL_UNDERDOTTED: 929 PUT_C(*hl_attrs, "underdotted", BOOLEAN_OBJ(true)); 930 break; 931 932 case HL_UNDERDASHED: 933 PUT_C(*hl_attrs, "underdashed", BOOLEAN_OBJ(true)); 934 break; 935 } 936 937 if (mask & HL_STANDOUT) { 938 PUT_C(*hl_attrs, "standout", BOOLEAN_OBJ(true)); 939 } 940 941 if (mask & HL_STRIKETHROUGH) { 942 PUT_C(*hl_attrs, "strikethrough", BOOLEAN_OBJ(true)); 943 } 944 945 if (mask & HL_ALTFONT) { 946 PUT_C(*hl_attrs, "altfont", BOOLEAN_OBJ(true)); 947 } 948 949 if (mask & HL_DIM) { 950 PUT_C(*hl_attrs, "dim", BOOLEAN_OBJ(true)); 951 } 952 953 if (mask & HL_BLINK) { 954 PUT_C(*hl_attrs, "blink", BOOLEAN_OBJ(true)); 955 } 956 957 if (mask & HL_CONCEALED) { 958 PUT_C(*hl_attrs, "conceal", BOOLEAN_OBJ(true)); 959 } 960 961 if (mask & HL_OVERLINE) { 962 PUT_C(*hl_attrs, "overline", BOOLEAN_OBJ(true)); 963 } 964 965 if (mask & HL_NOCOMBINE) { 966 PUT_C(*hl_attrs, "nocombine", BOOLEAN_OBJ(true)); 967 } 968 969 if (use_rgb) { 970 if (ae.rgb_fg_color != -1) { 971 PUT_C(*hl, short_keys ? "fg" : "foreground", INTEGER_OBJ(ae.rgb_fg_color)); 972 } 973 974 if (ae.rgb_bg_color != -1) { 975 PUT_C(*hl, short_keys ? "bg" : "background", INTEGER_OBJ(ae.rgb_bg_color)); 976 } 977 978 if (ae.rgb_sp_color != -1) { 979 PUT_C(*hl, short_keys ? "sp" : "special", INTEGER_OBJ(ae.rgb_sp_color)); 980 } 981 982 if (!short_keys) { 983 if (mask & HL_FG_INDEXED) { 984 PUT_C(*hl, "fg_indexed", BOOLEAN_OBJ(true)); 985 } 986 987 if (mask & HL_BG_INDEXED) { 988 PUT_C(*hl, "bg_indexed", BOOLEAN_OBJ(true)); 989 } 990 } 991 } else { 992 if (ae.cterm_fg_color != 0) { 993 PUT_C(*hl, short_keys ? "ctermfg" : "foreground", INTEGER_OBJ(ae.cterm_fg_color - 1)); 994 } 995 996 if (ae.cterm_bg_color != 0) { 997 PUT_C(*hl, short_keys ? "ctermbg" : "background", INTEGER_OBJ(ae.cterm_bg_color - 1)); 998 } 999 } 1000 1001 if (ae.hl_blend > -1 && (use_rgb || !short_keys)) { 1002 PUT_C(*hl, "blend", INTEGER_OBJ(ae.hl_blend)); 1003 } 1004 } 1005 1006 HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *err) 1007 { 1008 #define HAS_KEY_X(d, key) HAS_KEY(d, highlight, key) 1009 HlAttrs hlattrs = HLATTRS_INIT; 1010 int32_t fg = -1; 1011 int32_t bg = -1; 1012 int32_t ctermfg = -1; 1013 int32_t ctermbg = -1; 1014 int32_t sp = -1; 1015 int blend = -1; 1016 int32_t mask = 0; 1017 int32_t cterm_mask = 0; 1018 bool cterm_mask_provided = false; 1019 1020 #define CHECK_FLAG(d, m, name, extra, flag) \ 1021 if (d->name##extra) { \ 1022 if (flag & HL_UNDERLINE_MASK) { \ 1023 m &= ~HL_UNDERLINE_MASK; \ 1024 } \ 1025 m |= flag; \ 1026 } 1027 1028 CHECK_FLAG(dict, mask, reverse, , HL_INVERSE); 1029 CHECK_FLAG(dict, mask, bold, , HL_BOLD); 1030 CHECK_FLAG(dict, mask, italic, , HL_ITALIC); 1031 CHECK_FLAG(dict, mask, underline, , HL_UNDERLINE); 1032 CHECK_FLAG(dict, mask, undercurl, , HL_UNDERCURL); 1033 CHECK_FLAG(dict, mask, underdouble, , HL_UNDERDOUBLE); 1034 CHECK_FLAG(dict, mask, underdotted, , HL_UNDERDOTTED); 1035 CHECK_FLAG(dict, mask, underdashed, , HL_UNDERDASHED); 1036 CHECK_FLAG(dict, mask, standout, , HL_STANDOUT); 1037 CHECK_FLAG(dict, mask, strikethrough, , HL_STRIKETHROUGH); 1038 CHECK_FLAG(dict, mask, altfont, , HL_ALTFONT); 1039 CHECK_FLAG(dict, mask, dim, , HL_DIM); 1040 CHECK_FLAG(dict, mask, blink, , HL_BLINK); 1041 CHECK_FLAG(dict, mask, conceal, , HL_CONCEALED); 1042 CHECK_FLAG(dict, mask, overline, , HL_OVERLINE); 1043 if (use_rgb) { 1044 CHECK_FLAG(dict, mask, fg_indexed, , HL_FG_INDEXED); 1045 CHECK_FLAG(dict, mask, bg_indexed, , HL_BG_INDEXED); 1046 } 1047 CHECK_FLAG(dict, mask, nocombine, , HL_NOCOMBINE); 1048 CHECK_FLAG(dict, mask, default, _, HL_DEFAULT); 1049 1050 if (HAS_KEY_X(dict, fg)) { 1051 fg = object_to_color(dict->fg, "fg", use_rgb, err); 1052 } else if (HAS_KEY_X(dict, foreground)) { 1053 fg = object_to_color(dict->foreground, "foreground", use_rgb, err); 1054 } 1055 if (ERROR_SET(err)) { 1056 return hlattrs; 1057 } 1058 1059 if (HAS_KEY_X(dict, bg)) { 1060 bg = object_to_color(dict->bg, "bg", use_rgb, err); 1061 } else if (HAS_KEY_X(dict, background)) { 1062 bg = object_to_color(dict->background, "background", use_rgb, err); 1063 } 1064 if (ERROR_SET(err)) { 1065 return hlattrs; 1066 } 1067 1068 if (HAS_KEY_X(dict, sp)) { 1069 sp = object_to_color(dict->sp, "sp", true, err); 1070 } else if (HAS_KEY_X(dict, special)) { 1071 sp = object_to_color(dict->special, "special", true, err); 1072 } 1073 if (ERROR_SET(err)) { 1074 return hlattrs; 1075 } 1076 1077 if (HAS_KEY_X(dict, blend)) { 1078 Integer blend0 = dict->blend; 1079 VALIDATE_RANGE((blend0 >= 0 && blend0 <= 100), "blend", { 1080 return hlattrs; 1081 }); 1082 blend = (int)blend0; 1083 } 1084 1085 if (HAS_KEY_X(dict, link) || HAS_KEY_X(dict, global_link)) { 1086 if (!link_id) { 1087 api_set_error(err, kErrorTypeValidation, "Invalid Key: '%s'", 1088 HAS_KEY_X(dict, global_link) ? "global_link" : "link"); 1089 return hlattrs; 1090 } 1091 if (HAS_KEY_X(dict, global_link)) { 1092 *link_id = (int)dict->global_link; 1093 mask |= HL_GLOBAL; 1094 } else { 1095 *link_id = (int)dict->link; 1096 } 1097 1098 if (ERROR_SET(err)) { 1099 return hlattrs; 1100 } 1101 } 1102 1103 // Handle cterm attrs 1104 if (HAS_KEY_X(dict, cterm)) { 1105 Dict(highlight_cterm) cterm[1] = KEYDICT_INIT; 1106 if (!api_dict_to_keydict(cterm, KeyDict_highlight_cterm_get_field, 1107 dict->cterm, err)) { 1108 return hlattrs; 1109 } 1110 1111 cterm_mask_provided = true; 1112 CHECK_FLAG(cterm, cterm_mask, reverse, , HL_INVERSE); 1113 CHECK_FLAG(cterm, cterm_mask, bold, , HL_BOLD); 1114 CHECK_FLAG(cterm, cterm_mask, italic, , HL_ITALIC); 1115 CHECK_FLAG(cterm, cterm_mask, underline, , HL_UNDERLINE); 1116 CHECK_FLAG(cterm, cterm_mask, undercurl, , HL_UNDERCURL); 1117 CHECK_FLAG(cterm, cterm_mask, underdouble, , HL_UNDERDOUBLE); 1118 CHECK_FLAG(cterm, cterm_mask, underdotted, , HL_UNDERDOTTED); 1119 CHECK_FLAG(cterm, cterm_mask, underdashed, , HL_UNDERDASHED); 1120 CHECK_FLAG(cterm, cterm_mask, standout, , HL_STANDOUT); 1121 CHECK_FLAG(cterm, cterm_mask, strikethrough, , HL_STRIKETHROUGH); 1122 CHECK_FLAG(cterm, cterm_mask, altfont, , HL_ALTFONT); 1123 CHECK_FLAG(cterm, cterm_mask, dim, , HL_DIM); 1124 CHECK_FLAG(cterm, cterm_mask, blink, , HL_BLINK); 1125 CHECK_FLAG(cterm, cterm_mask, conceal, , HL_CONCEALED); 1126 CHECK_FLAG(cterm, cterm_mask, overline, , HL_OVERLINE); 1127 CHECK_FLAG(cterm, cterm_mask, nocombine, , HL_NOCOMBINE); 1128 } 1129 #undef CHECK_FLAG 1130 1131 if (HAS_KEY_X(dict, ctermfg)) { 1132 ctermfg = object_to_color(dict->ctermfg, "ctermfg", false, err); 1133 if (ERROR_SET(err)) { 1134 return hlattrs; 1135 } 1136 } 1137 1138 if (HAS_KEY_X(dict, ctermbg)) { 1139 ctermbg = object_to_color(dict->ctermbg, "ctermbg", false, err); 1140 if (ERROR_SET(err)) { 1141 return hlattrs; 1142 } 1143 } 1144 1145 if (use_rgb) { 1146 // apply gui mask as default for cterm mask 1147 if (!cterm_mask_provided) { 1148 cterm_mask = mask; 1149 } 1150 hlattrs.rgb_ae_attr = mask; 1151 hlattrs.rgb_bg_color = bg; 1152 hlattrs.rgb_fg_color = fg; 1153 hlattrs.rgb_sp_color = sp; 1154 hlattrs.hl_blend = blend; 1155 hlattrs.cterm_bg_color = ctermbg == -1 ? 0 : (int16_t)(ctermbg + 1); 1156 hlattrs.cterm_fg_color = ctermfg == -1 ? 0 : (int16_t)(ctermfg + 1); 1157 hlattrs.cterm_ae_attr = cterm_mask; 1158 } else { 1159 hlattrs.cterm_bg_color = bg == -1 ? 0 : (int16_t)(bg + 1); 1160 hlattrs.cterm_fg_color = fg == -1 ? 0 : (int16_t)(fg + 1); 1161 hlattrs.cterm_ae_attr = mask; 1162 } 1163 1164 return hlattrs; 1165 #undef HAS_KEY_X 1166 } 1167 1168 int object_to_color(Object val, char *key, bool rgb, Error *err) 1169 { 1170 if (val.type == kObjectTypeInteger) { 1171 return (int)val.data.integer; 1172 } else if (val.type == kObjectTypeString) { 1173 String str = val.data.string; 1174 // TODO(bfredl): be more fancy with "bg", "fg" etc 1175 if (!str.size || STRICMP(str.data, "NONE") == 0) { 1176 return -1; 1177 } 1178 int color; 1179 if (rgb) { 1180 int dummy; 1181 color = name_to_color(str.data, &dummy); 1182 } else { 1183 color = name_to_ctermcolor(str.data); 1184 } 1185 VALIDATE_S((color >= 0), "highlight color", str.data, { 1186 return color; 1187 }); 1188 return color; 1189 } else { 1190 VALIDATE_EXP(false, key, "String or Integer", NULL, { 1191 return 0; 1192 }); 1193 } 1194 } 1195 1196 Array hl_inspect(int attr, Arena *arena) 1197 { 1198 if (!hlstate_active) { 1199 return (Array)ARRAY_DICT_INIT; 1200 } 1201 Array ret = arena_array(arena, hl_inspect_size(attr)); 1202 hl_inspect_impl(&ret, attr, arena); 1203 return ret; 1204 } 1205 1206 static size_t hl_inspect_size(int attr) 1207 { 1208 if (attr <= 0 || attr >= (int)set_size(&attr_entries)) { 1209 return 0; 1210 } 1211 1212 HlEntry e = attr_entry(attr); 1213 if (e.kind == kHlCombine || e.kind == kHlBlend || e.kind == kHlBlendThrough) { 1214 return hl_inspect_size(e.id1) + hl_inspect_size(e.id2); 1215 } 1216 return 1; 1217 } 1218 1219 static void hl_inspect_impl(Array *arr, int attr, Arena *arena) 1220 { 1221 Dict item = ARRAY_DICT_INIT; 1222 if (attr <= 0 || attr >= (int)set_size(&attr_entries)) { 1223 return; 1224 } 1225 1226 HlEntry e = attr_entry(attr); 1227 switch (e.kind) { 1228 case kHlSyntax: 1229 item = arena_dict(arena, 3); 1230 PUT_C(item, "kind", CSTR_AS_OBJ("syntax")); 1231 PUT_C(item, "hi_name", CSTR_AS_OBJ(syn_id2name(e.id1))); 1232 break; 1233 1234 case kHlUI: 1235 item = arena_dict(arena, 4); 1236 PUT_C(item, "kind", CSTR_AS_OBJ("ui")); 1237 const char *ui_name = (e.id1 == -1) ? "Normal" : hlf_names[e.id1]; 1238 PUT_C(item, "ui_name", CSTR_AS_OBJ(ui_name)); 1239 PUT_C(item, "hi_name", CSTR_AS_OBJ(syn_id2name(e.id2))); 1240 break; 1241 1242 case kHlTerminal: 1243 item = arena_dict(arena, 2); 1244 PUT_C(item, "kind", CSTR_AS_OBJ("term")); 1245 break; 1246 1247 case kHlCombine: 1248 case kHlBlend: 1249 case kHlBlendThrough: 1250 // attribute combination is associative, so flatten to an array 1251 hl_inspect_impl(arr, e.id1, arena); 1252 hl_inspect_impl(arr, e.id2, arena); 1253 return; 1254 1255 case kHlUnknown: 1256 case kHlInvalid: 1257 return; 1258 } 1259 PUT_C(item, "id", INTEGER_OBJ(attr)); 1260 ADD_C(*arr, DICT_OBJ(item)); 1261 }