decoration.c (40759B)
1 #include <assert.h> 2 #include <limits.h> 3 #include <stddef.h> 4 #include <stdlib.h> 5 #include <string.h> 6 7 #include "nvim/api/extmark.h" 8 #include "nvim/api/private/defs.h" 9 #include "nvim/api/private/helpers.h" 10 #include "nvim/ascii_defs.h" 11 #include "nvim/buffer.h" 12 #include "nvim/buffer_defs.h" 13 #include "nvim/change.h" 14 #include "nvim/decoration.h" 15 #include "nvim/decoration_provider.h" 16 #include "nvim/drawscreen.h" 17 #include "nvim/extmark.h" 18 #include "nvim/fold.h" 19 #include "nvim/globals.h" 20 #include "nvim/grid.h" 21 #include "nvim/highlight.h" 22 #include "nvim/highlight_group.h" 23 #include "nvim/marktree.h" 24 #include "nvim/memory.h" 25 #include "nvim/memory_defs.h" 26 #include "nvim/move.h" 27 #include "nvim/option_vars.h" 28 #include "nvim/pos_defs.h" 29 #include "nvim/sign.h" 30 31 #include "decoration.c.generated.h" 32 33 uint32_t decor_freelist = UINT32_MAX; 34 35 // Decorations might be requested to be deleted in a callback in the middle of redrawing. 36 // In this case, there might still be live references to the memory allocated for the decoration. 37 // Keep a "to free" list which can be safely processed when redrawing is done. 38 DecorVirtText *to_free_virt = NULL; 39 uint32_t to_free_sh = UINT32_MAX; 40 41 /// Add highlighting to a buffer, bounded by two cursor positions, 42 /// with an offset. 43 /// 44 /// TODO(bfredl): make decoration powerful enough so that this 45 /// can be done with a single ephemeral decoration. 46 /// 47 /// @param buf Buffer to add highlights to 48 /// @param src_id src_id to use or 0 to use a new src_id group, 49 /// or -1 for ungrouped highlight. 50 /// @param hl_id Highlight group id 51 /// @param pos_start Cursor position to start the highlighting at 52 /// @param pos_end Cursor position to end the highlighting at 53 /// @param offset Move the whole highlighting this many columns to the right 54 void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start, lpos_T pos_end, 55 colnr_T offset) 56 { 57 colnr_T hl_start = 0; 58 colnr_T hl_end = 0; 59 DecorInline decor = DECOR_INLINE_INIT; 60 decor.data.hl.hl_id = hl_id; 61 62 // TODO(bfredl): if decoration had blocky mode, we could avoid this loop 63 for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum++) { 64 int end_off = 0; 65 if (pos_start.lnum < lnum && lnum < pos_end.lnum) { 66 // TODO(bfredl): This is quite ad-hoc, but the space between |num| and 67 // text being highlighted is the indication of \n being part of the 68 // substituted text. But it would be more consistent to highlight 69 // a space _after_ the previous line instead (like highlight EOL list 70 // char) 71 hl_start = MAX(offset - 1, 0); 72 end_off = 1; 73 hl_end = 0; 74 } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) { 75 hl_start = pos_start.col + offset; 76 end_off = 1; 77 hl_end = 0; 78 } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) { 79 hl_start = MAX(offset - 1, 0); 80 hl_end = pos_end.col + offset; 81 } else if (pos_start.lnum == lnum && pos_end.lnum == lnum) { 82 hl_start = pos_start.col + offset; 83 hl_end = pos_end.col + offset; 84 } 85 86 extmark_set(buf, (uint32_t)src_id, NULL, 87 (int)lnum - 1, hl_start, (int)lnum - 1 + end_off, hl_end, 88 decor, MT_FLAG_DECOR_HL, true, false, true, false, NULL); 89 } 90 } 91 92 void decor_redraw(buf_T *buf, int row1, int row2, int col1, DecorInline decor) 93 { 94 if (decor.ext) { 95 DecorVirtText *vt = decor.data.ext.vt; 96 while (vt) { 97 bool below = (vt->flags & kVTIsLines) && !(vt->flags & kVTLinesAbove); 98 linenr_T vt_lnum = row1 + 1 + below; 99 redraw_buf_line_later(buf, vt_lnum, true); 100 if (vt->flags & kVTIsLines || vt->pos == kVPosInline) { 101 // changed_lines_redraw_buf(buf, vt_lnum, vt_lnum + 1, 0); 102 colnr_T vt_col = vt->flags & kVTIsLines ? 0 : col1; 103 changed_lines_invalidate_buf(buf, vt_lnum, vt_col, vt_lnum + 1, 0); 104 } 105 vt = vt->next; 106 } 107 108 uint32_t idx = decor.data.ext.sh_idx; 109 while (idx != DECOR_ID_INVALID) { 110 DecorSignHighlight *sh = &kv_A(decor_items, idx); 111 decor_redraw_sh(buf, row1, row2, *sh); 112 idx = sh->next; 113 } 114 } else { 115 decor_redraw_sh(buf, row1, row2, decor_sh_from_inline(decor.data.hl)); 116 } 117 } 118 119 void decor_redraw_sh(buf_T *buf, int row1, int row2, DecorSignHighlight sh) 120 { 121 if (sh.hl_id || (sh.url != NULL) 122 || (sh.flags & (kSHIsSign | kSHSpellOn | kSHSpellOff | kSHConceal))) { 123 if (row2 >= row1) { 124 redraw_buf_range_later(buf, row1 + 1, row2 + 1); 125 } 126 } 127 if (sh.flags & kSHConcealLines) { 128 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 129 // TODO(luukvbaal): redraw only unconcealed lines, and scroll lines below 130 // it up or down. Also when opening/closing a fold. 131 if (wp->w_buffer == buf) { 132 changed_window_setting(wp); 133 } 134 } 135 } 136 if (sh.flags & kSHUIWatched) { 137 redraw_buf_line_later(buf, row1 + 1, false); 138 } 139 } 140 141 uint32_t decor_put_sh(DecorSignHighlight item) 142 { 143 if (decor_freelist != UINT32_MAX) { 144 uint32_t pos = decor_freelist; 145 decor_freelist = kv_A(decor_items, decor_freelist).next; 146 kv_A(decor_items, pos) = item; 147 return pos; 148 } else { 149 uint32_t pos = (uint32_t)kv_size(decor_items); 150 kv_push(decor_items, item); 151 return pos; 152 } 153 } 154 155 DecorVirtText *decor_put_vt(DecorVirtText vt, DecorVirtText *next) 156 { 157 DecorVirtText *decor_alloc = xmalloc(sizeof *decor_alloc); 158 *decor_alloc = vt; 159 decor_alloc->next = next; 160 return decor_alloc; 161 } 162 163 DecorSignHighlight decor_sh_from_inline(DecorHighlightInline item) 164 { 165 // TODO(bfredl): Eventually simple signs will be inlinable as well 166 assert(!(item.flags & kSHIsSign)); 167 DecorSignHighlight conv = { 168 .flags = item.flags, 169 .priority = item.priority, 170 .text[0] = item.conceal_char, 171 .hl_id = item.hl_id, 172 .number_hl_id = 0, 173 .line_hl_id = 0, 174 .cursorline_hl_id = 0, 175 .next = DECOR_ID_INVALID, 176 }; 177 178 return conv; 179 } 180 181 void buf_put_decor(buf_T *buf, DecorInline decor, int row, int row2) 182 { 183 if (decor.ext && row < buf->b_ml.ml_line_count) { 184 uint32_t idx = decor.data.ext.sh_idx; 185 row2 = MIN(buf->b_ml.ml_line_count - 1, row2); 186 while (idx != DECOR_ID_INVALID) { 187 DecorSignHighlight *sh = &kv_A(decor_items, idx); 188 buf_put_decor_sh(buf, sh, row, row2); 189 idx = sh->next; 190 } 191 } 192 } 193 194 /// When displaying signs in the 'number' column, if the width of the number 195 /// column is less than 2, then force recomputing the width after placing or 196 /// unplacing the first sign in "buf". 197 static void may_force_numberwidth_recompute(buf_T *buf, bool unplace) 198 { 199 FOR_ALL_TAB_WINDOWS(tp, wp) { 200 if (wp->w_buffer == buf 201 && wp->w_minscwidth == SCL_NUM 202 && (wp->w_p_nu || wp->w_p_rnu) 203 && (unplace || wp->w_nrwidth_width < 2)) { 204 wp->w_nrwidth_line_count = 0; 205 } 206 } 207 } 208 209 static int sign_add_id = 0; 210 void buf_put_decor_sh(buf_T *buf, DecorSignHighlight *sh, int row1, int row2) 211 { 212 if (sh->flags & kSHIsSign) { 213 sh->sign_add_id = sign_add_id++; 214 if (sh->text[0]) { 215 buf_signcols_count_range(buf, row1, row2, 1, kFalse); 216 may_force_numberwidth_recompute(buf, false); 217 } 218 } 219 } 220 221 void buf_decor_remove(buf_T *buf, int row1, int row2, int col1, DecorInline decor, bool free) 222 { 223 decor_redraw(buf, row1, row2, col1, decor); 224 if (decor.ext && row1 < buf->b_ml.ml_line_count) { 225 uint32_t idx = decor.data.ext.sh_idx; 226 row2 = MIN(buf->b_ml.ml_line_count - 1, row2); 227 while (idx != DECOR_ID_INVALID) { 228 DecorSignHighlight *sh = &kv_A(decor_items, idx); 229 buf_remove_decor_sh(buf, row1, row2, sh); 230 idx = sh->next; 231 } 232 } 233 if (free) { 234 decor_free(decor); 235 } 236 } 237 238 void buf_remove_decor_sh(buf_T *buf, int row1, int row2, DecorSignHighlight *sh) 239 { 240 if (sh->flags & kSHIsSign) { 241 if (sh->text[0]) { 242 if (buf_meta_total(buf, kMTMetaSignText)) { 243 buf_signcols_count_range(buf, row1, row2, -1, kFalse); 244 } else { 245 may_force_numberwidth_recompute(buf, true); 246 buf->b_signcols.count[0] = 0; 247 buf->b_signcols.max = 0; 248 } 249 } 250 } 251 } 252 253 void decor_free(DecorInline decor) 254 { 255 if (!decor.ext) { 256 return; 257 } 258 DecorVirtText *vt = decor.data.ext.vt; 259 uint32_t idx = decor.data.ext.sh_idx; 260 261 if (decor_state.running_decor_provider) { 262 while (vt) { 263 if (vt->next == NULL) { 264 vt->next = to_free_virt; 265 to_free_virt = decor.data.ext.vt; 266 break; 267 } 268 vt = vt->next; 269 } 270 while (idx != DECOR_ID_INVALID) { 271 DecorSignHighlight *sh = &kv_A(decor_items, idx); 272 if (sh->next == DECOR_ID_INVALID) { 273 sh->next = to_free_sh; 274 to_free_sh = decor.data.ext.sh_idx; 275 break; 276 } 277 idx = sh->next; 278 } 279 } else { 280 // safe to delete right now 281 decor_free_inner(vt, idx); 282 } 283 } 284 285 static void decor_free_inner(DecorVirtText *vt, uint32_t first_idx) 286 { 287 while (vt) { 288 if (vt->flags & kVTIsLines) { 289 clear_virtlines(&vt->data.virt_lines); 290 } else { 291 clear_virttext(&vt->data.virt_text); 292 } 293 DecorVirtText *tofree = vt; 294 vt = vt->next; 295 xfree(tofree); 296 } 297 298 uint32_t idx = first_idx; 299 while (idx != DECOR_ID_INVALID) { 300 DecorSignHighlight *sh = &kv_A(decor_items, idx); 301 if (sh->flags & kSHIsSign) { 302 XFREE_CLEAR(sh->sign_name); 303 } 304 sh->flags = 0; 305 if (sh->url != NULL) { 306 XFREE_CLEAR(sh->url); 307 } 308 if (sh->next == DECOR_ID_INVALID) { 309 sh->next = decor_freelist; 310 decor_freelist = first_idx; 311 break; 312 } 313 idx = sh->next; 314 } 315 } 316 317 /// Check if we are in a callback while drawing, which might invalidate the marktree iterator. 318 /// 319 /// This should be called whenever a structural modification has been done to a 320 /// marktree in a public API function (i e any change which adds or deletes marks). 321 void decor_state_invalidate(buf_T *buf) 322 { 323 if (decor_state.win && decor_state.win->w_buffer == buf) { 324 decor_state.itr_valid = false; 325 } 326 } 327 328 void decor_check_to_be_deleted(void) 329 { 330 assert(!decor_state.running_decor_provider); 331 decor_free_inner(to_free_virt, to_free_sh); 332 to_free_virt = NULL; 333 to_free_sh = DECOR_ID_INVALID; 334 decor_state.win = NULL; 335 } 336 337 void decor_state_free(DecorState *state) 338 { 339 kv_destroy(state->slots); 340 kv_destroy(state->ranges_i); 341 } 342 343 void clear_virttext(VirtText *text) 344 { 345 for (size_t i = 0; i < kv_size(*text); i++) { 346 xfree(kv_A(*text, i).text); 347 } 348 kv_destroy(*text); 349 *text = (VirtText)KV_INITIAL_VALUE; 350 } 351 352 void clear_virtlines(VirtLines *lines) 353 { 354 for (size_t i = 0; i < kv_size(*lines); i++) { 355 clear_virttext(&kv_A(*lines, i).line); 356 } 357 kv_destroy(*lines); 358 *lines = (VirtLines)KV_INITIAL_VALUE; 359 } 360 361 void decor_check_invalid_glyphs(void) 362 { 363 for (size_t i = 0; i < kv_size(decor_items); i++) { 364 DecorSignHighlight *it = &kv_A(decor_items, i); 365 int width = (it->flags & kSHIsSign) ? SIGN_WIDTH : ((it->flags & kSHConceal) ? 1 : 0); 366 for (int j = 0; j < width; j++) { 367 if (schar_high(it->text[j])) { 368 it->text[j] = schar_from_char(schar_get_first_codepoint(it->text[j])); 369 } 370 } 371 } 372 } 373 374 /// Get the next chunk of a virtual text item. 375 /// 376 /// @param[in] vt The virtual text item 377 /// @param[in,out] pos Position in the virtual text item 378 /// @param[in,out] attr Highlight attribute 379 /// 380 /// @return The text of the chunk, or NULL if there are no more chunks 381 char *next_virt_text_chunk(VirtText vt, size_t *pos, int *attr) 382 { 383 char *text = NULL; 384 for (; text == NULL && *pos < kv_size(vt); (*pos)++) { 385 text = kv_A(vt, *pos).text; 386 int hl_id = kv_A(vt, *pos).hl_id; 387 if (hl_id >= 0) { 388 *attr = MAX(*attr, 0); 389 if (hl_id > 0) { 390 *attr = hl_combine_attr(*attr, syn_id2attr(hl_id)); 391 } 392 } 393 } 394 return text; 395 } 396 397 DecorVirtText *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) 398 { 399 MarkTreeIter itr[1] = { 0 }; 400 marktree_itr_get(buf->b_marktree, row, 0, itr); 401 while (true) { 402 MTKey mark = marktree_itr_current(itr); 403 if (mark.pos.row < 0 || mark.pos.row > row) { 404 break; 405 } else if (mt_invalid(mark)) { 406 goto next_mark; 407 } 408 DecorVirtText *decor = mt_decor_virt(mark); 409 while (decor && (decor->flags & kVTIsLines)) { 410 decor = decor->next; 411 } 412 if ((ns_id == 0 || ns_id == mark.ns) && decor) { 413 return decor; 414 } 415 next_mark: 416 marktree_itr_next(buf->b_marktree, itr); 417 } 418 return NULL; 419 } 420 421 bool decor_redraw_reset(win_T *wp, DecorState *state) 422 { 423 state->row = -1; 424 state->win = wp; 425 426 int *const indices = state->ranges_i.items; 427 DecorRangeSlot *const slots = state->slots.items; 428 429 int const beg_pos[] = { 0, state->future_begin }; 430 int const end_pos[] = { state->current_end, (int)kv_size(state->ranges_i) }; 431 432 for (int pos_i = 0; pos_i < 2; pos_i++) { 433 for (int i = beg_pos[pos_i]; i < end_pos[pos_i]; i++) { 434 DecorRange *const r = &slots[indices[i]].range; 435 if (r->owned && r->kind == kDecorKindVirtText) { 436 clear_virttext(&r->data.vt->data.virt_text); 437 xfree(r->data.vt); 438 } 439 } 440 } 441 442 kv_size(state->slots) = 0; 443 kv_size(state->ranges_i) = 0; 444 state->free_slot_i = -1; 445 state->current_end = 0; 446 state->future_begin = 0; 447 state->new_range_ordering = 0; 448 449 return wp->w_buffer->b_marktree->n_keys; 450 } 451 452 /// @return true if decor has a virtual position (virtual text or ui_watched) 453 bool decor_virt_pos(const DecorRange *decor) 454 { 455 return (decor->kind == kDecorKindVirtText || decor->kind == kDecorKindUIWatched); 456 } 457 458 VirtTextPos decor_virt_pos_kind(const DecorRange *decor) 459 { 460 if (decor->kind == kDecorKindVirtText) { 461 return decor->data.vt->pos; 462 } 463 if (decor->kind == kDecorKindUIWatched) { 464 return decor->data.ui.pos; 465 } 466 return kVPosEndOfLine; // not used; return whatever 467 } 468 469 bool decor_redraw_start(win_T *wp, int top_row, DecorState *state) 470 { 471 buf_T *buf = wp->w_buffer; 472 state->top_row = top_row; 473 state->itr_valid = true; 474 475 if (!marktree_itr_get_overlap(buf->b_marktree, top_row, 0, state->itr)) { 476 return false; 477 } 478 MTPair pair; 479 480 while (marktree_itr_step_overlap(buf->b_marktree, state->itr, &pair)) { 481 MTKey m = pair.start; 482 if (mt_invalid(m) || !mt_decor_any(m)) { 483 continue; 484 } 485 486 decor_range_add_from_inline(state, pair.start.pos.row, pair.start.pos.col, pair.end_pos.row, 487 pair.end_pos.col, 488 mt_decor(m), false, m.ns, m.id); 489 } 490 491 return true; // TODO(bfredl): check if available in the region 492 } 493 494 static void decor_state_pack(DecorState *state) 495 { 496 int count = (int)kv_size(state->ranges_i); 497 int const cur_end = state->current_end; 498 int fut_beg = state->future_begin; 499 500 // Move future ranges to start right after current ranges. 501 // Otherwise future ranges will grow forward indefinitely. 502 if (fut_beg == count) { 503 fut_beg = count = cur_end; 504 } else if (fut_beg != cur_end) { 505 int *const indices = state->ranges_i.items; 506 memmove(indices + cur_end, indices + fut_beg, (size_t)(count - fut_beg) * sizeof(indices[0])); 507 508 count = cur_end + (count - fut_beg); 509 fut_beg = cur_end; 510 } 511 512 kv_size(state->ranges_i) = (size_t)count; 513 state->future_begin = fut_beg; 514 } 515 516 void decor_redraw_line(win_T *wp, int row, DecorState *state) 517 { 518 decor_state_pack(state); 519 520 if (state->row == -1) { 521 decor_redraw_start(wp, row, state); 522 } else if (!state->itr_valid) { 523 marktree_itr_get(wp->w_buffer->b_marktree, row, 0, state->itr); 524 state->itr_valid = true; 525 } 526 527 state->row = row; 528 state->col_until = -1; 529 state->eol_col = -1; 530 } 531 532 // Checks if there are (likely) more decorations on the current line. 533 bool decor_has_more_decorations(DecorState *state, int row) 534 { 535 if (state->current_end != 0 || state->future_begin != (int)kv_size(state->ranges_i)) { 536 return true; 537 } 538 539 MTKey k = marktree_itr_current(state->itr); 540 return (k.pos.row >= 0 && k.pos.row <= row); 541 } 542 543 static void decor_range_add_from_inline(DecorState *state, int start_row, int start_col, 544 int end_row, int end_col, DecorInline decor, bool owned, 545 uint32_t ns, uint32_t mark_id) 546 { 547 if (decor.ext) { 548 DecorVirtText *vt = decor.data.ext.vt; 549 while (vt) { 550 decor_range_add_virt(state, start_row, start_col, end_row, end_col, vt, owned); 551 vt = vt->next; 552 } 553 uint32_t idx = decor.data.ext.sh_idx; 554 while (idx != DECOR_ID_INVALID) { 555 DecorSignHighlight *sh = &kv_A(decor_items, idx); 556 decor_range_add_sh(state, start_row, start_col, end_row, end_col, sh, owned, ns, mark_id, 0); 557 idx = sh->next; 558 } 559 } else { 560 DecorSignHighlight sh = decor_sh_from_inline(decor.data.hl); 561 decor_range_add_sh(state, start_row, start_col, end_row, end_col, &sh, owned, ns, mark_id, 0); 562 } 563 } 564 565 static void decor_range_insert(DecorState *state, DecorRange *range) 566 { 567 range->ordering = state->new_range_ordering++; 568 569 int index; 570 // Get space for a new `DecorRange` from the freelist or allocate. 571 if (state->free_slot_i >= 0) { 572 index = state->free_slot_i; 573 DecorRangeSlot *slot = &kv_A(state->slots, index); 574 state->free_slot_i = slot->next_free_i; 575 slot->range = *range; 576 } else { 577 index = (int)kv_size(state->slots); 578 kv_pushp(state->slots)->range = *range; 579 } 580 581 int const row = range->start_row; 582 int const col = range->start_col; 583 584 int const count = (int)kv_size(state->ranges_i); 585 int *const indices = state->ranges_i.items; 586 DecorRangeSlot *const slots = state->slots.items; 587 588 int begin = state->future_begin; 589 int end = count; 590 while (begin < end) { 591 int const mid = begin + ((end - begin) >> 1); 592 DecorRange *const mr = &slots[indices[mid]].range; 593 594 int const mrow = mr->start_row; 595 int const mcol = mr->start_col; 596 if (mrow < row || (mrow == row && mcol <= col)) { 597 begin = mid + 1; 598 if (mrow == row && mcol == col) { 599 break; 600 } 601 } else { 602 end = mid; 603 } 604 } 605 606 kv_pushp(state->ranges_i); 607 int *const item = &kv_A(state->ranges_i, begin); 608 memmove(item + 1, item, (size_t)(count - begin) * sizeof(*item)); 609 *item = index; 610 } 611 612 void decor_range_add_virt(DecorState *state, int start_row, int start_col, int end_row, int end_col, 613 DecorVirtText *vt, bool owned) 614 { 615 bool is_lines = vt->flags & kVTIsLines; 616 DecorRange range = { 617 .start_row = start_row, .start_col = start_col, .end_row = end_row, .end_col = end_col, 618 .kind = is_lines ? kDecorKindVirtLines : kDecorKindVirtText, 619 .data.vt = vt, 620 .attr_id = 0, 621 .owned = owned, 622 .priority_internal = ((DecorPriorityInternal)vt->priority << 16), 623 .draw_col = -10, 624 }; 625 decor_range_insert(state, &range); 626 } 627 628 void decor_range_add_sh(DecorState *state, int start_row, int start_col, int end_row, int end_col, 629 DecorSignHighlight *sh, bool owned, uint32_t ns, uint32_t mark_id, 630 DecorPriority subpriority) 631 { 632 if (sh->flags & kSHIsSign) { 633 return; 634 } 635 636 DecorRange range = { 637 .start_row = start_row, .start_col = start_col, .end_row = end_row, .end_col = end_col, 638 .kind = kDecorKindHighlight, 639 .data.sh = *sh, 640 .attr_id = 0, 641 .owned = owned, 642 .priority_internal = ((DecorPriorityInternal)sh->priority << 16) + subpriority, 643 .draw_col = -10, 644 }; 645 646 if (sh->hl_id || (sh->url != NULL) 647 || (sh->flags & (kSHConceal | kSHSpellOn | kSHSpellOff))) { 648 if (sh->hl_id) { 649 range.attr_id = syn_id2attr(sh->hl_id); 650 } 651 decor_range_insert(state, &range); 652 } 653 654 if (sh->flags & (kSHUIWatched)) { 655 range.kind = kDecorKindUIWatched; 656 range.data.ui.ns_id = ns; 657 range.data.ui.mark_id = mark_id; 658 range.data.ui.pos = (sh->flags & kSHUIWatchedOverlay) ? kVPosOverlay : kVPosEndOfLine; 659 decor_range_insert(state, &range); 660 } 661 } 662 663 /// Initialize the draw_col of a newly-added virtual text item. 664 void decor_init_draw_col(int win_col, bool hidden, DecorRange *item) 665 { 666 DecorVirtText *vt = item->kind == kDecorKindVirtText ? item->data.vt : NULL; 667 VirtTextPos pos = decor_virt_pos_kind(item); 668 if (win_col < 0 && pos != kVPosInline) { 669 item->draw_col = win_col; 670 } else if (pos == kVPosOverlay) { 671 item->draw_col = (vt && (vt->flags & kVTHide) && hidden) ? INT_MIN : win_col; 672 } else { 673 item->draw_col = -1; 674 } 675 } 676 677 void decor_recheck_draw_col(int win_col, bool hidden, DecorState *state) 678 { 679 int const end = state->current_end; 680 int *const indices = state->ranges_i.items; 681 DecorRangeSlot *const slots = state->slots.items; 682 683 for (int i = 0; i < end; i++) { 684 DecorRange *const r = &slots[indices[i]].range; 685 if (r->draw_col == -3) { 686 decor_init_draw_col(win_col, hidden, r); 687 } 688 } 689 } 690 691 int decor_redraw_col_impl(win_T *wp, int col, int win_col, bool hidden, DecorState *state) 692 { 693 buf_T *const buf = wp->w_buffer; 694 int const row = state->row; 695 int col_until = MAXCOL; 696 697 while (true) { 698 // TODO(bfredl): check duplicate entry in "intersection" 699 // branch 700 MTKey mark = marktree_itr_current(state->itr); 701 if (mark.pos.row < 0 || mark.pos.row > row) { 702 break; 703 } else if (mark.pos.row == row && mark.pos.col > col) { 704 col_until = mark.pos.col - 1; 705 break; 706 } 707 708 if (mt_invalid(mark) || mt_end(mark) || !mt_decor_any(mark) || !ns_in_win(mark.ns, wp)) { 709 goto next_mark; 710 } 711 712 MTPos endpos = marktree_get_altpos(buf->b_marktree, mark, NULL); 713 decor_range_add_from_inline(state, mark.pos.row, mark.pos.col, endpos.row, endpos.col, 714 mt_decor(mark), false, mark.ns, mark.id); 715 716 next_mark: 717 marktree_itr_next(buf->b_marktree, state->itr); 718 } 719 720 int *const indices = state->ranges_i.items; 721 DecorRangeSlot *const slots = state->slots.items; 722 723 int count = (int)kv_size(state->ranges_i); 724 int cur_end = state->current_end; 725 int fut_beg = state->future_begin; 726 727 // Promote future ranges before the cursor to active. 728 for (; fut_beg < count; fut_beg++) { 729 int const index = indices[fut_beg]; 730 DecorRange *const r = &slots[index].range; 731 if (r->start_row > row || (r->start_row == row && r->start_col > col)) { 732 break; 733 } 734 int const ordering = r->ordering; 735 DecorPriorityInternal const priority = r->priority_internal; 736 737 int begin = 0; 738 int end = cur_end; 739 while (begin < end) { 740 int mid = begin + ((end - begin) >> 1); 741 int mi = indices[mid]; 742 DecorRange *mr = &slots[mi].range; 743 if (mr->priority_internal < priority 744 || (mr->priority_internal == priority && mr->ordering < ordering)) { 745 begin = mid + 1; 746 } else { 747 end = mid; 748 } 749 } 750 751 int *const item = indices + begin; 752 memmove(item + 1, item, (size_t)(cur_end - begin) * sizeof(*item)); 753 *item = index; 754 cur_end++; 755 } 756 757 if (fut_beg < count) { 758 DecorRange *r = &slots[indices[fut_beg]].range; 759 if (r->start_row == row) { 760 col_until = MIN(col_until, r->start_col - 1); 761 } 762 } 763 764 int new_cur_end = 0; 765 766 int attr = 0; 767 int conceal = 0; 768 schar_T conceal_char = 0; 769 int conceal_attr = 0; 770 TriState spell = kNone; 771 772 for (int i = 0; i < cur_end; i++) { 773 int const index = indices[i]; 774 DecorRangeSlot *const slot = slots + index; 775 DecorRange *const r = &slot->range; 776 777 bool keep; 778 if (r->end_row < row || (r->end_row == row && r->end_col <= col)) { 779 keep = r->start_row >= row && decor_virt_pos(r); 780 } else { 781 keep = true; 782 783 if (r->end_row == row && r->end_col > col) { 784 col_until = MIN(col_until, r->end_col - 1); 785 } 786 787 if (r->attr_id > 0) { 788 attr = hl_combine_attr(attr, r->attr_id); 789 } 790 791 if (r->kind == kDecorKindHighlight && (r->data.sh.flags & kSHConceal)) { 792 conceal = 1; 793 if (r->start_row == row && r->start_col == col) { 794 DecorSignHighlight *sh = &r->data.sh; 795 conceal = 2; 796 conceal_char = sh->text[0]; 797 col_until = MIN(col_until, r->start_col); 798 conceal_attr = r->attr_id; 799 } 800 } 801 802 if (r->kind == kDecorKindHighlight) { 803 if (r->data.sh.flags & kSHSpellOn) { 804 spell = kTrue; 805 } else if (r->data.sh.flags & kSHSpellOff) { 806 spell = kFalse; 807 } 808 if (r->data.sh.url != NULL) { 809 attr = hl_add_url(attr, r->data.sh.url); 810 } 811 } 812 } 813 814 if (r->start_row == row && r->start_col <= col 815 && decor_virt_pos(r) && r->draw_col == -10) { 816 decor_init_draw_col(win_col, hidden, r); 817 } 818 819 if (keep) { 820 indices[new_cur_end++] = index; 821 } else { 822 if (r->owned) { 823 if (r->kind == kDecorKindVirtText) { 824 clear_virttext(&r->data.vt->data.virt_text); 825 xfree(r->data.vt); 826 } else if (r->kind == kDecorKindHighlight) { 827 xfree((void *)r->data.sh.url); 828 } 829 } 830 831 int *fi = &state->free_slot_i; 832 slot->next_free_i = *fi; 833 *fi = index; 834 } 835 } 836 cur_end = new_cur_end; 837 838 if (fut_beg == count) { 839 fut_beg = count = cur_end; 840 } 841 842 kv_size(state->ranges_i) = (size_t)count; 843 state->future_begin = fut_beg; 844 state->current_end = cur_end; 845 state->col_until = col_until; 846 847 state->current = attr; 848 state->conceal = conceal; 849 state->conceal_char = conceal_char; 850 state->conceal_attr = conceal_attr; 851 state->spell = spell; 852 return attr; 853 } 854 855 static const uint32_t conceal_filter[kMTMetaCount] = {[kMTMetaConcealLines] = kMTFilterSelect }; 856 857 /// Called by draw, move and plines code to determine whether a line is concealed. 858 /// Scans the marktree for conceal_line marks on "row" and invokes any 859 /// _on_conceal_line decoration provider callbacks, if necessary. 860 /// 861 /// @param check_cursor If true, avoid an early return for an unconcealed cursorline. 862 /// Depending on the callsite, we still want to know whether the 863 /// cursor line would be concealed if it was not the cursorline. 864 /// 865 /// @return whether "row" is concealed 866 bool decor_conceal_line(win_T *wp, int row, bool check_cursor) 867 { 868 if (row < 0 || wp->w_p_cole < 2 869 || (!check_cursor && wp == curwin && row + 1 == wp->w_cursor.lnum 870 && !conceal_cursor_line(wp))) { 871 return false; 872 } 873 874 // No need to scan the marktree if there are no conceal_line marks. 875 if (!buf_meta_total(wp->w_buffer, kMTMetaConcealLines)) { 876 return decor_providers_invoke_conceal_line(wp, row); 877 } 878 879 // Scan the marktree for any conceal_line marks on this row. 880 MTPair pair; 881 MarkTreeIter itr[1]; 882 marktree_itr_get_overlap(wp->w_buffer->b_marktree, row, 0, itr); 883 while (marktree_itr_step_overlap(wp->w_buffer->b_marktree, itr, &pair)) { 884 if (mt_conceal_lines(pair.start) && ns_in_win(pair.start.ns, wp)) { 885 return true; 886 } 887 } 888 889 marktree_itr_step_out_filter(wp->w_buffer->b_marktree, itr, conceal_filter); 890 891 while (itr->x) { 892 MTKey mark = marktree_itr_current(itr); 893 if (mark.pos.row > row) { 894 break; 895 } 896 if (mt_conceal_lines(mark) && ns_in_win(pair.start.ns, wp)) { 897 return true; 898 } 899 marktree_itr_next_filter(wp->w_buffer->b_marktree, itr, row + 1, 0, conceal_filter); 900 } 901 902 return decor_providers_invoke_conceal_line(wp, row); 903 } 904 905 /// @return whether a window may have folded or concealed lines 906 bool win_lines_concealed(win_T *wp) 907 { 908 return hasAnyFolding(wp) || wp->w_p_cole >= 2; 909 } 910 911 int sign_item_cmp(const void *p1, const void *p2) 912 { 913 const SignItem *s1 = (SignItem *)p1; 914 const SignItem *s2 = (SignItem *)p2; 915 916 if (s1->sh->priority != s2->sh->priority) { 917 return s1->sh->priority < s2->sh->priority ? 1 : -1; 918 } 919 920 if (s1->id != s2->id) { 921 return s1->id < s2->id ? 1 : -1; 922 } 923 924 if (s1->sh->sign_add_id != s2->sh->sign_add_id) { 925 return s1->sh->sign_add_id < s2->sh->sign_add_id ? 1 : -1; 926 } 927 928 return 0; 929 } 930 931 static const uint32_t sign_filter[kMTMetaCount] = {[kMTMetaSignText] = kMTFilterSelect, 932 [kMTMetaSignHL] = kMTFilterSelect }; 933 934 /// Return the signs and highest priority sign attributes on a row. 935 /// 936 /// @param[out] sattrs Output array for sign text and texthl id 937 /// @param[out] line_id Highest priority linehl id 938 /// @param[out] cul_id Highest priority culhl id 939 /// @param[out] num_id Highest priority numhl id 940 void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[], int *line_id, 941 int *cul_id, int *num_id) 942 { 943 if (!buf_has_signs(buf)) { 944 return; 945 } 946 947 MTPair pair; 948 int num_text = 0; 949 MarkTreeIter itr[1]; 950 kvec_t(SignItem) signs = KV_INITIAL_VALUE; 951 // TODO(bfredl): integrate with main decor loop. 952 marktree_itr_get_overlap(buf->b_marktree, row, 0, itr); 953 while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { 954 if (!mt_invalid(pair.start) && mt_decor_sign(pair.start) && ns_in_win(pair.start.ns, wp)) { 955 DecorSignHighlight *sh = decor_find_sign(mt_decor(pair.start)); 956 num_text += (sh->text[0] != NUL); 957 kv_push(signs, ((SignItem){ sh, pair.start.id })); 958 } 959 } 960 961 marktree_itr_step_out_filter(buf->b_marktree, itr, sign_filter); 962 963 while (itr->x) { 964 MTKey mark = marktree_itr_current(itr); 965 if (mark.pos.row != row) { 966 break; 967 } 968 if (!mt_invalid(mark) && !mt_end(mark) && mt_decor_sign(mark) && ns_in_win(mark.ns, wp)) { 969 DecorSignHighlight *sh = decor_find_sign(mt_decor(mark)); 970 num_text += (sh->text[0] != NUL); 971 kv_push(signs, ((SignItem){ sh, mark.id })); 972 } 973 974 marktree_itr_next_filter(buf->b_marktree, itr, row + 1, 0, sign_filter); 975 } 976 977 if (kv_size(signs)) { 978 int width = wp->w_minscwidth == SCL_NUM ? 1 : wp->w_scwidth; 979 int len = MIN(width, num_text); 980 int idx = 0; 981 qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(kv_A(signs, 0)), sign_item_cmp); 982 983 for (size_t i = 0; i < kv_size(signs); i++) { 984 DecorSignHighlight *sh = kv_A(signs, i).sh; 985 if (sattrs && idx < len && sh->text[0]) { 986 memcpy(sattrs[idx].text, sh->text, SIGN_WIDTH * sizeof(sattr_T)); 987 sattrs[idx++].hl_id = sh->hl_id; 988 } 989 if (num_id != NULL && *num_id <= 0) { 990 *num_id = sh->number_hl_id; 991 } 992 if (line_id != NULL && *line_id <= 0) { 993 *line_id = sh->line_hl_id; 994 } 995 if (cul_id != NULL && *cul_id <= 0) { 996 *cul_id = sh->cursorline_hl_id; 997 } 998 } 999 kv_destroy(signs); 1000 } 1001 } 1002 1003 DecorSignHighlight *decor_find_sign(DecorInline decor) 1004 { 1005 if (!decor.ext) { 1006 return NULL; 1007 } 1008 uint32_t decor_id = decor.data.ext.sh_idx; 1009 while (true) { 1010 if (decor_id == DECOR_ID_INVALID) { 1011 return NULL; 1012 } 1013 DecorSignHighlight *sh = &kv_A(decor_items, decor_id); 1014 if (sh->flags & kSHIsSign) { 1015 return sh; 1016 } 1017 decor_id = sh->next; 1018 } 1019 } 1020 1021 static const uint32_t signtext_filter[kMTMetaCount] = {[kMTMetaSignText] = kMTFilterSelect }; 1022 1023 /// Count the number of signs in a range after adding/removing a sign, or to 1024 /// (re-)initialize a range in "b_signcols.count". 1025 /// 1026 /// @param add 1, -1 or 0 for an added, deleted or initialized range. 1027 /// @param clear kFalse, kTrue or kNone for an, added/deleted, cleared, or initialized range. 1028 void buf_signcols_count_range(buf_T *buf, int row1, int row2, int add, TriState clear) 1029 { 1030 if (!buf->b_signcols.autom || row2 < row1 || !buf_meta_total(buf, kMTMetaSignText)) { 1031 return; 1032 } 1033 1034 // Allocate an array of integers holding the number of signs in the range. 1035 int *count = xcalloc((size_t)(row2 + 1 - row1), sizeof(int)); 1036 MarkTreeIter itr[1]; 1037 MTPair pair = { 0 }; 1038 1039 // Increment count array for signs that start before "row1" but do overlap the range. 1040 marktree_itr_get_overlap(buf->b_marktree, row1, 0, itr); 1041 while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { 1042 if ((pair.start.flags & MT_FLAG_DECOR_SIGNTEXT) && !mt_invalid(pair.start)) { 1043 for (int i = row1; i <= MIN(row2, pair.end_pos.row); i++) { 1044 count[i - row1]++; 1045 } 1046 } 1047 } 1048 1049 marktree_itr_step_out_filter(buf->b_marktree, itr, signtext_filter); 1050 1051 // Continue traversing the marktree until beyond "row2". 1052 while (itr->x) { 1053 MTKey mark = marktree_itr_current(itr); 1054 if (mark.pos.row > row2) { 1055 break; 1056 } 1057 if ((mark.flags & MT_FLAG_DECOR_SIGNTEXT) && !mt_invalid(mark) && !mt_end(mark)) { 1058 // Increment count array for the range of a paired sign mark. 1059 MTPos end = marktree_get_altpos(buf->b_marktree, mark, NULL); 1060 for (int i = mark.pos.row; i <= MIN(row2, end.row); i++) { 1061 count[i - row1]++; 1062 } 1063 } 1064 1065 marktree_itr_next_filter(buf->b_marktree, itr, row2 + 1, 0, signtext_filter); 1066 } 1067 1068 // For each row increment "b_signcols.count" at the number of counted signs, 1069 // and decrement at the previous number of signs. These two operations are 1070 // split in separate calls if "clear" is not kFalse (surrounding a marktree splice). 1071 for (int i = 0; i < row2 + 1 - row1; i++) { 1072 int prevwidth = MIN(SIGN_SHOW_MAX, count[i] - add); 1073 if (clear != kNone && prevwidth > 0) { 1074 buf->b_signcols.count[prevwidth - 1]--; 1075 #ifndef RELDEBUG 1076 // TODO(bfredl): correct marktree splicing so that this doesn't fail 1077 assert(buf->b_signcols.count[prevwidth - 1] >= 0); 1078 #endif 1079 } 1080 int width = MIN(SIGN_SHOW_MAX, count[i]); 1081 if (clear != kTrue && width > 0) { 1082 buf->b_signcols.count[width - 1]++; 1083 if (width > buf->b_signcols.max) { 1084 buf->b_signcols.max = width; 1085 } 1086 } 1087 } 1088 1089 xfree(count); 1090 } 1091 1092 void decor_redraw_end(DecorState *state) 1093 { 1094 state->win = NULL; 1095 } 1096 1097 bool decor_redraw_eol(win_T *wp, DecorState *state, int *eol_attr, int eol_col) 1098 { 1099 decor_redraw_col(wp, MAXCOL, MAXCOL, false, state); 1100 state->eol_col = eol_col; 1101 1102 int const count = state->current_end; 1103 int *const indices = state->ranges_i.items; 1104 DecorRangeSlot *const slots = state->slots.items; 1105 1106 bool has_virt_pos = false; 1107 for (int i = 0; i < count; i++) { 1108 DecorRange *r = &slots[indices[i]].range; 1109 has_virt_pos |= r->start_row == state->row && decor_virt_pos(r); 1110 1111 if (r->kind == kDecorKindHighlight && (r->data.sh.flags & kSHHlEol)) { 1112 *eol_attr = hl_combine_attr(*eol_attr, r->attr_id); 1113 } 1114 } 1115 return has_virt_pos; 1116 } 1117 1118 static const uint32_t lines_filter[kMTMetaCount] = {[kMTMetaLines] = kMTFilterSelect }; 1119 1120 /// @param apply_folds Only count virtual lines that are not in folds. 1121 int decor_virt_lines(win_T *wp, int start_row, int end_row, int *num_below, VirtLines *lines, 1122 bool apply_folds) 1123 { 1124 buf_T *buf = wp->w_buffer; 1125 if (!buf_meta_total(buf, kMTMetaLines)) { 1126 // Only pay for what you use: in case virt_lines feature is not active 1127 // in a buffer, plines do not need to access the marktree at all 1128 return 0; 1129 } 1130 1131 MarkTreeIter itr[1] = { 0 }; 1132 if (!marktree_itr_get_filter(buf->b_marktree, MAX(start_row - 1, 0), 0, end_row, 0, 1133 lines_filter, itr)) { 1134 return 0; 1135 } 1136 1137 assert(start_row >= 0); 1138 1139 int virt_lines = 0; 1140 while (true) { 1141 MTKey mark = marktree_itr_current(itr); 1142 DecorVirtText *vt = mt_decor_virt(mark); 1143 if (!mt_invalid(mark) && ns_in_win(mark.ns, wp)) { 1144 while (vt) { 1145 if (vt->flags & kVTIsLines) { 1146 bool above = vt->flags & kVTLinesAbove; 1147 int mrow = mark.pos.row; 1148 int draw_row = mrow + (above ? 0 : 1); 1149 if (draw_row >= start_row && draw_row < end_row 1150 && (!apply_folds || !(hasFolding(wp, mrow + 1, NULL, NULL) 1151 || decor_conceal_line(wp, mrow, false)))) { 1152 virt_lines += (int)kv_size(vt->data.virt_lines); 1153 if (lines) { 1154 kv_splice(*lines, vt->data.virt_lines); 1155 } 1156 if (num_below && !above) { 1157 (*num_below) += (int)kv_size(vt->data.virt_lines); 1158 } 1159 } 1160 } 1161 vt = vt->next; 1162 } 1163 } 1164 1165 if (!marktree_itr_next_filter(buf->b_marktree, itr, end_row, 0, lines_filter)) { 1166 break; 1167 } 1168 } 1169 1170 return virt_lines; 1171 } 1172 1173 /// This assumes maximum one entry of each kind, which will not always be the case. 1174 /// 1175 /// NB: assumes caller has allocated enough space in dict for all fields! 1176 void decor_to_dict_legacy(Dict *dict, DecorInline decor, bool hl_name, Arena *arena) 1177 { 1178 DecorSignHighlight sh_hl = DECOR_SIGN_HIGHLIGHT_INIT; 1179 DecorSignHighlight sh_sign = DECOR_SIGN_HIGHLIGHT_INIT; 1180 DecorVirtText *virt_text = NULL; 1181 DecorVirtText *virt_lines = NULL; 1182 int32_t priority = -1; // sentinel value which cannot actually be set 1183 1184 if (decor.ext) { 1185 DecorVirtText *vt = decor.data.ext.vt; 1186 while (vt) { 1187 if (vt->flags & kVTIsLines) { 1188 virt_lines = vt; 1189 } else { 1190 virt_text = vt; 1191 } 1192 vt = vt->next; 1193 } 1194 1195 uint32_t idx = decor.data.ext.sh_idx; 1196 while (idx != DECOR_ID_INVALID) { 1197 DecorSignHighlight *sh = &kv_A(decor_items, idx); 1198 if (sh->flags & (kSHIsSign)) { 1199 sh_sign = *sh; 1200 } else { 1201 sh_hl = *sh; 1202 } 1203 idx = sh->next; 1204 } 1205 } else { 1206 sh_hl = decor_sh_from_inline(decor.data.hl); 1207 } 1208 1209 if (sh_hl.hl_id) { 1210 PUT_C(*dict, "hl_group", hl_group_name(sh_hl.hl_id, hl_name)); 1211 PUT_C(*dict, "hl_eol", BOOLEAN_OBJ(sh_hl.flags & kSHHlEol)); 1212 priority = sh_hl.priority; 1213 } 1214 1215 if (sh_hl.flags & kSHConceal) { 1216 char buf[MAX_SCHAR_SIZE]; 1217 schar_get(buf, sh_hl.text[0]); 1218 PUT_C(*dict, "conceal", CSTR_TO_ARENA_OBJ(arena, buf)); 1219 } 1220 1221 if (sh_hl.flags & kSHConcealLines) { 1222 PUT_C(*dict, "conceal_lines", STRING_OBJ(cstr_as_string(""))); 1223 } 1224 1225 if (sh_hl.flags & kSHSpellOn) { 1226 PUT_C(*dict, "spell", BOOLEAN_OBJ(true)); 1227 } else if (sh_hl.flags & kSHSpellOff) { 1228 PUT_C(*dict, "spell", BOOLEAN_OBJ(false)); 1229 } 1230 1231 if (sh_hl.flags & kSHUIWatched) { 1232 PUT_C(*dict, "ui_watched", BOOLEAN_OBJ(true)); 1233 } 1234 1235 if (sh_hl.url != NULL) { 1236 PUT_C(*dict, "url", STRING_OBJ(cstr_as_string(sh_hl.url))); 1237 } 1238 1239 if (virt_text) { 1240 if (virt_text->hl_mode) { 1241 PUT_C(*dict, "hl_mode", CSTR_AS_OBJ(hl_mode_str[virt_text->hl_mode])); 1242 } 1243 1244 Array chunks = virt_text_to_array(virt_text->data.virt_text, hl_name, arena); 1245 PUT_C(*dict, "virt_text", ARRAY_OBJ(chunks)); 1246 PUT_C(*dict, "virt_text_hide", BOOLEAN_OBJ(virt_text->flags & kVTHide)); 1247 PUT_C(*dict, "virt_text_repeat_linebreak", BOOLEAN_OBJ(virt_text->flags & kVTRepeatLinebreak)); 1248 if (virt_text->pos == kVPosWinCol) { 1249 PUT_C(*dict, "virt_text_win_col", INTEGER_OBJ(virt_text->col)); 1250 } 1251 PUT_C(*dict, "virt_text_pos", CSTR_AS_OBJ(virt_text_pos_str[virt_text->pos])); 1252 priority = virt_text->priority; 1253 } 1254 1255 if (virt_lines) { 1256 Array all_chunks = arena_array(arena, kv_size(virt_lines->data.virt_lines)); 1257 int virt_lines_flags = 0; 1258 for (size_t i = 0; i < kv_size(virt_lines->data.virt_lines); i++) { 1259 virt_lines_flags = kv_A(virt_lines->data.virt_lines, i).flags; 1260 Array chunks = virt_text_to_array(kv_A(virt_lines->data.virt_lines, i).line, hl_name, arena); 1261 ADD(all_chunks, ARRAY_OBJ(chunks)); 1262 } 1263 PUT_C(*dict, "virt_lines", ARRAY_OBJ(all_chunks)); 1264 PUT_C(*dict, "virt_lines_above", BOOLEAN_OBJ(virt_lines->flags & kVTLinesAbove)); 1265 PUT_C(*dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_flags & kVLLeftcol)); 1266 PUT_C(*dict, "virt_lines_overflow", 1267 CSTR_AS_OBJ(virt_lines_flags & kVLScroll ? "scroll" : "trunc")); 1268 priority = virt_lines->priority; 1269 } 1270 1271 if (sh_sign.flags & kSHIsSign) { 1272 if (sh_sign.text[0]) { 1273 char buf[SIGN_WIDTH * MAX_SCHAR_SIZE]; 1274 describe_sign_text(buf, sh_sign.text); 1275 PUT_C(*dict, "sign_text", CSTR_TO_ARENA_OBJ(arena, buf)); 1276 } 1277 1278 if (sh_sign.sign_name) { 1279 PUT_C(*dict, "sign_name", CSTR_AS_OBJ(sh_sign.sign_name)); 1280 } 1281 1282 // uncrustify:off 1283 1284 struct { char *name; const int val; } hls[] = { 1285 { "sign_hl_group" , sh_sign.hl_id }, 1286 { "number_hl_group" , sh_sign.number_hl_id }, 1287 { "line_hl_group" , sh_sign.line_hl_id }, 1288 { "cursorline_hl_group", sh_sign.cursorline_hl_id }, 1289 { NULL, 0 }, 1290 }; 1291 1292 // uncrustify:on 1293 1294 for (int j = 0; hls[j].name; j++) { 1295 if (hls[j].val) { 1296 PUT_C(*dict, hls[j].name, hl_group_name(hls[j].val, hl_name)); 1297 } 1298 } 1299 priority = sh_sign.priority; 1300 } 1301 1302 if (priority != -1) { 1303 PUT_C(*dict, "priority", INTEGER_OBJ(priority)); 1304 } 1305 } 1306 1307 uint16_t decor_type_flags(DecorInline decor) 1308 { 1309 if (decor.ext) { 1310 uint16_t type_flags = kExtmarkNone; 1311 DecorVirtText *vt = decor.data.ext.vt; 1312 while (vt) { 1313 type_flags |= (vt->flags & kVTIsLines) ? kExtmarkVirtLines : kExtmarkVirtText; 1314 vt = vt->next; 1315 } 1316 uint32_t idx = decor.data.ext.sh_idx; 1317 while (idx != DECOR_ID_INVALID) { 1318 DecorSignHighlight *sh = &kv_A(decor_items, idx); 1319 type_flags |= (sh->flags & kSHIsSign) ? kExtmarkSign : kExtmarkHighlight; 1320 idx = sh->next; 1321 } 1322 return type_flags; 1323 } else { 1324 return (decor.data.hl.flags & kSHIsSign) ? kExtmarkSign : kExtmarkHighlight; 1325 } 1326 } 1327 1328 Object hl_group_name(int hl_id, bool hl_name) 1329 { 1330 if (hl_name) { 1331 return CSTR_AS_OBJ(syn_id2name(hl_id)); 1332 } else { 1333 return INTEGER_OBJ(hl_id); 1334 } 1335 }