refactor(decorations): break up Decoration struct into smaller pieces

Remove the monolithic Decoration struct. Before this change, each extmark
could either represent just a hl_id + priority value as a inline
decoration, or it would take a pointer to this monolitic 112 byte struct
which has to be allocated.

This change separates the decorations into two pieces: DecorSignHighlight
for signs, highlights and simple set-flag decorations (like spell,
ui-watched), and DecorVirtText for virtual text and lines.

The main separation here is whether they are expected to allocate more
memory. Currently this is not really true as sign text has to be an
allocated string, but the plan is to get rid of this eventually (it can
just be an array of two schar_T:s). Further refactors are expected to
improve the representation of each decoration kind individually. The
goal of this particular PR is to get things started by cutting the
Gordian knot which was the monolithic struct Decoration.

Now, each extmark can either contain chained indicies/pointers to
these kinds of objects, or it can fit a subset of DecorSignHighlight
inline.

The point of this change is not only to make decorations smaller in
memory. In fact, the main motivation is to later allow them to grow
_larger_, but on a dynamic, on demand fashion. As a simple example, it
would be possible to augment highlights to take a list of multiple
`hl_group`:s, which then would trivially map to a chain of multiple
DecorSignHighlight entries.

One small feature improvement included with this refactor itself, is
that the restriction that extmarks cannot be removed inside a decoration
provider has been lifted. These are instead safely lifetime extended
on a "to free" list until the current iteration of screen drawing is done.

NB: flags is a mess. but DecorLevel is useless, this slightly less so
This commit is contained in:
bfredl 2023-03-08 15:18:02 +01:00
parent 8c6b0a5f21
commit 0b38fe4dbb
18 changed files with 1089 additions and 616 deletions

View File

@ -154,21 +154,25 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A
return 0; return 0;
} }
Decoration *existing = decor_find_virttext(buf, (int)line, ns_id); DecorVirtText *existing = decor_find_virttext(buf, (int)line, ns_id);
if (existing) { if (existing) {
clear_virttext(&existing->virt_text); clear_virttext(&existing->data.virt_text);
existing->virt_text = virt_text; existing->data.virt_text = virt_text;
existing->virt_text_width = width; existing->width = width;
return src_id; return src_id;
} }
Decoration decor = DECORATION_INIT; DecorVirtText *vt = xmalloc(sizeof *vt);
decor.virt_text = virt_text; *vt = (DecorVirtText)DECOR_VIRT_TEXT_INIT;
decor.virt_text_width = width; vt->data.virt_text = virt_text;
decor.priority = 0; 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; return src_id;
} }

View File

