From 8ade2f5b0495ee41868e52d48816d02249e2364d Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Wed, 13 Oct 2021 21:09:49 +0200 Subject: refactor(decorations): mark decorations directly on the marktree This allows to more quickly skip though regions which has non-decorative marks when redrawing. This might seem like a gratuitous micro-optimization in isolation. But! Soon decorations are gonna crop into other hot inner-loop paths, including the plines.c code for calculating the horizontal and vertical space of text. Then we want to quickly skip over regions with "only" overlaying decorations (which do not affect text size) --- src/nvim/decoration.c | 21 ++++++++++++--------- src/nvim/extmark.c | 6 ++++-- src/nvim/marktree.c | 8 ++++++-- src/nvim/marktree.h | 7 +++++++ 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 4e80528c74..931c3fa20a 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -118,6 +118,8 @@ Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) mtmark_t mark = marktree_itr_current(itr); if (mark.row < 0 || mark.row > row) { break; + } else if (mt_decor_level(mark.id) < 1) { + goto next_mark; } ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark.id, false); @@ -125,6 +127,7 @@ Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) && item->decor && kv_size(item->decor->virt_text)) { return item->decor; } +next_mark: marktree_itr_next(buf->b_marktree, itr); } return NULL; @@ -158,21 +161,22 @@ bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state) if (mark.row < 0) { // || mark.row > end_row break; } - if ((mark.row < top_row && mark.id&MARKTREE_END_FLAG)) { + if ((mark.row < top_row && mark.id&MARKTREE_END_FLAG) + || mt_decor_level(mark.id) < 1) { goto next_mark; } - mtpos_t altpos = marktree_lookup(buf->b_marktree, - mark.id^MARKTREE_END_FLAG, NULL); uint64_t start_id = mark.id & ~MARKTREE_END_FLAG; ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, start_id, false); if (!item || !item->decor) { - // TODO(bfredl): dedicated flag for being a decoration? goto next_mark; } Decoration *decor = item->decor; + mtpos_t altpos = marktree_lookup(buf->b_marktree, + mark.id^MARKTREE_END_FLAG, NULL); + if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row && !kv_size(decor->virt_text)) || ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) { @@ -251,21 +255,20 @@ int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden, DecorState * break; } - if ((mark.id&MARKTREE_END_FLAG)) { - // TODO(bfredl): check decoration flag + if ((mark.id&MARKTREE_END_FLAG) || mt_decor_level(mark.id) < 1) { goto next_mark; } - mtpos_t endpos = marktree_lookup(buf->b_marktree, - mark.id|MARKTREE_END_FLAG, NULL); ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark.id, false); if (!item || !item->decor) { - // TODO(bfredl): dedicated flag for being a decoration? goto next_mark; } Decoration *decor = item->decor; + mtpos_t endpos = marktree_lookup(buf->b_marktree, + mark.id|MARKTREE_END_FLAG, NULL); + if (endpos.row == -1) { endpos.row = mark.row; endpos.col = mark.col; diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index dc73e34111..dc3dc4268d 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -96,12 +96,14 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t *idp, int row, colnr_T } } + uint8_t decor_level = (decor != NULL) ? 1 : 0; + if (end_row > -1) { mark = marktree_put_pair(buf->b_marktree, row, col, right_gravity, - end_row, end_col, end_right_gravity); + end_row, end_col, end_right_gravity, decor_level); } else { - mark = marktree_put(buf->b_marktree, row, col, right_gravity); + mark = marktree_put(buf->b_marktree, row, col, right_gravity, decor_level); } revised: diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c index 39c1e36147..a7f540c748 100644 --- a/src/nvim/marktree.c +++ b/src/nvim/marktree.c @@ -221,9 +221,11 @@ static inline void marktree_putp_aux(MarkTree *b, mtnode_t *x, mtkey_t k) } } -uint64_t marktree_put(MarkTree *b, int row, int col, bool right_gravity) +uint64_t marktree_put(MarkTree *b, int row, int col, bool right_gravity, uint8_t decor_level) { uint64_t id = (b->next_id+=ID_INCR); + assert(decor_level < DECOR_LEVELS); + id = id | ((uint64_t)decor_level << DECOR_OFFSET); uint64_t keyid = id; if (right_gravity) { // order all right gravity keys after the left ones, for effortless @@ -235,9 +237,11 @@ uint64_t marktree_put(MarkTree *b, int row, int col, bool right_gravity) } uint64_t marktree_put_pair(MarkTree *b, int start_row, int start_col, bool start_right, int end_row, - int end_col, bool end_right) + int end_col, bool end_right, uint8_t decor_level) { uint64_t id = (b->next_id+=ID_INCR)|PAIRED; + assert(decor_level < DECOR_LEVELS); + id = id | ((uint64_t)decor_level << DECOR_OFFSET); uint64_t start_id = id|(start_right?RIGHT_GRAVITY:0); uint64_t end_id = id|END_FLAG|(end_right?RIGHT_GRAVITY:0); marktree_put_key(b, start_row, start_col, start_id); diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h index 02f73b8052..aec5def879 100644 --- a/src/nvim/marktree.h +++ b/src/nvim/marktree.h @@ -75,4 +75,11 @@ typedef struct { #define MARKTREE_PAIRED_FLAG (((uint64_t)1) << 1) #define MARKTREE_END_FLAG (((uint64_t)1) << 0) +#define DECOR_LEVELS 4 +#define DECOR_OFFSET 61 +#define DECOR_MASK (((uint64_t)(DECOR_LEVELS-1)) << DECOR_OFFSET) +static inline uint8_t mt_decor_level(uint64_t id) { + return (uint8_t)((id&DECOR_MASK) >> DECOR_OFFSET); +} + #endif // NVIM_MARKTREE_H -- cgit From 8d7816cf27c4ab08d9eff9e7ce3c541105c30ece Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Wed, 13 Oct 2021 21:35:37 +0200 Subject: feat(decorations): support more than one virt_lines block --- src/nvim/api/buffer.c | 38 +++++------------ src/nvim/buffer.c | 1 - src/nvim/buffer_defs.h | 7 +-- src/nvim/decoration.c | 75 ++++++++++++++++++++++----------- src/nvim/decoration.h | 18 ++++++-- src/nvim/extmark.c | 36 +++++++--------- src/nvim/extmark_defs.h | 10 +++-- src/nvim/lib/kvec.h | 12 ++++++ src/nvim/marktree.h | 4 +- src/nvim/plines.c | 4 +- src/nvim/screen.c | 20 +++++---- test/functional/ui/decorations_spec.lua | 7 +-- test/unit/marktree_spec.lua | 4 +- 13 files changed, 130 insertions(+), 106 deletions(-) diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 31d44c68bf..92fcb59b34 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -247,7 +247,6 @@ Boolean nvim_buf_detach(uint64_t channel_id, Buffer buffer, Error *err) } void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last, Error *err) - FUNC_API_LUA_ONLY { buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { @@ -1531,12 +1530,6 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// option is still used for hard tabs. By default lines are /// placed below the buffer line containing the mark. /// -/// Note: currently virtual lines are limited to one block -/// per buffer. Thus setting a new mark disables any previous -/// `virt_lines` decoration. However plugins should not rely -/// on this behaviour, as this limitation is planned to be -/// removed. -/// /// - virt_lines_above: place virtual lines above instead. /// - virt_lines_leftcol: Place extmarks in the leftmost /// column of the window, bypassing @@ -1680,9 +1673,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer goto error; } - VirtLines virt_lines = KV_INITIAL_VALUE; - bool virt_lines_above = false; bool virt_lines_leftcol = false; + OPTION_TO_BOOL(virt_lines_leftcol, virt_lines_leftcol, false); if (opts->virt_lines.type == kObjectTypeArray) { Array a = opts->virt_lines.data.array; @@ -1693,7 +1685,7 @@ 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(virt_lines, jtem); + kv_push(decor.virt_lines, ((struct virt_line){ jtem, virt_lines_leftcol })); if (ERROR_SET(err)) { goto error; } @@ -1703,8 +1695,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer goto error; } - OPTION_TO_BOOL(virt_lines_above, virt_lines_above, false); - OPTION_TO_BOOL(virt_lines_leftcol, virt_lines_leftcol, false); + + OPTION_TO_BOOL(decor.virt_lines_above, virt_lines_above, false); if (opts->priority.type == kObjectTypeInteger) { Integer val = opts->priority.data.integer; @@ -1774,7 +1766,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer if (ephemeral) { d = &decor; - } else if (kv_size(decor.virt_text) + } else if (kv_size(decor.virt_text) || kv_size(decor.virt_lines) || decor.priority != DECOR_PRIORITY_BASE || decor.hl_eol) { // TODO(bfredl): this is a bit sketchy. eventually we should @@ -1794,22 +1786,11 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer goto error; } - if (kv_size(virt_lines) && buf->b_virt_line_mark) { - mtpos_t pos = marktree_lookup(buf->b_marktree, buf->b_virt_line_mark, NULL); - clear_virt_lines(buf, pos.row); // handles pos.row == -1 - } + extmark_set(buf, (uint64_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2, + d, right_gravity, end_right_gravity, kExtmarkNoUndo); - uint64_t mark = extmark_set(buf, (uint64_t)ns_id, &id, (int)line, (colnr_T)col, - line2, col2, d, right_gravity, - end_right_gravity, kExtmarkNoUndo); - - if (kv_size(virt_lines)) { - buf->b_virt_lines = virt_lines; - buf->b_virt_line_mark = mark; - buf->b_virt_line_pos = -1; - buf->b_virt_line_above = virt_lines_above; - buf->b_virt_line_leftcol = virt_lines_leftcol; - redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count, line+1+(virt_lines_above?0:1))); + if (kv_size(decor.virt_lines)) { + redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count, line+1+(decor.virt_lines_above?0:1))); } } @@ -2013,6 +1994,7 @@ Dictionary nvim__buf_stats(Buffer buffer, Error *err) // this exists to debug issues PUT(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes)); PUT(rv, "dirty_bytes2", INTEGER_OBJ((Integer)buf->deleted_bytes2)); + PUT(rv, "virt_blocks", INTEGER_OBJ((Integer)buf->b_virt_line_blocks)); u_header_T *uhp = NULL; if (buf->b_u_curhead != NULL) { diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 826a197454..0cc5dda018 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -818,7 +818,6 @@ static void free_buffer_stuff(buf_T *buf, int free_flags) uc_clear(&buf->b_ucmds); // clear local user commands buf_delete_signs(buf, (char_u *)"*"); // delete any signs extmark_free_all(buf); // delete any extmarks - clear_virt_lines(buf, -1); map_clear_int(buf, MAP_ALL_MODES, true, false); // clear local mappings map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs XFREE_CLEAR(buf->b_start_fenc); diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 19b0a3c5c6..c0a0376088 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -866,12 +866,7 @@ struct file_buffer { MarkTree b_marktree[1]; Map(uint64_t, ExtmarkItem) b_extmark_index[1]; Map(uint64_t, ExtmarkNs) b_extmark_ns[1]; // extmark namespaces - - VirtLines b_virt_lines; - uint64_t b_virt_line_mark; - int b_virt_line_pos; - bool b_virt_line_above; - bool b_virt_line_leftcol; + size_t b_virt_line_blocks; // number of virt_line blocks // array of channel_id:s which have asked to receive updates for this // buffer. diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 931c3fa20a..c0f3c32f93 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -91,12 +91,31 @@ void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) if (kv_size(decor->virt_text)) { redraw_buf_line_later(buf, row1+1); } + + if (kv_size(decor->virt_lines)) { + redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count, + row1+1+(decor->virt_lines_above?0:1))); + } +} + +void decor_remove(buf_T *buf, int row, int row2, Decoration *decor) +{ + if (kv_size(decor->virt_lines)) { + assert(buf->b_virt_line_blocks > 0); + buf->b_virt_line_blocks--; + } + decor_redraw(buf, row, row2, decor); + decor_free(decor); } void decor_free(Decoration *decor) { if (decor && !decor->shared) { 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); + } + kv_destroy(decor->virt_lines); xfree(decor); } } @@ -118,7 +137,7 @@ Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) mtmark_t mark = marktree_itr_current(itr); if (mark.row < 0 || mark.row > row) { break; - } else if (mt_decor_level(mark.id) < 1) { + } else if (marktree_decor_level(mark.id) < kDecorLevelVisible) { goto next_mark; } ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, @@ -162,7 +181,7 @@ bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state) break; } if ((mark.row < top_row && mark.id&MARKTREE_END_FLAG) - || mt_decor_level(mark.id) < 1) { + || marktree_decor_level(mark.id) < kDecorLevelVisible) { goto next_mark; } @@ -255,7 +274,8 @@ int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden, DecorState * break; } - if ((mark.id&MARKTREE_END_FLAG) || mt_decor_level(mark.id) < 1) { + if ((mark.id&MARKTREE_END_FLAG) + || marktree_decor_level(mark.id) < kDecorLevelVisible) { goto next_mark; } @@ -417,33 +437,38 @@ void decor_free_all_mem(void) } -int decor_virtual_lines(win_T *wp, linenr_T lnum) +int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines) { buf_T *buf = wp->w_buffer; - if (!buf->b_virt_line_mark) { + if (!buf->b_virt_line_blocks) { + // Only pay for what you use: in case virt_lines feature is not active + // in a buffer, plines do not need to access the marktree at all return 0; } - if (buf->b_virt_line_pos < 0) { - mtpos_t pos = marktree_lookup(buf->b_marktree, buf->b_virt_line_mark, NULL); - if (pos.row < 0) { - buf->b_virt_line_mark = 0; + + int virt_lines = 0; + int row = (int)MAX(lnum - 2, 0); + int end_row = (int)lnum; + MarkTreeIter itr[1] = { 0 }; + marktree_itr_get(buf->b_marktree, row, 0, itr); + while (true) { + mtmark_t mark = marktree_itr_current(itr); + if (mark.row < 0 || mark.row >= end_row) { + break; + } else if (marktree_decor_level(mark.id) < kDecorLevelVirtLine) { + goto next_mark; + } + bool above = mark.row > (int)(lnum - 2); + ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark.id, false); + if (item && item->decor && item->decor->virt_lines_above == above) { + virt_lines += (int)kv_size(item->decor->virt_lines); + if (lines) { + kv_splice(*lines, item->decor->virt_lines); + } } - buf->b_virt_line_pos = pos.row + (buf->b_virt_line_above ? 0 : 1); +next_mark: + marktree_itr_next(buf->b_marktree, itr); } - return (lnum-1 == buf->b_virt_line_pos) ? (int)kv_size(buf->b_virt_lines) : 0; -} - -void clear_virt_lines(buf_T *buf, int row) -{ - if (row > -1) { - redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count, - row+1+(buf->b_virt_line_above?0:1))); - } - for (size_t i = 0; i < kv_size(buf->b_virt_lines); i++) { - clear_virttext(&kv_A(buf->b_virt_lines, i)); - } - kv_destroy(buf->b_virt_lines); // re-initializes - buf->b_virt_line_pos = -1; - buf->b_virt_line_mark = 0; + return virt_lines; } diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index e44fbab0a5..97ae9ed5d8 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -24,22 +24,34 @@ typedef enum { kHlModeBlend, } HlMode; +typedef kvec_t(VirtTextChunk) VirtText; +#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 shared; // shared decoration, don't free + bool virt_lines_above; // TODO(bfredl): style, signs, etc DecorPriority priority; int col; // fixed col value, like win_col int virt_text_width; // width of virt_text }; -#define DECORATION_INIT { KV_INITIAL_VALUE, 0, kVTEndOfLine, kHlModeUnknown, \ - false, false, false, DECOR_PRIORITY_BASE, 0, 0 } +#define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, kHlModeUnknown, \ + false, false, false, false, DECOR_PRIORITY_BASE, 0, 0 } typedef struct { int start_row; @@ -60,9 +72,7 @@ typedef struct { int row; int col_until; int current; - int eol_col; - VirtText *virt_text; } DecorState; typedef struct { diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index dc3dc4268d..05d1d69f9d 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -83,8 +83,7 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t *idp, int row, colnr_T ExtmarkItem it = map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, old_mark); if (it.decor) { - decor_redraw(buf, row, row, it.decor); - decor_free(it.decor); + decor_remove(buf, row, row, it.decor); } mark = marktree_revise(buf->b_marktree, itr); goto revised; @@ -96,7 +95,13 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t *idp, int row, colnr_T } } - uint8_t decor_level = (decor != NULL) ? 1 : 0; + uint8_t decor_level = kDecorLevelNone; // no decor + if (decor) { + decor_level = kDecorLevelVisible; // decor affects redraw + if (kv_size(decor->virt_lines)) { + decor_level = kDecorLevelVirtLine; // decor affects horizontal size + } + } if (end_row > -1) { mark = marktree_put_pair(buf->b_marktree, @@ -119,6 +124,9 @@ revised: } if (decor) { + if (kv_size(decor->virt_lines)) { + buf->b_virt_line_blocks++; + } decor_redraw(buf, row, end_row > -1 ? end_row : row, decor); } @@ -172,12 +180,7 @@ bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id) } if (item.decor) { - decor_redraw(buf, pos.row, pos2.row, item.decor); - decor_free(item.decor); - } - - if (mark == buf->b_virt_line_mark) { - clear_virt_lines(buf, pos.row); + decor_remove(buf, pos.row, pos2.row, item.decor); } map_del(uint64_t, uint64_t)(ns->map, id); @@ -230,17 +233,13 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r marktree_del_itr(buf->b_marktree, itr, false); if (*del_status >= 0) { // we had a decor_id DecorItem it = kv_A(decors, *del_status); - decor_redraw(buf, it.row1, mark.row, it.decor); - decor_free(it.decor); + decor_remove(buf, it.row1, mark.row, it.decor); } map_del(uint64_t, ssize_t)(&delete_set, mark.id); continue; } uint64_t start_id = mark.id & ~MARKTREE_END_FLAG; - if (start_id == buf->b_virt_line_mark) { - clear_virt_lines(buf, mark.row); - } ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, start_id); @@ -259,8 +258,7 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r } map_put(uint64_t, ssize_t)(&delete_set, other, decor_id); } else if (item.decor) { - decor_redraw(buf, mark.row, mark.row, item.decor); - decor_free(item.decor); + decor_remove(buf, mark.row, mark.row, item.decor); } ExtmarkNs *my_ns = all_ns ? buf_ns_ref(buf, item.ns_id, false) : ns; map_del(uint64_t, uint64_t)(my_ns->map, item.mark_id); @@ -278,8 +276,7 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r marktree_del_itr(buf->b_marktree, itr, false); if (decor_id >= 0) { DecorItem it = kv_A(decors, decor_id); - decor_redraw(buf, it.row1, pos.row, it.decor); - decor_free(it.decor); + decor_remove(buf, it.row1, pos.row, it.decor); } }); map_clear(uint64_t, ssize_t)(&delete_set); @@ -510,7 +507,6 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo) kExtmarkNoUndo); } } - curbuf->b_virt_line_pos = -1; } @@ -590,7 +586,6 @@ void extmark_splice_impl(buf_T *buf, int start_row, colnr_T start_col, bcount_t colnr_T new_col, bcount_t new_byte, ExtmarkOp undo) { buf->deleted_bytes2 = 0; - buf->b_virt_line_pos = -1; buf_updates_send_splice(buf, start_row, start_col, start_byte, old_row, old_col, old_byte, new_row, new_col, new_byte); @@ -682,7 +677,6 @@ void extmark_move_region(buf_T *buf, int start_row, colnr_T start_col, bcount_t colnr_T new_col, bcount_t new_byte, ExtmarkOp undo) { buf->deleted_bytes2 = 0; - buf->b_virt_line_pos = -1; // TODO(bfredl): this is not synced to the buffer state inside the callback. // But unless we make the undo implementation smarter, this is not ensured // anyway. diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h index c0a4f4014f..4d4d9aefb5 100644 --- a/src/nvim/extmark_defs.h +++ b/src/nvim/extmark_defs.h @@ -11,10 +11,6 @@ typedef struct { int hl_id; } VirtTextChunk; -typedef kvec_t(VirtTextChunk) VirtText; -#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) -typedef kvec_t(VirtText) VirtLines; - typedef struct { @@ -38,4 +34,10 @@ typedef enum { kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable } ExtmarkOp; +typedef enum { + kDecorLevelNone = 0, + kDecorLevelVisible = 1, + kDecorLevelVirtLine = 2, +} DecorLevel; + #endif // NVIM_EXTMARK_DEFS_H diff --git a/src/nvim/lib/kvec.h b/src/nvim/lib/kvec.h index eb2d9bbb77..ff0a8bcb7c 100644 --- a/src/nvim/lib/kvec.h +++ b/src/nvim/lib/kvec.h @@ -94,6 +94,17 @@ memcpy((v1).items, (v0).items, sizeof((v1).items[0]) * (v0).size); \ } while (0) +#define kv_splice(v1, v0) \ + do { \ + if ((v1).capacity < (v1).size + (v0).size) { \ + (v1).capacity = (v1).size + (v0).size; \ + kv_roundup32((v1).capacity); \ + kv_resize((v1), (v1).capacity); \ + } \ + memcpy((v1).items + (v1).size, (v0).items, sizeof((v1).items[0]) * (v0).size); \ + (v1).size = (v1).size + (v0).size; \ + } while (0) + #define kv_pushp(v) \ ((((v).size == (v).capacity) ? (kv_resize_full(v), 0) : 0), \ ((v).items + ((v).size++))) @@ -101,6 +112,7 @@ #define kv_push(v, x) \ (*kv_pushp(v) = (x)) + #define kv_a(v, i) \ (*(((v).capacity <= (size_t)(i) \ ? ((v).capacity = (v).size = (i) + 1, \ diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h index aec5def879..a1dcdf5164 100644 --- a/src/nvim/marktree.h +++ b/src/nvim/marktree.h @@ -78,7 +78,9 @@ typedef struct { #define DECOR_LEVELS 4 #define DECOR_OFFSET 61 #define DECOR_MASK (((uint64_t)(DECOR_LEVELS-1)) << DECOR_OFFSET) -static inline uint8_t mt_decor_level(uint64_t id) { + +static inline uint8_t marktree_decor_level(uint64_t id) +{ return (uint8_t)((id&DECOR_MASK) >> DECOR_OFFSET); } diff --git a/src/nvim/plines.c b/src/nvim/plines.c index 5b0418ed92..ed28521d80 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -54,7 +54,7 @@ int plines_win(win_T *wp, linenr_T lnum, bool winheight) /// @return Number of filler lines above lnum int win_get_fill(win_T *wp, linenr_T lnum) { - int virt_lines = decor_virtual_lines(wp, lnum); + int virt_lines = decor_virt_lines(wp, lnum, NULL); // be quick when there are no filler lines if (diffopt_filler()) { @@ -69,7 +69,7 @@ int win_get_fill(win_T *wp, linenr_T lnum) bool win_may_fill(win_T *wp) { - return (wp->w_p_diff && diffopt_filler()) || wp->w_buffer->b_virt_line_mark; + return (wp->w_p_diff && diffopt_filler()) || wp->w_buffer->b_virt_line_blocks; } /// @param winheight when true limit to window height diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 32eb28e761..976b933c73 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2372,11 +2372,12 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc filler_lines = 0; area_highlighting = true; } - int virtual_lines = decor_virtual_lines(wp, lnum); - filler_lines += virtual_lines; + VirtLines virt_lines = KV_INITIAL_VALUE; + int n_virt_lines = decor_virt_lines(wp, lnum, &virt_lines); + filler_lines += n_virt_lines; if (lnum == wp->w_topline) { filler_lines = wp->w_topfill; - virtual_lines = MIN(virtual_lines, filler_lines); + n_virt_lines = MIN(n_virt_lines, filler_lines); } filler_todo = filler_lines; @@ -2904,7 +2905,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc if (draw_state == WL_SBR - 1 && n_extra == 0) { draw_state = WL_SBR; - if (filler_todo > filler_lines - virtual_lines) { + if (filler_todo > filler_lines - n_virt_lines) { // TODO(bfredl): check this doesn't inhibit TUI-style // clear-to-end-of-line. c_extra = ' '; @@ -4423,12 +4424,12 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc int draw_col = col - boguscols; if (filler_todo > 0) { - int index = filler_todo - (filler_lines - virtual_lines); + int index = filler_todo - (filler_lines - n_virt_lines); if (index > 0) { - int fpos = kv_size(buf->b_virt_lines) - index; - assert(fpos >= 0); - int offset = buf->b_virt_line_leftcol ? 0 : win_col_offset; - draw_virt_text_item(buf, offset, kv_A(buf->b_virt_lines, fpos), + int i = kv_size(virt_lines) - index; + assert(i >= 0); + int offset = kv_A(virt_lines, i).left_col ? 0 : win_col_offset; + draw_virt_text_item(buf, offset, kv_A(virt_lines, i).line, kHlModeReplace, grid->Columns, offset); } } else { @@ -4506,6 +4507,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc cap_col = 0; } + kv_destroy(virt_lines); xfree(p_extra_free); return row; } diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 8074f91215..40c33d9a4d 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -901,15 +901,15 @@ if (h->n_buckets < new_n_buckets) { // expand screen:expect{grid=[[ if (h->n_buckets < new_n_buckets) { // expand | khkey_t *new_keys = (khkey_t *) | + {1:>> }{2:krealloc}: change the size of an allocation | {3:krealloc}((void *)h->keys, new_n_buckets * sizeof(k| hkey_t)); | h->keys = new_keys; | if (kh_is_map && val_size) { | - char *new_vals = {3:krealloc}( h->vals_buf, new_n_| - buck^ets * val_size); | + ^char *new_vals = {3:krealloc}( h->vals_buf, new_n_| + buckets * val_size); | {5:^^ REVIEW:}{6: new_vals variable seems unneccesary?} | h->vals_buf = new_vals; | - } | | ]]} @@ -1235,6 +1235,7 @@ if (h->n_buckets < new_n_buckets) { // expand | ]]} + meths.buf_clear_namespace(0, ns, 0, -1) meths.buf_set_extmark(0, ns, 2, 0, { virt_lines={ {{"Some special", "Special"}}; diff --git a/test/unit/marktree_spec.lua b/test/unit/marktree_spec.lua index cd9c7bef13..10d02d2eb4 100644 --- a/test/unit/marktree_spec.lua +++ b/test/unit/marktree_spec.lua @@ -97,7 +97,7 @@ describe('marktree', function() for i = 1,100 do for j = 1,100 do local gravitate = (i%2) > 0 - local id = tonumber(lib.marktree_put(tree, j, i, gravitate)) + local id = tonumber(lib.marktree_put(tree, j, i, gravitate, 0)) ok(id > 0) eq(nil, shadow[id]) shadow[id] = {j,i,gravitate} @@ -191,7 +191,7 @@ describe('marktree', function() -- https://github.com/neovim/neovim/pull/14719 lib.marktree_clear(tree) for i = 1,20 do - lib.marktree_put(tree, i, i, false) + lib.marktree_put(tree, i, i, false, 0) end lib.marktree_itr_get(tree, 10, 10, iter) -- cgit