aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/decoration.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/decoration.c')
-rw-r--r--src/nvim/decoration.c1026
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);
+ }
+}