diff options
-rw-r--r-- | src/nvim/api/deprecated.c | 22 | ||||
-rw-r--r-- | src/nvim/api/extmark.c | 279 | ||||
-rw-r--r-- | src/nvim/decoration.c | 745 | ||||
-rw-r--r-- | src/nvim/decoration.h | 99 | ||||
-rw-r--r-- | src/nvim/decoration_defs.h | 129 | ||||
-rw-r--r-- | src/nvim/decoration_provider.c | 8 | ||||
-rw-r--r-- | src/nvim/drawline.c | 49 | ||||
-rw-r--r-- | src/nvim/extmark.c | 113 | ||||
-rw-r--r-- | src/nvim/extmark.h | 2 | ||||
-rw-r--r-- | src/nvim/extmark_defs.h | 13 | ||||
-rw-r--r-- | src/nvim/marktree.c | 25 | ||||
-rw-r--r-- | src/nvim/marktree.h | 64 | ||||
-rw-r--r-- | src/nvim/plines.c | 32 | ||||
-rw-r--r-- | src/nvim/sign.c | 113 | ||||
-rw-r--r-- | src/nvim/types.h | 2 | ||||
-rw-r--r-- | test/functional/api/extmark_spec.lua | 1 | ||||
-rw-r--r-- | test/functional/ui/bufhl_spec.lua | 3 | ||||
-rw-r--r-- | test/functional/ui/decorations_spec.lua | 4 |
18 files changed, 1088 insertions, 615 deletions
diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index 849897b529..b4aa6fe99e 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -154,21 +154,25 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A return 0; } - Decoration *existing = decor_find_virttext(buf, (int)line, ns_id); + DecorVirtText *existing = decor_find_virttext(buf, (int)line, ns_id); if (existing) { - clear_virttext(&existing->virt_text); - existing->virt_text = virt_text; - existing->virt_text_width = width; + clear_virttext(&existing->data.virt_text); + existing->data.virt_text = virt_text; + existing->width = width; return src_id; } - Decoration decor = DECORATION_INIT; - decor.virt_text = virt_text; - decor.virt_text_width = width; - decor.priority = 0; + DecorVirtText *vt = xmalloc(sizeof *vt); + *vt = (DecorVirtText)DECOR_VIRT_TEXT_INIT; + vt->data.virt_text = virt_text; + vt->width = width; + vt->priority = 0; - extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, &decor, true, false, false, false, NULL); + DecorInline decor = { .ext = true, .data.ext.vt = vt, .data.ext.sh_idx = DECOR_ID_INVALID }; + + extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, decor, 0, true, + false, false, false, NULL); return src_id; } diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 31fc01403a..8a2cde8372 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -103,15 +103,6 @@ bool ns_initialized(uint32_t ns) return ns < (uint32_t)next_namespace_id; } -static 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); - } -} - Array virt_text_to_array(VirtText vt, bool hl_name) { Array chunks = ARRAY_DICT_INIT; @@ -176,85 +167,9 @@ static Array extmark_to_array(MTPair extmark, bool id, bool add_dict, bool hl_na PUT(dict, "invalid", BOOLEAN_OBJ(true)); } - // pretend this is a pointer for a short while, Decoration will be factored away very soon - const Decoration decor[1] = { get_decor(start) }; - if (decor->hl_id) { - PUT(dict, "hl_group", hl_group_name(decor->hl_id, hl_name)); - PUT(dict, "hl_eol", BOOLEAN_OBJ(decor->hl_eol)); - } - if (decor->hl_mode) { - PUT(dict, "hl_mode", CSTR_TO_OBJ(hl_mode_str[decor->hl_mode])); - } - - if (kv_size(decor->virt_text)) { - Array chunks = virt_text_to_array(decor->virt_text, hl_name); - PUT(dict, "virt_text", ARRAY_OBJ(chunks)); - PUT(dict, "virt_text_hide", BOOLEAN_OBJ(decor->virt_text_hide)); - if (decor->virt_text_pos == kVTWinCol) { - PUT(dict, "virt_text_win_col", INTEGER_OBJ(decor->col)); - } - PUT(dict, "virt_text_pos", - CSTR_TO_OBJ(virt_text_pos_str[decor->virt_text_pos])); - } - - if (decor->ui_watched) { - PUT(dict, "ui_watched", BOOLEAN_OBJ(true)); - } - - if (kv_size(decor->virt_lines)) { - Array all_chunks = ARRAY_DICT_INIT; - bool virt_lines_leftcol = false; - for (size_t i = 0; i < kv_size(decor->virt_lines); i++) { - virt_lines_leftcol = kv_A(decor->virt_lines, i).left_col; - Array chunks = virt_text_to_array(kv_A(decor->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(decor->virt_lines_above)); - PUT(dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol)); - } - - if (decor->sign_text) { - PUT(dict, "sign_text", CSTR_TO_OBJ(decor->sign_text)); - } - - // uncrustify:off - - struct { char *name; const int val; } hls[] = { - { "sign_hl_group" , decor->sign_hl_id }, - { "number_hl_group" , decor->number_hl_id }, - { "line_hl_group" , decor->line_hl_id }, - { "cursorline_hl_group", decor->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)); - } - } - - if (decor->sign_text - || decor->hl_id - || kv_size(decor->virt_text) - || decor->ui_watched) { - PUT(dict, "priority", INTEGER_OBJ(decor->priority)); - } - - if (decor->conceal) { - String name = cstr_to_string((char *)&decor->conceal_char); - PUT(dict, "conceal", STRING_OBJ(name)); - } - - if (decor->spell != kNone) { - PUT(dict, "spell", BOOLEAN_OBJ(decor->spell == kTrue)); - } + decor_to_dict_legacy(&dict, mt_decor(start), hl_name); - if (dict.size) { - ADD(rv, DICTIONARY_OBJ(dict)); - } + ADD(rv, DICTIONARY_OBJ(dict)); } return rv; @@ -581,8 +496,14 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer Dict(set_extmark) *opts, Error *err) FUNC_API_SINCE(7) { - Decoration decor = DECORATION_INIT; - bool has_decor = false; + DecorHighlightInline hl = DECOR_HIGHLIGHT_INLINE_INIT; + // TODO(bfredl): in principle signs with max one (1) hl group and max 4 bytes of text. + // should be a candidate for inlining as well. + DecorSignHighlight sign = DECOR_SIGN_HIGHLIGHT_INIT; + DecorVirtText virt_text = DECOR_VIRT_TEXT_INIT; + DecorVirtText virt_lines = DECOR_VIRT_LINES_INIT; + bool has_hl = false; + String conceal_char_large = STRING_INIT; buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { @@ -643,11 +564,11 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer Object *opt; int *dest; } hls[] = { - { "hl_group" , &opts->hl_group , &decor.hl_id }, - { "sign_hl_group" , &opts->sign_hl_group , &decor.sign_hl_id }, - { "number_hl_group" , &opts->number_hl_group , &decor.number_hl_id }, - { "line_hl_group" , &opts->line_hl_group , &decor.line_hl_id }, - { "cursorline_hl_group", &opts->cursorline_hl_group, &decor.cursorline_hl_id }, + { "hl_group" , &opts->hl_group , &hl.hl_id }, + { "sign_hl_group" , &opts->sign_hl_group , &sign.hl_id }, + { "number_hl_group" , &opts->number_hl_group , &sign.number_hl_id }, + { "line_hl_group" , &opts->line_hl_group , &sign.line_hl_id }, + { "cursorline_hl_group", &opts->cursorline_hl_group, &sign.cursorline_hl_id }, { NULL, NULL, NULL }, }; @@ -655,26 +576,33 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer for (int j = 0; hls[j].name && hls[j].dest; j++) { if (hls[j].opt->type != kObjectTypeNil) { + if (j > 0) { + sign.flags |= kSHIsSign; + } else { + has_hl = true; + } *hls[j].dest = object_to_hl_id(*hls[j].opt, hls[j].name, err); if (ERROR_SET(err)) { goto error; } - has_decor = true; } } if (HAS_KEY(opts, set_extmark, conceal)) { + hl.flags |= kSHConceal; + has_hl = true; String c = opts->conceal; - decor.conceal = true; - if (c.size) { - decor.conceal_char = utf_ptr2char(c.data); + if (c.size > 0) { + if (c.size <= 4) { + memcpy(hl.conceal_char, c.data, c.size + (c.size < 4 ? 1 : 0)); + } else { + conceal_char_large = c; + } } - has_decor = true; } if (HAS_KEY(opts, set_extmark, virt_text)) { - decor.virt_text = parse_virt_text(opts->virt_text, err, &decor.virt_text_width); - has_decor = true; + virt_text.data.virt_text = parse_virt_text(opts->virt_text, err, &virt_text.width); if (ERROR_SET(err)) { goto error; } @@ -683,13 +611,13 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer if (HAS_KEY(opts, set_extmark, virt_text_pos)) { String str = opts->virt_text_pos; if (strequal("eol", str.data)) { - decor.virt_text_pos = kVTEndOfLine; + virt_text.pos = kVPosEndOfLine; } else if (strequal("overlay", str.data)) { - decor.virt_text_pos = kVTOverlay; + virt_text.pos = kVPosOverlay; } else if (strequal("right_align", str.data)) { - decor.virt_text_pos = kVTRightAlign; + virt_text.pos = kVPosRightAlign; } else if (strequal("inline", str.data)) { - decor.virt_text_pos = kVTInline; + virt_text.pos = kVPosInline; } else { VALIDATE_S(false, "virt_text_pos", str.data, { goto error; @@ -698,26 +626,26 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer } if (HAS_KEY(opts, set_extmark, virt_text_win_col)) { - decor.col = (int)opts->virt_text_win_col; - decor.virt_text_pos = kVTWinCol; + virt_text.col = (int)opts->virt_text_win_col; + virt_text.pos = kVPosWinCol; } - decor.hl_eol = opts->hl_eol; - decor.virt_text_hide = opts->virt_text_hide; + hl.flags |= opts->hl_eol ? kSHHlEol : 0; + virt_text.flags |= opts->virt_text_hide ? kVTHide : 0; if (HAS_KEY(opts, set_extmark, hl_mode)) { String str = opts->hl_mode; if (strequal("replace", str.data)) { - decor.hl_mode = kHlModeReplace; + virt_text.hl_mode = kHlModeReplace; } else if (strequal("combine", str.data)) { - decor.hl_mode = kHlModeCombine; + virt_text.hl_mode = kHlModeCombine; } else if (strequal("blend", str.data)) { - if (decor.virt_text_pos == kVTInline) { + if (virt_text.pos == kVPosInline) { VALIDATE(false, "%s", "cannot use 'blend' hl_mode with inline virtual text", { goto error; }); } - decor.hl_mode = kHlModeBlend; + virt_text.hl_mode = kHlModeBlend; } else { VALIDATE_S(false, "hl_mode", str.data, { goto error; @@ -735,29 +663,32 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer }); int dummig; VirtText jtem = parse_virt_text(a.items[j].data.array, err, &dummig); - kv_push(decor.virt_lines, ((struct virt_line){ jtem, virt_lines_leftcol })); + kv_push(virt_lines.data.virt_lines, ((struct virt_line){ jtem, virt_lines_leftcol })); if (ERROR_SET(err)) { goto error; } - has_decor = true; } } - decor.virt_lines_above = opts->virt_lines_above; + virt_lines.flags |= opts->virt_lines_above ? kVTLinesAbove : 0; if (HAS_KEY(opts, set_extmark, priority)) { VALIDATE_RANGE((opts->priority >= 0 && opts->priority <= UINT16_MAX), "priority", { goto error; }); - decor.priority = (DecorPriority)opts->priority; + hl.priority = (DecorPriority)opts->priority; + sign.priority = (DecorPriority)opts->priority; + virt_text.priority = (DecorPriority)opts->priority; + virt_lines.priority = (DecorPriority)opts->priority; } if (HAS_KEY(opts, set_extmark, sign_text)) { - VALIDATE_S(init_sign_text(NULL, &decor.sign_text, opts->sign_text.data), + sign.text.ptr = NULL; + VALIDATE_S(init_sign_text(NULL, &sign.text.ptr, opts->sign_text.data), "sign_text", "", { goto error; }); - has_decor = true; + sign.flags |= kSHIsSign; } bool right_gravity = GET_BOOL_OR_TRUE(opts, set_extmark, right_gravity); @@ -771,16 +702,18 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer size_t len = 0; - if (!HAS_KEY(opts, set_extmark, spell)) { - decor.spell = kNone; - } else { - decor.spell = opts->spell ? kTrue : kFalse; - has_decor = true; + if (HAS_KEY(opts, set_extmark, spell)) { + hl.flags |= (opts->spell) ? kSHSpellOn : kSHSpellOff; + has_hl = true; } - decor.ui_watched = opts->ui_watched; - if (decor.ui_watched) { - has_decor = true; + if (opts->ui_watched) { + hl.flags |= kSHUIWatched; + if (virt_text.pos == kVPosOverlay) { + // TODO(bfredl): in a revised interface this should be the default. + hl.flags |= kSHUIWatchedOverlay; + } + has_hl = true; } VALIDATE_RANGE((line >= 0), "line", { @@ -829,28 +762,90 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer col2 = 0; } - // TODO(bfredl): synergize these two branches even more if (opts->ephemeral && decor_state.win && decor_state.win->w_buffer == buf) { - decor_push_ephemeral((int)line, (int)col, line2, col2, &decor, (uint64_t)ns_id, id); + int r = (int)line; + int c = (int)col; + if (line2 == -1) { + line2 = r; + col2 = c; + } + + if (kv_size(virt_text.data.virt_text)) { + decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_text, NULL), true); + } + if (kv_size(virt_lines.data.virt_lines)) { + decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_lines, NULL), true); + } + if (has_hl) { + DecorSignHighlight sh = decor_sh_from_inline(hl, conceal_char_large); + decor_range_add_sh(&decor_state, r, c, line2, col2, &sh, true, (uint32_t)ns_id, id); + } + if (sign.flags & kSHIsSign) { + decor_range_add_sh(&decor_state, r, c, line2, col2, &sign, true, (uint32_t)ns_id, id); + } } else { if (opts->ephemeral) { api_set_error(err, kErrorTypeException, "not yet implemented"); goto error; } + uint16_t decor_flags = 0; + + DecorVirtText *decor_alloc = NULL; + if (kv_size(virt_text.data.virt_text)) { + decor_alloc = decor_put_vt(virt_text, decor_alloc); + if (virt_text.pos == kVPosInline) { + decor_flags |= MT_FLAG_DECOR_VIRT_TEXT_INLINE; + } + } + if (kv_size(virt_lines.data.virt_lines)) { + decor_alloc = decor_put_vt(virt_lines, decor_alloc); + decor_flags |= MT_FLAG_DECOR_VIRT_LINES; + } + + uint32_t decor_indexed = DECOR_ID_INVALID; + if (sign.flags & kSHIsSign) { + decor_indexed = decor_put_sh(sign); + if (sign.text.ptr != NULL) { + decor_flags |= MT_FLAG_DECOR_SIGNTEXT; + } + if (sign.number_hl_id || sign.line_hl_id || sign.cursorline_hl_id) { + decor_flags |= MT_FLAG_DECOR_SIGNHL; + } + } + + DecorInline decor = DECOR_INLINE_INIT; + if (decor_alloc || decor_indexed != DECOR_ID_INVALID || conceal_char_large.size) { + if (has_hl) { + DecorSignHighlight sh = decor_sh_from_inline(hl, conceal_char_large); + sh.next = decor_indexed; + decor_indexed = decor_put_sh(sh); + } + decor.ext = true; + decor.data.ext = (DecorExt){ .sh_idx = decor_indexed, .vt = decor_alloc }; + } else { + decor.data.hl = hl; + } + + if (has_hl) { + decor_flags |= MT_FLAG_DECOR_HL; + } + extmark_set(buf, (uint32_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2, - has_decor ? &decor : NULL, right_gravity, opts->end_right_gravity, + decor, decor_flags, right_gravity, opts->end_right_gravity, !GET_BOOL_OR_TRUE(opts, set_extmark, undo_restore), opts->invalidate, err); if (ERROR_SET(err)) { - goto error; + decor_free(decor); + return 0; } } return (Integer)id; error: - decor_clear(&decor); + clear_virttext(&virt_text.data.virt_text); + clear_virtlines(&virt_lines.data.virt_lines); return 0; } @@ -873,11 +868,6 @@ Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, Error *er return false; }); - if (decor_state.running_on_lines) { - api_set_error(err, kErrorTypeValidation, "Cannot remove extmarks during on_line callbacks"); - return false; - } - return extmark_del_id(buf, (uint32_t)ns_id, (uint32_t)id); } @@ -962,13 +952,11 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In end_line++; } - Decoration decor = DECORATION_INIT; - decor.hl_id = hl_id; + DecorInline decor = DECOR_INLINE_INIT; + decor.data.hl.hl_id = hl_id; - extmark_set(buf, ns, NULL, - (int)line, (colnr_T)col_start, - end_line, (colnr_T)col_end, - &decor, true, false, false, false, NULL); + extmark_set(buf, ns, NULL, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end, + decor, MT_FLAG_DECOR_HL, true, false, false, false, NULL); return ns_id; } @@ -997,11 +985,6 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, return; }); - if (decor_state.running_on_lines) { - api_set_error(err, kErrorTypeValidation, "Cannot remove extmarks during on_line callbacks"); - return; - } - if (line_end < 0 || line_end > MAXLNUM) { line_end = MAXLNUM; } diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index b784d8bea3..91d5bfcc54 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -1,6 +1,7 @@ #include <assert.h> #include <limits.h> +#include "nvim/api/extmark.h" #include "nvim/buffer.h" #include "nvim/decoration.h" #include "nvim/drawscreen.h" @@ -17,6 +18,17 @@ # include "decoration.c.generated.h" #endif +// TODO(bfredl): These should maybe be per-buffer, so that all resources +// asssociated 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. /// @@ -35,8 +47,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++) { @@ -61,113 +73,271 @@ 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, true, false, NULL); + 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, (String)STRING_INIT)); } +} - if (decor && decor_virt_pos(decor)) { - redraw_buf_line_later(buf, row1 + 1, false); - if (decor->virt_text_pos == kVTInline) { - changed_line_display_buf(buf); +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); - changed_line_display_buf(buf); +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; } } -static int sign_add_id = 0; +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; +} -void decor_add(buf_T *buf, int row, int row2, Decoration *decor, bool hl_id) +DecorSignHighlight decor_sh_from_inline(DecorHighlightInline item, String conceal_large) { - if (decor) { - if (kv_size(decor->virt_text) && decor->virt_text_pos == kVTInline) { - buf->b_virt_text_inline++; + // TODO(bfredl): Eventually simple signs will be inlinable as well + assert(!(item.flags & kSHIsSign)); + DecorSignHighlight conv = { + .flags = item.flags, + .priority = item.priority, + .text.data = { 0 }, + .hl_id = item.hl_id, + .number_hl_id = 0, + .line_hl_id = 0, + .cursorline_hl_id = 0, + .next = DECOR_ID_INVALID, + }; + + // TODO(bfredl): 'tis a little bullshit. Won't need it once conceals and signs to use schar_T + if (conceal_large.size) { + String c = conceal_large; + if (c.size <= 8) { + memcpy(conv.text.data, c.data, c.size + (c.size < 8 ? 1 : 0)); + } else { + conv.flags |= kSHConcealAlloc; + conv.text.ptr = xstrdup(conceal_large.data); + } + } else { + memcpy(conv.text.data, item.conceal_char, 4); + conv.text.data[4] = NUL; + } + return conv; +} + +void buf_put_decor(buf_T *buf, DecorInline decor, int row) +{ + if (decor.ext) { + if (decor.data.ext.vt) { + buf_put_decor_virt(buf, decor.data.ext.vt); } - if (kv_size(decor->virt_lines)) { - buf->b_virt_line_blocks++; + if (decor.data.ext.sh_idx != DECOR_ID_INVALID) { + buf_put_decor_sh(buf, &kv_A(decor_items, decor.data.ext.sh_idx), row); } - if (decor_has_sign(decor)) { - decor->sign_add_id = sign_add_id++; - buf->b_signs++; + } +} + +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 (decor->sign_text) { + } + 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) +{ + 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); } } - if (decor || hl_id) { - decor_redraw(buf, row, row2 > -1 ? row2 : row, decor); - } } -void decor_remove(buf_T *buf, int row, int row2, Decoration *decor, bool invalidate) +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_text) && decor->virt_text_pos == kVTInline) { + if (decor.ext) { + DecorVirtText *vt = decor.data.ext.vt; + while (vt) { + buf_remove_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_remove_decor_sh(buf, row, row2, sh); + idx = sh->next; + } + if (free) { + decor_free(decor); + } + } +} + +void buf_remove_decor_virt(buf_T *buf, DecorVirtText *vt) +{ + 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--; } - if (kv_size(decor->virt_lines)) { - assert(buf->b_virt_line_blocks > 0); - buf->b_virt_line_blocks--; - } - if (decor_has_sign(decor)) { - assert(buf->b_signs > 0); - buf->b_signs--; - if (decor->sign_text) { - 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 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); } } } - if (!invalidate) { - decor_free(decor); - } } -void decor_clear(Decoration *decor) +void decor_free(DecorInline 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 (!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); } - kv_destroy(decor->virt_lines); - xfree(decor->sign_text); - xfree(decor->sign_name); } -void decor_free(Decoration *decor) +void decor_free_inner(DecorVirtText *vt, uint32_t first_idx) { - if (decor) { - decor_clear(decor); - xfree(decor); + 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 | kSHConcealAlloc)) { + xfree(sh->text.ptr); + } + if (sh->flags & kSHIsSign) { + xfree(sh->sign_name); + } + 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) @@ -179,6 +349,15 @@ void clear_virttext(VirtText *text) *text = (VirtText)KV_INITIAL_VALUE; } +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; +} + /// Get the next chunk of a virtual text item. /// /// @param[in] vt The virtual text item @@ -197,7 +376,7 @@ char *next_virt_text_chunk(VirtText vt, size_t *pos, int *attr) return text; } -Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) +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); @@ -205,12 +384,14 @@ Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) MTKey mark = marktree_itr_current(itr); if (mark.pos.row < 0 || mark.pos.row > row) { break; - } else if (mt_invalid(mark) || 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: @@ -225,30 +406,30 @@ bool decor_redraw_reset(win_T *wp, DecorState *state) 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 wp->w_buffer->b_marktree->n_keys; } -Decoration get_decor(MTKey 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) -bool decor_virt_pos(const Decoration *const 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(win_T *wp, int top_row, DecorState *state) @@ -261,14 +442,14 @@ bool decor_redraw_start(win_T *wp, int top_row, DecorState *state) MTPair pair; while (marktree_itr_step_overlap(buf->b_marktree, state->itr, &pair)) { - if (mt_invalid(pair.start) || marktree_decor_level(pair.start) < kDecorLevelVisible) { + MTKey m = pair.start; + if (mt_invalid(m) || !mt_decor_any(m)) { continue; } - Decoration decor = get_decor(pair.start); - - decor_push(state, pair.start.pos.row, pair.start.pos.col, pair.end_pos.row, pair.end_pos.col, - &decor, false, pair.start.ns, pair.start.id); + 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 @@ -291,20 +472,35 @@ bool decor_redraw_line(win_T *wp, int row, DecorState *state) return (k.pos.row >= 0 && k.pos.row <= row); } -static void decor_push(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, -10, 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, (String)STRING_INIT); + 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); @@ -312,13 +508,60 @@ static void decor_push(DecorState *state, int start_row, int start_col, int end_ kv_A(state->active, index) = range; } +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) +{ + 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 & (kSHIsSign | 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) { - if (win_col < 0 && item->decor.virt_text_pos != kVTInline) { + 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 (item->decor.virt_text_pos == kVTOverlay) { - item->draw_col = (item->decor.virt_text_hide && hidden) ? INT_MIN : win_col; + } else if (pos == kVPosOverlay) { + item->draw_col = (vt && (vt->flags & kVTHide) && hidden) ? INT_MIN : win_col; } else { item->draw_col = -1; } @@ -352,19 +595,17 @@ int decor_redraw_col(win_T *wp, int col, int win_col, bool hidden, DecorState *s break; } - if (mt_invalid(mark) || 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 endpos = marktree_get_altpos(buf->b_marktree, mark, NULL); if (endpos.row == -1) { endpos = mark.pos; } - decor_push(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); @@ -382,7 +623,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 { @@ -401,26 +642,35 @@ next_mark: if (active && item.attr_id > 0) { attr = hl_combine_attr(attr, item.attr_id); } - if (active && item.decor.conceal) { + 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 = item.decor.conceal_char; + char *text = (sh->flags & kSHConcealAlloc) ? sh->text.ptr : sh->text.data; + // TODO(bfredl): kSHConcealAlloc is obviously a waste unless we change + // `conceal_char` to schar_T + conceal_char = utf_ptr2char(text); 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.draw_col == -10) { + && 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; @@ -432,6 +682,21 @@ next_mark: return attr; } +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 @@ -450,13 +715,15 @@ void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[], MTPair pair; int num_text = 0; - kvec_t(MTKey) signs = KV_INITIAL_VALUE; + 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) && pair.start.decor_full && decor_has_sign(pair.start.decor_full)) { - pair.start.pos.row = row; - num_text += (pair.start.decor_full->sign_text != NULL); - kv_push(signs, pair.start); + if (!mt_invalid(pair.start) && mt_decor_sign(pair.start)) { + DecorSignHighlight *sh = decor_find_sign(mt_decor(pair.start)); + if (sh) { + num_text += (sh->text.ptr != NULL); + kv_push(signs, ((SignItem){ sh, pair.start.id })); + } } } @@ -465,38 +732,60 @@ void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[], if (mark.pos.row != row) { break; } - if (!mt_end(mark) && !mt_invalid(mark) && mark.decor_full && decor_has_sign(mark.decor_full)) { - num_text += (mark.decor_full->sign_text != NULL); - kv_push(signs, mark); + if (!mt_end(mark) && !mt_invalid(mark) && mt_decor_sign(mark)) { + DecorSignHighlight *sh = decor_find_sign(mt_decor(mark)); + if (sh) { + num_text += (sh->text.ptr != NULL); + kv_push(signs, ((SignItem){ sh, mark.id })); + } } + marktree_itr_next(buf->b_marktree, itr); } 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(MTKey), sign_cmp); + qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(kv_A(signs, 0)), sign_item_cmp); for (size_t i = 0; i < kv_size(signs); i++) { - Decoration *decor = kv_A(signs, i).decor_full; - if (idx >= 0 && decor->sign_text) { - sattrs[idx].text = decor->sign_text; - sattrs[idx--].hl_id = decor->sign_hl_id; + 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 (*num_id == 0) { - *num_id = decor->number_hl_id; + *num_id = sh->number_hl_id; } if (*line_id == 0) { - *line_id = decor->line_hl_id; + *line_id = sh->line_hl_id; } if (*cul_id == 0) { - *cul_id = decor->cursorline_hl_id; + *cul_id = sh->cursorline_hl_id; } } kv_destroy(signs); } } +DecorSignHighlight *decor_find_sign(DecorInline decor) +{ + if (!decor.ext) { + return NULL; + } + uint32_t decor_id = decor.data.ext.sh_idx; + while (true) { + if (decor_id == DECOR_ID_INVALID) { + return NULL; + } + DecorSignHighlight *sh = &kv_A(decor_items, decor_id); + if (sh->flags & kSHIsSign) { + return sh; + } + decor_id = sh->next; + } +} + // Get the maximum required amount of sign columns needed between row and // end_row. int decor_signcols(buf_T *buf, int row, int end_row, int max) @@ -510,6 +799,8 @@ int decor_signcols(buf_T *buf, int row, int end_row, int max) } int signcols = 0; // highest value of count + // 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 (int currow = row; currow <= end_row; currow++) { MarkTreeIter itr[1]; if (!marktree_itr_get_overlap(buf->b_marktree, currow, 0, itr)) { @@ -519,7 +810,7 @@ int decor_signcols(buf_T *buf, int row, int end_row, int max) int count = 0; MTPair pair; while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { - if (!mt_invalid(pair.start) && pair.start.decor_full && pair.start.decor_full->sign_text) { + if (!mt_invalid(pair.start) && (pair.start.flags & MT_FLAG_DECOR_SIGNTEXT)) { count++; } } @@ -529,8 +820,11 @@ int decor_signcols(buf_T *buf, int row, int end_row, int max) if (mark.pos.row != currow) { break; } - if (!mt_invalid(mark) && !mt_end(mark) && mark.decor_full && mark.decor_full->sign_text) { - count++; + if (!mt_invalid(mark) && !mt_end(mark) && (mark.flags & MT_FLAG_DECOR_SIGNTEXT)) { + DecorSignHighlight *sh = decor_find_sign(mt_decor(mark)); + if (sh && sh->text.ptr) { + count++; + } } marktree_itr_next(buf->b_marktree, itr); } @@ -558,28 +852,19 @@ bool decor_redraw_eol(win_T *wp, DecorState *state, int *eol_attr, int eol_col) { 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_push_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_push(&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 @@ -612,18 +897,22 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fo MTKey mark = marktree_itr_current(itr); if (mark.pos.row < 0 || mark.pos.row >= end_row) { break; - } else if (mt_end(mark) - || marktree_decor_level(mark) < kDecorLevelVirtLine - || !mark.decor_full) { + } else if (mt_end(mark) || !(mark.flags & MT_FLAG_DECOR_VIRT_LINES)) { goto next_mark; } - Decoration *const decor = mark.decor_full; - const int draw_row = mark.pos.row + (decor->virt_lines_above ? 0 : 1); - if (draw_row == row) { - 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); @@ -631,3 +920,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, (String)STRING_INIT); + } + + 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) { + String name; + if (sh_hl.flags & kSHConcealAlloc) { + name = cstr_to_string(sh_hl.text.ptr); + } else { + name = cbuf_to_string(sh_hl.text.data, strnlen(sh_hl.text.data, 8)); + } + PUT(*dict, "conceal", STRING_OBJ(name)); + } + + 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)); + } + + // 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); + } +} diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index dacdc7683a..d5fd83e9d4 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -6,91 +6,54 @@ #include "klib/kvec.h" #include "nvim/buffer_defs.h" +#include "nvim/decoration_defs.h" #include "nvim/extmark_defs.h" #include "nvim/macros.h" #include "nvim/marktree.h" #include "nvim/pos.h" #include "nvim/types.h" -// actual Decoration data is in extmark_defs.h - -typedef uint16_t DecorPriority; -#define DECOR_PRIORITY_BASE 0x1000 - -typedef enum { - kVTEndOfLine, - kVTOverlay, - kVTWinCol, - kVTRightAlign, - kVTInline, -} VirtTextPos; +// actual Decor* data is in decoration_defs.h EXTERN const char *const virt_text_pos_str[] INIT( = { "eol", "overlay", "win_col", "right_align", "inline" }); -typedef enum { - kHlModeUnknown, - kHlModeReplace, - kHlModeCombine, - kHlModeBlend, -} HlMode; - EXTERN const char *const hl_mode_str[] INIT( = { "", "replace", "combine", "blend" }); -#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) - -typedef kvec_t(struct virt_line { VirtText line; bool left_col; }) VirtLines; - -struct Decoration { - VirtText virt_text; - VirtLines virt_lines; - - int hl_id; // highlight group - VirtTextPos virt_text_pos; - HlMode hl_mode; - - // TODO(bfredl): at some point turn this into FLAGS - bool virt_text_hide; - bool hl_eol; - bool virt_lines_above; - bool conceal; - TriState spell; - // TODO(bfredl): style, etc - DecorPriority priority; - int col; // fixed col value, like win_col - int virt_text_width; // width of virt_text - char *sign_text; - char *sign_name; - int sign_hl_id; - int sign_add_id; - int number_hl_id; - int line_hl_id; - int cursorline_hl_id; - // TODO(bfredl): in principle this should be a schar_T, but we - // probably want some kind of glyph cache for that.. - int conceal_char; - bool ui_watched; // watched for win_extmark -}; -#define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, \ - kHlModeUnknown, false, false, false, false, kNone, \ - DECOR_PRIORITY_BASE, 0, 0, NULL, NULL, 0, 0, 0, 0, 0, 0, false } +typedef enum { + kDecorKindHighlight, + kDecorKindSign, + kDecorKindVirtText, + kDecorKindVirtLines, + kDecorKindUIWatched, +} DecorRangeKind; typedef struct { int start_row; int start_col; int end_row; int end_col; - Decoration decor; - int attr_id; // cached lookup of decor.hl_id - bool virt_text_owned; + // next pointers MUST NOT be used, these are separate ranges + // vt->next could be pointing to freelist memory at this point + union { + DecorSignHighlight sh; + DecorVirtText *vt; + struct { + uint32_t ns_id; + uint32_t mark_id; + VirtTextPos pos; + } ui; + } data; + int attr_id; // cached lookup of inl.hl_id if it was a highlight + bool owned; // ephemeral decoration, free memory immediately + DecorPriority priority; + DecorRangeKind kind; /// Screen column to draw the virtual text. /// When -1, the virtual text may be drawn after deciding where. /// When -3, the virtual text should be drawn on the next screen line. /// When -10, the virtual text has just been added. /// When INT_MIN, the virtual text should no longer be drawn. int draw_col; - uint64_t ns_id; - uint64_t mark_id; } DecorRange; typedef struct { @@ -109,23 +72,11 @@ typedef struct { TriState spell; - // This is used to prevent removing/updating extmarks inside - // on_lines callbacks which is not allowed since it can lead to - // heap-use-after-free errors. - bool running_on_lines; + bool running_decor_provider; } DecorState; EXTERN DecorState decor_state INIT( = { 0 }); -static inline bool decor_has_sign(Decoration *decor) -{ - return decor->sign_text - || decor->sign_hl_id - || decor->number_hl_id - || decor->line_hl_id - || decor->cursorline_hl_id; -} - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "decoration.h.generated.h" #endif diff --git a/src/nvim/decoration_defs.h b/src/nvim/decoration_defs.h new file mode 100644 index 0000000000..d73fd669b7 --- /dev/null +++ b/src/nvim/decoration_defs.h @@ -0,0 +1,129 @@ +#pragma once + +#include <stdint.h> + +#include "klib/kvec.h" + +#define DECOR_ID_INVALID UINT32_MAX + +typedef struct { + char *text; + int hl_id; +} VirtTextChunk; + +typedef kvec_t(VirtTextChunk) VirtText; +#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) + +typedef enum { + kVPosEndOfLine, + kVPosOverlay, + kVPosWinCol, + kVPosRightAlign, + kVPosInline, +} VirtTextPos; + +typedef kvec_t(struct virt_line { VirtText line; bool left_col; }) VirtLines; + +typedef uint16_t DecorPriority; +#define DECOR_PRIORITY_BASE 0x1000 + +typedef enum { + kHlModeUnknown, + kHlModeReplace, + kHlModeCombine, + kHlModeBlend, +} HlMode; + +enum { + kSHIsSign = 1, + kSHHlEol = 2, + kSHUIWatched = 4, + kSHUIWatchedOverlay = 8, + kSHSpellOn = 16, + kSHSpellOff = 32, + kSHConceal = 64, + kSHConcealAlloc = 128, +}; + +typedef struct { + uint16_t flags; + DecorPriority priority; + int hl_id; + char conceal_char[4]; +} DecorHighlightInline; + +#define DECOR_HIGHLIGHT_INLINE_INIT { 0, DECOR_PRIORITY_BASE, 0, { 0 } } +typedef struct { + uint16_t flags; + DecorPriority priority; + int hl_id; // if sign: highlight of sign text + // TODO(bfredl): Later this should be schar_T[2], but then it needs to handle + // invalidations of the cache + union { + // for now: + // 1. sign is always allocated (drawline.c expects a `char *` for a sign) + // 2. conceal char is allocated if larger than 8 bytes. + char *ptr; // sign or conceal text + char data[8]; + } text; + // NOTE: if more functionality is added to a Highlight these should be overloaded + // or restructured + char *sign_name; + int sign_add_id; + int number_hl_id; + int line_hl_id; + int cursorline_hl_id; + uint32_t next; +} DecorSignHighlight; + +#define DECOR_SIGN_HIGHLIGHT_INIT { 0, DECOR_PRIORITY_BASE, 0, { .ptr = NULL }, NULL, 0, 0, 0, 0, \ + DECOR_ID_INVALID } + +enum { + kVTIsLines = 1, + kVTHide = 2, + kVTLinesAbove = 4, +}; + +typedef struct DecorVirtText DecorVirtText; +struct DecorVirtText { + uint8_t flags; + uint8_t hl_mode; + DecorPriority priority; + int width; // width of virt_text + int col; + VirtTextPos pos; + // TODO(bfredl): reduce this to one datatype, later + union { + VirtText virt_text; + VirtLines virt_lines; + } data; + DecorVirtText *next; +}; +#define DECOR_VIRT_TEXT_INIT { 0, kHlModeUnknown, DECOR_PRIORITY_BASE, 0, 0, kVPosEndOfLine, \ + { .virt_text = KV_INITIAL_VALUE }, NULL, } +#define DECOR_VIRT_LINES_INIT { kVTIsLines, kHlModeUnknown, DECOR_PRIORITY_BASE, 0, 0, \ + kVPosEndOfLine, { .virt_lines = KV_INITIAL_VALUE }, NULL, } + +typedef struct { + uint32_t sh_idx; + DecorVirtText *vt; +} DecorExt; + +// Stored inline in marktree, with MT_FLAG_DECOR_EXT in MTKey.flags +typedef union { + DecorHighlightInline hl; + DecorExt ext; +} DecorInlineData; + +// Not stored in the marktree, but used when passing around args +// +// Convention: an empty "no decoration" value should always be encoded +// with ext=false and an unset DecorHighlightInline (no flags, no hl_id) +typedef struct { + bool ext; + DecorInlineData data; +} DecorInline; + +// initializes in a valid state for the DecorHighlightInline branch +#define DECOR_INLINE_INIT { .ext = false, .data.hl = DECOR_HIGHLIGHT_INLINE_INIT } diff --git a/src/nvim/decoration_provider.c b/src/nvim/decoration_provider.c index 2a00e9f373..2c0be2fe8a 100644 --- a/src/nvim/decoration_provider.c +++ b/src/nvim/decoration_provider.c @@ -122,6 +122,9 @@ void decor_providers_invoke_win(win_T *wp, DecorProviders *providers, DecorProviders *line_providers) { kvi_init(*line_providers); + // this might change in the future + // then we would need decor_state.running_decor_provider just like "on_line" below + assert(kv_size(decor_state.active) == 0); linenr_T knownmax = MIN(wp->w_buffer->b_ml.ml_line_count, ((wp->w_valid & VALID_BOTLINE) @@ -153,7 +156,7 @@ void decor_providers_invoke_win(win_T *wp, DecorProviders *providers, /// @param[out] err Provider error void decor_providers_invoke_line(win_T *wp, DecorProviders *providers, int row, bool *has_decor) { - decor_state.running_on_lines = true; + decor_state.running_decor_provider = true; for (size_t k = 0; k < kv_size(*providers); k++) { DecorProvider *p = kv_A(*providers, k); if (p && p->redraw_line != LUA_NOREF) { @@ -171,7 +174,7 @@ void decor_providers_invoke_line(win_T *wp, DecorProviders *providers, int row, hl_check_ns(); } } - decor_state.running_on_lines = false; + decor_state.running_decor_provider = false; } /// For each provider invoke the 'buf' callback for a given buffer. @@ -207,6 +210,7 @@ void decor_providers_invoke_end(DecorProviders *providers) decor_provider_invoke(p, "end", p->redraw_end, args, true); } } + decor_check_to_be_deleted(); } /// Mark all cached state of per-namespace highlights as invalid. Revalidate diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index c1d4e55fe5..6f34767907 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -265,18 +265,25 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int bool do_eol = state->eol_col > -1; 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))) { + if (!(item->start_row == state->row && decor_virt_pos(item))) { continue; } - if (item->draw_col == -1) { + + DecorVirtText *vt = NULL; + if (item->kind == kDecorKindVirtText) { + assert(item->data.vt); + vt = item->data.vt; + } + if (decor_virt_pos(item) && item->draw_col == -1) { bool updated = true; - if (item->decor.virt_text_pos == kVTRightAlign) { - right_pos -= item->decor.virt_text_width; + VirtTextPos pos = decor_virt_pos_kind(item); + if (pos == kVPosRightAlign) { + right_pos -= vt->width; item->draw_col = right_pos; - } else if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { + } else if (pos == kVPosEndOfLine && do_eol) { item->draw_col = state->eol_col; - } else if (item->decor.virt_text_pos == kVTWinCol) { - item->draw_col = MAX(col_off + item->decor.col, 0); + } else if (pos == kVPosWinCol) { + item->draw_col = MAX(col_off + vt->col, 0); } else { updated = false; } @@ -289,19 +296,19 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int continue; } int col = 0; - if (item->decor.ui_watched) { + if (item->kind == kDecorKindUIWatched) { // send mark position to UI col = item->draw_col; - WinExtmark m = { (NS)item->ns_id, item->mark_id, win_row, col }; + WinExtmark m = { (NS)item->data.ui.ns_id, item->data.ui.mark_id, win_row, col }; kv_push(win_extmark_arr, m); } - if (kv_size(item->decor.virt_text)) { + if (vt) { int vcol = item->draw_col - col_off; - col = draw_virt_text_item(buf, item->draw_col, item->decor.virt_text, - item->decor.hl_mode, max_col, vcol); + col = draw_virt_text_item(buf, item->draw_col, vt->data.virt_text, + vt->hl_mode, max_col, vcol); } item->draw_col = INT_MIN; // deactivate - if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { + if (vt && vt->pos == kVPosEndOfLine && do_eol) { state->eol_col = col + 1; } @@ -807,9 +814,9 @@ static bool has_more_inline_virt(winlinevars_T *wlv, ptrdiff_t v) for (size_t i = 0; i < kv_size(state->active); i++) { DecorRange *item = &kv_A(state->active, i); if (item->start_row != state->row - || !kv_size(item->decor.virt_text) - || item->decor.virt_text_pos != kVTInline - || item->decor.virt_text_width == 0) { + || item->kind != kDecorKindVirtText + || item->data.vt->pos != kVPosInline + || item->data.vt->width == 0) { continue; } if (item->draw_col >= -1 && item->start_col >= v) { @@ -830,14 +837,14 @@ static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t for (size_t i = 0; i < kv_size(state->active); i++) { DecorRange *item = &kv_A(state->active, i); if (item->start_row != state->row - || !kv_size(item->decor.virt_text) - || item->decor.virt_text_pos != kVTInline - || item->decor.virt_text_width == 0) { + || item->kind != kDecorKindVirtText + || item->data.vt->pos != kVPosInline + || item->data.vt->width == 0) { continue; } if (item->draw_col >= -1 && item->start_col == v) { - wlv->virt_inline = item->decor.virt_text; - wlv->virt_inline_hl_mode = item->decor.hl_mode; + wlv->virt_inline = item->data.vt->data.virt_text; + wlv->virt_inline_hl_mode = item->data.vt->hl_mode; item->draw_col = INT_MIN; break; } diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 3481f44064..08a1539bfc 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -50,67 +50,35 @@ /// /// must not be used during iteration! void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col, int end_row, - colnr_T end_col, Decoration *decor, bool right_gravity, bool end_right_gravity, - bool no_undo, bool invalidate, Error *err) + colnr_T end_col, DecorInline decor, uint16_t decor_flags, bool right_gravity, + bool end_right_gravity, bool no_undo, bool invalidate, Error *err) { uint32_t *ns = map_put_ref(uint32_t, uint32_t)(buf->b_extmark_ns, ns_id, NULL, NULL); uint32_t id = idp ? *idp : 0; - bool decor_full = false; - bool hl_eol = false; - - uint8_t decor_level = kDecorLevelNone; // no decor - if (decor) { - if (kv_size(decor->virt_text) - || kv_size(decor->virt_lines) - || decor->conceal - || decor_has_sign(decor) - || decor->ui_watched - || decor->spell != kNone) { - decor_full = true; - decor = xmemdup(decor, sizeof *decor); - } - decor_level = kDecorLevelVisible; // decor affects redraw - hl_eol = decor->hl_eol; - if (kv_size(decor->virt_lines)) { - decor_level = kDecorLevelVirtLine; // decor affects horizontal size - } - } - uint16_t flags = mt_flags(right_gravity, hl_eol, no_undo, invalidate, decor_level); + uint16_t flags = mt_flags(right_gravity, no_undo, invalidate, decor.ext) | decor_flags; if (id == 0) { id = ++*ns; } else { MarkTreeIter itr[1] = { 0 }; MTKey old_mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr); if (old_mark.id) { - if (decor_state.running_on_lines) { - if (err) { - api_set_error(err, kErrorTypeException, - "Cannot change extmarks during on_line callbacks"); - } - goto error; - } if (mt_paired(old_mark) || end_row > -1) { extmark_del_id(buf, ns_id, id); } else { - // TODO(bfredl): we need to do more if "revising" a decoration mark. assert(marktree_itr_valid(itr)); if (old_mark.pos.row == row && old_mark.pos.col == col) { - if (marktree_decor_level(old_mark) > kDecorLevelNone) { - decor_remove(buf, row, row, old_mark.decor_full, false); - old_mark.decor_full = NULL; + if (mt_decor_any(old_mark)) { + buf_decor_remove(buf, row, row, mt_decor(old_mark), true); } - old_mark.flags = flags; - if (decor_full) { - old_mark.decor_full = decor; - } else if (decor) { - old_mark.hl_id = decor->hl_id; - old_mark.priority = decor->priority; - } - marktree_revise(buf->b_marktree, itr, decor_level, old_mark); + + // not paired: we can revise in place + mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_EXTERNAL_MASK; + mt_itr_rawkey(itr).flags |= flags; + mt_itr_rawkey(itr).decor_data = decor.data; goto revised; } - decor_remove(buf, old_mark.pos.row, old_mark.pos.row, old_mark.decor_full, false); + buf_decor_remove(buf, old_mark.pos.row, old_mark.pos.row, mt_decor(old_mark), true); marktree_del_itr(buf->b_marktree, itr, false); } } else { @@ -118,29 +86,19 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col } } - MTKey mark = { { row, col }, ns_id, id, 0, flags, 0, NULL }; - if (decor_full) { - mark.decor_full = decor; - } else if (decor) { - mark.hl_id = decor->hl_id; - mark.priority = decor->priority; - } + MTKey mark = { { row, col }, ns_id, id, flags, decor.data }; marktree_put(buf->b_marktree, mark, end_row, end_col, end_right_gravity); revised: - decor_add(buf, row, end_row, decor, decor && decor->hl_id); + if (decor_flags || decor.ext) { + buf_put_decor(buf, decor, row); + decor_redraw(buf, row, end_row > -1 ? end_row : row, decor); + } if (idp) { *idp = id; } - - return; - -error: - if (decor_full) { - decor_free(decor); - } } static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col) @@ -189,8 +147,8 @@ void extmark_del(buf_T *buf, MarkTreeIter *itr, MTKey key, bool restore) } } - if (marktree_decor_level(key) > kDecorLevelNone) { - decor_remove(buf, key.pos.row, key2.pos.row, key.decor_full, false); + if (mt_decor_any(key)) { + buf_decor_remove(buf, key.pos.row, key2.pos.row, mt_decor(key), true); } // TODO(bfredl): delete it from current undo header, opportunistically? @@ -231,7 +189,6 @@ bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_r marktree_itr_next(buf->b_marktree, itr); } } - return marks_cleared; } @@ -294,24 +251,11 @@ static void push_mark(ExtmarkInfoArray *array, uint32_t ns_id, ExtmarkType type_ if (!(ns_id == UINT32_MAX || mark.start.ns == ns_id)) { return; } - uint16_t type_flags = kExtmarkNone; if (type_filter != kExtmarkNone) { - Decoration *decor = mark.start.decor_full; - if (decor && (decor->sign_text || decor->number_hl_id)) { - type_flags |= (kExtmarkSignHL|kExtmarkSign); - } - if (decor && (decor->line_hl_id || decor->cursorline_hl_id)) { - type_flags |= (kExtmarkSignHL|kExtmarkHighlight); - } - if (decor && decor->virt_text.size) { - type_flags |= kExtmarkVirtText; - } - if (decor && decor->virt_lines.size) { - type_flags |= kExtmarkVirtLines; - } - if (mark.start.hl_id) { - type_flags |= kExtmarkHighlight; + if (!mt_decor_any(mark.start)) { + return; } + uint16_t type_flags = decor_type_flags(mt_decor(mark.start)); if (!(type_flags & type_filter)) { return; @@ -349,9 +293,9 @@ void extmark_free_all(buf_T *buf) break; } - // don't free mark.decor_full twice for a paired mark. + // don't free mark.decor twice for a paired mark. if (!(mt_paired(mark) && mt_end(mark))) { - decor_free(mark.decor_full); + decor_free(mt_decor(mark)); } marktree_itr_next(buf->b_marktree, itr); @@ -398,9 +342,8 @@ void extmark_splice_delete(buf_T *buf, int l_row, colnr_T l_col, int u_row, coln continue; } else { invalidated = true; - mark.flags |= MT_FLAG_INVALID; - marktree_revise(curbuf->b_marktree, itr, marktree_decor_level(mark), mark); - decor_remove(buf, mark.pos.row, endpos.row, mark.decor_full, true); + mt_itr_rawkey(itr).flags |= MT_FLAG_INVALID; + buf_decor_remove(buf, mark.pos.row, endpos.row, mt_decor(mark), false); } } } @@ -451,10 +394,8 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo) if (pos.invalidated) { MarkTreeIter itr[1] = { 0 }; MTKey mark = marktree_lookup(curbuf->b_marktree, pos.mark, itr); - MTKey end = marktree_get_alt(curbuf->b_marktree, mark, NULL); - mark.flags &= (uint16_t) ~MT_FLAG_INVALID; - marktree_revise(curbuf->b_marktree, itr, marktree_decor_level(mark), mark); - decor_add(curbuf, mark.pos.row, end.pos.row, mark.decor_full, mark.hl_id); + mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_INVALID; + buf_put_decor(curbuf, mt_decor(mark), mark.pos.row); } if (pos.old_row >= 0) { extmark_setraw(curbuf, pos.mark, pos.old_row, pos.old_col); diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h index 9d0795c947..1288714462 100644 --- a/src/nvim/extmark.h +++ b/src/nvim/extmark.h @@ -64,6 +64,8 @@ typedef enum { kExtmarkClear, } UndoObjectType; +// TODO(bfredl): if possible unify these with marktree flags, +// so it is possible to filter extmarks directly on top-level flags typedef enum { kExtmarkNone = 0x1, kExtmarkSign = 0x2, diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h index 6615551df4..d1bc13f9f2 100644 --- a/src/nvim/extmark_defs.h +++ b/src/nvim/extmark_defs.h @@ -3,13 +3,6 @@ #include "klib/kvec.h" #include "nvim/types.h" -typedef struct { - char *text; - int hl_id; -} VirtTextChunk; - -typedef kvec_t(VirtTextChunk) VirtText; - typedef struct undo_object ExtmarkUndoObject; typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t; @@ -21,9 +14,3 @@ typedef enum { kExtmarkNoUndo, // Operation should not be reversible kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable } ExtmarkOp; - -typedef enum { - kDecorLevelNone = 0, - kDecorLevelVisible = 1, - kDecorLevelVirtLine = 2, -} DecorLevel; diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c index f350001977..110683a35c 100644 --- a/src/nvim/marktree.c +++ b/src/nvim/marktree.c @@ -287,7 +287,7 @@ static inline void marktree_putp_aux(MarkTree *b, MTNode *x, MTKey k) void marktree_put(MarkTree *b, MTKey key, int end_row, int end_col, bool end_right) { - assert(!(key.flags & ~MT_FLAG_EXTERNAL_MASK)); + assert(!(key.flags & ~(MT_FLAG_EXTERNAL_MASK | MT_FLAG_RIGHT_GRAVITY))); if (end_row >= 0) { key.flags |= MT_FLAG_PAIRED; } @@ -1137,25 +1137,6 @@ static void marktree_free_node(MarkTree *b, MTNode *x) b->n_nodes--; } -/// NB: caller must check not pair! -void marktree_revise(MarkTree *b, MarkTreeIter *itr, uint8_t decor_level, MTKey key) -{ - // TODO(bfredl): clean up this mess and re-instantiate &= and |= forms - // once we upgrade to a non-broken version of gcc in functionaltest-lua CI - rawkey(itr).flags = (uint16_t)(rawkey(itr).flags & (uint16_t) ~MT_FLAG_DECOR_MASK); - rawkey(itr).flags = (uint16_t)(rawkey(itr).flags & (uint16_t) ~MT_FLAG_INVALID); - rawkey(itr).flags = (uint16_t)(rawkey(itr).flags - | (uint16_t)(decor_level << MT_FLAG_DECOR_OFFSET) - | (uint16_t)(key.flags & MT_FLAG_DECOR_MASK) - | (uint16_t)(key.flags & MT_FLAG_HL_EOL) - | (uint16_t)(key.flags & MT_FLAG_NO_UNDO) - | (uint16_t)(key.flags & MT_FLAG_INVALID) - | (uint16_t)(key.flags & MT_FLAG_INVALIDATE)); - rawkey(itr).decor_full = key.decor_full; - rawkey(itr).hl_id = key.hl_id; - rawkey(itr).priority = key.priority; -} - /// @param itr iterator is invalid after call void marktree_move(MarkTree *b, MarkTreeIter *itr, int row, int col) { @@ -2003,8 +1984,8 @@ static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr) void marktree_put_test(MarkTree *b, uint32_t ns, uint32_t id, int row, int col, bool right_gravity, int end_row, int end_col, bool end_right) { - uint16_t flags = mt_flags(right_gravity, false, false, false, 0); - MTKey key = { { row, col }, ns, id, 0, flags, 0, NULL }; + uint16_t flags = mt_flags(right_gravity, false, false, false); + MTKey key = { { row, col }, ns, id, flags, { .hl = DECOR_HIGHLIGHT_INLINE_INIT } }; marktree_put(b, key, end_row, end_col, end_right); } diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h index 6d014b5742..c692cd8863 100644 --- a/src/nvim/marktree.h +++ b/src/nvim/marktree.h @@ -7,6 +7,7 @@ #include "klib/kvec.h" #include "nvim/assert.h" +#include "nvim/decoration_defs.h" #include "nvim/garray.h" #include "nvim/map.h" #include "nvim/pos.h" @@ -47,6 +48,8 @@ typedef struct { } MarkTreeIter; #define marktree_itr_valid(itr) ((itr)->x != NULL) +// accces raw key: flags in MT_FLAG_EXTERNAL_MASK and decor_data are safe to modify. +#define mt_itr_rawkey(itr) ((itr)->x->key[(itr)->i]) // Internal storage // @@ -56,10 +59,8 @@ typedef struct { MTPos pos; uint32_t ns; uint32_t id; - int32_t hl_id; uint16_t flags; - uint16_t priority; - Decoration *decor_full; + DecorInlineData decor_data; // "ext" tag in flags } MTKey; typedef struct { @@ -68,28 +69,40 @@ typedef struct { bool end_right_gravity; } MTPair; -#define MT_INVALID_KEY (MTKey) { { -1, -1 }, 0, 0, 0, 0, 0, NULL } +#define MT_INVALID_KEY (MTKey) { { -1, -1 }, 0, 0, 0, { .hl = DECOR_HIGHLIGHT_INLINE_INIT } } #define MT_FLAG_REAL (((uint16_t)1) << 0) #define MT_FLAG_END (((uint16_t)1) << 1) #define MT_FLAG_PAIRED (((uint16_t)1) << 2) // orphaned: the other side of this paired mark was deleted. this mark must be deleted very soon! #define MT_FLAG_ORPHANED (((uint16_t)1) << 3) -#define MT_FLAG_HL_EOL (((uint16_t)1) << 4) -#define MT_FLAG_NO_UNDO (((uint16_t)1) << 5) -#define MT_FLAG_INVALIDATE (((uint16_t)1) << 6) -#define MT_FLAG_INVALID (((uint16_t)1) << 7) - -#define DECOR_LEVELS 4 -#define MT_FLAG_DECOR_OFFSET 8 -#define MT_FLAG_DECOR_MASK (((uint16_t)(DECOR_LEVELS - 1)) << MT_FLAG_DECOR_OFFSET) +#define MT_FLAG_NO_UNDO (((uint16_t)1) << 4) +#define MT_FLAG_INVALIDATE (((uint16_t)1) << 5) +#define MT_FLAG_INVALID (((uint16_t)1) << 6) +// discriminant for union +#define MT_FLAG_DECOR_EXT (((uint16_t)1) << 7) + +// TODO(bfredl): flags for decorations. These cover the cases where we quickly needs +// to skip over irrelevant marks internally. When we refactor this more, also make all info +// for ExtmarkType included here +#define MT_FLAG_DECOR_HL (((uint16_t)1) << 8) +#define MT_FLAG_DECOR_SIGNTEXT (((uint16_t)1) << 9) +// TODO(bfredl): for now this means specifically number_hl, line_hl, cursorline_hl +// needs to clean up the name. +#define MT_FLAG_DECOR_SIGNHL (((uint16_t)1) << 10) +#define MT_FLAG_DECOR_VIRT_LINES (((uint16_t)1) << 11) +#define MT_FLAG_DECOR_VIRT_TEXT_INLINE (((uint16_t)1) << 12) // These _must_ be last to preserve ordering of marks #define MT_FLAG_RIGHT_GRAVITY (((uint16_t)1) << 14) #define MT_FLAG_LAST (((uint16_t)1) << 15) -#define MT_FLAG_EXTERNAL_MASK (MT_FLAG_DECOR_MASK | MT_FLAG_RIGHT_GRAVITY | MT_FLAG_HL_EOL \ - | MT_FLAG_NO_UNDO | MT_FLAG_INVALIDATE | MT_FLAG_INVALID) +#define MT_FLAG_DECOR_MASK (MT_FLAG_DECOR_EXT| MT_FLAG_DECOR_HL | MT_FLAG_DECOR_SIGNTEXT \ + | MT_FLAG_DECOR_SIGNHL | MT_FLAG_DECOR_VIRT_LINES \ + | MT_FLAG_DECOR_VIRT_TEXT_INLINE) + +#define MT_FLAG_EXTERNAL_MASK (MT_FLAG_DECOR_MASK | MT_FLAG_NO_UNDO \ + | MT_FLAG_INVALIDATE | MT_FLAG_INVALID) // this is defined so that start and end of the same range have adjacent ids #define MARKTREE_END_FLAG ((uint64_t)1) @@ -143,20 +156,22 @@ static inline bool mt_invalid(MTKey key) return key.flags & MT_FLAG_INVALID; } -static inline uint8_t marktree_decor_level(MTKey key) +static inline bool mt_decor_any(MTKey key) +{ + return key.flags & MT_FLAG_DECOR_MASK; +} + +static inline bool mt_decor_sign(MTKey key) { - return (uint8_t)((key.flags&MT_FLAG_DECOR_MASK) >> MT_FLAG_DECOR_OFFSET); + return key.flags & (MT_FLAG_DECOR_SIGNTEXT | MT_FLAG_DECOR_SIGNHL); } -static inline uint16_t mt_flags(bool right_gravity, bool hl_eol, bool no_undo, bool invalidate, - uint8_t decor_level) +static inline uint16_t mt_flags(bool right_gravity, bool no_undo, bool invalidate, bool decor_ext) { - assert(decor_level < DECOR_LEVELS); return (uint16_t)((right_gravity ? MT_FLAG_RIGHT_GRAVITY : 0) - | (hl_eol ? MT_FLAG_HL_EOL : 0) | (no_undo ? MT_FLAG_NO_UNDO : 0) | (invalidate ? MT_FLAG_INVALIDATE : 0) - | (decor_level << MT_FLAG_DECOR_OFFSET)); + | (decor_ext ? MT_FLAG_DECOR_EXT : 0)); } static inline MTPair mtpair_from(MTKey start, MTKey end) @@ -164,6 +179,11 @@ static inline MTPair mtpair_from(MTKey start, MTKey end) return (MTPair){ .start = start, .end_pos = end.pos, .end_right_gravity = mt_right(end) }; } +static inline DecorInline mt_decor(MTKey key) +{ + return (DecorInline){ .ext = key.flags & MT_FLAG_DECOR_EXT, .data = key.decor_data }; +} + typedef kvec_withinit_t(uint64_t, 4) Intersection; struct mtnode_s { @@ -186,8 +206,6 @@ static inline uint64_t mt_dbg_id(uint64_t id) typedef struct { MTNode *root; size_t n_keys, n_nodes; - // TODO(bfredl): the pointer to node could be part of the larger - // Map(uint64_t, ExtmarkItem) essentially; PMap(uint64_t) id2node[1]; } MarkTree; diff --git a/src/nvim/plines.c b/src/nvim/plines.c index b51d262cd9..acbb9637a4 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -222,21 +222,25 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) if (mark.pos.row != cts->cts_row || mark.pos.col > col) { break; } else if (mark.pos.col == col) { - if (!mt_end(mark)) { - Decoration decor = get_decor(mark); - if (decor.virt_text_pos == kVTInline) { - if (mt_right(mark)) { - cts->cts_cur_text_width_right += decor.virt_text_width; - } else { - cts->cts_cur_text_width_left += decor.virt_text_width; - } - size += decor.virt_text_width; - if (*s == TAB) { - // tab size changes because of the inserted text - size -= tab_size; - tab_size = win_chartabsize(wp, s, vcol + size); - size += tab_size; + if (!mt_end(mark) && mark.flags & (MT_FLAG_DECOR_VIRT_TEXT_INLINE)) { + DecorInline decor = mt_decor(mark); + DecorVirtText *vt = decor.ext ? decor.data.ext.vt : NULL; + while (vt) { + if (!(vt->flags & kVTIsLines) && vt->pos == kVPosInline) { + if (mt_right(mark)) { + cts->cts_cur_text_width_right += vt->width; + } else { + cts->cts_cur_text_width_left += vt->width; + } + size += vt->width; + if (*s == TAB) { + // tab size changes because of the inserted text + size -= tab_size; + tab_size = win_chartabsize(wp, s, vcol + size); + size += tab_size; + } } + vt = vt->next; } } } diff --git a/src/nvim/sign.c b/src/nvim/sign.c index 0136937ad2..9a48b1b1a5 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -71,9 +71,9 @@ static int64_t group_get_ns(const char *group) return ns ? ns : -1; } -static const char *sign_get_name(MTKey mark) +static const char *sign_get_name(DecorSignHighlight *sh) { - char *name = mark.decor_full->sign_name; + char *name = sh->sign_name; return !name ? "" : map_has(cstr_t, &sign_map, name) ? name : "[Deleted]"; } @@ -92,15 +92,24 @@ static void buf_set_sign(buf_T *buf, uint32_t *id, char *group, int prio, linenr } uint32_t ns = group ? (uint32_t)nvim_create_namespace(cstr_as_string(group)) : 0; - Decoration decor = DECORATION_INIT; - decor.sign_text = sp->sn_text ? xstrdup(sp->sn_text) : NULL; - decor.sign_name = xstrdup(sp->sn_name); - decor.sign_hl_id = sp->sn_text_hl; - decor.line_hl_id = sp->sn_line_hl; - decor.number_hl_id = sp->sn_num_hl; - decor.cursorline_hl_id = sp->sn_cul_hl; - decor.priority = (DecorPriority)prio; - extmark_set(buf, ns, id, lnum - 1, 0, -1, -1, &decor, true, false, true, true, NULL); + DecorSignHighlight sign = DECOR_SIGN_HIGHLIGHT_INIT; + + sign.flags |= kSHIsSign; + sign.text.ptr = sp->sn_text ? xstrdup(sp->sn_text) : NULL; + sign.sign_name = xstrdup(sp->sn_name); + sign.hl_id = sp->sn_text_hl; + sign.line_hl_id = sp->sn_line_hl; + sign.number_hl_id = sp->sn_num_hl; + sign.cursorline_hl_id = sp->sn_cul_hl; + sign.priority = (DecorPriority)prio; + + bool has_hl = (sp->sn_line_hl || sp->sn_num_hl || sp->sn_cul_hl); + uint16_t decor_flags = (sp->sn_text ? MT_FLAG_DECOR_SIGNTEXT : 0) + | (has_hl ? MT_FLAG_DECOR_SIGNHL : 0); + + DecorInline decor = { .ext = true, .data.ext = { .vt = NULL, .sh_idx = decor_put_sh(sign) } }; + extmark_set(buf, ns, id, lnum - 1, 0, -1, -1, decor, decor_flags, true, + false, true, true, NULL); } /// For an existing, placed sign with "id", modify the sign, group or priority. @@ -148,9 +157,18 @@ int sign_cmp(const void *p1, const void *p2) const MTKey *s2 = (MTKey *)p2; int n = s1->pos.row - s2->pos.row; - return n ? n : (n = s2->decor_full->priority - s1->decor_full->priority) - ? n : (n = (int)(s2->id - s1->id)) - ? n : (s2->decor_full->sign_add_id - s1->decor_full->sign_add_id); + if (n) { + return n; + } + + DecorSignHighlight *sh1 = decor_find_sign(mt_decor(*s1)); + DecorSignHighlight *sh2 = decor_find_sign(mt_decor(*s2)); + assert(sh1 && sh2); + + n = sh2->priority - sh1->priority; + + return n ? n : (n = (int)(s2->id - s1->id)) + ? n : (sh2->sign_add_id - sh1->sign_add_id); } /// Delete the specified signs @@ -177,8 +195,7 @@ static int buf_delete_signs(buf_T *buf, char *group, int id, linenr_T atlnum) MTPair pair; while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { - if ((ns == UINT32_MAX || ns == pair.start.ns) - && pair.start.decor_full && decor_has_sign(pair.start.decor_full)) { + if ((ns == UINT32_MAX || ns == pair.start.ns) && mt_decor_sign(pair.start)) { kv_push(signs, pair.start); } } @@ -191,7 +208,7 @@ static int buf_delete_signs(buf_T *buf, char *group, int id, linenr_T atlnum) if (row && mark.pos.row > row) { break; } - if (!mt_end(mark) && mark.decor_full && decor_has_sign(mark.decor_full) + if (!mt_end(mark) && mt_decor_sign(mark) && (id == 0 || (int)mark.id == id) && (ns == UINT32_MAX || ns == mark.ns)) { if (atlnum > 0) { @@ -248,7 +265,7 @@ static void sign_list_placed(buf_T *rbuf, char *group) while (itr->x) { MTKey mark = marktree_itr_current(itr); - if (!mt_end(mark) && mark.decor_full && decor_has_sign(mark.decor_full) + if (!mt_end(mark) && mt_decor_sign(mark) && (ns == UINT32_MAX || ns == mark.ns)) { kv_push(signs, mark); } @@ -262,14 +279,16 @@ static void sign_list_placed(buf_T *rbuf, char *group) namebuf[0] = '\0'; groupbuf[0] = '\0'; MTKey mark = kv_A(signs, i); - if (mark.decor_full->sign_name != NULL) { - vim_snprintf(namebuf, MSG_BUF_LEN, _(" name=%s"), sign_get_name(mark)); + + DecorSignHighlight *sh = decor_find_sign(mt_decor(mark)); + if (sh->sign_name != NULL) { + vim_snprintf(namebuf, MSG_BUF_LEN, _(" name=%s"), sign_get_name(sh)); } if (mark.ns != 0) { vim_snprintf(groupbuf, MSG_BUF_LEN, _(" group=%s"), describe_ns((int)mark.ns, "")); } vim_snprintf(lbuf, MSG_BUF_LEN, _(" line=%" PRIdLINENR " id=%u%s%s priority=%d"), - mark.pos.row + 1, mark.id, groupbuf, namebuf, mark.decor_full->priority); + mark.pos.row + 1, mark.id, groupbuf, namebuf, sh->priority); msg_puts(lbuf); msg_putchar('\n'); } @@ -841,21 +860,12 @@ void ex_sign(exarg_T *eap) } } -/// Append dictionary of information for a defined sign "sp", or placed -/// sign "mark" to "retlist". Either "sp", or "mark" is NULL. -static void sign_list_append_info(sign_T *sp, MTKey *mark, list_T *retlist) +/// Get dictionary of information for a defined sign "sp" +static dict_T *sign_get_info_dict(sign_T *sp) { dict_T *d = tv_dict_alloc(); - tv_list_append_dict(retlist, d); - tv_dict_add_str(d, S_LEN("name"), sp ? sp->sn_name : sign_get_name(*mark)); - if (mark != NULL) { - tv_dict_add_nr(d, S_LEN("id"), (int)mark->id); - tv_dict_add_str(d, S_LEN("group"), describe_ns((int)mark->ns, "")); - tv_dict_add_nr(d, S_LEN("lnum"), mark->pos.row + 1); - tv_dict_add_nr(d, S_LEN("priority"), mark->decor_full->priority); - return; - } + tv_dict_add_str(d, S_LEN("name"), sp->sn_name); if (sp->sn_icon != NULL) { tv_dict_add_str(d, S_LEN("icon"), sp->sn_icon); @@ -871,6 +881,22 @@ static void sign_list_append_info(sign_T *sp, MTKey *mark, list_T *retlist) tv_dict_add_str(d, arg[i], strlen(arg[i]), p ? p : "NONE"); } } + return d; +} + +/// Get dictionary of information for placed sign "mark" +static dict_T *sign_get_placed_info_dict(MTKey mark) +{ + dict_T *d = tv_dict_alloc(); + + DecorSignHighlight *sh = decor_find_sign(mt_decor(mark)); + + tv_dict_add_str(d, S_LEN("name"), sign_get_name(sh)); + tv_dict_add_nr(d, S_LEN("id"), (int)mark.id); + tv_dict_add_str(d, S_LEN("group"), describe_ns((int)mark.ns, "")); + tv_dict_add_nr(d, S_LEN("lnum"), mark.pos.row + 1); + tv_dict_add_nr(d, S_LEN("priority"), sh->priority); + return d; } /// Returns information about signs placed in a buffer as list of dicts. @@ -883,8 +909,8 @@ list_T *get_buffer_signs(buf_T *buf) while (itr->x) { MTKey mark = marktree_itr_current(itr); - if (!mt_end(mark) && mark.decor_full && decor_has_sign(mark.decor_full)) { - sign_list_append_info(NULL, &mark, l); + if (!mt_end(mark) && mt_decor_sign(mark)) { + tv_list_append_dict(l, sign_get_placed_info_dict(mark)); } marktree_itr_next(buf->b_marktree, itr); } @@ -918,13 +944,15 @@ static void sign_get_placed_in_buf(buf_T *buf, linenr_T lnum, int sign_id, const if (lnum && mark.pos.row >= lnum) { break; } - if (!mt_end(mark) && mark.decor_full && decor_has_sign(mark.decor_full) + if (!mt_end(mark) && (ns == UINT32_MAX || ns == mark.ns) && ((lnum == 0 && sign_id == 0) || (sign_id == 0 && lnum == mark.pos.row + 1) || (lnum == 0 && sign_id == (int)mark.id) || (lnum == mark.pos.row + 1 && sign_id == (int)mark.id))) { - kv_push(signs, mark); + if (mt_decor_sign(mark)) { + kv_push(signs, mark); + } } marktree_itr_next(buf->b_marktree, itr); } @@ -932,7 +960,7 @@ static void sign_get_placed_in_buf(buf_T *buf, linenr_T lnum, int sign_id, const if (kv_size(signs)) { qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_cmp); for (size_t i = 0; i < kv_size(signs); i++) { - sign_list_append_info(NULL, &kv_A(signs, i), l); + tv_list_append_dict(l, sign_get_placed_info_dict(kv_A(signs, i))); } kv_destroy(signs); } @@ -1222,18 +1250,17 @@ void f_sign_define(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "sign_getdefined()" function void f_sign_getdefined(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - sign_T *sp; - tv_list_alloc_ret(rettv, 0); if (argvars[0].v_type == VAR_UNKNOWN) { + sign_T *sp; map_foreach_value(&sign_map, sp, { - sign_list_append_info(sp, NULL, rettv->vval.v_list); + tv_list_append_dict(rettv->vval.v_list, sign_get_info_dict(sp)); }); } else { - sp = pmap_get(cstr_t)(&sign_map, tv_get_string(&argvars[0])); + sign_T *sp = pmap_get(cstr_t)(&sign_map, tv_get_string(&argvars[0])); if (sp != NULL) { - sign_list_append_info(sp, NULL, rettv->vval.v_list); + tv_list_append_dict(rettv->vval.v_list, sign_get_info_dict(sp)); } } } diff --git a/src/nvim/types.h b/src/nvim/types.h index 0e5f8e6504..623efe0c8d 100644 --- a/src/nvim/types.h +++ b/src/nvim/types.h @@ -43,6 +43,4 @@ typedef enum { #define TRISTATE_FROM_INT(val) ((val) == 0 ? kFalse : ((val) >= 1 ? kTrue : kNone)) -typedef struct Decoration Decoration; - typedef int64_t OptInt; diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index 6e00be611d..2115c7c244 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -1579,6 +1579,7 @@ describe('API/extmarks', function() eq({0, 0, { ns_id = 1, cursorline_hl_group = "Statement", + priority = 4096, right_gravity = true, } }, get_extmark_by_id(ns, marks[3], { details = true })) end) diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua index 5263fb4c24..81e514c9aa 100644 --- a/test/functional/ui/bufhl_spec.lua +++ b/test/functional/ui/bufhl_spec.lua @@ -762,8 +762,6 @@ describe('Buffer highlighting', function() local s1 = {{'Köttbullar', 'Comment'}, {'Kräuterbutter'}} local s2 = {{'こんにちは', 'Comment'}} - -- TODO: only a virtual text from the same ns currently overrides - -- an existing virtual text. We might add a prioritation system. set_virtual_text(id1, 0, s1, {}) eq({{1, 0, 0, { ns_id = 1, @@ -775,7 +773,6 @@ describe('Buffer highlighting', function() virt_text_hide = false, }}}, get_extmarks(id1, {0,0}, {0, -1}, {details=true})) - -- TODO: is this really valid? shouldn't the max be line_count()-1? local lastline = line_count() set_virtual_text(id1, line_count(), s2, {}) eq({{3, lastline, 0, { diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index b3b3128fdd..59a41a6de6 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -663,7 +663,7 @@ describe('decorations providers', function() ]]) end) - it('does not allow removing extmarks during on_line callbacks', function() + it('does allow removing extmarks during on_line callbacks', function() exec_lua([[ eok = true ]]) @@ -676,7 +676,7 @@ describe('decorations providers', function() end ]]) exec_lua([[ - assert(eok == false) + assert(eok == true) ]]) end) |