mirror of
https://github.com/neovim/neovim.git
synced 2024-09-17 20:58:20 -04:00
perf(extmarks): add metadata for efficient filtering of special decorations
This expands on the global "don't pay for what you don't use" rules for these special extmark decorations: - inline virtual text, which needs to be processed in plines.c when we calculate the size of text on screen - virtual lines, which are needed when calculating "filler" lines - signs, with text and/or highlights, both of which needs to be processed for the entire line already at the beginning of a line. This adds a count to each node of the marktree, for how many special marks of each kind can be found in the subtree for this node. This makes it possible to quickly skip over these extra checks, when working in regions of the buffer not containing these kind of marks, instead of before where this could just be skipped if the entire _buffer_ didn't contain such marks.
This commit is contained in:
parent
cb6320e13f
commit
9af2be292d
@ -32,6 +32,7 @@
|
||||
#include "nvim/mapping.h"
|
||||
#include "nvim/mark.h"
|
||||
#include "nvim/mark_defs.h"
|
||||
#include "nvim/marktree.h"
|
||||
#include "nvim/memline.h"
|
||||
#include "nvim/memline_defs.h"
|
||||
#include "nvim/memory.h"
|
||||
@ -1282,7 +1283,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));
|
||||
PUT(rv, "virt_blocks", INTEGER_OBJ((Integer)buf_meta_total(buf, kMTMetaLines)));
|
||||
|
||||
u_header_T *uhp = NULL;
|
||||
if (buf->b_u_curhead != NULL) {
|
||||
|
@ -81,3 +81,8 @@ static inline varnumber_T buf_get_changedtick(const buf_T *const buf)
|
||||
{
|
||||
return buf->changedtick_di.di_tv.vval.v_number;
|
||||
}
|
||||
|
||||
static inline uint32_t buf_meta_total(const buf_T *b, MetaIndex m)
|
||||
{
|
||||
return b->b_marktree->meta_root[m];
|
||||
}
|
||||
|
@ -701,10 +701,6 @@ struct file_buffer {
|
||||
|
||||
MarkTree b_marktree[1];
|
||||
Map(uint32_t, uint32_t) b_extmark_ns[1]; // extmark namespaces
|
||||
size_t b_virt_text_inline; // number of inline virtual texts
|
||||
size_t b_virt_line_blocks; // number of virt_line blocks
|
||||
size_t b_signs; // number of sign extmarks
|
||||
size_t b_signs_with_text; // number of sign extmarks with text
|
||||
|
||||
// array of channel_id:s which have asked to receive updates for this
|
||||
// buffer.
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "nvim/macros_defs.h"
|
||||
#include "nvim/mark.h"
|
||||
#include "nvim/mark_defs.h"
|
||||
#include "nvim/marktree.h"
|
||||
#include "nvim/mbyte.h"
|
||||
#include "nvim/mbyte_defs.h"
|
||||
#include "nvim/memline.h"
|
||||
@ -329,7 +330,7 @@ static void changed_common(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnum
|
||||
// changed line may become invalid.
|
||||
if (i == 0 || wp->w_lines[i].wl_lnum < lnume
|
||||
|| (wp->w_lines[i].wl_lnum == lnume
|
||||
&& wp->w_buffer->b_virt_line_blocks > 0)) {
|
||||
&& buf_meta_total(wp->w_buffer, kMTMetaLines) > 0)) {
|
||||
// line included in change
|
||||
wp->w_lines[i].wl_valid = false;
|
||||
} else if (xtra != 0) {
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/ascii_defs.h"
|
||||
#include "nvim/buffer.h"
|
||||
#include "nvim/buffer_defs.h"
|
||||
#include "nvim/decoration.h"
|
||||
#include "nvim/drawscreen.h"
|
||||
@ -170,12 +171,6 @@ DecorSignHighlight decor_sh_from_inline(DecorHighlightInline item)
|
||||
void buf_put_decor(buf_T *buf, DecorInline decor, int row, int row2)
|
||||
{
|
||||
if (decor.ext) {
|
||||
DecorVirtText *vt = decor.data.ext.vt;
|
||||
while (vt) {
|
||||
buf_put_decor_virt(buf, vt);
|
||||
vt = vt->next;
|
||||
}
|
||||
|
||||
uint32_t idx = decor.data.ext.sh_idx;
|
||||
while (idx != DECOR_ID_INVALID) {
|
||||
DecorSignHighlight *sh = &kv_A(decor_items, idx);
|
||||
@ -185,28 +180,12 @@ void buf_put_decor(buf_T *buf, DecorInline decor, int row, int row2)
|
||||
}
|
||||
}
|
||||
|
||||
void buf_put_decor_virt(buf_T *buf, DecorVirtText *vt)
|
||||
{
|
||||
if (vt->flags &kVTIsLines) {
|
||||
buf->b_virt_line_blocks++;
|
||||
} else {
|
||||
if (vt->pos == kVPosInline) {
|
||||
buf->b_virt_text_inline++;
|
||||
}
|
||||
}
|
||||
if (vt->next) {
|
||||
buf_put_decor_virt(buf, vt->next);
|
||||
}
|
||||
}
|
||||
|
||||
static int sign_add_id = 0;
|
||||
void buf_put_decor_sh(buf_T *buf, DecorSignHighlight *sh, int row1, int row2)
|
||||
{
|
||||
if (sh->flags & kSHIsSign) {
|
||||
sh->sign_add_id = sign_add_id++;
|
||||
buf->b_signs++;
|
||||
if (sh->text[0]) {
|
||||
buf->b_signs_with_text++;
|
||||
buf_signcols_count_range(buf, row1, row2, 1, kFalse);
|
||||
}
|
||||
}
|
||||
@ -216,11 +195,6 @@ void buf_decor_remove(buf_T *buf, int row1, int row2, DecorInline decor, bool fr
|
||||
{
|
||||
decor_redraw(buf, row1, row2, decor);
|
||||
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);
|
||||
@ -233,28 +207,11 @@ void buf_decor_remove(buf_T *buf, int row1, int row2, DecorInline decor, bool fr
|
||||
}
|
||||
}
|
||||
|
||||
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--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void buf_remove_decor_sh(buf_T *buf, int row1, int row2, DecorSignHighlight *sh)
|
||||
{
|
||||
if (sh->flags & kSHIsSign) {
|
||||
assert(buf->b_signs > 0);
|
||||
buf->b_signs--;
|
||||
if (sh->text[0]) {
|
||||
assert(buf->b_signs_with_text > 0);
|
||||
buf->b_signs_with_text--;
|
||||
if (buf->b_signs_with_text) {
|
||||
if (buf_meta_total(buf, kMTMetaSignText)) {
|
||||
buf_signcols_count_range(buf, row1, row2, -1, kFalse);
|
||||
} else {
|
||||
buf->b_signcols.resized = true;
|
||||
@ -395,10 +352,10 @@ DecorVirtText *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) || !(mark.flags & MT_FLAG_DECOR_EXT)) {
|
||||
} else if (mt_invalid(mark)) {
|
||||
goto next_mark;
|
||||
}
|
||||
DecorVirtText *decor = mark.decor_data.ext.vt;
|
||||
DecorVirtText *decor = mt_decor_virt(mark);
|
||||
while (decor && (decor->flags & kVTIsLines)) {
|
||||
decor = decor->next;
|
||||
}
|
||||
@ -705,6 +662,9 @@ int sign_item_cmp(const void *p1, const void *p2)
|
||||
? n : (s2->sh->sign_add_id - s1->sh->sign_add_id);
|
||||
}
|
||||
|
||||
const uint32_t sign_filter[4] = {[kMTMetaSignText] = kMTFilterSelect,
|
||||
[kMTMetaSignHL] = kMTFilterSelect };
|
||||
|
||||
/// Return the sign attributes on the currently refreshed row.
|
||||
///
|
||||
/// @param[out] sattrs Output array for sign text and texthl id
|
||||
@ -731,6 +691,8 @@ void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[],
|
||||
}
|
||||
}
|
||||
|
||||
marktree_itr_step_out_filter(buf->b_marktree, itr, sign_filter);
|
||||
|
||||
while (itr->x) {
|
||||
MTKey mark = marktree_itr_current(itr);
|
||||
if (mark.pos.row != row) {
|
||||
@ -742,7 +704,7 @@ void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[],
|
||||
kv_push(signs, ((SignItem){ sh, mark.id }));
|
||||
}
|
||||
|
||||
marktree_itr_next(buf->b_marktree, itr);
|
||||
marktree_itr_next_filter(buf->b_marktree, itr, row + 1, 0, sign_filter);
|
||||
}
|
||||
|
||||
if (kv_size(signs)) {
|
||||
@ -788,6 +750,8 @@ DecorSignHighlight *decor_find_sign(DecorInline decor)
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t signtext_filter[4] = {[kMTMetaSignText] = kMTFilterSelect };
|
||||
|
||||
/// Count the number of signs in a range after adding/removing a sign, or to
|
||||
/// (re-)initialize a range in "b_signcols.count".
|
||||
///
|
||||
@ -795,7 +759,7 @@ DecorSignHighlight *decor_find_sign(DecorInline decor)
|
||||
/// @param clear kFalse, kTrue or kNone for an, added/deleted, cleared, or initialized range.
|
||||
void buf_signcols_count_range(buf_T *buf, int row1, int row2, int add, TriState clear)
|
||||
{
|
||||
if (!buf->b_signcols.autom || !buf->b_signs_with_text) {
|
||||
if (!buf->b_signcols.autom || !buf_meta_total(buf, kMTMetaSignText)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -821,6 +785,8 @@ void buf_signcols_count_range(buf_T *buf, int row1, int row2, int add, TriState
|
||||
}
|
||||
}
|
||||
|
||||
marktree_itr_step_out_filter(buf->b_marktree, itr, signtext_filter);
|
||||
|
||||
// Continue traversing the marktree until beyond "row2".
|
||||
while (itr->x) {
|
||||
MTKey mark = marktree_itr_current(itr);
|
||||
@ -835,7 +801,7 @@ void buf_signcols_count_range(buf_T *buf, int row1, int row2, int add, TriState
|
||||
}
|
||||
}
|
||||
|
||||
marktree_itr_next(buf->b_marktree, itr);
|
||||
marktree_itr_next_filter(buf->b_marktree, itr, row2 + 1, 0, signtext_filter);
|
||||
}
|
||||
|
||||
// For each row increment "b_signcols.count" at the number of counted signs,
|
||||
@ -884,11 +850,13 @@ bool decor_redraw_eol(win_T *wp, DecorState *state, int *eol_attr, int eol_col)
|
||||
return has_virt_pos;
|
||||
}
|
||||
|
||||
uint32_t lines_filter[4] = {[kMTMetaLines] = kMTFilterSelect };
|
||||
|
||||
/// @param has_fold whether line "lnum" has a fold, or kNone when not calculated yet
|
||||
int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fold)
|
||||
{
|
||||
buf_T *buf = wp->w_buffer;
|
||||
if (!buf->b_virt_line_blocks) {
|
||||
if (!buf_meta_total(buf, kMTMetaLines)) {
|
||||
// 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;
|
||||
@ -907,17 +875,15 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fo
|
||||
return 0;
|
||||
}
|
||||
|
||||
int virt_lines = 0;
|
||||
MarkTreeIter itr[1] = { 0 };
|
||||
marktree_itr_get(buf->b_marktree, start_row, 0, itr);
|
||||
if (!marktree_itr_get_filter(buf->b_marktree, start_row, 0, end_row, 0, lines_filter, itr)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int virt_lines = 0;
|
||||
while (true) {
|
||||
MTKey mark = marktree_itr_current(itr);
|
||||
if (mark.pos.row < 0 || mark.pos.row >= end_row) {
|
||||
break;
|
||||
} else if (mt_end(mark) || !(mark.flags & MT_FLAG_DECOR_VIRT_LINES)) {
|
||||
goto next_mark;
|
||||
}
|
||||
DecorVirtText *vt = mark.decor_data.ext.vt;
|
||||
DecorVirtText *vt = mt_decor_virt(mark);
|
||||
while (vt) {
|
||||
if (vt->flags & kVTIsLines) {
|
||||
bool above = vt->flags & kVTLinesAbove;
|
||||
@ -931,8 +897,10 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fo
|
||||
}
|
||||
vt = vt->next;
|
||||
}
|
||||
next_mark:
|
||||
marktree_itr_next(buf->b_marktree, itr);
|
||||
|
||||
if (!marktree_itr_next_filter(buf->b_marktree, itr, end_row, 0, lines_filter)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return virt_lines;
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "nvim/highlight_group.h"
|
||||
#include "nvim/indent.h"
|
||||
#include "nvim/mark_defs.h"
|
||||
#include "nvim/marktree.h"
|
||||
#include "nvim/match.h"
|
||||
#include "nvim/mbyte.h"
|
||||
#include "nvim/memline.h"
|
||||
@ -1645,7 +1646,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
|
||||
&decor_state);
|
||||
}
|
||||
|
||||
if (!has_foldtext && wp->w_buffer->b_virt_text_inline > 0) {
|
||||
if (!has_foldtext && buf_meta_total(wp->w_buffer, kMTMetaInline) > 0) {
|
||||
handle_inline_virtual_text(wp, &wlv, ptr - line);
|
||||
if (wlv.n_extra > 0 && wlv.virt_inline_hl_mode <= kHlModeReplace) {
|
||||
// restore search_attr and area_attr when n_extra is down to zero
|
||||
@ -1662,9 +1663,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
|
||||
}
|
||||
}
|
||||
|
||||
int *area_attr_p
|
||||
= wlv.extra_for_extmark && wlv.virt_inline_hl_mode <= kHlModeReplace
|
||||
? &saved_area_attr : &area_attr;
|
||||
int *area_attr_p = wlv.extra_for_extmark && wlv.virt_inline_hl_mode <= kHlModeReplace
|
||||
? &saved_area_attr : &area_attr;
|
||||
|
||||
// handle Visual or match highlighting in this line
|
||||
if (wlv.vcol == wlv.fromcol
|
||||
|
@ -88,6 +88,7 @@
|
||||
#include "nvim/highlight_defs.h"
|
||||
#include "nvim/highlight_group.h"
|
||||
#include "nvim/insexpand.h"
|
||||
#include "nvim/marktree.h"
|
||||
#include "nvim/match.h"
|
||||
#include "nvim/mbyte.h"
|
||||
#include "nvim/memline.h"
|
||||
@ -1220,7 +1221,7 @@ static bool win_redraw_signcols(win_T *wp)
|
||||
if (rebuild_stc) {
|
||||
wp->w_nrwidth_line_count = 0;
|
||||
} else if (wp->w_minscwidth == 0 && wp->w_maxscwidth == 1) {
|
||||
width = buf->b_signs_with_text > 0;
|
||||
width = buf_meta_total(buf, kMTMetaSignText) > 0;
|
||||
}
|
||||
|
||||
int scwidth = wp->w_scwidth;
|
||||
@ -2628,7 +2629,7 @@ int number_width(win_T *wp)
|
||||
|
||||
// If 'signcolumn' is set to 'number' and there is a sign to display, then
|
||||
// the minimal width for the number column is 2.
|
||||
if (n < 2 && wp->w_buffer->b_signs_with_text && wp->w_minscwidth == SCL_NUM) {
|
||||
if (n < 2 && buf_meta_total(wp->w_buffer, kMTMetaSignText) && wp->w_minscwidth == SCL_NUM) {
|
||||
n = 2;
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include "nvim/mapping.h"
|
||||
#include "nvim/mark.h"
|
||||
#include "nvim/mark_defs.h"
|
||||
#include "nvim/marktree.h"
|
||||
#include "nvim/mbyte.h"
|
||||
#include "nvim/mbyte_defs.h"
|
||||
#include "nvim/memline.h"
|
||||
@ -240,7 +241,7 @@ static void insert_enter(InsertState *s)
|
||||
|
||||
// need to position cursor again when on a TAB and
|
||||
// when on a char with inline virtual text
|
||||
if (gchar_cursor() == TAB || curbuf->b_virt_text_inline > 0) {
|
||||
if (gchar_cursor() == TAB || buf_meta_total(curbuf, kMTMetaInline) > 0) {
|
||||
curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL);
|
||||
}
|
||||
|
||||
@ -3472,7 +3473,7 @@ static bool ins_esc(int *count, int cmdchar, bool nomove)
|
||||
may_trigger_modechanged();
|
||||
// need to position cursor again when on a TAB and
|
||||
// when on a char with inline virtual text
|
||||
if (gchar_cursor() == TAB || curbuf->b_virt_text_inline > 0) {
|
||||
if (gchar_cursor() == TAB || buf_meta_total(curbuf, kMTMetaInline) > 0) {
|
||||
curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL);
|
||||
}
|
||||
|
||||
|
@ -507,7 +507,7 @@ static dict_T *get_buffer_info(buf_T *buf)
|
||||
}
|
||||
tv_dict_add_list(dict, S_LEN("windows"), windows);
|
||||
|
||||
if (buf->b_signs) {
|
||||
if (buf_has_signs(buf)) {
|
||||
// List of signs placed in this buffer
|
||||
tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf));
|
||||
}
|
||||
|
@ -225,7 +225,7 @@ ExtmarkInfoArray extmark_get(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_co
|
||||
} else {
|
||||
// Find all the marks beginning with the start position
|
||||
marktree_itr_get_ext(buf->b_marktree, MTPos(l_row, l_col),
|
||||
itr, reverse, false, NULL);
|
||||
itr, reverse, false, NULL, NULL);
|
||||
}
|
||||
|
||||
int order = reverse ? -1 : 1;
|
||||
|
@ -62,7 +62,7 @@
|
||||
#include "nvim/garray_defs.h"
|
||||
|
||||
#define T MT_BRANCH_FACTOR
|
||||
#define ILEN (sizeof(MTNode) + (2 * T) * sizeof(void *))
|
||||
#define ILEN (sizeof(MTNode) + sizeof(struct mtnode_inner_s))
|
||||
|
||||
#define ID_INCR (((uint64_t)1) << 2)
|
||||
|
||||
@ -181,6 +181,8 @@ static MTNode *id2node(MarkTree *b, uint64_t id)
|
||||
return pmap_get(uint64_t)(b->id2node, id);
|
||||
}
|
||||
|
||||
#define ptr s->i_ptr
|
||||
#define meta s->i_meta
|
||||
// put functions
|
||||
|
||||
// x must be an internal node, which is not full
|
||||
@ -226,15 +228,17 @@ static inline void split_node(MarkTree *b, MTNode *x, const int i, MTKey next)
|
||||
}
|
||||
if (y->level) {
|
||||
memcpy(z->ptr, &y->ptr[T], sizeof(MTNode *) * T);
|
||||
memcpy(z->meta, &y->meta[T], sizeof(z->meta[0]) * T);
|
||||
for (int j = 0; j < T; j++) {
|
||||
z->ptr[j]->parent = z;
|
||||
z->ptr[j]->p_idx = (int16_t)j;
|
||||
}
|
||||
}
|
||||
y->n = T - 1;
|
||||
memmove(&x->ptr[i + 2], &x->ptr[i + 1],
|
||||
sizeof(MTNode *) * (size_t)(x->n - i));
|
||||
memmove(&x->ptr[i + 2], &x->ptr[i + 1], sizeof(MTNode *) * (size_t)(x->n - i));
|
||||
memmove(&x->meta[i + 2], &x->meta[i + 1], sizeof(x->meta[0]) * (size_t)(x->n - i));
|
||||
x->ptr[i + 1] = z;
|
||||
meta_describe_node(x->meta[i + 1], z);
|
||||
z->parent = x; // == y->parent
|
||||
for (int j = i + 1; j < x->n + 2; j++) {
|
||||
x->ptr[j]->p_idx = (int16_t)j;
|
||||
@ -246,6 +250,13 @@ static inline void split_node(MarkTree *b, MTNode *x, const int i, MTKey next)
|
||||
refkey(b, x, i);
|
||||
x->n++;
|
||||
|
||||
uint32_t meta_inc[4];
|
||||
meta_describe_key(meta_inc, x->key[i]);
|
||||
for (int m = 0; m < kMTMetaCount; m++) {
|
||||
// y used contain all of z and x->key[i], discount those
|
||||
x->meta[i][m] -= (x->meta[i + 1][m] + meta_inc[m]);
|
||||
}
|
||||
|
||||
for (int j = 0; j < T - 1; j++) {
|
||||
relative(x->key[i].pos, &z->key[j].pos);
|
||||
}
|
||||
@ -262,7 +273,7 @@ static inline void split_node(MarkTree *b, MTNode *x, const int i, MTKey next)
|
||||
}
|
||||
|
||||
// x must not be a full node (even if there might be internal space)
|
||||
static inline void marktree_putp_aux(MarkTree *b, MTNode *x, MTKey k)
|
||||
static inline void marktree_putp_aux(MarkTree *b, MTNode *x, MTKey k, uint32_t *meta_inc)
|
||||
{
|
||||
// TODO(bfredl): ugh, make sure this is the _last_ valid (pos, gravity) position,
|
||||
// to minimize movement
|
||||
@ -285,7 +296,10 @@ static inline void marktree_putp_aux(MarkTree *b, MTNode *x, MTKey k)
|
||||
if (i > 0) {
|
||||
relative(x->key[i - 1].pos, &k.pos);
|
||||
}
|
||||
marktree_putp_aux(b, x->ptr[i], k);
|
||||
marktree_putp_aux(b, x->ptr[i], k, meta_inc);
|
||||
for (int m = 0; m < kMTMetaCount; m++) {
|
||||
x->meta[i][m] += meta_inc[m];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -416,7 +430,7 @@ void marktree_intersect_pair(MarkTree *b, uint64_t id, MarkTreeIter *itr, MarkTr
|
||||
}
|
||||
}
|
||||
}
|
||||
marktree_itr_next_skip(b, itr, skip, true, NULL);
|
||||
marktree_itr_next_skip(b, itr, skip, true, NULL, NULL);
|
||||
}
|
||||
#undef iat
|
||||
}
|
||||
@ -429,24 +443,75 @@ static MTNode *marktree_alloc_node(MarkTree *b, bool internal)
|
||||
return x;
|
||||
}
|
||||
|
||||
// really meta_inc[kMTMetaCount]
|
||||
static void meta_describe_key_inc(uint32_t *meta_inc, MTKey *k)
|
||||
{
|
||||
if (!mt_end(*k)) {
|
||||
meta_inc[kMTMetaInline] += (k->flags & MT_FLAG_DECOR_VIRT_TEXT_INLINE) ? 1 : 0;
|
||||
meta_inc[kMTMetaLines] += (k->flags & MT_FLAG_DECOR_VIRT_LINES) ? 1 : 0;
|
||||
meta_inc[kMTMetaSignHL] += (k->flags & MT_FLAG_DECOR_SIGNHL) ? 1 : 0;
|
||||
meta_inc[kMTMetaSignText] += (k->flags & MT_FLAG_DECOR_SIGNTEXT) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void meta_describe_key(uint32_t *meta_inc, MTKey k)
|
||||
{
|
||||
memset(meta_inc, 0, kMTMetaCount * sizeof(*meta_inc));
|
||||
meta_describe_key_inc(meta_inc, &k);
|
||||
}
|
||||
|
||||
// if x is internal, asumes x->meta[..] of children are correct
|
||||
static void meta_describe_node(uint32_t *meta_node, MTNode *x)
|
||||
{
|
||||
memset(meta_node, 0, kMTMetaCount * sizeof(meta_node[0]));
|
||||
for (int i = 0; i < x->n; i++) {
|
||||
meta_describe_key_inc(meta_node, &x->key[i]);
|
||||
}
|
||||
if (x->level) {
|
||||
for (int i = 0; i < x->n + 1; i++) {
|
||||
for (int m = 0; m < kMTMetaCount; m++) {
|
||||
meta_node[m] += x->meta[i][m];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool meta_has(const uint32_t *meta_count, MetaFilter meta_filter)
|
||||
{
|
||||
uint32_t count = 0;
|
||||
for (int m = 0; m < kMTMetaCount; m++) {
|
||||
count += meta_count[m] & meta_filter[m];
|
||||
}
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
void marktree_put_key(MarkTree *b, MTKey k)
|
||||
{
|
||||
k.flags |= MT_FLAG_REAL; // let's be real.
|
||||
if (!b->root) {
|
||||
b->root = marktree_alloc_node(b, true);
|
||||
}
|
||||
b->n_keys++;
|
||||
MTNode *r = b->root;
|
||||
if (r->n == 2 * T - 1) {
|
||||
MTNode *s = marktree_alloc_node(b, true);
|
||||
b->root = s; s->level = r->level + 1; s->n = 0;
|
||||
s->ptr[0] = r;
|
||||
for (int m = 0; m < kMTMetaCount; m++) {
|
||||
s->meta[0][m] = b->meta_root[m];
|
||||
}
|
||||
r->parent = s;
|
||||
r->p_idx = 0;
|
||||
split_node(b, s, 0, k);
|
||||
r = s;
|
||||
}
|
||||
marktree_putp_aux(b, r, k);
|
||||
|
||||
uint32_t meta_inc[4];
|
||||
meta_describe_key(meta_inc, k);
|
||||
marktree_putp_aux(b, r, k, meta_inc);
|
||||
for (int m = 0; m < 4; m++) {
|
||||
b->meta_root[m] += meta_inc[m];
|
||||
}
|
||||
b->n_keys++;
|
||||
}
|
||||
|
||||
/// INITIATING DELETION PROTOCOL:
|
||||
@ -498,6 +563,7 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
|
||||
}
|
||||
}
|
||||
|
||||
// 2.
|
||||
if (itr->x->level) {
|
||||
if (rev) {
|
||||
abort();
|
||||
@ -512,6 +578,9 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
|
||||
MTNode *x = itr->x;
|
||||
assert(x->level == 0);
|
||||
MTKey intkey = x->key[itr->i];
|
||||
|
||||
uint32_t meta_inc[4];
|
||||
meta_describe_key(meta_inc, intkey);
|
||||
if (x->n > itr->i + 1) {
|
||||
memmove(&x->key[itr->i], &x->key[itr->i + 1],
|
||||
sizeof(MTKey) * (size_t)(x->n - itr->i - 1));
|
||||
@ -557,11 +626,16 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
|
||||
}
|
||||
}
|
||||
|
||||
for (int m = 0; m < kMTMetaCount; m++) {
|
||||
p->meta[lnode->p_idx][m] -= meta_inc[m];
|
||||
}
|
||||
|
||||
lnode = p;
|
||||
ilvl--;
|
||||
} while (lnode != cur);
|
||||
|
||||
MTKey deleted = cur->key[curi];
|
||||
meta_describe_key(meta_inc, deleted);
|
||||
cur->key[curi] = intkey;
|
||||
refkey(b, cur, curi);
|
||||
// if `did_bubble` then we already added `start_id` to some parent
|
||||
@ -586,6 +660,20 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
|
||||
itr->i--;
|
||||
}
|
||||
|
||||
MTNode *lnode = cur;
|
||||
while (lnode->parent) {
|
||||
uint32_t *meta_p = lnode->parent->meta[lnode->p_idx];
|
||||
for (int m = 0; m < kMTMetaCount; m++) {
|
||||
meta_p[m] -= meta_inc[m];
|
||||
}
|
||||
|
||||
lnode = lnode->parent;
|
||||
}
|
||||
for (int m = 0; m < kMTMetaCount; m++) {
|
||||
assert(b->meta_root[m] >= meta_inc[m]);
|
||||
b->meta_root[m] -= meta_inc[m];
|
||||
}
|
||||
|
||||
// 5.
|
||||
bool itr_dirty = false;
|
||||
int rlvl = itr->lvl - 1;
|
||||
@ -646,6 +734,10 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
|
||||
if (b->root->level) {
|
||||
MTNode *oldroot = b->root;
|
||||
b->root = b->root->ptr[0];
|
||||
for (int m = 0; m < kMTMetaCount; m++) {
|
||||
assert(b->meta_root[m] == oldroot->meta[0][m]);
|
||||
}
|
||||
|
||||
b->root->parent = NULL;
|
||||
marktree_free_node(b, oldroot);
|
||||
} else {
|
||||
@ -910,10 +1002,10 @@ static MTNode *merge_node(MarkTree *b, MTNode *p, int i)
|
||||
{
|
||||
MTNode *x = p->ptr[i];
|
||||
MTNode *y = p->ptr[i + 1];
|
||||
Intersection m;
|
||||
kvi_init(m);
|
||||
Intersection mi;
|
||||
kvi_init(mi);
|
||||
|
||||
intersect_merge(&m, &x->intersect, &y->intersect);
|
||||
intersect_merge(&mi, &x->intersect, &y->intersect);
|
||||
|
||||
x->key[x->n] = p->key[i];
|
||||
refkey(b, x, x->n);
|
||||
@ -921,6 +1013,9 @@ static MTNode *merge_node(MarkTree *b, MTNode *p, int i)
|
||||
relative(p->key[i - 1].pos, &x->key[x->n].pos);
|
||||
}
|
||||
|
||||
uint32_t meta_inc[4];
|
||||
meta_describe_key(meta_inc, x->key[x->n]);
|
||||
|
||||
memmove(&x->key[x->n + 1], y->key, (size_t)y->n * sizeof(MTKey));
|
||||
for (int k = 0; k < y->n; k++) {
|
||||
refkey(b, x, x->n + 1 + k);
|
||||
@ -930,6 +1025,7 @@ static MTNode *merge_node(MarkTree *b, MTNode *p, int i)
|
||||
// bubble down: ranges that intersected old-x but not old-y or vice versa
|
||||
// must be moved to their respective children
|
||||
memmove(&x->ptr[x->n + 1], y->ptr, ((size_t)y->n + 1) * sizeof(MTNode *));
|
||||
memmove(&x->meta[x->n + 1], y->meta, ((size_t)y->n + 1) * sizeof(y->meta[0]));
|
||||
for (int k = 0; k < x->n + 1; k++) {
|
||||
// TODO(bfredl): dedicated impl for "Z |= Y"
|
||||
for (size_t idx = 0; idx < kv_size(x->intersect); idx++) {
|
||||
@ -948,9 +1044,14 @@ static MTNode *merge_node(MarkTree *b, MTNode *p, int i)
|
||||
}
|
||||
}
|
||||
x->n += y->n + 1;
|
||||
for (int m = 0; m < kMTMetaCount; m++) {
|
||||
// x now contains everything of y plus old p->key[i]
|
||||
p->meta[i][m] += (p->meta[i + 1][m] + meta_inc[m]);
|
||||
}
|
||||
|
||||
memmove(&p->key[i], &p->key[i + 1], (size_t)(p->n - i - 1) * sizeof(MTKey));
|
||||
memmove(&p->ptr[i + 1], &p->ptr[i + 2],
|
||||
(size_t)(p->n - i - 1) * sizeof(MTKey *));
|
||||
memmove(&p->ptr[i + 1], &p->ptr[i + 2], (size_t)(p->n - i - 1) * sizeof(MTKey *));
|
||||
memmove(&p->meta[i + 1], &p->meta[i + 2], (size_t)(p->n - i - 1) * sizeof(p->meta[0]));
|
||||
for (int j = i + 1; j < p->n; j++) { // note: one has been deleted
|
||||
p->ptr[j]->p_idx = (int16_t)j;
|
||||
}
|
||||
@ -961,7 +1062,7 @@ static MTNode *merge_node(MarkTree *b, MTNode *p, int i)
|
||||
|
||||
// move of a kvec_withinit_t, messy!
|
||||
// TODO(bfredl): special case version of intersect_merge(x_out, x_in_m_out, y) to avoid this
|
||||
kvi_move(&x->intersect, &m);
|
||||
kvi_move(&x->intersect, &mi);
|
||||
|
||||
return x;
|
||||
}
|
||||
@ -991,16 +1092,34 @@ static void pivot_right(MarkTree *b, MTPos p_pos, MTNode *p, const int i)
|
||||
memmove(&y->key[1], y->key, (size_t)y->n * sizeof(MTKey));
|
||||
if (y->level) {
|
||||
memmove(&y->ptr[1], y->ptr, ((size_t)y->n + 1) * sizeof(MTNode *));
|
||||
memmove(&y->meta[1], y->meta, ((size_t)y->n + 1) * sizeof(y->meta[0]));
|
||||
for (int j = 1; j < y->n + 2; j++) {
|
||||
y->ptr[j]->p_idx = (int16_t)j;
|
||||
}
|
||||
}
|
||||
|
||||
y->key[0] = p->key[i];
|
||||
refkey(b, y, 0);
|
||||
p->key[i] = x->key[x->n - 1];
|
||||
refkey(b, p, i);
|
||||
|
||||
uint32_t meta_inc_y[4];
|
||||
meta_describe_key(meta_inc_y, y->key[0]);
|
||||
uint32_t meta_inc_x[4];
|
||||
meta_describe_key(meta_inc_x, p->key[i]);
|
||||
|
||||
for (int m = 0; m < kMTMetaCount; m++) {
|
||||
p->meta[i + 1][m] += meta_inc_y[m];
|
||||
p->meta[i][m] -= meta_inc_x[m];
|
||||
}
|
||||
|
||||
if (x->level) {
|
||||
y->ptr[0] = x->ptr[x->n];
|
||||
memcpy(y->meta[0], x->meta[x->n], sizeof(y->meta[0]));
|
||||
for (int m = 0; m < kMTMetaCount; m++) {
|
||||
p->meta[i + 1][m] += y->meta[0][m];
|
||||
p->meta[i][m] -= y->meta[0][m];
|
||||
}
|
||||
y->ptr[0]->parent = y;
|
||||
y->ptr[0]->p_idx = 0;
|
||||
}
|
||||
@ -1069,14 +1188,30 @@ static void pivot_left(MarkTree *b, MTPos p_pos, MTNode *p, int i)
|
||||
refkey(b, x, x->n);
|
||||
p->key[i] = y->key[0];
|
||||
refkey(b, p, i);
|
||||
|
||||
uint32_t meta_inc_x[4];
|
||||
meta_describe_key(meta_inc_x, x->key[x->n]);
|
||||
uint32_t meta_inc_y[4];
|
||||
meta_describe_key(meta_inc_y, p->key[i]);
|
||||
for (int m = 0; m < kMTMetaCount; m++) {
|
||||
p->meta[i][m] += meta_inc_x[m];
|
||||
p->meta[i + 1][m] -= meta_inc_y[m];
|
||||
}
|
||||
|
||||
if (x->level) {
|
||||
x->ptr[x->n + 1] = y->ptr[0];
|
||||
memcpy(x->meta[x->n + 1], y->meta[0], sizeof(y->meta[0]));
|
||||
for (int m = 0; m < kMTMetaCount; m++) {
|
||||
p->meta[i + 1][m] -= y->meta[0][m];
|
||||
p->meta[i][m] += y->meta[0][m];
|
||||
}
|
||||
x->ptr[x->n + 1]->parent = x;
|
||||
x->ptr[x->n + 1]->p_idx = (int16_t)(x->n + 1);
|
||||
}
|
||||
memmove(y->key, &y->key[1], (size_t)(y->n - 1) * sizeof(MTKey));
|
||||
if (y->level) {
|
||||
memmove(y->ptr, &y->ptr[1], (size_t)y->n * sizeof(MTNode *));
|
||||
memmove(y->meta, &y->meta[1], (size_t)y->n * sizeof(y->meta[0]));
|
||||
for (int j = 0; j < y->n; j++) { // note: last item deleted
|
||||
y->ptr[j]->p_idx = (int16_t)j;
|
||||
}
|
||||
@ -1131,6 +1266,7 @@ void marktree_clear(MarkTree *b)
|
||||
}
|
||||
map_destroy(uint64_t, b->id2node);
|
||||
b->n_keys = 0;
|
||||
memset(b->meta_root, 0, kMTMetaCount * sizeof(b->meta_root[0]));
|
||||
assert(b->n_nodes == 0);
|
||||
}
|
||||
|
||||
@ -1231,11 +1367,11 @@ void marktree_restore_pair(MarkTree *b, MTKey key)
|
||||
|
||||
bool marktree_itr_get(MarkTree *b, int32_t row, int col, MarkTreeIter *itr)
|
||||
{
|
||||
return marktree_itr_get_ext(b, MTPos(row, col), itr, false, false, NULL);
|
||||
return marktree_itr_get_ext(b, MTPos(row, col), itr, false, false, NULL, NULL);
|
||||
}
|
||||
|
||||
bool marktree_itr_get_ext(MarkTree *b, MTPos p, MarkTreeIter *itr, bool last, bool gravity,
|
||||
MTPos *oldbase)
|
||||
MTPos *oldbase, MetaFilter meta_filter)
|
||||
{
|
||||
if (b->n_keys == 0) {
|
||||
itr->x = NULL;
|
||||
@ -1258,6 +1394,12 @@ bool marktree_itr_get_ext(MarkTree *b, MTPos p, MarkTreeIter *itr, bool last, bo
|
||||
if (itr->x->level == 0) {
|
||||
break;
|
||||
}
|
||||
if (meta_filter) {
|
||||
if (!meta_has(itr->x->meta[itr->i], meta_filter)) {
|
||||
// this takes us to the interal position after the first rejected node
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
itr->s[itr->lvl].i = itr->i;
|
||||
itr->s[itr->lvl].oldcol = itr->pos.col;
|
||||
@ -1276,6 +1418,7 @@ bool marktree_itr_get_ext(MarkTree *b, MTPos p, MarkTreeIter *itr, bool last, bo
|
||||
if (last) {
|
||||
return marktree_itr_prev(b, itr);
|
||||
} else if (itr->i >= itr->x->n) {
|
||||
// no need for "meta_filter" here, this just goes up one step
|
||||
return marktree_itr_next(b, itr);
|
||||
}
|
||||
return true;
|
||||
@ -1333,16 +1476,21 @@ int marktree_itr_last(MarkTree *b, MarkTreeIter *itr)
|
||||
|
||||
bool marktree_itr_next(MarkTree *b, MarkTreeIter *itr)
|
||||
{
|
||||
return marktree_itr_next_skip(b, itr, false, false, NULL);
|
||||
return marktree_itr_next_skip(b, itr, false, false, NULL, NULL);
|
||||
}
|
||||
|
||||
static bool marktree_itr_next_skip(MarkTree *b, MarkTreeIter *itr, bool skip, bool preload,
|
||||
MTPos oldbase[])
|
||||
MTPos oldbase[], MetaFilter meta_filter)
|
||||
{
|
||||
if (!itr->x) {
|
||||
return false;
|
||||
}
|
||||
itr->i++;
|
||||
if (meta_filter && itr->x->level > 0) {
|
||||
if (!meta_has(itr->x->meta[itr->i], meta_filter)) {
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
if (itr->x->level == 0 || skip) {
|
||||
if (preload && itr->x->level == 0 && skip) {
|
||||
// skip rest of this leaf node
|
||||
@ -1384,14 +1532,96 @@ static bool marktree_itr_next_skip(MarkTree *b, MarkTreeIter *itr, bool skip, bo
|
||||
if (preload && itr->x->level) {
|
||||
itr->i = -1;
|
||||
break;
|
||||
} else {
|
||||
itr->i = 0;
|
||||
}
|
||||
itr->i = 0;
|
||||
if (meta_filter && itr->x->level) {
|
||||
if (!meta_has(itr->x->meta[0], meta_filter)) {
|
||||
// itr->x has filtered keys but x->ptr[0] does not, don't enter the latter
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool marktree_itr_get_filter(MarkTree *b, int32_t row, int col, int stop_row, int stop_col,
|
||||
MetaFilter meta_filter, MarkTreeIter *itr)
|
||||
{
|
||||
if (!meta_has(b->meta_root, meta_filter)) {
|
||||
return false;
|
||||
}
|
||||
if (!marktree_itr_get_ext(b, MTPos(row, col), itr, false, false, NULL, meta_filter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return marktree_itr_check_filter(b, itr, stop_row, stop_col, meta_filter);
|
||||
}
|
||||
|
||||
/// use after marktree_itr_get_overlap() to continue in a filtered fashion
|
||||
///
|
||||
/// not strictly needed but steps out to the right parent node where there
|
||||
/// might be "start" keys matching the filter ("end" keys are properly handled
|
||||
/// by marktree_itr_step_overlap() already)
|
||||
bool marktree_itr_step_out_filter(MarkTree *b, MarkTreeIter *itr, MetaFilter meta_filter)
|
||||
{
|
||||
if (!meta_has(b->meta_root, meta_filter)) {
|
||||
itr->x = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
while (itr->x && itr->x->parent) {
|
||||
if (meta_has(itr->x->parent->meta[itr->x->p_idx], meta_filter)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
itr->i = itr->x->n;
|
||||
marktree_itr_next(b, itr); // no filter, just reuse the code for step to parent
|
||||
}
|
||||
|
||||
return itr->x;
|
||||
}
|
||||
|
||||
bool marktree_itr_next_filter(MarkTree *b, MarkTreeIter *itr, int stop_row, int stop_col,
|
||||
MetaFilter meta_filter)
|
||||
{
|
||||
if (!marktree_itr_next_skip(b, itr, false, false, NULL, meta_filter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return marktree_itr_check_filter(b, itr, stop_row, stop_col, meta_filter);
|
||||
}
|
||||
|
||||
const uint32_t meta_map[4] = { MT_FLAG_DECOR_VIRT_TEXT_INLINE, MT_FLAG_DECOR_VIRT_LINES,
|
||||
MT_FLAG_DECOR_SIGNHL, MT_FLAG_DECOR_SIGNTEXT };
|
||||
static bool marktree_itr_check_filter(MarkTree *b, MarkTreeIter *itr, int stop_row, int stop_col,
|
||||
MetaFilter meta_filter)
|
||||
{
|
||||
MTPos stop_pos = MTPos(stop_row, stop_col);
|
||||
|
||||
uint32_t key_filter = 0;
|
||||
for (int m = 0; m < kMTMetaCount; m++) {
|
||||
key_filter |= meta_map[m]&meta_filter[m];
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (pos_leq(stop_pos, marktree_itr_pos(itr))) {
|
||||
itr->x = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
MTKey k = rawkey(itr);
|
||||
if (!mt_end(k) && (k.flags & key_filter)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// this skips subtrees, but not keys, thus the outer loop
|
||||
if (!marktree_itr_next_skip(b, itr, false, false, NULL, meta_filter)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool marktree_itr_prev(MarkTree *b, MarkTreeIter *itr)
|
||||
{
|
||||
if (!itr->x) {
|
||||
@ -1602,6 +1832,33 @@ static void swap_keys(MarkTree *b, MarkTreeIter *itr1, MarkTreeIter *itr2, Damag
|
||||
kvi_push(*damage, ((Damage){ mt_lookup_key(rawkey(itr2)), itr2->x, itr1->x,
|
||||
itr2->i, itr1->i }));
|
||||
}
|
||||
|
||||
uint32_t meta_inc_1[4];
|
||||
meta_describe_key(meta_inc_1, rawkey(itr1));
|
||||
uint32_t meta_inc_2[4];
|
||||
meta_describe_key(meta_inc_2, rawkey(itr2));
|
||||
|
||||
if (memcmp(meta_inc_1, meta_inc_2, sizeof(meta_inc_1)) != 0) {
|
||||
MTNode *x1 = itr1->x;
|
||||
MTNode *x2 = itr2->x;
|
||||
while (x1 != x2) {
|
||||
if (x1->level <= x2->level) {
|
||||
// as the root node uniquely has the highest level, x1 cannot be it
|
||||
uint32_t *meta_node = x1->parent->meta[x1->p_idx];
|
||||
for (int m = 0; m < kMTMetaCount; m++) {
|
||||
meta_node[m] += meta_inc_2[m] - meta_inc_1[m];
|
||||
}
|
||||
x1 = x1->parent;
|
||||
}
|
||||
if (x2->level < x1->level) {
|
||||
uint32_t *meta_node = x2->parent->meta[x2->p_idx];
|
||||
for (int m = 0; m < kMTMetaCount; m++) {
|
||||
meta_node[m] += meta_inc_1[m] - meta_inc_2[m];
|
||||
}
|
||||
x2 = x2->parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MTKey key1 = rawkey(itr1);
|
||||
@ -1638,7 +1895,7 @@ bool marktree_splice(MarkTree *b, int32_t start_line, int start_col, int old_ext
|
||||
|
||||
MTPos oldbase[MT_MAX_DEPTH] = { 0 };
|
||||
|
||||
marktree_itr_get_ext(b, start, itr, false, true, oldbase);
|
||||
marktree_itr_get_ext(b, start, itr, false, true, oldbase, NULL);
|
||||
if (!itr->x) {
|
||||
// den e FÄRDIG
|
||||
return false;
|
||||
@ -1651,7 +1908,7 @@ bool marktree_splice(MarkTree *b, int32_t start_line, int start_col, int old_ext
|
||||
if (!pos_leq(old_extent, ipos)
|
||||
|| (old_extent.row == ipos.row && old_extent.col == ipos.col
|
||||
&& !mt_right(rawkey(itr)))) {
|
||||
marktree_itr_get_ext(b, old_extent, enditr, true, true, NULL);
|
||||
marktree_itr_get_ext(b, old_extent, enditr, true, true, NULL, NULL);
|
||||
assert(enditr->x);
|
||||
// "assert" (itr <= enditr)
|
||||
} else {
|
||||
@ -1706,7 +1963,7 @@ continue_same_node:
|
||||
oldbase[itr->lvl + 1] = rawkey(itr).pos;
|
||||
unrelative(oldbase[itr->lvl], &oldbase[itr->lvl + 1]);
|
||||
rawkey(itr).pos = loc_start;
|
||||
marktree_itr_next_skip(b, itr, false, false, oldbase);
|
||||
marktree_itr_next_skip(b, itr, false, false, oldbase, NULL);
|
||||
} else {
|
||||
rawkey(itr).pos = loc_start;
|
||||
if (itr->i < itr->x->n - 1) {
|
||||
@ -1739,7 +1996,7 @@ past_continue_same_node:
|
||||
oldbase[itr->lvl + 1] = oldpos;
|
||||
unrelative(oldbase[itr->lvl], &oldbase[itr->lvl + 1]);
|
||||
|
||||
marktree_itr_next_skip(b, itr, false, false, oldbase);
|
||||
marktree_itr_next_skip(b, itr, false, false, oldbase, NULL);
|
||||
} else {
|
||||
if (itr->i < itr->x->n - 1) {
|
||||
itr->i++;
|
||||
@ -1774,7 +2031,7 @@ past_continue_same_node:
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
marktree_itr_next_skip(b, itr, true, false, NULL);
|
||||
marktree_itr_next_skip(b, itr, true, false, NULL, NULL);
|
||||
}
|
||||
|
||||
if (kv_size(damage)) {
|
||||
@ -1844,7 +2101,7 @@ void marktree_move_region(MarkTree *b, int start_row, colnr_T start_col, int ext
|
||||
MTPos end = size;
|
||||
unrelative(start, &end);
|
||||
MarkTreeIter itr[1] = { 0 };
|
||||
marktree_itr_get_ext(b, start, itr, false, true, NULL);
|
||||
marktree_itr_get_ext(b, start, itr, false, true, NULL, NULL);
|
||||
kvec_t(MTKey) saved = KV_INITIAL_VALUE;
|
||||
while (itr->x) {
|
||||
MTKey k = marktree_itr_current(itr);
|
||||
@ -1996,9 +2253,12 @@ static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr)
|
||||
|
||||
// for unit test
|
||||
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, bool meta_inline)
|
||||
{
|
||||
uint16_t flags = mt_flags(right_gravity, false, false, false);
|
||||
// The specific choice is irrelevant here, we pick one counted decor
|
||||
// type to test the counting and filtering logic.
|
||||
flags |= meta_inline ? MT_FLAG_DECOR_VIRT_TEXT_INLINE : 0;
|
||||
MTKey key = { { row, col }, ns, id, flags, { .hl = DECOR_HIGHLIGHT_INLINE_INIT } };
|
||||
marktree_put(b, key, end_row, end_col, end_right);
|
||||
}
|
||||
@ -2033,7 +2293,8 @@ void marktree_check(MarkTree *b)
|
||||
|
||||
MTPos dummy;
|
||||
bool last_right = false;
|
||||
size_t nkeys = marktree_check_node(b, b->root, &dummy, &last_right);
|
||||
|
||||
size_t nkeys = marktree_check_node(b, b->root, &dummy, &last_right, b->meta_root);
|
||||
assert(b->n_keys == nkeys);
|
||||
assert(b->n_keys == map_size(b->id2node));
|
||||
#else
|
||||
@ -2043,7 +2304,8 @@ void marktree_check(MarkTree *b)
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
size_t marktree_check_node(MarkTree *b, MTNode *x, MTPos *last, bool *last_right)
|
||||
size_t marktree_check_node(MarkTree *b, MTNode *x, MTPos *last, bool *last_right,
|
||||
const uint32_t *meta_node_ref)
|
||||
{
|
||||
assert(x->n <= 2 * T - 1);
|
||||
// TODO(bfredl): too strict if checking "in repair" post-delete tree.
|
||||
@ -2052,7 +2314,7 @@ size_t marktree_check_node(MarkTree *b, MTNode *x, MTPos *last, bool *last_right
|
||||
|
||||
for (int i = 0; i < x->n; i++) {
|
||||
if (x->level) {
|
||||
n_keys += marktree_check_node(b, x->ptr[i], last, last_right);
|
||||
n_keys += marktree_check_node(b, x->ptr[i], last, last_right, x->meta[i]);
|
||||
} else {
|
||||
*last = (MTPos) { 0, 0 };
|
||||
}
|
||||
@ -2069,7 +2331,7 @@ size_t marktree_check_node(MarkTree *b, MTNode *x, MTPos *last, bool *last_right
|
||||
}
|
||||
|
||||
if (x->level) {
|
||||
n_keys += marktree_check_node(b, x->ptr[x->n], last, last_right);
|
||||
n_keys += marktree_check_node(b, x->ptr[x->n], last, last_right, x->meta[x->n]);
|
||||
unrelative(x->key[x->n - 1].pos, last);
|
||||
|
||||
for (int i = 0; i < x->n + 1; i++) {
|
||||
@ -2084,6 +2346,13 @@ size_t marktree_check_node(MarkTree *b, MTNode *x, MTPos *last, bool *last_right
|
||||
} else if (x->n > 0) {
|
||||
*last = x->key[x->n - 1].pos;
|
||||
}
|
||||
|
||||
uint32_t meta_node[4];
|
||||
meta_describe_node(meta_node, x);
|
||||
for (int m = 0; m < kMTMetaCount; m++) {
|
||||
assert(meta_node_ref[m] == meta_node[m]);
|
||||
}
|
||||
|
||||
return n_keys;
|
||||
}
|
||||
|
||||
|
@ -125,6 +125,11 @@ static inline DecorInline mt_decor(MTKey key)
|
||||
return (DecorInline){ .ext = key.flags & MT_FLAG_DECOR_EXT, .data = key.decor_data };
|
||||
}
|
||||
|
||||
static inline DecorVirtText *mt_decor_virt(MTKey mark)
|
||||
{
|
||||
return (mark.flags & MT_FLAG_DECOR_EXT) ? mark.decor_data.ext.vt : NULL;
|
||||
}
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "marktree.h.generated.h"
|
||||
#endif
|
||||
|
@ -22,6 +22,21 @@ typedef struct {
|
||||
} MTPos;
|
||||
#define MTPos(r, c) ((MTPos){ .row = (r), .col = (c) })
|
||||
|
||||
// Currently there are four counts, which makes for a uint32_t[4] per node
|
||||
// which makes for nice autovectorization into a single XMM or NEON register
|
||||
typedef enum {
|
||||
kMTMetaInline,
|
||||
kMTMetaLines,
|
||||
kMTMetaSignHL,
|
||||
kMTMetaSignText,
|
||||
kMTMetaCount, // sentinel, must be last
|
||||
} MetaIndex;
|
||||
|
||||
#define kMTFilterSelect ((uint32_t)-1)
|
||||
|
||||
// a filter should be set to kMTFilterSelect for the selected kinds, zero otherwise
|
||||
typedef const uint32_t *MetaFilter;
|
||||
|
||||
typedef struct mtnode_s MTNode;
|
||||
|
||||
typedef struct {
|
||||
@ -63,20 +78,26 @@ typedef struct {
|
||||
|
||||
typedef kvec_withinit_t(uint64_t, 4) Intersection;
|
||||
|
||||
// part of mtnode_s which is only allocated for inner nodes:
|
||||
// pointer to children as well as their meta counts
|
||||
struct mtnode_inner_s {
|
||||
MTNode *i_ptr[2 * MT_BRANCH_FACTOR];
|
||||
uint32_t i_meta[2 * MT_BRANCH_FACTOR][kMTMetaCount];
|
||||
};
|
||||
|
||||
struct mtnode_s {
|
||||
int32_t n;
|
||||
int16_t level;
|
||||
int16_t p_idx; // index in parent
|
||||
Intersection intersect;
|
||||
// TODO(bfredl): we could consider having a only-sometimes-valid
|
||||
// index into parent for faster "cached" lookup.
|
||||
MTNode *parent;
|
||||
MTKey key[2 * MT_BRANCH_FACTOR - 1];
|
||||
MTNode *ptr[];
|
||||
struct mtnode_inner_s s[];
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
MTNode *root;
|
||||
uint32_t meta_root[kMTMetaCount];
|
||||
size_t n_keys, n_nodes;
|
||||
PMap(uint64_t) id2node[1];
|
||||
} MarkTree;
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "nvim/ascii_defs.h"
|
||||
#include "nvim/buffer.h"
|
||||
#include "nvim/buffer_defs.h"
|
||||
#include "nvim/charset.h"
|
||||
#include "nvim/decoration.h"
|
||||
@ -75,6 +76,7 @@ int linetabsize(win_T *wp, linenr_T lnum)
|
||||
return win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum), (colnr_T)MAXCOL);
|
||||
}
|
||||
|
||||
const uint32_t inline_filter[4] = {[kMTMetaInline] = kMTFilterSelect };
|
||||
/// Prepare the structure passed to charsize functions.
|
||||
///
|
||||
/// "line" is the start of the line.
|
||||
@ -90,10 +92,9 @@ CSType init_charsize_arg(CharsizeArg *csarg, win_T *wp, linenr_T lnum, char *lin
|
||||
csarg->indent_width = INT_MIN;
|
||||
csarg->use_tabstop = !wp->w_p_list || wp->w_p_lcs_chars.tab1;
|
||||
|
||||
if (lnum > 0 && wp->w_buffer->b_virt_text_inline > 0) {
|
||||
marktree_itr_get(wp->w_buffer->b_marktree, lnum - 1, 0, csarg->iter);
|
||||
MTKey mark = marktree_itr_current(csarg->iter);
|
||||
if (mark.pos.row == lnum - 1) {
|
||||
if (lnum > 0) {
|
||||
if (marktree_itr_get_filter(wp->w_buffer->b_marktree, lnum - 1, 0, lnum, 0,
|
||||
inline_filter, csarg->iter)) {
|
||||
csarg->virt_row = lnum - 1;
|
||||
}
|
||||
}
|
||||
@ -173,7 +174,8 @@ CharSize charsize_regular(CharsizeArg *csarg, char *const cur, colnr_T const vco
|
||||
}
|
||||
}
|
||||
}
|
||||
marktree_itr_next(wp->w_buffer->b_marktree, csarg->iter);
|
||||
marktree_itr_next_filter(wp->w_buffer->b_marktree, csarg->iter, csarg->virt_row + 1, 0,
|
||||
inline_filter);
|
||||
}
|
||||
}
|
||||
|
||||
@ -661,7 +663,8 @@ void getvcols(win_T *wp, pos_T *pos1, pos_T *pos2, colnr_T *left, colnr_T *right
|
||||
/// Check if there may be filler lines anywhere in window "wp".
|
||||
bool win_may_fill(win_T *wp)
|
||||
{
|
||||
return (wp->w_p_diff && diffopt_filler()) || wp->w_buffer->b_virt_line_blocks;
|
||||
return ((wp->w_p_diff && diffopt_filler())
|
||||
|| buf_meta_total(wp->w_buffer, kMTMetaLines));
|
||||
}
|
||||
|
||||
/// Return the number of filler lines above "lnum".
|
||||
|
@ -250,13 +250,18 @@ static int buf_delete_signs(buf_T *buf, char *group, int id, linenr_T atlnum)
|
||||
|
||||
// When deleting the last sign need to redraw the windows to remove the
|
||||
// sign column. Not when curwin is NULL (this means we're exiting).
|
||||
if (!buf->b_signs_with_text && curwin != NULL) {
|
||||
if (!buf_meta_total(buf, kMTMetaSignText) && curwin != NULL) {
|
||||
changed_line_abv_curs();
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
bool buf_has_signs(const buf_T *buf)
|
||||
{
|
||||
return (buf_meta_total(buf, kMTMetaSignHL) + buf_meta_total(buf, kMTMetaSignText));
|
||||
}
|
||||
|
||||
/// List placed signs for "rbuf". If "rbuf" is NULL do it for all buffers.
|
||||
static void sign_list_placed(buf_T *rbuf, char *group)
|
||||
{
|
||||
@ -270,7 +275,7 @@ static void sign_list_placed(buf_T *rbuf, char *group)
|
||||
msg_putchar('\n');
|
||||
|
||||
while (buf != NULL && !got_int) {
|
||||
if (buf->b_signs) {
|
||||
if (buf_has_signs(buf)) {
|
||||
vim_snprintf(lbuf, MSG_BUF_LEN, _("Signs for %s:"), buf->b_fname);
|
||||
msg_puts_attr(lbuf, HL_ATTR(HLF_D));
|
||||
msg_putchar('\n');
|
||||
@ -415,7 +420,7 @@ static int sign_define_by_name(char *name, char *icon, char *text, char *linehl,
|
||||
} else {
|
||||
// Signs may already exist, a redraw is needed in windows with a non-empty sign list.
|
||||
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
|
||||
if (wp->w_buffer->b_signs) {
|
||||
if (buf_has_signs(wp->w_buffer)) {
|
||||
redraw_buf_later(wp->w_buffer, UPD_NOT_VALID);
|
||||
}
|
||||
}
|
||||
@ -542,7 +547,7 @@ static int sign_place(uint32_t *id, char *group, char *name, buf_T *buf, linenr_
|
||||
|
||||
static int sign_unplace_inner(buf_T *buf, int id, char *group, linenr_T atlnum)
|
||||
{
|
||||
if (!buf->b_signs) { // No signs in the buffer
|
||||
if (!buf_has_signs(buf)) { // No signs in the buffer
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
@ -562,7 +567,7 @@ static int sign_unplace_inner(buf_T *buf, int id, char *group, linenr_T atlnum)
|
||||
// When all the signs in a buffer are removed, force recomputing the
|
||||
// number column width (if enabled) in all the windows displaying the
|
||||
// buffer if 'signcolumn' is set to 'number' in that window.
|
||||
if (!buf->b_signs_with_text) {
|
||||
if (!buf_meta_total(buf, kMTMetaSignText)) {
|
||||
may_force_numberwidth_recompute(buf, true);
|
||||
}
|
||||
|
||||
@ -962,7 +967,7 @@ static void sign_get_placed_in_buf(buf_T *buf, linenr_T lnum, int sign_id, const
|
||||
tv_dict_add_list(d, S_LEN("signs"), l);
|
||||
|
||||
int64_t ns = group_get_ns(group);
|
||||
if (!buf->b_signs || ns < 0) {
|
||||
if (!buf_has_signs(buf) || ns < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1006,7 +1011,7 @@ static void sign_get_placed(buf_T *buf, linenr_T lnum, int id, const char *group
|
||||
sign_get_placed_in_buf(buf, lnum, id, group, retlist);
|
||||
} else {
|
||||
FOR_ALL_BUFFERS(cbuf) {
|
||||
if (cbuf->b_signs) {
|
||||
if (buf_has_signs(cbuf)) {
|
||||
sign_get_placed_in_buf(cbuf, 0, id, group, retlist);
|
||||
}
|
||||
}
|
||||
|
@ -67,6 +67,7 @@
|
||||
// Uncomment the next line for including the u_check() function. This warns
|
||||
// for errors in the debug information.
|
||||
// #define U_DEBUG 1
|
||||
#include "nvim/marktree.h"
|
||||
#define UH_MAGIC 0x18dade // value for uh_magic when in use
|
||||
#define UE_MAGIC 0xabc123 // value for ue_magic when in use
|
||||
|
||||
@ -2390,7 +2391,7 @@ static void u_undoredo(bool undo, bool do_buf_event)
|
||||
// may have SpellCap that should be removed or it needs to be
|
||||
// displayed. Schedule the next line for redrawing just in case.
|
||||
// Also just in case the line had a sign which needs to be removed.
|
||||
if ((spell_check_window(curwin) || curbuf->b_signs_with_text)
|
||||
if ((spell_check_window(curwin) || buf_meta_total(curbuf, kMTMetaSignText))
|
||||
&& bot <= curbuf->b_ml.ml_line_count) {
|
||||
redrawWinline(curwin, bot);
|
||||
}
|
||||
@ -2431,7 +2432,7 @@ static void u_undoredo(bool undo, bool do_buf_event)
|
||||
int row3 = -1;
|
||||
// Tricky: ExtmarkSavePos may come after ExtmarkSplice which does call
|
||||
// buf_signcols_count_range() but then misses the yet unrestored marks.
|
||||
if (curbuf->b_signcols.autom && curbuf->b_signs_with_text) {
|
||||
if (curbuf->b_signcols.autom && buf_meta_total(curbuf, kMTMetaSignText)) {
|
||||
for (int i = 0; i < (int)kv_size(curhead->uh_extmark); i++) {
|
||||
ExtmarkUndoObject undo_info = kv_A(curhead->uh_extmark, i);
|
||||
if (undo_info.type == kExtmarkSplice) {
|
||||
|
@ -34,21 +34,8 @@ local function shadoworder(tree, shadow, iter, giveorder)
|
||||
local mark = lib.marktree_itr_current(iter)
|
||||
local id = tonumber(mark.id)
|
||||
local spos = shadow[id]
|
||||
if mark.pos.row ~= spos[1] or mark.pos.col ~= spos[2] then
|
||||
error(
|
||||
'invalid pos for '
|
||||
.. id
|
||||
.. ':('
|
||||
.. mark.pos.row
|
||||
.. ', '
|
||||
.. mark.pos.col
|
||||
.. ') instead of ('
|
||||
.. spos[1]
|
||||
.. ', '
|
||||
.. spos[2]
|
||||
.. ')'
|
||||
)
|
||||
end
|
||||
eq(mark.pos.row, spos[1], mark.id)
|
||||
eq(mark.pos.col, spos[2], mark.id)
|
||||
if lib.mt_right_test(mark) ~= spos[3] then
|
||||
error('invalid gravity for ' .. id .. ':(' .. mark.pos.row .. ', ' .. mark.pos.col .. ')')
|
||||
end
|
||||
@ -100,31 +87,31 @@ local function shadowsplice(shadow, start, old_extent, new_extent)
|
||||
end
|
||||
end
|
||||
|
||||
local function dosplice(tree, shadow, start, old_extent, new_extent)
|
||||
lib.marktree_splice(
|
||||
tree,
|
||||
start[1],
|
||||
start[2],
|
||||
old_extent[1],
|
||||
old_extent[2],
|
||||
new_extent[1],
|
||||
new_extent[2]
|
||||
)
|
||||
shadowsplice(shadow, start, old_extent, new_extent)
|
||||
local function dosplice(tree, shadow, start, old, new)
|
||||
lib.marktree_splice(tree, start[1], start[2], old[1], old[2], new[1], new[2])
|
||||
shadowsplice(shadow, start, old, new)
|
||||
end
|
||||
|
||||
local ns = 10
|
||||
local last_id = nil
|
||||
|
||||
local function put(tree, row, col, gravitate, end_row, end_col, end_gravitate)
|
||||
local function put(tree, row, col, gravity, end_row, end_col, end_gravity)
|
||||
last_id = last_id + 1
|
||||
local my_id = last_id
|
||||
|
||||
end_row = end_row or -1
|
||||
end_col = end_col or -1
|
||||
end_gravitate = end_gravitate or false
|
||||
end_gravity = end_gravity or false
|
||||
|
||||
lib.marktree_put_test(tree, ns, my_id, row, col, gravitate, end_row, end_col, end_gravitate)
|
||||
lib.marktree_put_test(tree, ns, my_id, row, col, gravity, end_row, end_col, end_gravity, false)
|
||||
return my_id
|
||||
end
|
||||
|
||||
local function put_meta(tree, row, col, gravitate, meta)
|
||||
last_id = last_id + 1
|
||||
local my_id = last_id
|
||||
|
||||
lib.marktree_put_test(tree, ns, my_id, row, col, gravitate, -1, -1, false, meta)
|
||||
return my_id
|
||||
end
|
||||
|
||||
@ -580,4 +567,75 @@ describe('marktree', function()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
itp('works with meta counts', function()
|
||||
local tree = ffi.new('MarkTree[1]') -- zero initialized by luajit
|
||||
|
||||
-- add
|
||||
local shadow = {}
|
||||
for i = 1, 100 do
|
||||
for j = 1, 100 do
|
||||
local gravitate = (i % 2) > 0
|
||||
local inline = (j == 3 or j == 50 or j == 51 or j == 55) and i % 11 == 1
|
||||
inline = inline or ((j >= 80 and j < 85) and i % 3 == 1)
|
||||
local id = put_meta(tree, j, i, gravitate, inline)
|
||||
if inline then
|
||||
shadow[id] = { j, i, gravitate }
|
||||
end
|
||||
end
|
||||
-- checking every insert is too slow, but this is ok
|
||||
lib.marktree_check(tree)
|
||||
end
|
||||
|
||||
lib.marktree_check(tree)
|
||||
local iter = ffi.new('MarkTreeIter[1]')
|
||||
local filter = ffi.new('uint32_t[4]')
|
||||
filter[0] = -1
|
||||
ok(lib.marktree_itr_get_filter(tree, 0, 0, 101, 0, filter, iter))
|
||||
local seen = {}
|
||||
repeat
|
||||
local mark = lib.marktree_itr_current(iter)
|
||||
eq(nil, seen[mark.id])
|
||||
seen[mark.id] = true
|
||||
eq(mark.pos.row, shadow[mark.id][1])
|
||||
eq(mark.pos.col, shadow[mark.id][2])
|
||||
until not lib.marktree_itr_next_filter(tree, iter, 101, 0, filter)
|
||||
eq(tablelength(seen), tablelength(shadow))
|
||||
|
||||
-- delete
|
||||
for id = 1, 10000, 2 do
|
||||
lib.marktree_lookup_ns(tree, ns, id, false, iter)
|
||||
if shadow[id] then
|
||||
local mark = lib.marktree_itr_current(iter)
|
||||
eq(mark.pos.row, shadow[id][1])
|
||||
eq(mark.pos.col, shadow[id][2])
|
||||
shadow[id] = nil
|
||||
end
|
||||
lib.marktree_del_itr(tree, iter, false)
|
||||
if id % 100 == 1 then
|
||||
lib.marktree_check(tree)
|
||||
end
|
||||
end
|
||||
|
||||
-- Splice!
|
||||
dosplice(tree, shadow, { 82, 0 }, { 0, 50 }, { 0, 0 })
|
||||
lib.marktree_check(tree)
|
||||
|
||||
dosplice(tree, shadow, { 81, 50 }, { 2, 50 }, { 1, 0 })
|
||||
lib.marktree_check(tree)
|
||||
|
||||
dosplice(tree, shadow, { 2, 50 }, { 1, 50 }, { 0, 10 })
|
||||
lib.marktree_check(tree)
|
||||
|
||||
ok(lib.marktree_itr_get_filter(tree, 0, 0, 101, 0, filter, iter))
|
||||
seen = {}
|
||||
repeat
|
||||
local mark = lib.marktree_itr_current(iter)
|
||||
eq(nil, seen[mark.id])
|
||||
seen[mark.id] = true
|
||||
eq(mark.pos.row, shadow[mark.id][1])
|
||||
eq(mark.pos.col, shadow[mark.id][2])
|
||||
until not lib.marktree_itr_next_filter(tree, iter, 101, 0, filter)
|
||||
eq(tablelength(seen), tablelength(shadow))
|
||||
end)
|
||||
end)
|
||||
|
Loading…
Reference in New Issue
Block a user