@ -103,15 +103,6 @@ bool ns_initialized(uint32_t ns)
return ns < (uint32_t)next_namespace_id; 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 virt_text_to_array(VirtText vt, bool hl_name)
{ {
Array chunks = ARRAY_DICT_INIT; 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)); PUT(dict, "invalid", BOOLEAN_OBJ(true));
} }
// pretend this is a pointer for a short while, Decoration will be factored away very soon decor_to_dict_legacy(&dict, mt_decor(start), hl_name);
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)) { ADD(rv, DICTIONARY_OBJ(dict));
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));
}
if (dict.size) {
ADD(rv, DICTIONARY_OBJ(dict));
}
} }
return rv; 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) Dict(set_extmark) *opts, Error *err)
FUNC_API_SINCE(7) FUNC_API_SINCE(7)
{ {
Decoration decor = DECORATION_INIT; DecorHighlightInline hl = DECOR_HIGHLIGHT_INLINE_INIT;
bool has_decor = false; // 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); buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) { if (!buf) {
@ -643,11 +564,11 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
Object *opt; Object *opt;
int *dest; int *dest;
} hls[] = { } hls[] = {
{ "hl_group" , &opts->hl_group , &decor.hl_id }, { "hl_group" , &opts->hl_group , &hl.hl_id },
{ "sign_hl_group" , &opts->sign_hl_group , &decor.sign_hl_id }, { "sign_hl_group" , &opts->sign_hl_group , &sign.hl_id },
{ "number_hl_group" , &opts->number_hl_group , &decor.number_hl_id }, { "number_hl_group" , &opts->number_hl_group , &sign.number_hl_id },
{ "line_hl_group" , &opts->line_hl_group , &decor.line_hl_id }, { "line_hl_group" , &opts->line_hl_group , &sign.line_hl_id },
{ "cursorline_hl_group", &opts->cursorline_hl_group, &decor.cursorline_hl_id }, { "cursorline_hl_group", &opts->cursorline_hl_group, &sign.cursorline_hl_id },
{ NULL, NULL, NULL }, { 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++) { for (int j = 0; hls[j].name && hls[j].dest; j++) {
if (hls[j].opt->type != kObjectTypeNil) { 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); *hls[j].dest = object_to_hl_id(*hls[j].opt, hls[j].name, err);
if (ERROR_SET(err)) { if (ERROR_SET(err)) {
goto error; goto error;
} }
has_decor = true;
} }
} }
if (HAS_KEY(opts, set_extmark, conceal)) { if (HAS_KEY(opts, set_extmark, conceal)) {
hl.flags |= kSHConceal;
has_hl = true;
String c = opts->conceal; String c = opts->conceal;
decor.conceal = true; if (c.size > 0) {
if (c.size) { if (c.size <= 4) {
decor.conceal_char = utf_ptr2char(c.data); 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)) { if (HAS_KEY(opts, set_extmark, virt_text)) {
decor.virt_text = parse_virt_text(opts->virt_text, err, &decor.virt_text_width); virt_text.data.virt_text = parse_virt_text(opts->virt_text, err, &virt_text.width);
has_decor = true;
if (ERROR_SET(err)) { if (ERROR_SET(err)) {
goto error; 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)) { if (HAS_KEY(opts, set_extmark, virt_text_pos)) {
String str = opts->virt_text_pos; String str = opts->virt_text_pos;
if (strequal("eol", str.data)) { if (strequal("eol", str.data)) {
decor.virt_text_pos = kVTEndOfLine; virt_text.pos = kVPosEndOfLine;
} else if (strequal("overlay", str.data)) { } else if (strequal("overlay", str.data)) {
decor.virt_text_pos = kVTOverlay; virt_text.pos = kVPosOverlay;
} else if (strequal("right_align", str.data)) { } else if (strequal("right_align", str.data)) {
decor.virt_text_pos = kVTRightAlign; virt_text.pos = kVPosRightAlign;
} else if (strequal("inline", str.data)) { } else if (strequal("inline", str.data)) {
decor.virt_text_pos = kVTInline; virt_text.pos = kVPosInline;
} else { } else {
VALIDATE_S(false, "virt_text_pos", str.data, { VALIDATE_S(false, "virt_text_pos", str.data, {
goto error; 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)) { if (HAS_KEY(opts, set_extmark, virt_text_win_col)) {
decor.col = (int)opts->virt_text_win_col; virt_text.col = (int)opts->virt_text_win_col;
decor.virt_text_pos = kVTWinCol; virt_text.pos = kVPosWinCol;
} }
decor.hl_eol = opts->hl_eol; hl.flags |= opts->hl_eol ? kSHHlEol : 0;
decor.virt_text_hide = opts->virt_text_hide; virt_text.flags |= opts->virt_text_hide ? kVTHide : 0;
if (HAS_KEY(opts, set_extmark, hl_mode)) { if (HAS_KEY(opts, set_extmark, hl_mode)) {
String str = opts->hl_mode; String str = opts->hl_mode;
if (strequal("replace", str.data)) { if (strequal("replace", str.data)) {
decor.hl_mode = kHlModeReplace; virt_text.hl_mode = kHlModeReplace;
} else if (strequal("combine", str.data)) { } else if (strequal("combine", str.data)) {
decor.hl_mode = kHlModeCombine; virt_text.hl_mode = kHlModeCombine;
} else if (strequal("blend", str.data)) { } 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", { VALIDATE(false, "%s", "cannot use 'blend' hl_mode with inline virtual text", {
goto error; goto error;
}); });
} }
decor.hl_mode = kHlModeBlend; virt_text.hl_mode = kHlModeBlend;
} else { } else {
VALIDATE_S(false, "hl_mode", str.data, { VALIDATE_S(false, "hl_mode", str.data, {
goto error; goto error;
@ -735,29 +663,32 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
}); });
int dummig; int dummig;
VirtText jtem = parse_virt_text(a.items[j].data.array, err, &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)) { if (ERROR_SET(err)) {
goto error; 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)) { if (HAS_KEY(opts, set_extmark, priority)) {
VALIDATE_RANGE((opts->priority >= 0 && opts->priority <= UINT16_MAX), "priority", { VALIDATE_RANGE((opts->priority >= 0 && opts->priority <= UINT16_MAX), "priority", {
goto error; 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)) { 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", "", { "sign_text", "", {
goto error; goto error;
}); });
has_decor = true; sign.flags |= kSHIsSign;
} }
bool right_gravity = GET_BOOL_OR_TRUE(opts, set_extmark, right_gravity); 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; size_t len = 0;
if (!HAS_KEY(opts, set_extmark, spell)) { if (HAS_KEY(opts, set_extmark, spell)) {
decor.spell = kNone; hl.flags |= (opts->spell) ? kSHSpellOn : kSHSpellOff;
} else { has_hl = true;
decor.spell = opts->spell ? kTrue : kFalse;
has_decor = true;
} }
decor.ui_watched = opts->ui_watched; if (opts->ui_watched) {
if (decor.ui_watched) { hl.flags |= kSHUIWatched;
has_decor = true; 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", { VALIDATE_RANGE((line >= 0), "line", {
@ -829,28 +762,90 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
col2 = 0; col2 = 0;
} }
// TODO(bfredl): synergize these two branches even more
if (opts->ephemeral && decor_state.win && decor_state.win->w_buffer == buf) { 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 { } else {
if (opts->ephemeral) { if (opts->ephemeral) {
api_set_error(err, kErrorTypeException, "not yet implemented"); api_set_error(err, kErrorTypeException, "not yet implemented");
goto error; 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, 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), !GET_BOOL_OR_TRUE(opts, set_extmark, undo_restore),
opts->invalidate, err); opts->invalidate, err);
if (ERROR_SET(err)) { if (ERROR_SET(err)) {
goto error; decor_free(decor);
return 0;
} }
} }
return (Integer)id; return (Integer)id;
error: error:
decor_clear(&decor); clear_virttext(&virt_text.data.virt_text);
clear_virtlines(&virt_lines.data.virt_lines);
return 0; return 0;
} }
@ -873,11 +868,6 @@ Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, Error *er
return false; 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); 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++; end_line++;
} }
Decoration decor = DECORATION_INIT; DecorInline decor = DECOR_INLINE_INIT;
decor.hl_id = hl_id; decor.data.hl.hl_id = hl_id;
extmark_set(buf, ns, NULL, extmark_set(buf, ns, NULL, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end,
(int)line, (colnr_T)col_start, decor, MT_FLAG_DECOR_HL, true, false, false, false, NULL);
end_line, (colnr_T)col_end,
&decor, true, false, false, false, NULL);
return ns_id; return ns_id;
} }
@ -997,11 +985,6 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
return; 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) { if (line_end < 0 || line_end > MAXLNUM) {
line_end = MAXLNUM; line_end = MAXLNUM;
} }

