diff options
Diffstat (limited to 'src/nvim/decoration.c')
-rw-r--r-- | src/nvim/decoration.c | 1026 |
1 files changed, 749 insertions, 277 deletions
diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 63c55ec602..11204a1b31 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -1,23 +1,40 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - #include <assert.h> +#include <limits.h> +#include <stddef.h> +#include <stdlib.h> +#include "nvim/api/extmark.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" #include "nvim/buffer.h" #include "nvim/decoration.h" #include "nvim/drawscreen.h" #include "nvim/extmark.h" #include "nvim/fold.h" +#include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/memory.h" -#include "nvim/pos.h" -#include "nvim/sign_defs.h" +#include "nvim/move.h" +#include "nvim/option_vars.h" +#include "nvim/pos_defs.h" +#include "nvim/sign.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "decoration.c.generated.h" #endif +// TODO(bfredl): These should maybe be per-buffer, so that all resources +// associated with a buffer can be freed when the buffer is unloaded. +kvec_t(DecorSignHighlight) decor_items = KV_INITIAL_VALUE; +uint32_t decor_freelist = UINT32_MAX; + +// Decorations might be requested to be deleted in a callback in the middle of redrawing. +// In this case, there might still be live references to the memory allocated for the decoration. +// Keep a "to free" list which can be safely processed when redrawing is done. +DecorVirtText *to_free_virt = NULL; +uint32_t to_free_sh = UINT32_MAX; + /// Add highlighting to a buffer, bounded by two cursor positions, /// with an offset. /// @@ -36,8 +53,8 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start { colnr_T hl_start = 0; colnr_T hl_end = 0; - Decoration decor = DECORATION_INIT; - decor.hl_id = hl_id; + DecorInline decor = DECOR_INLINE_INIT; + decor.data.hl.hl_id = hl_id; // TODO(bfredl): if decoration had blocky mode, we could avoid this loop for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum++) { @@ -62,68 +79,265 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start hl_start = pos_start.col + offset; hl_end = pos_end.col + offset; } + extmark_set(buf, (uint32_t)src_id, NULL, (int)lnum - 1, hl_start, (int)lnum - 1 + end_off, hl_end, - &decor, true, false, kExtmarkNoUndo); + decor, MT_FLAG_DECOR_HL, true, false, true, false, NULL); } } -void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) +void decor_redraw(buf_T *buf, int row1, int row2, DecorInline decor) { if (row2 >= row1) { - if (!decor - || decor->hl_id - || decor_has_sign(decor) - || decor->conceal - || decor->spell != kNone) { - redraw_buf_range_later(buf, row1 + 1, row2 + 1); + redraw_buf_range_later(buf, row1 + 1, row2 + 1); + } + + if (decor.ext) { + DecorVirtText *vt = decor.data.ext.vt; + while (vt) { + if (vt->flags & kVTIsLines) { + redraw_buf_line_later(buf, row1 + 1 + ((vt->flags & kVTLinesAbove) ? 0 : 1), true); + changed_line_display_buf(buf); + } else { + if (vt->pos == kVPosInline) { + changed_line_display_buf(buf); + } + } + vt = vt->next; + } + + uint32_t idx = decor.data.ext.sh_idx; + while (idx != DECOR_ID_INVALID) { + DecorSignHighlight *sh = &kv_A(decor_items, idx); + decor_redraw_sh(buf, row1, row2, *sh); + idx = sh->next; } + } else { + decor_redraw_sh(buf, row1, row2, decor_sh_from_inline(decor.data.hl)); } +} - if (decor && decor_virt_pos(*decor)) { +void decor_redraw_sh(buf_T *buf, int row1, int row2, DecorSignHighlight sh) +{ + if (sh.hl_id || (sh.flags & (kSHIsSign|kSHSpellOn|kSHSpellOff))) { + if (row2 >= row1) { + redraw_buf_range_later(buf, row1 + 1, row2 + 1); + } + } + if (sh.flags & kSHUIWatched) { redraw_buf_line_later(buf, row1 + 1, false); } +} - if (decor && kv_size(decor->virt_lines)) { - redraw_buf_line_later(buf, row1 + 1 + (decor->virt_lines_above?0:1), true); +uint32_t decor_put_sh(DecorSignHighlight item) +{ + if (decor_freelist != UINT32_MAX) { + uint32_t pos = decor_freelist; + decor_freelist = kv_A(decor_items, decor_freelist).next; + kv_A(decor_items, pos) = item; + return pos; + } else { + uint32_t pos = (uint32_t)kv_size(decor_items); + kv_push(decor_items, item); + return pos; } } -void decor_remove(buf_T *buf, int row, int row2, Decoration *decor) +DecorVirtText *decor_put_vt(DecorVirtText vt, DecorVirtText *next) +{ + DecorVirtText *decor_alloc = xmalloc(sizeof *decor_alloc); + *decor_alloc = vt; + decor_alloc->next = next; + return decor_alloc; +} + +DecorSignHighlight decor_sh_from_inline(DecorHighlightInline item) +{ + // TODO(bfredl): Eventually simple signs will be inlinable as well + assert(!(item.flags & kSHIsSign)); + DecorSignHighlight conv = { + .flags = item.flags, + .priority = item.priority, + .text.sc[0] = item.conceal_char, + .hl_id = item.hl_id, + .number_hl_id = 0, + .line_hl_id = 0, + .cursorline_hl_id = 0, + .next = DECOR_ID_INVALID, + }; + + return conv; +} + +void buf_put_decor(buf_T *buf, DecorInline decor, int row, int row2) +{ + if (decor.ext) { + DecorVirtText *vt = decor.data.ext.vt; + while (vt) { + buf_put_decor_virt(buf, vt); + vt = vt->next; + } + + uint32_t idx = decor.data.ext.sh_idx; + while (idx != DECOR_ID_INVALID) { + DecorSignHighlight *sh = &kv_A(decor_items, idx); + buf_put_decor_sh(buf, sh, row, row2); + idx = sh->next; + } + } +} + +void buf_put_decor_virt(buf_T *buf, DecorVirtText *vt) +{ + if (vt->flags &kVTIsLines) { + buf->b_virt_line_blocks++; + } else { + if (vt->pos == kVPosInline) { + buf->b_virt_text_inline++; + } + } + if (vt->next) { + buf_put_decor_virt(buf, vt->next); + } +} + +static int sign_add_id = 0; +void buf_put_decor_sh(buf_T *buf, DecorSignHighlight *sh, int row, int row2) +{ + if (sh->flags & kSHIsSign) { + sh->sign_add_id = sign_add_id++; + buf->b_signs++; + if (sh->text.ptr) { + buf->b_signs_with_text++; + buf_signcols_add_check(buf, row + 1, row2 + 1); + } + } +} + +void buf_decor_remove(buf_T *buf, int row, int row2, DecorInline decor, bool free) { decor_redraw(buf, row, row2, decor); - if (decor) { - if (kv_size(decor->virt_lines)) { - assert(buf->b_virt_line_blocks > 0); - buf->b_virt_line_blocks--; + if (decor.ext) { + DecorVirtText *vt = decor.data.ext.vt; + while (vt) { + buf_remove_decor_virt(buf, vt); + vt = vt->next; } - if (decor_has_sign(decor)) { - assert(buf->b_signs > 0); - buf->b_signs--; + uint32_t idx = decor.data.ext.sh_idx; + while (idx != DECOR_ID_INVALID) { + DecorSignHighlight *sh = &kv_A(decor_items, idx); + buf_remove_decor_sh(buf, row, row2, sh); + idx = sh->next; } - if (row2 >= row && decor->sign_text) { - buf_signcols_del_check(buf, row + 1, row2 + 1); + if (free) { + decor_free(decor); } } - decor_free(decor); } -void decor_free(Decoration *decor) +void buf_remove_decor_virt(buf_T *buf, DecorVirtText *vt) { - if (decor) { - clear_virttext(&decor->virt_text); - for (size_t i = 0; i < kv_size(decor->virt_lines); i++) { - clear_virttext(&kv_A(decor->virt_lines, i).line); + if (vt->flags &kVTIsLines) { + assert(buf->b_virt_line_blocks > 0); + buf->b_virt_line_blocks--; + } else { + if (vt->pos == kVPosInline) { + assert(buf->b_virt_text_inline > 0); + buf->b_virt_text_inline--; + } + } +} + +void buf_remove_decor_sh(buf_T *buf, int row, int row2, DecorSignHighlight *sh) +{ + if (sh->flags & kSHIsSign) { + assert(buf->b_signs > 0); + buf->b_signs--; + if (sh->text.ptr) { + assert(buf->b_signs_with_text > 0); + buf->b_signs_with_text--; + if (row2 >= row) { + buf_signcols_del_check(buf, row + 1, row2 + 1); + } + } + } +} + +void decor_free(DecorInline decor) +{ + if (!decor.ext) { + return; + } + DecorVirtText *vt = decor.data.ext.vt; + uint32_t idx = decor.data.ext.sh_idx; + + if (decor_state.running_decor_provider) { + while (vt) { + if (vt->next == NULL) { + vt->next = to_free_virt; + to_free_virt = decor.data.ext.vt; + break; + } + vt = vt->next; + } + while (idx != DECOR_ID_INVALID) { + DecorSignHighlight *sh = &kv_A(decor_items, idx); + if (sh->next == DECOR_ID_INVALID) { + sh->next = to_free_sh; + to_free_sh = decor.data.ext.sh_idx; + break; + } + idx = sh->next; + } + } else { + // safe to delete right now + decor_free_inner(vt, idx); + } +} + +void decor_free_inner(DecorVirtText *vt, uint32_t first_idx) +{ + while (vt) { + if (vt->flags & kVTIsLines) { + clear_virtlines(&vt->data.virt_lines); + } else { + clear_virttext(&vt->data.virt_text); + } + DecorVirtText *tofree = vt; + vt = vt->next; + xfree(tofree); + } + + uint32_t idx = first_idx; + while (idx != DECOR_ID_INVALID) { + DecorSignHighlight *sh = &kv_A(decor_items, idx); + if (sh->flags & kSHIsSign) { + xfree(sh->text.ptr); + } + if (sh->flags & kSHIsSign) { + xfree(sh->sign_name); } - kv_destroy(decor->virt_lines); - xfree(decor->sign_text); - xfree(decor); + sh->flags = 0; + if (sh->next == DECOR_ID_INVALID) { + sh->next = decor_freelist; + decor_freelist = first_idx; + break; + } + idx = sh->next; } } +void decor_check_to_be_deleted(void) +{ + assert(!decor_state.running_decor_provider); + decor_free_inner(to_free_virt, to_free_sh); + to_free_virt = NULL; + to_free_sh = DECOR_ID_INVALID; +} + void decor_state_free(DecorState *state) { - xfree(state->active.items); + kv_destroy(state->active); } void clear_virttext(VirtText *text) @@ -135,20 +349,59 @@ void clear_virttext(VirtText *text) *text = (VirtText)KV_INITIAL_VALUE; } -Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) +void clear_virtlines(VirtLines *lines) +{ + for (size_t i = 0; i < kv_size(*lines); i++) { + clear_virttext(&kv_A(*lines, i).line); + } + kv_destroy(*lines); + *lines = (VirtLines)KV_INITIAL_VALUE; +} + +void decor_check_invalid_glyphs(void) +{ + for (size_t i = 0; i < kv_size(decor_items); i++) { + DecorSignHighlight *it = &kv_A(decor_items, i); + if ((it->flags & kSHConceal) && schar_high(it->text.sc[0])) { + it->text.sc[0] = schar_from_char(schar_get_first_codepoint(it->text.sc[0])); + } + } +} + +/// Get the next chunk of a virtual text item. +/// +/// @param[in] vt The virtual text item +/// @param[in,out] pos Position in the virtual text item +/// @param[in,out] attr Highlight attribute +/// +/// @return The text of the chunk, or NULL if there are no more chunks +char *next_virt_text_chunk(VirtText vt, size_t *pos, int *attr) +{ + char *text = NULL; + for (; text == NULL && *pos < kv_size(vt); (*pos)++) { + text = kv_A(vt, *pos).text; + int hl_id = kv_A(vt, *pos).hl_id; + *attr = hl_combine_attr(*attr, hl_id > 0 ? syn_id2attr(hl_id) : 0); + } + return text; +} + +DecorVirtText *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) { MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, row, 0, itr); while (true) { - mtkey_t mark = marktree_itr_current(itr); + MTKey mark = marktree_itr_current(itr); if (mark.pos.row < 0 || mark.pos.row > row) { break; - } else if (marktree_decor_level(mark) < kDecorLevelVisible) { + } else if (mt_invalid(mark) || !(mark.flags & MT_FLAG_DECOR_EXT)) { goto next_mark; } - Decoration *decor = mark.decor_full; - if ((ns_id == 0 || ns_id == mark.ns) - && decor && kv_size(decor->virt_text)) { + DecorVirtText *decor = mark.decor_data.ext.vt; + while (decor && (decor->flags & kVTIsLines)) { + decor = decor->next; + } + if ((ns_id == 0 || ns_id == mark.ns) && decor) { return decor; } next_mark: @@ -157,115 +410,107 @@ next_mark: return NULL; } -bool decor_redraw_reset(buf_T *buf, DecorState *state) +bool decor_redraw_reset(win_T *wp, DecorState *state) { state->row = -1; - state->buf = buf; + state->win = wp; for (size_t i = 0; i < kv_size(state->active); i++) { DecorRange item = kv_A(state->active, i); - if (item.virt_text_owned) { - clear_virttext(&item.decor.virt_text); + if (item.owned && item.kind == kDecorKindVirtText) { + clear_virttext(&item.data.vt->data.virt_text); + xfree(item.data.vt); } } kv_size(state->active) = 0; - return buf->b_marktree->n_keys; + return wp->w_buffer->b_marktree->n_keys; } -Decoration get_decor(mtkey_t mark) +/// @return true if decor has a virtual position (virtual text or ui_watched) +bool decor_virt_pos(const DecorRange *decor) { - if (mark.decor_full) { - return *mark.decor_full; - } - Decoration fake = DECORATION_INIT; - fake.hl_id = mark.hl_id; - fake.priority = mark.priority; - fake.hl_eol = (mark.flags & MT_FLAG_HL_EOL); - return fake; + return (decor->kind == kDecorKindVirtText || decor->kind == kDecorKindUIWatched); } -/// @return true if decor has a virtual position (virtual text or ui_watched) -static bool decor_virt_pos(Decoration decor) +VirtTextPos decor_virt_pos_kind(const DecorRange *decor) { - return kv_size(decor.virt_text) || decor.ui_watched; + if (decor->kind == kDecorKindVirtText) { + return decor->data.vt->pos; + } + if (decor->kind == kDecorKindUIWatched) { + return decor->data.ui.pos; + } + return kVPosEndOfLine; // not used; return whatever } -bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state) +bool decor_redraw_start(win_T *wp, int top_row, DecorState *state) { + buf_T *buf = wp->w_buffer; state->top_row = top_row; - marktree_itr_get(buf->b_marktree, top_row, 0, state->itr); - if (!state->itr->node) { + if (!marktree_itr_get_overlap(buf->b_marktree, top_row, 0, state->itr)) { return false; } - marktree_itr_rewind(buf->b_marktree, state->itr); - while (true) { - mtkey_t mark = marktree_itr_current(state->itr); - if (mark.pos.row < 0) { // || mark.row > end_row - break; - } - if ((mark.pos.row < top_row && mt_end(mark)) - || marktree_decor_level(mark) < kDecorLevelVisible) { - goto next_mark; - } + MTPair pair; - Decoration decor = get_decor(mark); - - mtpos_t altpos = marktree_get_altpos(buf->b_marktree, mark, NULL); - - // Exclude start marks if the end mark position is above the top row - // Exclude end marks if we have already added the start mark - if ((mt_start(mark) && altpos.row < top_row && !decor_virt_pos(decor)) - || (mt_end(mark) && altpos.row >= top_row)) { - goto next_mark; + while (marktree_itr_step_overlap(buf->b_marktree, state->itr, &pair)) { + MTKey m = pair.start; + if (mt_invalid(m) || !mt_decor_any(m)) { + continue; } - if (mt_end(mark)) { - decor_add(state, altpos.row, altpos.col, mark.pos.row, mark.pos.col, - &decor, false, mark.ns, mark.id); - } else { - if (altpos.row == -1) { - altpos.row = mark.pos.row; - altpos.col = mark.pos.col; - } - decor_add(state, mark.pos.row, mark.pos.col, altpos.row, altpos.col, - &decor, false, mark.ns, mark.id); - } - -next_mark: - if (marktree_itr_node_done(state->itr)) { - marktree_itr_next(buf->b_marktree, state->itr); - break; - } - marktree_itr_next(buf->b_marktree, state->itr); + decor_range_add_from_inline(state, pair.start.pos.row, pair.start.pos.col, pair.end_pos.row, + pair.end_pos.col, + mt_decor(m), false, m.ns, m.id); } return true; // TODO(bfredl): check if available in the region } -bool decor_redraw_line(buf_T *buf, int row, DecorState *state) +bool decor_redraw_line(win_T *wp, int row, DecorState *state) { if (state->row == -1) { - decor_redraw_start(buf, row, state); + decor_redraw_start(wp, row, state); } state->row = row; state->col_until = -1; state->eol_col = -1; - return true; // TODO(bfredl): be more precise + + if (kv_size(state->active)) { + return true; + } + + MTKey k = marktree_itr_current(state->itr); + return (k.pos.row >= 0 && k.pos.row <= row); } -static void decor_add(DecorState *state, int start_row, int start_col, int end_row, int end_col, - Decoration *decor, bool owned, uint64_t ns_id, uint64_t mark_id) +static void decor_range_add_from_inline(DecorState *state, int start_row, int start_col, + int end_row, int end_col, DecorInline decor, bool owned, + uint32_t ns, uint32_t mark_id) { - int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; - - DecorRange range = { start_row, start_col, end_row, end_col, - *decor, attr_id, - kv_size(decor->virt_text) && owned, -1, ns_id, mark_id }; + if (decor.ext) { + DecorVirtText *vt = decor.data.ext.vt; + while (vt) { + decor_range_add_virt(state, start_row, start_col, end_row, end_col, vt, owned); + vt = vt->next; + } + uint32_t idx = decor.data.ext.sh_idx; + while (idx != DECOR_ID_INVALID) { + DecorSignHighlight *sh = &kv_A(decor_items, idx); + decor_range_add_sh(state, start_row, start_col, end_row, end_col, sh, owned, ns, mark_id); + idx = sh->next; + } + } else { + DecorSignHighlight sh = decor_sh_from_inline(decor.data.hl); + decor_range_add_sh(state, start_row, start_col, end_row, end_col, &sh, owned, ns, mark_id); + } +} +static void decor_range_insert(DecorState *state, DecorRange range) +{ kv_pushp(state->active); size_t index; for (index = kv_size(state->active) - 1; index > 0; index--) { DecorRange item = kv_A(state->active, index - 1); - if (item.decor.priority <= range.decor.priority) { + if (item.priority <= range.priority) { break; } kv_A(state->active, index) = kv_A(state->active, index - 1); @@ -273,8 +518,82 @@ static void decor_add(DecorState *state, int start_row, int start_col, int end_r kv_A(state->active, index) = range; } -int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden, DecorState *state) +void decor_range_add_virt(DecorState *state, int start_row, int start_col, int end_row, int end_col, + DecorVirtText *vt, bool owned) +{ + bool is_lines = vt->flags & kVTIsLines; + DecorRange range = { + .start_row = start_row, .start_col = start_col, .end_row = end_row, .end_col = end_col, + .kind = is_lines ? kDecorKindVirtLines : kDecorKindVirtText, + .data.vt = vt, + .attr_id = 0, + .owned = owned, + .priority = vt->priority, + .draw_col = -10, + }; + decor_range_insert(state, range); +} + +void decor_range_add_sh(DecorState *state, int start_row, int start_col, int end_row, int end_col, + DecorSignHighlight *sh, bool owned, uint32_t ns, uint32_t mark_id) +{ + if (sh->flags & kSHIsSign) { + return; + } + + DecorRange range = { + .start_row = start_row, .start_col = start_col, .end_row = end_row, .end_col = end_col, + .kind = kDecorKindHighlight, + .data.sh = *sh, + .attr_id = 0, + .owned = owned, + .priority = sh->priority, + .draw_col = -10, + }; + + if (sh->hl_id || (sh->flags & (kSHConceal | kSHSpellOn | kSHSpellOff))) { + if (sh->hl_id) { + range.attr_id = syn_id2attr(sh->hl_id); + } + decor_range_insert(state, range); + } + + if (sh->flags & (kSHUIWatched)) { + range.kind = kDecorKindUIWatched; + range.data.ui.ns_id = ns; + range.data.ui.mark_id = mark_id; + range.data.ui.pos = (sh->flags & kSHUIWatchedOverlay) ? kVPosOverlay : kVPosEndOfLine; + decor_range_insert(state, range); + } +} + +/// Initialize the draw_col of a newly-added virtual text item. +static void decor_init_draw_col(int win_col, bool hidden, DecorRange *item) { + DecorVirtText *vt = item->kind == kDecorKindVirtText ? item->data.vt : NULL; + VirtTextPos pos = decor_virt_pos_kind(item); + if (win_col < 0 && pos != kVPosInline) { + item->draw_col = win_col; + } else if (pos == kVPosOverlay) { + item->draw_col = (vt && (vt->flags & kVTHide) && hidden) ? INT_MIN : win_col; + } else { + item->draw_col = -1; + } +} + +void decor_recheck_draw_col(int win_col, bool hidden, DecorState *state) +{ + for (size_t i = 0; i < kv_size(state->active); i++) { + DecorRange *item = &kv_A(state->active, i); + if (item->draw_col == -3) { + decor_init_draw_col(win_col, hidden, item); + } + } +} + +int decor_redraw_col(win_T *wp, int col, int win_col, bool hidden, DecorState *state) +{ + buf_T *buf = wp->w_buffer; if (col <= state->col_until) { return state->current; } @@ -282,7 +601,7 @@ int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden, DecorState * while (true) { // TODO(bfredl): check duplicate entry in "intersection" // branch - mtkey_t mark = marktree_itr_current(state->itr); + MTKey mark = marktree_itr_current(state->itr); if (mark.pos.row < 0 || mark.pos.row > state->row) { break; } else if (mark.pos.row == state->row && mark.pos.col > col) { @@ -290,21 +609,17 @@ int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden, DecorState * break; } - if (mt_end(mark) - || marktree_decor_level(mark) < kDecorLevelVisible) { + if (mt_invalid(mark) || mt_end(mark) || !mt_decor_any(mark)) { goto next_mark; } - Decoration decor = get_decor(mark); - - mtpos_t endpos = marktree_get_altpos(buf->b_marktree, mark, NULL); - + MTPos endpos = marktree_get_altpos(buf->b_marktree, mark, NULL); if (endpos.row == -1) { endpos = mark.pos; } - decor_add(state, mark.pos.row, mark.pos.col, endpos.row, endpos.col, - &decor, false, mark.ns, mark.id); + decor_range_add_from_inline(state, mark.pos.row, mark.pos.col, endpos.row, endpos.col, + mt_decor(mark), false, mark.ns, mark.id); next_mark: marktree_itr_next(buf->b_marktree, state->itr); @@ -312,8 +627,8 @@ next_mark: int attr = 0; size_t j = 0; - bool conceal = 0; - int conceal_char = 0; + int conceal = 0; + schar_T conceal_char = 0; int conceal_attr = 0; TriState spell = kNone; @@ -322,7 +637,7 @@ next_mark: bool active = false, keep = true; if (item.end_row < state->row || (item.end_row == state->row && item.end_col <= col)) { - if (!(item.start_row >= state->row && decor_virt_pos(item.decor))) { + if (!(item.start_row >= state->row && decor_virt_pos(&item))) { keep = false; } } else { @@ -341,26 +656,32 @@ next_mark: if (active && item.attr_id > 0) { attr = hl_combine_attr(attr, item.attr_id); } - if (active && item.decor.conceal) { - conceal = true; - if (item.start_row == state->row && item.start_col == col && item.decor.conceal_char) { - conceal_char = item.decor.conceal_char; + if (active && item.kind == kDecorKindHighlight && (item.data.sh.flags & kSHConceal)) { + conceal = 1; + if (item.start_row == state->row && item.start_col == col) { + DecorSignHighlight *sh = &item.data.sh; + conceal = 2; + conceal_char = sh->text.sc[0]; state->col_until = MIN(state->col_until, item.start_col); conceal_attr = item.attr_id; } } - if (active && item.decor.spell != kNone) { - spell = item.decor.spell; + if (active && item.kind == kDecorKindHighlight) { + if (item.data.sh.flags & kSHSpellOn) { + spell = kTrue; + } else if (item.data.sh.flags & kSHSpellOff) { + spell = kFalse; + } } - if ((item.start_row == state->row && item.start_col <= col) - && decor_virt_pos(item.decor) - && item.decor.virt_text_pos == kVTOverlay && item.win_col == -1) { - item.win_col = (item.decor.virt_text_hide && hidden) ? -2 : win_col; + if (item.start_row == state->row && item.start_col <= col + && decor_virt_pos(&item) && item.draw_col == -10) { + decor_init_draw_col(win_col, hidden, &item); } if (keep) { kv_A(state->active, j++) = item; - } else if (item.virt_text_owned) { - clear_virttext(&item.decor.virt_text); + } else if (item.owned && item.kind == kDecorKindVirtText) { + clear_virttext(&item.data.vt->data.virt_text); + xfree(item.data.vt); } } kv_size(state->active) = j; @@ -372,183 +693,172 @@ next_mark: return attr; } -void decor_redraw_signs(buf_T *buf, int row, int *num_signs, SignTextAttrs sattrs[], - HlPriAttr *num_attrs, HlPriAttr *line_attrs, HlPriAttr *cul_attrs) +typedef struct { + DecorSignHighlight *sh; + uint32_t id; +} SignItem; + +int sign_item_cmp(const void *p1, const void *p2) +{ + const SignItem *s1 = (SignItem *)p1; + const SignItem *s2 = (SignItem *)p2; + int n = s2->sh->priority - s1->sh->priority; + + return n ? n : (n = (int)(s2->id - s1->id)) + ? n : (s2->sh->sign_add_id - s1->sh->sign_add_id); +} + +/// Return the sign attributes on the currently refreshed row. +/// +/// @param[out] sattrs Output array for sign text and texthl id +/// @param[out] line_attr Highest priority linehl id +/// @param[out] cul_attr Highest priority culhl id +/// @param[out] num_attr Highest priority numhl id +void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[], int *line_id, + int *cul_id, int *num_id) { - if (!buf->b_signs) { + MarkTreeIter itr[1]; + if (!buf->b_signs || !marktree_itr_get_overlap(buf->b_marktree, row, 0, itr)) { return; } - MarkTreeIter itr[1] = { 0 }; - marktree_itr_get(buf->b_marktree, row, 0, itr); + MTPair pair; + int num_text = 0; + kvec_t(SignItem) signs = KV_INITIAL_VALUE; + // TODO(bfredl): integrate with main decor loop. + while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { + if (!mt_invalid(pair.start) && mt_decor_sign(pair.start)) { + DecorSignHighlight *sh = decor_find_sign(mt_decor(pair.start)); + num_text += (sh->text.ptr != NULL); + kv_push(signs, ((SignItem){ sh, pair.start.id })); + } + } - while (true) { - mtkey_t mark = marktree_itr_current(itr); - if (mark.pos.row < 0 || mark.pos.row > row) { + while (itr->x) { + MTKey mark = marktree_itr_current(itr); + if (mark.pos.row != row) { break; } - - if (mt_end(mark) || marktree_decor_level(mark) < kDecorLevelVisible) { - goto next_mark; + if (!mt_end(mark) && !mt_invalid(mark) && mt_decor_sign(mark)) { + DecorSignHighlight *sh = decor_find_sign(mt_decor(mark)); + num_text += (sh->text.ptr != NULL); + kv_push(signs, ((SignItem){ sh, mark.id })); } - Decoration *decor = mark.decor_full; + marktree_itr_next(buf->b_marktree, itr); + } - if (!decor || !decor_has_sign(decor)) { - goto next_mark; - } + if (kv_size(signs)) { + int width = wp->w_minscwidth == SCL_NUM ? 1 : wp->w_scwidth; + int idx = MIN(width, num_text) - 1; + qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(kv_A(signs, 0)), sign_item_cmp); - if (decor->sign_text) { - int j; - for (j = (*num_signs); j > 0; j--) { - if (sattrs[j - 1].priority >= decor->priority) { - break; - } - sattrs[j] = sattrs[j - 1]; + for (size_t i = 0; i < kv_size(signs); i++) { + DecorSignHighlight *sh = kv_A(signs, i).sh; + if (idx >= 0 && sh->text.ptr) { + sattrs[idx].text = sh->text.ptr; + sattrs[idx--].hl_id = sh->hl_id; } - if (j < SIGN_SHOW_MAX) { - sattrs[j] = (SignTextAttrs) { - .text = decor->sign_text, - .hl_attr_id = decor->sign_hl_id == 0 ? 0 : syn_id2attr(decor->sign_hl_id), - .priority = decor->priority - }; - (*num_signs)++; + if (*num_id == 0) { + *num_id = sh->number_hl_id; } - } - - struct { HlPriAttr *dest; int hl; } cattrs[] = { - { line_attrs, decor->line_hl_id }, - { num_attrs, decor->number_hl_id }, - { cul_attrs, decor->cursorline_hl_id }, - { NULL, -1 }, - }; - for (int i = 0; cattrs[i].dest; i++) { - if (cattrs[i].hl != 0 && decor->priority >= cattrs[i].dest->priority) { - *cattrs[i].dest = (HlPriAttr) { - .attr_id = syn_id2attr(cattrs[i].hl), - .priority = decor->priority - }; + if (*line_id == 0) { + *line_id = sh->line_hl_id; + } + if (*cul_id == 0) { + *cul_id = sh->cursorline_hl_id; } } - -next_mark: - marktree_itr_next(buf->b_marktree, itr); + kv_destroy(signs); } } -// Get the maximum required amount of sign columns needed between row and -// end_row. -int decor_signcols(buf_T *buf, DecorState *state, int row, int end_row, int max) +DecorSignHighlight *decor_find_sign(DecorInline decor) { - int count = 0; // count for the number of signs on a given row - int count_remove = 0; // how much to decrement count by when iterating marks for a new row - int signcols = 0; // highest value of count - int currow = -1; // current row - - if (max <= 1 && buf->b_signs >= (size_t)max) { - return max; - } - - if (buf->b_signs == 0) { - return 0; + if (!decor.ext) { + return NULL; } - - MarkTreeIter itr[1] = { 0 }; - marktree_itr_get(buf->b_marktree, 0, -1, itr); + uint32_t decor_id = decor.data.ext.sh_idx; while (true) { - mtkey_t mark = marktree_itr_current(itr); - if (mark.pos.row < 0 || mark.pos.row > end_row) { - break; + if (decor_id == DECOR_ID_INVALID) { + return NULL; } - - if ((mark.pos.row < row && mt_end(mark)) - || marktree_decor_level(mark) < kDecorLevelVisible - || !mark.decor_full) { - goto next_mark; - } - - Decoration decor = get_decor(mark); - - if (!decor.sign_text) { - goto next_mark; + DecorSignHighlight *sh = &kv_A(decor_items, decor_id); + if (sh->flags & kSHIsSign) { + return sh; } + decor_id = sh->next; + } +} - if (mark.pos.row > currow) { - count -= count_remove; - count_remove = 0; - currow = mark.pos.row; +// Increase the signcolumn size and update the sentinel line if necessary for +// the invalidated range. +void decor_validate_signcols(buf_T *buf, int max) +{ + int signcols = 0; // highest value of count + int currow = buf->b_signcols.invalid_top - 1; + // TODO(bfredl): only need to use marktree_itr_get_overlap once. + // then we can process both start and end events and update state for each row + for (; currow < buf->b_signcols.invalid_bot; currow++) { + MarkTreeIter itr[1]; + if (!marktree_itr_get_overlap(buf->b_marktree, currow, 0, itr)) { + continue; } - if (!mt_paired(mark)) { - if (mark.pos.row >= row) { + int count = 0; + MTPair pair; + while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { + if (!mt_invalid(pair.start) && (pair.start.flags & MT_FLAG_DECOR_SIGNTEXT)) { count++; - if (count > signcols) { - signcols = count; - if (signcols >= max) { - return max; - } - } - count_remove++; } - goto next_mark; } - mtpos_t altpos = marktree_get_altpos(buf->b_marktree, mark, NULL); - - if (mt_end(mark)) { - if (mark.pos.row >= row && altpos.row <= end_row) { - count_remove++; + while (itr->x) { + MTKey mark = marktree_itr_current(itr); + if (mark.pos.row != currow) { + break; } - } else { - if (altpos.row >= row) { + if (!mt_invalid(mark) && !mt_end(mark) && (mark.flags & MT_FLAG_DECOR_SIGNTEXT)) { count++; - if (count > signcols) { - signcols = count; - if (signcols >= max) { - return max; - } - } } + marktree_itr_next(buf->b_marktree, itr); } -next_mark: - marktree_itr_next(buf->b_marktree, itr); + if (count > signcols) { + if (count >= buf->b_signcols.size) { + buf->b_signcols.size = count; + buf->b_signcols.sentinel = currow + 1; + } + if (count >= max) { + return; + } + signcols = count; + } } - - return signcols; } void decor_redraw_end(DecorState *state) { - state->buf = NULL; + state->win = NULL; } -bool decor_redraw_eol(buf_T *buf, DecorState *state, int *eol_attr, int eol_col) +bool decor_redraw_eol(win_T *wp, DecorState *state, int *eol_attr, int eol_col) { - decor_redraw_col(buf, MAXCOL, MAXCOL, false, state); + decor_redraw_col(wp, MAXCOL, MAXCOL, false, state); state->eol_col = eol_col; - bool has_virttext = false; + bool has_virt_pos = false; for (size_t i = 0; i < kv_size(state->active); i++) { DecorRange item = kv_A(state->active, i); - if (item.start_row == state->row && decor_virt_pos(item.decor)) { - has_virttext = true; + if (item.start_row == state->row && decor_virt_pos(&item)) { + has_virt_pos = true; } - if (item.decor.hl_eol && item.start_row <= state->row) { + if (item.kind == kDecorKindHighlight + && (item.data.sh.flags & kSHHlEol) && item.start_row <= state->row) { *eol_attr = hl_combine_attr(*eol_attr, item.attr_id); } } - return has_virttext; -} - -void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col, Decoration *decor, - uint64_t ns_id, uint64_t mark_id) -{ - if (end_row == -1) { - end_row = start_row; - end_col = start_col; - } - decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true, ns_id, mark_id); + return has_virt_pos; } /// @param has_fold whether line "lnum" has a fold, or kNone when not calculated yet @@ -561,30 +871,42 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fo return 0; } - int virt_lines = 0; - int row = MAX(lnum - 2, 0); - int end_row = (int)lnum; - MarkTreeIter itr[1] = { 0 }; - marktree_itr_get(buf->b_marktree, row, 0, itr); + assert(lnum > 0); bool below_fold = lnum > 1 && hasFoldingWin(wp, lnum - 1, NULL, NULL, true, NULL); if (has_fold == kNone) { has_fold = hasFoldingWin(wp, lnum, NULL, NULL, true, NULL); } + + const int row = lnum - 1; + const int start_row = below_fold ? row : MAX(row - 1, 0); + const int end_row = has_fold ? row : row + 1; + if (start_row >= end_row) { + return 0; + } + + int virt_lines = 0; + MarkTreeIter itr[1] = { 0 }; + marktree_itr_get(buf->b_marktree, start_row, 0, itr); while (true) { - mtkey_t mark = marktree_itr_current(itr); + MTKey mark = marktree_itr_current(itr); if (mark.pos.row < 0 || mark.pos.row >= end_row) { break; - } else if (marktree_decor_level(mark) < kDecorLevelVirtLine) { + } else if (mt_end(mark) || !(mark.flags & MT_FLAG_DECOR_VIRT_LINES)) { goto next_mark; } - bool above = mark.pos.row > (lnum - 2); - bool has_fold_cur = above ? has_fold : below_fold; - Decoration *decor = mark.decor_full; - if (!has_fold_cur && decor && decor->virt_lines_above == above) { - virt_lines += (int)kv_size(decor->virt_lines); - if (lines) { - kv_splice(*lines, decor->virt_lines); + DecorVirtText *vt = mark.decor_data.ext.vt; + while (vt) { + if (vt->flags & kVTIsLines) { + bool above = vt->flags & kVTLinesAbove; + int draw_row = mark.pos.row + (above ? 0 : 1); + if (draw_row == row) { + virt_lines += (int)kv_size(vt->data.virt_lines); + if (lines) { + kv_splice(*lines, vt->data.virt_lines); + } + } } + vt = vt->next; } next_mark: marktree_itr_next(buf->b_marktree, itr); @@ -592,3 +914,153 @@ next_mark: return virt_lines; } + +/// This assumes maximum one entry of each kind, which will not always be the case. +void decor_to_dict_legacy(Dictionary *dict, DecorInline decor, bool hl_name) +{ + DecorSignHighlight sh_hl = DECOR_SIGN_HIGHLIGHT_INIT; + DecorSignHighlight sh_sign = DECOR_SIGN_HIGHLIGHT_INIT; + DecorVirtText *virt_text = NULL; + DecorVirtText *virt_lines = NULL; + int32_t priority = -1; // sentinel value which cannot actually be set + + if (decor.ext) { + DecorVirtText *vt = decor.data.ext.vt; + while (vt) { + if (vt->flags & kVTIsLines) { + virt_lines = vt; + } else { + virt_text = vt; + } + vt = vt->next; + } + + uint32_t idx = decor.data.ext.sh_idx; + while (idx != DECOR_ID_INVALID) { + DecorSignHighlight *sh = &kv_A(decor_items, idx); + if (sh->flags & (kSHIsSign)) { + sh_sign = *sh; + } else { + sh_hl = *sh; + } + idx = sh->next; + } + } else { + sh_hl = decor_sh_from_inline(decor.data.hl); + } + + if (sh_hl.hl_id) { + PUT(*dict, "hl_group", hl_group_name(sh_hl.hl_id, hl_name)); + PUT(*dict, "hl_eol", BOOLEAN_OBJ(sh_hl.flags & kSHHlEol)); + if (sh_hl.flags & kSHConceal) { + char buf[MAX_SCHAR_SIZE]; + schar_get(buf, sh_hl.text.sc[0]); + PUT(*dict, "conceal", CSTR_TO_OBJ(buf)); + } + + if (sh_hl.flags & kSHSpellOn) { + PUT(*dict, "spell", BOOLEAN_OBJ(true)); + } else if (sh_hl.flags & kSHSpellOff) { + PUT(*dict, "spell", BOOLEAN_OBJ(false)); + } + + priority = sh_hl.priority; + } + + if (sh_hl.flags & kSHUIWatched) { + PUT(*dict, "ui_watched", BOOLEAN_OBJ(true)); + } + + if (virt_text) { + if (virt_text->hl_mode) { + PUT(*dict, "hl_mode", CSTR_TO_OBJ(hl_mode_str[virt_text->hl_mode])); + } + + Array chunks = virt_text_to_array(virt_text->data.virt_text, hl_name); + PUT(*dict, "virt_text", ARRAY_OBJ(chunks)); + PUT(*dict, "virt_text_hide", BOOLEAN_OBJ(virt_text->flags & kVTHide)); + if (virt_text->pos == kVPosWinCol) { + PUT(*dict, "virt_text_win_col", INTEGER_OBJ(virt_text->col)); + } + PUT(*dict, "virt_text_pos", + CSTR_TO_OBJ(virt_text_pos_str[virt_text->pos])); + priority = virt_text->priority; + } + + if (virt_lines) { + Array all_chunks = ARRAY_DICT_INIT; + bool virt_lines_leftcol = false; + for (size_t i = 0; i < kv_size(virt_lines->data.virt_lines); i++) { + virt_lines_leftcol = kv_A(virt_lines->data.virt_lines, i).left_col; + Array chunks = virt_text_to_array(kv_A(virt_lines->data.virt_lines, i).line, hl_name); + ADD(all_chunks, ARRAY_OBJ(chunks)); + } + PUT(*dict, "virt_lines", ARRAY_OBJ(all_chunks)); + PUT(*dict, "virt_lines_above", BOOLEAN_OBJ(virt_lines->flags & kVTLinesAbove)); + PUT(*dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol)); + priority = virt_lines->priority; + } + + if (sh_sign.flags & kSHIsSign) { + if (sh_sign.text.ptr) { + PUT(*dict, "sign_text", CSTR_TO_OBJ(sh_sign.text.ptr)); + } + + if (sh_sign.sign_name) { + PUT(*dict, "sign_name", CSTR_TO_OBJ(sh_sign.sign_name)); + } + + // uncrustify:off + + struct { char *name; const int val; } hls[] = { + { "sign_hl_group" , sh_sign.hl_id }, + { "number_hl_group" , sh_sign.number_hl_id }, + { "line_hl_group" , sh_sign.line_hl_id }, + { "cursorline_hl_group", sh_sign.cursorline_hl_id }, + { NULL, 0 }, + }; + + // uncrustify:on + + for (int j = 0; hls[j].name; j++) { + if (hls[j].val) { + PUT(*dict, hls[j].name, hl_group_name(hls[j].val, hl_name)); + } + } + priority = sh_sign.priority; + } + + if (priority != -1) { + PUT(*dict, "priority", INTEGER_OBJ(priority)); + } +} + +uint16_t decor_type_flags(DecorInline decor) +{ + if (decor.ext) { + uint16_t type_flags = kExtmarkNone; + DecorVirtText *vt = decor.data.ext.vt; + while (vt) { + type_flags |= (vt->flags & kVTIsLines) ? kExtmarkVirtLines : kExtmarkVirtText; + vt = vt->next; + } + uint32_t idx = decor.data.ext.sh_idx; + while (idx != DECOR_ID_INVALID) { + DecorSignHighlight *sh = &kv_A(decor_items, idx); + type_flags |= (sh->flags & kSHIsSign) ? kExtmarkSign : kExtmarkHighlight; + idx = sh->next; + } + return type_flags; + } else { + return (decor.data.hl.flags & kSHIsSign) ? kExtmarkSign : kExtmarkHighlight; + } +} + +Object hl_group_name(int hl_id, bool hl_name) +{ + if (hl_name) { + return CSTR_TO_OBJ(syn_id2name(hl_id)); + } else { + return INTEGER_OBJ(hl_id); + } +} |