File diff suppressed because it is too large Load Diff

View File

@ -6,91 +6,54 @@
#include "klib/kvec.h" #include "klib/kvec.h"
#include "nvim/buffer_defs.h" #include "nvim/buffer_defs.h"
#include "nvim/decoration_defs.h"
#include "nvim/extmark_defs.h" #include "nvim/extmark_defs.h"
#include "nvim/macros.h" #include "nvim/macros.h"
#include "nvim/marktree.h" #include "nvim/marktree.h"
#include "nvim/pos.h" #include "nvim/pos.h"
#include "nvim/types.h" #include "nvim/types.h"
// actual Decoration data is in extmark_defs.h // actual Decor* data is in decoration_defs.h
typedef uint16_t DecorPriority;
#define DECOR_PRIORITY_BASE 0x1000
typedef enum {
kVTEndOfLine,
kVTOverlay,
kVTWinCol,
kVTRightAlign,
kVTInline,
} VirtTextPos;
EXTERN const char *const virt_text_pos_str[] INIT( = { "eol", "overlay", "win_col", "right_align", EXTERN const char *const virt_text_pos_str[] INIT( = { "eol", "overlay", "win_col", "right_align",
"inline" }); "inline" });
typedef enum {
kHlModeUnknown,
kHlModeReplace,
kHlModeCombine,
kHlModeBlend,
} HlMode;
EXTERN const char *const hl_mode_str[] INIT( = { "", "replace", "combine", "blend" }); EXTERN const char *const hl_mode_str[] INIT( = { "", "replace", "combine", "blend" });
#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) typedef enum {
kDecorKindHighlight,
typedef kvec_t(struct virt_line { VirtText line; bool left_col; }) VirtLines; kDecorKindSign,
kDecorKindVirtText,
struct Decoration { kDecorKindVirtLines,
VirtText virt_text; kDecorKindUIWatched,
VirtLines virt_lines; } DecorRangeKind;
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 struct { typedef struct {
int start_row; int start_row;
int start_col; int start_col;
int end_row; int end_row;
int end_col; int end_col;
Decoration decor; // next pointers MUST NOT be used, these are separate ranges
int attr_id; // cached lookup of decor.hl_id // vt->next could be pointing to freelist memory at this point
bool virt_text_owned; 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. /// Screen column to draw the virtual text.
/// When -1, the virtual text may be drawn after deciding where. /// 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 -3, the virtual text should be drawn on the next screen line.
/// When -10, the virtual text has just been added. /// When -10, the virtual text has just been added.
/// When INT_MIN, the virtual text should no longer be drawn. /// When INT_MIN, the virtual text should no longer be drawn.
int draw_col; int draw_col;
uint64_t ns_id;
uint64_t mark_id;
} DecorRange; } DecorRange;
typedef struct { typedef struct {
@ -109,23 +72,11 @@ typedef struct {
TriState spell; TriState spell;
// This is used to prevent removing/updating extmarks inside bool running_decor_provider;
// on_lines callbacks which is not allowed since it can lead to
// heap-use-after-free errors.
bool running_on_lines;
} DecorState; } DecorState;
EXTERN DecorState decor_state INIT( = { 0 }); 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 #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "decoration.h.generated.h" # include "decoration.h.generated.h"
#endif #endif

129
src/nvim/decoration_defs.h Normal file
View File

@ -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 }

View File

@ -122,6 +122,9 @@ void decor_providers_invoke_win(win_T *wp, DecorProviders *providers,
DecorProviders *line_providers) DecorProviders *line_providers)
{ {
kvi_init(*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, linenr_T knownmax = MIN(wp->w_buffer->b_ml.ml_line_count,
((wp->w_valid & VALID_BOTLINE) ((wp->w_valid & VALID_BOTLINE)
@ -153,7 +156,7 @@ void decor_providers_invoke_win(win_T *wp, DecorProviders *providers,
/// @param[out] err Provider error /// @param[out] err Provider error
void decor_providers_invoke_line(win_T *wp, DecorProviders *providers, int row, bool *has_decor) 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++) { for (size_t k = 0; k < kv_size(*providers); k++) {
DecorProvider *p = kv_A(*providers, k); DecorProvider *p = kv_A(*providers, k);
if (p && p->redraw_line != LUA_NOREF) { 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(); 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. /// 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_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 /// Mark all cached state of per-namespace highlights as invalid. Revalidate

View File

@ -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; bool do_eol = state->eol_col > -1;
for (size_t i = 0; i < kv_size(state->active); i++) { for (size_t i = 0; i < kv_size(state->active); i++) {
DecorRange *item = &kv_A(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; 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; bool updated = true;
if (item->decor.virt_text_pos == kVTRightAlign) { VirtTextPos pos = decor_virt_pos_kind(item);
right_pos -= item->decor.virt_text_width; if (pos == kVPosRightAlign) {
right_pos -= vt->width;
item->draw_col = right_pos; 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; item->draw_col = state->eol_col;
} else if (item->decor.virt_text_pos == kVTWinCol) { } else if (pos == kVPosWinCol) {
item->draw_col = MAX(col_off + item->decor.col, 0); item->draw_col = MAX(col_off + vt->col, 0);
} else { } else {
updated = false; 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; continue;
} }
int col = 0; int col = 0;
if (item->decor.ui_watched) { if (item->kind == kDecorKindUIWatched) {
// send mark position to UI // send mark position to UI
col = item->draw_col; 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); kv_push(win_extmark_arr, m);
} }
if (kv_size(item->decor.virt_text)) { if (vt) {
int vcol = item->draw_col - col_off; int vcol = item->draw_col - col_off;
col = draw_virt_text_item(buf, item->draw_col, item->decor.virt_text, col = draw_virt_text_item(buf, item->draw_col, vt->data.virt_text,
item->decor.hl_mode, max_col, vcol); vt->hl_mode, max_col, vcol);
} }
item->draw_col = INT_MIN; // deactivate 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; 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++) { for (size_t i = 0; i < kv_size(state->active); i++) {
DecorRange *item = &kv_A(state->active, i); DecorRange *item = &kv_A(state->active, i);
if (item->start_row != state->row if (item->start_row != state->row
|| !kv_size(item->decor.virt_text) || item->kind != kDecorKindVirtText
|| item->decor.virt_text_pos != kVTInline || item->data.vt->pos != kVPosInline
|| item->decor.virt_text_width == 0) { || item->data.vt->width == 0) {
continue; continue;
} }
if (item->draw_col >= -1 && item->start_col >= v) { 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++) { for (size_t i = 0; i < kv_size(state->active); i++) {
DecorRange *item = &kv_A(state->active, i); DecorRange *item = &kv_A(state->active, i);
if (item->start_row != state->row if (item->start_row != state->row
|| !kv_size(item->decor.virt_text) || item->kind != kDecorKindVirtText
|| item->decor.virt_text_pos != kVTInline || item->data.vt->pos != kVPosInline
|| item->decor.virt_text_width == 0) { || item->data.vt->width == 0) {
continue; continue;
} }
if (item->draw_col >= -1 && item->start_col == v) { if (item->draw_col >= -1 && item->start_col == v) {
wlv->virt_inline = item->decor.virt_text; wlv->virt_inline = item->data.vt->data.virt_text;
wlv->virt_inline_hl_mode = item->decor.hl_mode; wlv->virt_inline_hl_mode = item->data.vt->hl_mode;
item->draw_col = INT_MIN; item->draw_col = INT_MIN;
break; break;
} }

View File

@ -50,67 +50,35 @@
/// ///
/// must not be used during iteration! /// 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, 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, colnr_T end_col, DecorInline decor, uint16_t decor_flags, bool right_gravity,
bool no_undo, bool invalidate, Error *err) 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 *ns = map_put_ref(uint32_t, uint32_t)(buf->b_extmark_ns, ns_id, NULL, NULL);
uint32_t id = idp ? *idp : 0; 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) { if (id == 0) {
id = ++*ns; id = ++*ns;
} else { } else {
MarkTreeIter itr[1] = { 0 }; MarkTreeIter itr[1] = { 0 };
MTKey old_mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr); MTKey old_mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr);
if (old_mark.id) { 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) { if (mt_paired(old_mark) || end_row > -1) {
extmark_del_id(buf, ns_id, id); extmark_del_id(buf, ns_id, id);
} else { } else {
// TODO(bfredl): we need to do more if "revising" a decoration mark.
assert(marktree_itr_valid(itr)); assert(marktree_itr_valid(itr));
if (old_mark.pos.row == row && old_mark.pos.col == col) { if (old_mark.pos.row == row && old_mark.pos.col == col) {
if (marktree_decor_level(old_mark) > kDecorLevelNone) { if (mt_decor_any(old_mark)) {
decor_remove(buf, row, row, old_mark.decor_full, false); buf_decor_remove(buf, row, row, mt_decor(old_mark), true);
old_mark.decor_full = NULL;
} }
old_mark.flags = flags;
if (decor_full) { // not paired: we can revise in place
old_mark.decor_full = decor; mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_EXTERNAL_MASK;
} else if (decor) { mt_itr_rawkey(itr).flags |= flags;
old_mark.hl_id = decor->hl_id; mt_itr_rawkey(itr).decor_data = decor.data;
old_mark.priority = decor->priority;
}
marktree_revise(buf->b_marktree, itr, decor_level, old_mark);
goto revised; 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); marktree_del_itr(buf->b_marktree, itr, false);
} }
} else { } 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 }; MTKey mark = { { row, col }, ns_id, id, flags, decor.data };
if (decor_full) {
mark.decor_full = decor;
} else if (decor) {
mark.hl_id = decor->hl_id;
mark.priority = decor->priority;
}
marktree_put(buf->b_marktree, mark, end_row, end_col, end_right_gravity); marktree_put(buf->b_marktree, mark, end_row, end_col, end_right_gravity);
revised: 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) { if (idp) {
*idp = id; *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) 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) { if (mt_decor_any(key)) {
decor_remove(buf, key.pos.row, key2.pos.row, key.decor_full, false); buf_decor_remove(buf, key.pos.row, key2.pos.row, mt_decor(key), true);
} }
// TODO(bfredl): delete it from current undo header, opportunistically? // 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); marktree_itr_next(buf->b_marktree, itr);
} }
} }
return marks_cleared; 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)) { if (!(ns_id == UINT32_MAX || mark.start.ns == ns_id)) {
return; return;
} }
uint16_t type_flags = kExtmarkNone;
if (type_filter != kExtmarkNone) { if (type_filter != kExtmarkNone) {
Decoration *decor = mark.start.decor_full; if (!mt_decor_any(mark.start)) {
if (decor && (decor->sign_text || decor->number_hl_id)) { return;
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;
} }
uint16_t type_flags = decor_type_flags(mt_decor(mark.start));
if (!(type_flags & type_filter)) { if (!(type_flags & type_filter)) {
return; return;
@ -349,9 +293,9 @@ void extmark_free_all(buf_T *buf)
break; 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))) { if (!(mt_paired(mark) && mt_end(mark))) {
decor_free(mark.decor_full); decor_free(mt_decor(mark));
} }
marktree_itr_next(buf->b_marktree, itr); 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; continue;
} else { } else {
invalidated = true; invalidated = true;
mark.flags |= MT_FLAG_INVALID; mt_itr_rawkey(itr).flags |= MT_FLAG_INVALID;
marktree_revise(curbuf->b_marktree, itr, marktree_decor_level(mark), mark); buf_decor_remove(buf, mark.pos.row, endpos.row, mt_decor(mark), false);
decor_remove(buf, mark.pos.row, endpos.row, mark.decor_full, true);
} }
} }
} }
@ -451,10 +394,8 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo)
if (pos.invalidated) { if (pos.invalidated) {
MarkTreeIter itr[1] = { 0 }; MarkTreeIter itr[1] = { 0 };
MTKey mark = marktree_lookup(curbuf->b_marktree, pos.mark, itr); MTKey mark = marktree_lookup(curbuf->b_marktree, pos.mark, itr);
MTKey end = marktree_get_alt(curbuf->b_marktree, mark, NULL); mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_INVALID;
mark.flags &= (uint16_t) ~MT_FLAG_INVALID; buf_put_decor(curbuf, mt_decor(mark), mark.pos.row);
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);
} }
if (pos.old_row >= 0) { if (pos.old_row >= 0) {
extmark_setraw(curbuf, pos.mark, pos.old_row, pos.old_col); extmark_setraw(curbuf, pos.mark, pos.old_row, pos.old_col);

View File

@ -64,6 +64,8 @@ typedef enum {
kExtmarkClear, kExtmarkClear,
} UndoObjectType; } UndoObjectType;
// TODO(bfredl): if possible unify these with marktree flags,
// so it is possible to filter extmarks directly on top-level flags
typedef enum { typedef enum {
kExtmarkNone = 0x1, kExtmarkNone = 0x1,
kExtmarkSign = 0x2, kExtmarkSign = 0x2,

View File

@ -3,13 +3,6 @@
#include "klib/kvec.h" #include "klib/kvec.h"
#include "nvim/types.h" #include "nvim/types.h"
typedef struct {
char *text;
int hl_id;
} VirtTextChunk;
typedef kvec_t(VirtTextChunk) VirtText;
typedef struct undo_object ExtmarkUndoObject; typedef struct undo_object ExtmarkUndoObject;
typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t; typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t;
@ -21,9 +14,3 @@ typedef enum {
kExtmarkNoUndo, // Operation should not be reversible kExtmarkNoUndo, // Operation should not be reversible
kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable
} ExtmarkOp; } ExtmarkOp;
typedef enum {
kDecorLevelNone = 0,
kDecorLevelVisible = 1,
kDecorLevelVirtLine = 2,
} DecorLevel;

View File

@ -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) 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) { if (end_row >= 0) {
key.flags |= MT_FLAG_PAIRED; key.flags |= MT_FLAG_PAIRED;
} }
@ -1137,25 +1137,6 @@ static void marktree_free_node(MarkTree *b, MTNode *x)
b->n_nodes--; 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 /// @param itr iterator is invalid after call
void marktree_move(MarkTree *b, MarkTreeIter *itr, int row, int col) 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, 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) int end_row, int end_col, bool end_right)
{ {
uint16_t flags = mt_flags(right_gravity, false, false, false, 0); uint16_t flags = mt_flags(right_gravity, false, false, false);
MTKey key = { { row, col }, ns, id, 0, flags, 0, NULL }; MTKey key = { { row, col }, ns, id, flags, { .hl = DECOR_HIGHLIGHT_INLINE_INIT } };
marktree_put(b, key, end_row, end_col, end_right); marktree_put(b, key, end_row, end_col, end_right);
} }

View File

@ -7,6 +7,7 @@
#include "klib/kvec.h" #include "klib/kvec.h"
#include "nvim/assert.h" #include "nvim/assert.h"
#include "nvim/decoration_defs.h"
#include "nvim/garray.h" #include "nvim/garray.h"
#include "nvim/map.h" #include "nvim/map.h"
#include "nvim/pos.h" #include "nvim/pos.h"
@ -47,6 +48,8 @@ typedef struct {
} MarkTreeIter; } MarkTreeIter;
#define marktree_itr_valid(itr) ((itr)->x != NULL) #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 // Internal storage
// //
@ -56,10 +59,8 @@ typedef struct {
MTPos pos; MTPos pos;
uint32_t ns; uint32_t ns;
uint32_t id; uint32_t id;
int32_t hl_id;
uint16_t flags; uint16_t flags;
uint16_t priority; DecorInlineData decor_data; // "ext" tag in flags
Decoration *decor_full;
} MTKey; } MTKey;
typedef struct { typedef struct {
@ -68,28 +69,40 @@ typedef struct {
bool end_right_gravity; bool end_right_gravity;
} MTPair; } 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_REAL (((uint16_t)1) << 0)
#define MT_FLAG_END (((uint16_t)1) << 1) #define MT_FLAG_END (((uint16_t)1) << 1)
#define MT_FLAG_PAIRED (((uint16_t)1) << 2) #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! // 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_ORPHANED (((uint16_t)1) << 3)
#define MT_FLAG_HL_EOL (((uint16_t)1) << 4) #define MT_FLAG_NO_UNDO (((uint16_t)1) << 4)
#define MT_FLAG_NO_UNDO (((uint16_t)1) << 5) #define MT_FLAG_INVALIDATE (((uint16_t)1) << 5)
#define MT_FLAG_INVALIDATE (((uint16_t)1) << 6) #define MT_FLAG_INVALID (((uint16_t)1) << 6)
#define MT_FLAG_INVALID (((uint16_t)1) << 7) // discriminant for union
#define MT_FLAG_DECOR_EXT (((uint16_t)1) << 7)
#define DECOR_LEVELS 4 // TODO(bfredl): flags for decorations. These cover the cases where we quickly needs
#define MT_FLAG_DECOR_OFFSET 8 // to skip over irrelevant marks internally. When we refactor this more, also make all info
#define MT_FLAG_DECOR_MASK (((uint16_t)(DECOR_LEVELS - 1)) << MT_FLAG_DECOR_OFFSET) // 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 // These _must_ be last to preserve ordering of marks
#define MT_FLAG_RIGHT_GRAVITY (((uint16_t)1) << 14) #define MT_FLAG_RIGHT_GRAVITY (((uint16_t)1) << 14)
#define MT_FLAG_LAST (((uint16_t)1) << 15) #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 \ #define MT_FLAG_DECOR_MASK (MT_FLAG_DECOR_EXT| MT_FLAG_DECOR_HL | MT_FLAG_DECOR_SIGNTEXT \
| MT_FLAG_NO_UNDO | MT_FLAG_INVALIDATE | MT_FLAG_INVALID) | 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 // this is defined so that start and end of the same range have adjacent ids
#define MARKTREE_END_FLAG ((uint64_t)1) #define MARKTREE_END_FLAG ((uint64_t)1)
@ -143,20 +156,22 @@ static inline bool mt_invalid(MTKey key)
return key.flags & MT_FLAG_INVALID; return key.flags & MT_FLAG_INVALID;
} }
static inline uint8_t marktree_decor_level(MTKey key) static inline bool mt_decor_any(MTKey key)
{ {
return (uint8_t)((key.flags&MT_FLAG_DECOR_MASK) >> MT_FLAG_DECOR_OFFSET); return key.flags & MT_FLAG_DECOR_MASK;
} }
static inline uint16_t mt_flags(bool right_gravity, bool hl_eol, bool no_undo, bool invalidate, static inline bool mt_decor_sign(MTKey key)
uint8_t decor_level) {
return key.flags & (MT_FLAG_DECOR_SIGNTEXT | MT_FLAG_DECOR_SIGNHL);
}
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) return (uint16_t)((right_gravity ? MT_FLAG_RIGHT_GRAVITY : 0)
| (hl_eol ? MT_FLAG_HL_EOL : 0)
| (no_undo ? MT_FLAG_NO_UNDO : 0) | (no_undo ? MT_FLAG_NO_UNDO : 0)
| (invalidate ? MT_FLAG_INVALIDATE : 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) 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) }; 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; typedef kvec_withinit_t(uint64_t, 4) Intersection;
struct mtnode_s { struct mtnode_s {
@ -186,8 +206,6 @@ static inline uint64_t mt_dbg_id(uint64_t id)
typedef struct { typedef struct {
MTNode *root; MTNode *root;
size_t n_keys, n_nodes; 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]; PMap(uint64_t) id2node[1];
} MarkTree; } MarkTree;

View File

@ -222,21 +222,25 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp)
if (mark.pos.row != cts->cts_row || mark.pos.col > col) { if (mark.pos.row != cts->cts_row || mark.pos.col > col) {
break; break;
} else if (mark.pos.col == col) { } else if (mark.pos.col == col) {
if (!mt_end(mark)) { if (!mt_end(mark) && mark.flags & (MT_FLAG_DECOR_VIRT_TEXT_INLINE)) {
Decoration decor = get_decor(mark); DecorInline decor = mt_decor(mark);
if (decor.virt_text_pos == kVTInline) { DecorVirtText *vt = decor.ext ? decor.data.ext.vt : NULL;
if (mt_right(mark)) { while (vt) {
cts->cts_cur_text_width_right += decor.virt_text_width; if (!(vt->flags & kVTIsLines) && vt->pos == kVPosInline) {
} else { if (mt_right(mark)) {
cts->cts_cur_text_width_left += decor.virt_text_width; cts->cts_cur_text_width_right += vt->width;
} } else {
size += decor.virt_text_width; cts->cts_cur_text_width_left += vt->width;
if (*s == TAB) { }
// tab size changes because of the inserted text size += vt->width;
size -= tab_size; if (*s == TAB) {
tab_size = win_chartabsize(wp, s, vcol + size); // tab size changes because of the inserted text
size += tab_size; size -= tab_size;
tab_size = win_chartabsize(wp, s, vcol + size);
size += tab_size;
}
} }
vt = vt->next;
} }
} }
} }

View File

@ -71,9 +71,9 @@ static int64_t group_get_ns(const char *group)
return ns ? ns : -1; 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]"; 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; uint32_t ns = group ? (uint32_t)nvim_create_namespace(cstr_as_string(group)) : 0;
Decoration decor = DECORATION_INIT; DecorSignHighlight sign = DECOR_SIGN_HIGHLIGHT_INIT;
decor.sign_text = sp->sn_text ? xstrdup(sp->sn_text) : NULL;
decor.sign_name = xstrdup(sp->sn_name); sign.flags |= kSHIsSign;
decor.sign_hl_id = sp->sn_text_hl; sign.text.ptr = sp->sn_text ? xstrdup(sp->sn_text) : NULL;
decor.line_hl_id = sp->sn_line_hl; sign.sign_name = xstrdup(sp->sn_name);
decor.number_hl_id = sp->sn_num_hl; sign.hl_id = sp->sn_text_hl;
decor.cursorline_hl_id = sp->sn_cul_hl; sign.line_hl_id = sp->sn_line_hl;
decor.priority = (DecorPriority)prio; sign.number_hl_id = sp->sn_num_hl;
extmark_set(buf, ns, id, lnum - 1, 0, -1, -1, &decor, true, false, true, true, NULL); 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. /// 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; const MTKey *s2 = (MTKey *)p2;
int n = s1->pos.row - s2->pos.row; int n = s1->pos.row - s2->pos.row;
return n ? n : (n = s2->decor_full->priority - s1->decor_full->priority) if (n) {
? n : (n = (int)(s2->id - s1->id)) return n;
? n : (s2->decor_full->sign_add_id - s1->decor_full->sign_add_id); }
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 /// 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; MTPair pair;
while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) {
if ((ns == UINT32_MAX || ns == pair.start.ns) if ((ns == UINT32_MAX || ns == pair.start.ns) && mt_decor_sign(pair.start)) {
&& pair.start.decor_full && decor_has_sign(pair.start.decor_full)) {
kv_push(signs, 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) { if (row && mark.pos.row > row) {
break; 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) && (id == 0 || (int)mark.id == id)
&& (ns == UINT32_MAX || ns == mark.ns)) { && (ns == UINT32_MAX || ns == mark.ns)) {
if (atlnum > 0) { if (atlnum > 0) {
@ -248,7 +265,7 @@ static void sign_list_placed(buf_T *rbuf, char *group)
while (itr->x) { while (itr->x) {
MTKey mark = marktree_itr_current(itr); 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)) { && (ns == UINT32_MAX || ns == mark.ns)) {
kv_push(signs, mark); kv_push(signs, mark);
} }
@ -262,14 +279,16 @@ static void sign_list_placed(buf_T *rbuf, char *group)
namebuf[0] = '\0'; namebuf[0] = '\0';
groupbuf[0] = '\0'; groupbuf[0] = '\0';
MTKey mark = kv_A(signs, i); 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) { if (mark.ns != 0) {
vim_snprintf(groupbuf, MSG_BUF_LEN, _(" group=%s"), describe_ns((int)mark.ns, "")); 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"), 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_puts(lbuf);
msg_putchar('\n'); msg_putchar('\n');
} }
@ -841,21 +860,12 @@ void ex_sign(exarg_T *eap)
} }
} }
/// Append dictionary of information for a defined sign "sp", or placed /// Get dictionary of information for a defined sign "sp"
/// sign "mark" to "retlist". Either "sp", or "mark" is NULL. static dict_T *sign_get_info_dict(sign_T *sp)
static void sign_list_append_info(sign_T *sp, MTKey *mark, list_T *retlist)
{ {
dict_T *d = tv_dict_alloc(); 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)); tv_dict_add_str(d, S_LEN("name"), sp->sn_name);
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;
}
if (sp->sn_icon != NULL) { if (sp->sn_icon != NULL) {
tv_dict_add_str(d, S_LEN("icon"), sp->sn_icon); 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"); 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. /// 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) { while (itr->x) {
MTKey mark = marktree_itr_current(itr); 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)) {
sign_list_append_info(NULL, &mark, l); tv_list_append_dict(l, sign_get_placed_info_dict(mark));
} }
marktree_itr_next(buf->b_marktree, itr); 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) { if (lnum && mark.pos.row >= lnum) {
break; break;
} }
if (!mt_end(mark) && mark.decor_full && decor_has_sign(mark.decor_full) if (!mt_end(mark)
&& (ns == UINT32_MAX || ns == mark.ns) && (ns == UINT32_MAX || ns == mark.ns)
&& ((lnum == 0 && sign_id == 0) && ((lnum == 0 && sign_id == 0)
|| (sign_id == 0 && lnum == mark.pos.row + 1) || (sign_id == 0 && lnum == mark.pos.row + 1)
|| (lnum == 0 && sign_id == (int)mark.id) || (lnum == 0 && sign_id == (int)mark.id)
|| (lnum == mark.pos.row + 1 && 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); 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)) { if (kv_size(signs)) {
qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_cmp); qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_cmp);
for (size_t i = 0; i < kv_size(signs); i++) { 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); kv_destroy(signs);
} }
@ -1222,18 +1250,17 @@ void f_sign_define(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "sign_getdefined()" function /// "sign_getdefined()" function
void f_sign_getdefined(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) void f_sign_getdefined(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{ {
sign_T *sp;
tv_list_alloc_ret(rettv, 0); tv_list_alloc_ret(rettv, 0);
if (argvars[0].v_type == VAR_UNKNOWN) { if (argvars[0].v_type == VAR_UNKNOWN) {
sign_T *sp;
map_foreach_value(&sign_map, 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 { } 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) { 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));
} }
} }
} }

View File

@ -43,6 +43,4 @@ typedef enum {
#define TRISTATE_FROM_INT(val) ((val) == 0 ? kFalse : ((val) >= 1 ? kTrue : kNone)) #define TRISTATE_FROM_INT(val) ((val) == 0 ? kFalse : ((val) >= 1 ? kTrue : kNone))
typedef struct Decoration Decoration;
typedef int64_t OptInt; typedef int64_t OptInt;

View File

@ -1579,6 +1579,7 @@ describe('API/extmarks', function()
eq({0, 0, { eq({0, 0, {
ns_id = 1, ns_id = 1,
cursorline_hl_group = "Statement", cursorline_hl_group = "Statement",
priority = 4096,
right_gravity = true, right_gravity = true,
} }, get_extmark_by_id(ns, marks[3], { details = true })) } }, get_extmark_by_id(ns, marks[3], { details = true }))
end) end)

View File

@ -762,8 +762,6 @@ describe('Buffer highlighting', function()
local s1 = {{'Köttbullar', 'Comment'}, {'Kräuterbutter'}} local s1 = {{'Köttbullar', 'Comment'}, {'Kräuterbutter'}}
local s2 = {{'こんにちは', 'Comment'}} 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, {}) set_virtual_text(id1, 0, s1, {})
eq({{1, 0, 0, { eq({{1, 0, 0, {
ns_id = 1, ns_id = 1,
@ -775,7 +773,6 @@ describe('Buffer highlighting', function()
virt_text_hide = false, virt_text_hide = false,
}}}, get_extmarks(id1, {0,0}, {0, -1}, {details=true})) }}}, 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() local lastline = line_count()
set_virtual_text(id1, line_count(), s2, {}) set_virtual_text(id1, line_count(), s2, {})
eq({{3, lastline, 0, { eq({{3, lastline, 0, {

View File

@ -663,7 +663,7 @@ describe('decorations providers', function()
]]) ]])
end) end)
it('does not allow removing extmarks during on_line callbacks', function() it('does allow removing extmarks during on_line callbacks', function()
exec_lua([[ exec_lua([[
eok = true eok = true
]]) ]])
@ -676,7 +676,7 @@ describe('decorations providers', function()
end end
]]) ]])
exec_lua([[ exec_lua([[
assert(eok == false) assert(eok == true)
]]) ]])
end) end)