feat(extmark): support proper multiline ranges

The removes the previous restriction that nvim_buf_set_extmark()
could not be used to highlight arbitrary multi-line regions

The problem can be summarized as follows: let's assume an extmark with a
hl_group is placed covering the region (5,0) to (50,0) Now, consider
what happens if nvim needs to redraw a window covering the lines 20-30.
It needs to be able to ask the marktree what extmarks cover this region,
even if they don't begin or end here.

Therefore the marktree needs to be augmented with the information covers
a point, not just what marks begin or end there. To do this, we augment
each node with a field "intersect" which is a set the ids of the
marks which overlap this node, but only if it is not part of the set of
any parent. This ensures the number of nodes that need to be explicitly
marked grows only logarithmically with the total number of explicitly
nodes (and thus the number of of overlapping marks).

Thus we can quickly iterate all marks which overlaps any query position
by looking up what leaf node contains that position. Then we only need
to consider all "start" marks within that leaf node, and the "intersect"
set of that node and all its parents.

Now, and the major source of complexity is that the tree restructuring
operations (to ensure that each node has T-1 <= size <= 2*T-1) also need
to update these sets. If a full inner node is split in two, one of the
new parents might start to completely overlap some ranges and its ids
will need to be moved from its children's sets to its own set.
Similarly, if two undersized nodes gets joined into one, it might no
longer completely overlap some ranges, and now the children which do
needs to have the have the ids in its set instead. And then there are
the pivots! Yes the pivot operations when a child gets moved from one
parent to another.
This commit is contained in:
bfredl 2020-11-22 10:10:37 +01:00
parent 6b5f44817e
commit b04286a187
25 changed files with 2033 additions and 505 deletions

View File

@ -41,3 +41,4 @@ Checks: >
-readability-redundant-declaration,
-readability-redundant-function-ptr-dereference,
-readability-suspicious-call-argument,
-readability-non-const-parameter,

View File

@ -2546,7 +2546,7 @@ nvim_buf_get_extmark_by_id({buffer}, {ns_id}, {id}, {opts})
0-indexed (row, col) tuple or empty list () if extmark id was absent
*nvim_buf_get_extmarks()*
nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts})
nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {*opts})
Gets |extmarks| in "traversal order" from a |charwise| region defined by
buffer positions (inclusive, 0-indexed |api-indexing|).
@ -2560,6 +2560,10 @@ nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts})
If `end` is less than `start`, traversal works backwards. (Useful with
`limit`, to get the first marks prior to a given position.)
Note: when using extmark ranges (marks with a end_row/end_col position)
the `overlap` option might be useful. Otherwise only the start position of
an extmark will be considered.
Example: >lua
local api = vim.api
local pos = api.nvim_win_get_cursor(0)
@ -2589,6 +2593,8 @@ nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts})
• details: Whether to include the details dict
• hl_name: Whether to include highlight group name instead
of id, true if omitted
• overlap: Also include marks which overlap the range, even
if their start position is less than `start`
• type: Filter marks by type: "highlight", "sign",
"virt_text" and "virt_lines"
@ -2608,6 +2614,11 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {*opts})
Using the optional arguments, it is possible to use this to highlight a
range of text, and also to associate virtual text to the mark.
If present, the position defined by `end_col` and `end_row` should be
after the start position in order for the extmark to cover a range. An
earlier end position is not an error, but then it behaves like an empty
range (no highlighting).
Parameters: ~
• {buffer} Buffer handle, or 0 for current buffer
• {ns_id} Namespace id from |nvim_create_namespace()|

View File

@ -221,6 +221,15 @@ The following changes to existing APIs or features add new behavior.
"virtual_text" table, which gives users more control over how diagnostic
virtual text is displayed.
• Extmarks now fully support multi-line ranges, and a single extmark can be
used to highlight a range of arbitrary length. The |nvim_buf_set_extmark()|
API function already allowed you to define such ranges, but highlight regions
were not rendered consistently for a range that covers more than one line break.
This has now been fixed. Signs defined as part of a multi-line extmark also
apply to every line in the range, not just the first.
In addition, |nvim_buf_get_extmarks()| has gained an "overlap" option to
return such ranges even if they started before the specified position.
==============================================================================
REMOVED FEATURES *news-removed*

View File

@ -5,6 +5,13 @@ error('Cannot require a meta file')
vim.api = {}
--- @private
--- @param buffer integer
--- @param keys boolean
--- @param dot boolean
--- @return string
function vim.api.nvim__buf_debug_extmarks(buffer, keys, dot) end
--- @private
--- @param buffer integer
--- @param first integer
@ -313,6 +320,9 @@ function vim.api.nvim_buf_get_extmark_by_id(buffer, ns_id, id, opts) end
--- ```
--- If `end` is less than `start`, traversal works backwards. (Useful with
--- `limit`, to get the first marks prior to a given position.)
--- Note: when using extmark ranges (marks with a end_row/end_col position)
--- the `overlap` option might be useful. Otherwise only the start position of
--- an extmark will be considered.
--- Example:
--- ```lua
--- local api = vim.api
@ -337,11 +347,13 @@ function vim.api.nvim_buf_get_extmark_by_id(buffer, ns_id, id, opts) end
--- @param end_ any End of range (inclusive): a 0-indexed (row, col) or valid
--- extmark id (whose position defines the bound).
--- `api-indexing`
--- @param opts table<string,any> Optional parameters. Keys:
--- @param opts vim.api.keyset.get_extmarks Optional parameters. Keys:
--- • limit: Maximum number of marks to return
--- • details: Whether to include the details dict
--- • hl_name: Whether to include highlight group name instead
--- of id, true if omitted
--- • overlap: Also include marks which overlap the range, even
--- if their start position is less than `start`
--- • type: Filter marks by type: "highlight", "sign",
--- "virt_text" and "virt_lines"
--- @return any[]
@ -457,6 +469,10 @@ function vim.api.nvim_buf_line_count(buffer) end
--- waiting for the return value.)
--- Using the optional arguments, it is possible to use this to highlight a
--- range of text, and also to associate virtual text to the mark.
--- If present, the position defined by `end_col` and `end_row` should be
--- after the start position in order for the extmark to cover a range. An
--- earlier end position is not an error, but then it behaves like an empty
--- range (no highlighting).
---
--- @param buffer integer Buffer handle, or 0 for current buffer
--- @param ns_id integer Namespace id from `nvim_create_namespace()`

View File

@ -122,6 +122,13 @@ error('Cannot require a meta file')
--- @class vim.api.keyset.get_commands
--- @field builtin? boolean
--- @class vim.api.keyset.get_extmarks
--- @field limit? integer
--- @field details? boolean
--- @field hl_name? boolean
--- @field overlap? boolean
--- @field type? string
--- @class vim.api.keyset.get_highlight
--- @field id? integer
--- @field name? string

View File

@ -55,6 +55,9 @@ function M.range(bufnr, ns, higroup, start, finish, opts)
local inclusive = opts.inclusive or false
local priority = opts.priority or M.priorities.user
-- TODO: in case of 'v', 'V' (not block), this should calculate equivalent
-- bounds (row, col, end_row, end_col) as multiline regions are natively
-- supported now
local region = vim.region(bufnr, start, finish, regtype, inclusive)
for linenr, cols in pairs(region) do
local end_row

View File

@ -207,6 +207,16 @@ static inline void *_memcpy_free(void *const restrict dest, void *const restrict
/* 2^x initial array size. */ \
kvi_resize(v, (v).capacity << 1)
/// fit at least "len" more items
#define kvi_ensure_more_space(v, len) \
do { \
if ((v).capacity < (v).size + len) { \
(v).capacity = (v).size + len; \
kv_roundup32((v).capacity); \
kvi_resize((v), (v).capacity); \
} \
} while (0)
/// Get location where to store new element to a vector with preallocated array
///
/// @param[in,out] v Vector to push to.
@ -223,6 +233,19 @@ static inline void *_memcpy_free(void *const restrict dest, void *const restrict
#define kvi_push(v, x) \
(*kvi_pushp(v) = (x))
/// Copy a vector to a preallocated vector
///
/// @param[out] v1 destination
/// @param[in] v0 source (can be either vector or preallocated vector)
#define kvi_copy(v1, v0) \
do { \
if ((v1).capacity < (v0).size) { \
kvi_resize(v1, (v0).size); \
} \
(v1).size = (v0).size; \
memcpy((v1).items, (v0).items, sizeof((v1).items[0]) * (v0).size); \
} while (0)
/// Free array of elements of a vector with preallocated array if needed
///
/// @param[out] v Vector to free.

View File

@ -308,6 +308,10 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
/// If `end` is less than `start`, traversal works backwards. (Useful
/// with `limit`, to get the first marks prior to a given position.)
///
/// Note: when using extmark ranges (marks with a end_row/end_col position)
/// the `overlap` option might be useful. Otherwise only the start position
/// of an extmark will be considered.
///
/// Example:
/// <pre>lua
/// local api = vim.api
@ -334,11 +338,13 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
/// - limit: Maximum number of marks to return
/// - details: Whether to include the details dict
/// - hl_name: Whether to include highlight group name instead of id, true if omitted
/// - overlap: Also include marks which overlap the range, even if
/// their start position is less than `start`
/// - type: Filter marks by type: "highlight", "sign", "virt_text" and "virt_lines"
/// @param[out] err Error details, if any
/// @return List of [extmark_id, row, col] tuples in "traversal order".
Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object end, Dictionary opts,
Error *err)
Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object end,
Dict(get_extmarks) *opts, Error *err)
FUNC_API_SINCE(7)
{
Array rv = ARRAY_DICT_INIT;
@ -348,63 +354,32 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
return rv;
}
bool all_ns;
if (ns_id == -1) {
all_ns = true;
} else {
VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
return rv;
});
all_ns = false;
}
VALIDATE_INT(ns_id == -1 || ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
return rv;
});
bool details = opts->details;
bool hl_name = GET_BOOL_OR_TRUE(opts, get_extmarks, hl_name);
Integer limit = -1;
bool details = false;
bool hl_name = true;
ExtmarkType type = kExtmarkNone;
for (size_t i = 0; i < opts.size; i++) {
String k = opts.items[i].key;
Object *v = &opts.items[i].value;
if (strequal("limit", k.data)) {
VALIDATE_T("limit", kObjectTypeInteger, v->type, {
return rv;
});
limit = v->data.integer;
} else if (strequal("details", k.data)) {
details = api_object_to_bool(*v, "details", false, err);
if (ERROR_SET(err)) {
return rv;
}
} else if (strequal("hl_name", k.data)) {
hl_name = api_object_to_bool(*v, "hl_name", false, err);
if (ERROR_SET(err)) {
return rv;
}
} else if (strequal("type", k.data)) {
VALIDATE_EXP(v->type == kObjectTypeString, "type", "String", api_typename(v->type), {
return rv;
});
if (strequal(v->data.string.data, "sign")) {
type = kExtmarkSign;
} else if (strequal(v->data.string.data, "virt_text")) {
type = kExtmarkVirtText;
} else if (strequal(v->data.string.data, "virt_lines")) {
type = kExtmarkVirtLines;
} else if (strequal(v->data.string.data, "highlight")) {
type = kExtmarkHighlight;
} else {
VALIDATE_EXP(false, "type", "sign, virt_text, virt_lines or highlight", v->data.string.data, {
return rv;
});
}
if (HAS_KEY(opts, get_extmarks, type)) {
if (strequal(opts->type.data, "sign")) {
type = kExtmarkSign;
} else if (strequal(opts->type.data, "virt_text")) {
type = kExtmarkVirtText;
} else if (strequal(opts->type.data, "virt_lines")) {
type = kExtmarkVirtLines;
} else if (strequal(opts->type.data, "highlight")) {
type = kExtmarkHighlight;
} else {
VALIDATE_S(false, "'opts' key", k.data, {
VALIDATE_EXP(false, "type", "sign, virt_text, virt_lines or highlight", opts->type.data, {
return rv;
});
}
}
Integer limit = HAS_KEY(opts, get_extmarks, limit) ? opts->limit : -1;
if (limit == 0) {
return rv;
} else if (limit < 0) {
@ -429,11 +404,12 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
reverse = true;
}
// note: ns_id=-1 allowed, represented as UINT32_MAX
ExtmarkInfoArray marks = extmark_get(buf, (uint32_t)ns_id, l_row, l_col, u_row,
u_col, (int64_t)limit, reverse, all_ns, type);
u_col, (int64_t)limit, reverse, type, opts->overlap);
for (size_t i = 0; i < kv_size(marks); i++) {
ADD(rv, ARRAY_OBJ(extmark_to_array(&kv_A(marks, i), true, (bool)details, hl_name)));
ADD(rv, ARRAY_OBJ(extmark_to_array(&kv_A(marks, i), true, details, hl_name)));
}
kv_destroy(marks);
@ -451,6 +427,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// Using the optional arguments, it is possible to use this to highlight
/// a range of text, and also to associate virtual text to the mark.
///
/// If present, the position defined by `end_col` and `end_row` should be after
/// the start position in order for the extmark to cover a range.
/// An earlier end position is not an error, but then it behaves like an empty
/// range (no highlighting).
///
/// @param buffer Buffer handle, or 0 for current buffer
/// @param ns_id Namespace id from |nvim_create_namespace()|
/// @param line Line where to place the mark, 0-based. |api-indexing|
@ -1230,3 +1211,14 @@ free_exit:
clear_virttext(&virt_text);
return virt_text;
}
String nvim__buf_debug_extmarks(Buffer buffer, Boolean keys, Boolean dot, Error *err)
FUNC_API_SINCE(7)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
return NULL_STRING;
}
return mt_inspect(buf->b_marktree, keys, dot);
}

View File

@ -50,6 +50,15 @@ typedef struct {
Boolean ui_watched;
} Dict(set_extmark);
typedef struct {
OptionalKeys is_set__get_extmarks_;
Integer limit;
Boolean details;
Boolean hl_name;
Boolean overlap;
String type;
} Dict(get_extmarks);
typedef struct {
OptionalKeys is_set__keymap_;
Boolean noremap;

View File

@ -747,6 +747,7 @@ void buf_clear_file(buf_T *buf)
void buf_clear(void)
{
linenr_T line_count = curbuf->b_ml.ml_line_count;
extmark_free_all(curbuf); // delete any extmarks
while (!(curbuf->b_ml.ml_flags & ML_EMPTY)) {
ml_delete((linenr_T)1, false);
}

View File

@ -158,7 +158,7 @@ Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id)
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, row, 0, itr);
while (true) {
mtkey_t mark = marktree_itr_current(itr);
MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0 || mark.pos.row > row) {
break;
} else if (marktree_decor_level(mark) < kDecorLevelVisible) {
@ -189,7 +189,7 @@ bool decor_redraw_reset(win_T *wp, DecorState *state)
return wp->w_buffer->b_marktree->n_keys;
}
Decoration get_decor(mtkey_t mark)
Decoration get_decor(MTKey mark)
{
if (mark.decor_full) {
return *mark.decor_full;
@ -211,50 +211,20 @@ bool decor_redraw_start(win_T *wp, int top_row, DecorState *state)
{
buf_T *buf = wp->w_buffer;
state->top_row = top_row;
marktree_itr_get(buf->b_marktree, top_row, 0, state->itr);
if (!state->itr->node) {
if (!marktree_itr_get_overlap(buf->b_marktree, top_row, 0, state->itr)) {
return false;
}
marktree_itr_rewind(buf->b_marktree, state->itr);
while (true) {
mtkey_t mark = marktree_itr_current(state->itr);
if (mark.pos.row < 0) { // || mark.row > end_row
break;
}
if ((mark.pos.row < top_row && mt_end(mark))
|| marktree_decor_level(mark) < kDecorLevelVisible) {
goto next_mark;
MTPair pair;
while (marktree_itr_step_overlap(buf->b_marktree, state->itr, &pair)) {
if (marktree_decor_level(pair.start) < kDecorLevelVisible) {
continue;
}
Decoration decor = get_decor(mark);
Decoration decor = get_decor(pair.start);
mtpos_t altpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
// Exclude start marks if the end mark position is above the top row
// Exclude end marks if we have already added the start mark
if ((mt_start(mark) && altpos.row < top_row && !decor_virt_pos(&decor))
|| (mt_end(mark) && altpos.row >= top_row)) {
goto next_mark;
}
if (mt_end(mark)) {
decor_add(state, altpos.row, altpos.col, mark.pos.row, mark.pos.col,
&decor, false, mark.ns, mark.id);
} else {
if (altpos.row == -1) {
altpos.row = mark.pos.row;
altpos.col = mark.pos.col;
}
decor_add(state, mark.pos.row, mark.pos.col, altpos.row, altpos.col,
&decor, false, mark.ns, mark.id);
}
next_mark:
if (marktree_itr_node_done(state->itr)) {
marktree_itr_next(buf->b_marktree, state->itr);
break;
}
marktree_itr_next(buf->b_marktree, state->itr);
decor_add(state, pair.start.pos.row, pair.start.pos.col, pair.end_pos.row, pair.end_pos.col,
&decor, false, pair.start.ns, pair.start.id);
}
return true; // TODO(bfredl): check if available in the region
@ -268,7 +238,13 @@ bool decor_redraw_line(win_T *wp, int row, DecorState *state)
state->row = row;
state->col_until = -1;
state->eol_col = -1;
return true; // TODO(bfredl): be more precise
if (kv_size(state->active)) {
return true;
}
MTKey k = marktree_itr_current(state->itr);
return (k.pos.row >= 0 && k.pos.row <= row);
}
static void decor_add(DecorState *state, int start_row, int start_col, int end_row, int end_col,
@ -302,7 +278,7 @@ int decor_redraw_col(win_T *wp, int col, int win_col, bool hidden, DecorState *s
while (true) {
// TODO(bfredl): check duplicate entry in "intersection"
// branch
mtkey_t mark = marktree_itr_current(state->itr);
MTKey mark = marktree_itr_current(state->itr);
if (mark.pos.row < 0 || mark.pos.row > state->row) {
break;
} else if (mark.pos.row == state->row && mark.pos.col > col) {
@ -317,8 +293,7 @@ int decor_redraw_col(win_T *wp, int col, int win_col, bool hidden, DecorState *s
Decoration decor = get_decor(mark);
mtpos_t endpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
MTPos endpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
if (endpos.row == -1) {
endpos = mark.pos;
}
@ -412,8 +387,28 @@ void decor_redraw_signs(buf_T *buf, int row, int *num_signs, SignTextAttrs sattr
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, row, 0, itr);
// TODO(bfredl): integrate with main decor loop.
if (!marktree_itr_get_overlap(buf->b_marktree, row, 0, itr)) {
return;
}
MTPair pair;
while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) {
if (marktree_decor_level(pair.start) < kDecorLevelVisible) {
continue;
}
Decoration *decor = pair.start.decor_full;
if (!decor || !decor_has_sign(decor)) {
continue;
}
decor_to_sign(decor, num_signs, sattrs, num_id, line_id, cul_id);
}
while (true) {
mtkey_t mark = marktree_itr_current(itr);
MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0 || mark.pos.row > row) {
break;
}
@ -428,46 +423,52 @@ void decor_redraw_signs(buf_T *buf, int row, int *num_signs, SignTextAttrs sattr
goto next_mark;
}
if (decor->sign_text) {
int j;
for (j = (*num_signs); j > 0; j--) {
if (sattrs[j - 1].priority >= decor->priority) {
break;
}
if (j < SIGN_SHOW_MAX) {
sattrs[j] = sattrs[j - 1];
}
}
if (j < SIGN_SHOW_MAX) {
sattrs[j] = (SignTextAttrs) {
.text = decor->sign_text,
.hl_id = decor->sign_hl_id,
.priority = decor->priority
};
(*num_signs)++;
}
}
struct { HlPriId *dest; int hl; } cattrs[] = {
{ line_id, decor->line_hl_id },
{ num_id, decor->number_hl_id },
{ cul_id, decor->cursorline_hl_id },
{ NULL, -1 },
};
for (int i = 0; cattrs[i].dest; i++) {
if (cattrs[i].hl != 0 && decor->priority >= cattrs[i].dest->priority) {
*cattrs[i].dest = (HlPriId) {
.hl_id = cattrs[i].hl,
.priority = decor->priority
};
}
}
decor_to_sign(decor, num_signs, sattrs, num_id, line_id, cul_id);
next_mark:
marktree_itr_next(buf->b_marktree, itr);
}
}
static void decor_to_sign(Decoration *decor, int *num_signs, SignTextAttrs sattrs[],
HlPriId *num_id, HlPriId *line_id, HlPriId *cul_id)
{
if (decor->sign_text) {
int j;
for (j = (*num_signs); j > 0; j--) {
if (sattrs[j - 1].priority >= decor->priority) {
break;
}
if (j < SIGN_SHOW_MAX) {
sattrs[j] = sattrs[j - 1];
}
}
if (j < SIGN_SHOW_MAX) {
sattrs[j] = (SignTextAttrs) {
.text = decor->sign_text,
.hl_id = decor->sign_hl_id,
.priority = decor->priority
};
(*num_signs)++;
}
}
struct { HlPriId *dest; int hl; } cattrs[] = {
{ line_id, decor->line_hl_id },
{ num_id, decor->number_hl_id },
{ cul_id, decor->cursorline_hl_id },
{ NULL, -1 },
};
for (int i = 0; cattrs[i].dest; i++) {
if (cattrs[i].hl != 0 && decor->priority >= cattrs[i].dest->priority) {
*cattrs[i].dest = (HlPriId) {
.hl_id = cattrs[i].hl,
.priority = decor->priority
};
}
}
}
// Get the maximum required amount of sign columns needed between row and
// end_row.
int decor_signcols(buf_T *buf, DecorState *state, int row, int end_row, int max)
@ -488,7 +489,7 @@ int decor_signcols(buf_T *buf, DecorState *state, int row, int end_row, int max)
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, 0, -1, itr);
while (true) {
mtkey_t mark = marktree_itr_current(itr);
MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0 || mark.pos.row > end_row) {
break;
}
@ -525,7 +526,7 @@ int decor_signcols(buf_T *buf, DecorState *state, int row, int end_row, int max)
goto next_mark;
}
mtpos_t altpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
MTPos altpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
if (mt_end(mark)) {
if (mark.pos.row >= row && altpos.row <= end_row) {
@ -610,7 +611,7 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fo
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, start_row, 0, itr);
while (true) {
mtkey_t mark = marktree_itr_current(itr);
MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0 || mark.pos.row >= end_row) {
break;
} else if (mt_end(mark)

View File

@ -2889,15 +2889,21 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
&& !wp->w_p_wrap
&& wlv.filler_todo <= 0
&& (wp->w_p_rl ? wlv.col == 0 : wlv.col == grid->cols - 1)
&& !has_fold
&& (*ptr != NUL
|| lcs_eol_one > 0
|| (wlv.n_extra > 0 && (wlv.c_extra != NUL || *wlv.p_extra != NUL))
|| wlv.more_virt_inline_chunks)) {
c = wp->w_p_lcs_chars.ext;
wlv.char_attr = win_hl_attr(wp, HLF_AT);
mb_c = c;
mb_utf8 = check_mb_utf8(&c, u8cc);
&& !has_fold) {
if (*ptr == NUL && lcs_eol_one == 0 && has_decor) {
// Tricky: there might be a virtual text just _after_ the last char
decor_redraw_col(wp, (colnr_T)v, wlv.off, false, &decor_state);
handle_inline_virtual_text(wp, &wlv, v);
}
if (*ptr != NUL
|| lcs_eol_one > 0
|| (wlv.n_extra > 0 && (wlv.c_extra != NUL || *wlv.p_extra != NUL))
|| wlv.more_virt_inline_chunks) {
c = wp->w_p_lcs_chars.ext;
wlv.char_attr = win_hl_attr(wp, HLF_AT);
mb_c = c;
mb_utf8 = check_mb_utf8(&c, u8cc);
}
}
// advance to the next 'colorcolumn'
@ -3079,6 +3085,15 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
wlv.char_attr = saved_attr2;
}
if ((wp->w_p_rl ? (wlv.col < 0) : (wlv.col >= grid->cols)) && has_decor) {
// At the end of screen line: might need to peek for decorations just after
// this position. Without wrapping, we might need to display win_pos overlays
// from the entire text line.
colnr_T nextpos = wp->w_p_wrap ? (colnr_T)(ptr - line) : (colnr_T)strlen(line);
decor_redraw_col(wp, nextpos, wlv.off, true, &decor_state);
handle_inline_virtual_text(wp, &wlv, v);
}
// At end of screen line and there is more to come: Display the line
// so far. If there is no more to display it is caught above.
if ((wp->w_p_rl ? (wlv.col < 0) : (wlv.col >= grid->cols))

View File

@ -2410,6 +2410,9 @@ static void cmdpreview_restore_state(CpInfo *cpinfo)
buf->b_changed = cp_bufinfo.save_b_changed;
// Clear preview highlights.
extmark_clear(buf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL);
if (buf->b_u_seq_cur != cp_bufinfo.undo_info.save_b_u_seq_cur) {
int count = 0;
@ -2439,9 +2442,6 @@ static void cmdpreview_restore_state(CpInfo *cpinfo)
}
buf->b_p_ul = cp_bufinfo.save_b_p_ul; // Restore 'undolevels'
// Clear preview highlights.
extmark_clear(buf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL);
}
for (size_t i = 0; i < cpinfo->win_info.size; i++) {

View File

@ -82,7 +82,7 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col
id = ++*ns;
} else {
MarkTreeIter itr[1] = { 0 };
mtkey_t 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 (decor_state.running_on_lines) {
if (err) {
@ -124,8 +124,8 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col
}
}
mtkey_t mark = { { row, col }, ns_id, id, 0,
mt_flags(right_gravity, decor_level), 0, NULL };
MTKey mark = { { row, col }, ns_id, id, 0,
mt_flags(right_gravity, decor_level), 0, NULL };
if (decor_full) {
mark.decor_full = decor;
} else if (decor) {
@ -180,7 +180,7 @@ error:
static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col)
{
MarkTreeIter itr[1] = { 0 };
mtkey_t key = marktree_lookup(buf->b_marktree, mark, itr);
MTKey key = marktree_lookup(buf->b_marktree, mark, itr);
if (key.pos.row == -1) {
return false;
}
@ -199,14 +199,14 @@ static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col)
bool extmark_del(buf_T *buf, uint32_t ns_id, uint32_t id)
{
MarkTreeIter itr[1] = { 0 };
mtkey_t key = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr);
MTKey key = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr);
if (!key.id) {
return false;
}
assert(key.pos.row >= 0);
uint64_t other = marktree_del_itr(buf->b_marktree, itr, false);
mtkey_t key2 = key;
MTKey key2 = key;
if (other) {
key2 = marktree_lookup(buf->b_marktree, other, itr);
@ -250,7 +250,7 @@ bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_r
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, l_row, l_col, itr);
while (true) {
mtkey_t mark = marktree_itr_current(itr);
MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0
|| mark.pos.row > u_row
|| (mark.pos.row == u_row && mark.pos.col > u_col)) {
@ -292,7 +292,7 @@ bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_r
uint64_t id;
ssize_t decor_id;
map_foreach(&delete_set, id, decor_id, {
mtkey_t mark = marktree_lookup(buf->b_marktree, id, itr);
MTKey mark = marktree_lookup(buf->b_marktree, id, itr);
assert(marktree_itr_valid(itr));
marktree_del_itr(buf->b_marktree, itr, false);
if (decor_id >= 0) {
@ -313,17 +313,31 @@ bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_r
/// dir can be set to control the order of the array
/// amount = amount of marks to find or -1 for all
ExtmarkInfoArray extmark_get(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_row,
colnr_T u_col, int64_t amount, bool reverse, bool all_ns,
ExtmarkType type_filter)
colnr_T u_col, int64_t amount, bool reverse, ExtmarkType type_filter,
bool overlap)
{
ExtmarkInfoArray array = KV_INITIAL_VALUE;
MarkTreeIter itr[1];
// Find all the marks
marktree_itr_get_ext(buf->b_marktree, mtpos_t(l_row, l_col),
itr, reverse, false, NULL);
if (overlap) {
// Find all the marks overlapping the start position
if (!marktree_itr_get_overlap(buf->b_marktree, l_row, l_col, itr)) {
return array;
}
MTPair pair;
while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) {
push_mark(&array, ns_id, type_filter, pair.start, pair.end_pos, pair.end_right_gravity);
}
} 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);
}
int order = reverse ? -1 : 1;
while ((int64_t)kv_size(array) < amount) {
mtkey_t mark = marktree_itr_current(itr);
MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0
|| (mark.pos.row - u_row) * order > 0
|| (mark.pos.row == u_row && (mark.pos.col - u_col) * order > 0)) {
@ -333,35 +347,8 @@ ExtmarkInfoArray extmark_get(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_co
goto next_mark;
}
uint16_t type_flags = kExtmarkNone;
if (type_filter != kExtmarkNone) {
Decoration *decor = mark.decor_full;
if (decor && (decor->sign_text || decor->number_hl_id)) {
type_flags |= kExtmarkSign;
}
if (decor && decor->virt_text.size) {
type_flags |= kExtmarkVirtText;
}
if (decor && decor->virt_lines.size) {
type_flags |= kExtmarkVirtLines;
}
if ((decor && (decor->line_hl_id || decor->cursorline_hl_id))
|| mark.hl_id) {
type_flags |= kExtmarkHighlight;
}
}
if ((all_ns || mark.ns == ns_id) && type_flags & type_filter) {
mtkey_t end = marktree_get_alt(buf->b_marktree, mark, NULL);
kv_push(array, ((ExtmarkInfo) { .ns_id = mark.ns,
.mark_id = mark.id,
.row = mark.pos.row, .col = mark.pos.col,
.end_row = end.pos.row,
.end_col = end.pos.col,
.right_gravity = mt_right(mark),
.end_right_gravity = mt_right(end),
.decor = get_decor(mark) }));
}
MTKey end = marktree_get_alt(buf->b_marktree, mark, NULL);
push_mark(&array, ns_id, type_filter, mark, end.pos, mt_right(end));
next_mark:
if (reverse) {
marktree_itr_prev(buf->b_marktree, itr);
@ -372,16 +359,54 @@ next_mark:
return array;
}
static void push_mark(ExtmarkInfoArray *array, uint32_t ns_id, ExtmarkType type_filter, MTKey mark,
MTPos end_pos, bool end_right)
{
if (!(ns_id == UINT32_MAX || mark.ns == ns_id)) {
return;
}
uint16_t type_flags = kExtmarkNone;
if (type_filter != kExtmarkNone) {
Decoration *decor = mark.decor_full;
if (decor && (decor->sign_text || decor->number_hl_id)) {
type_flags |= kExtmarkSign;
}
if (decor && decor->virt_text.size) {
type_flags |= kExtmarkVirtText;
}
if (decor && decor->virt_lines.size) {
type_flags |= kExtmarkVirtLines;
}
if ((decor && (decor->line_hl_id || decor->cursorline_hl_id))
|| mark.hl_id) {
type_flags |= kExtmarkHighlight;
}
if (!(type_flags & type_filter)) {
return;
}
}
kv_push(*array, ((ExtmarkInfo) { .ns_id = mark.ns,
.mark_id = mark.id,
.row = mark.pos.row, .col = mark.pos.col,
.end_row = end_pos.row,
.end_col = end_pos.col,
.right_gravity = mt_right(mark),
.end_right_gravity = end_right,
.decor = get_decor(mark) }));
}
/// Lookup an extmark by id
ExtmarkInfo extmark_from_id(buf_T *buf, uint32_t ns_id, uint32_t id)
{
ExtmarkInfo ret = { 0, 0, -1, -1, -1, -1, false, false, DECORATION_INIT };
mtkey_t mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, NULL);
MTKey mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, NULL);
if (!mark.id) {
return ret;
}
assert(mark.pos.row >= 0);
mtkey_t end = marktree_get_alt(buf->b_marktree, mark, NULL);
MTKey end = marktree_get_alt(buf->b_marktree, mark, NULL);
ret.ns_id = ns_id;
ret.mark_id = id;
@ -406,7 +431,7 @@ void extmark_free_all(buf_T *buf)
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, 0, 0, itr);
while (true) {
mtkey_t mark = marktree_itr_current(itr);
MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0) {
break;
}
@ -462,7 +487,7 @@ void u_extmark_copy(buf_T *buf, int l_row, colnr_T l_col, int u_row, colnr_T u_c
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, (int32_t)l_row, l_col, itr);
while (true) {
mtkey_t mark = marktree_itr_current(itr);
MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0
|| mark.pos.row > u_row
|| (mark.pos.row == u_row && mark.pos.col > u_col)) {

File diff suppressed because it is too large Load Diff

View File

@ -6,29 +6,37 @@
#include <stddef.h>
#include <stdint.h>
#include "klib/kvec.h"
#include "nvim/assert.h"
#include "nvim/garray.h"
#include "nvim/map.h"
#include "nvim/pos.h"
#include "nvim/types.h"
// only for debug functions:
#include "api/private/defs.h"
struct mtnode_s;
#define MT_MAX_DEPTH 20
#define MT_BRANCH_FACTOR 10
// note max branch is actually 2*MT_BRANCH_FACTOR
// and strictly this is ceil(log2(2*MT_BRANCH_FACTOR + 1))
// as we need a pseudo-index for "right before this node"
#define MT_LOG2_BRANCH 5
typedef struct {
int32_t row;
int32_t col;
} mtpos_t;
#define mtpos_t(r, c) ((mtpos_t){ .row = (r), .col = (c) })
} MTPos;
#define MTPos(r, c) ((MTPos){ .row = (r), .col = (c) })
typedef struct mtnode_s mtnode_t;
typedef struct mtnode_s MTNode;
typedef struct {
mtpos_t pos;
MTPos pos;
int lvl;
mtnode_t *node;
MTNode *x;
int i;
struct {
int oldcol;
@ -36,33 +44,43 @@ typedef struct {
} s[MT_MAX_DEPTH];
size_t intersect_idx;
mtpos_t intersect_pos;
MTPos intersect_pos;
MTPos intersect_pos_x;
} MarkTreeIter;
#define marktree_itr_valid(itr) ((itr)->node != NULL)
#define marktree_itr_valid(itr) ((itr)->x != NULL)
// Internal storage
//
// NB: actual marks have flags > 0, so we can use (row,col,0) pseudo-key for
// "space before (row,col)"
typedef struct {
mtpos_t pos;
MTPos pos;
uint32_t ns;
uint32_t id;
int32_t hl_id;
uint16_t flags;
uint16_t priority;
Decoration *decor_full;
} mtkey_t;
#define MT_INVALID_KEY (mtkey_t) { { -1, -1 }, 0, 0, 0, 0, 0, NULL }
} MTKey;
typedef struct {
MTKey start;
MTPos end_pos;
bool end_right_gravity;
} MTPair;
#define MT_INVALID_KEY (MTKey) { { -1, -1 }, 0, 0, 0, 0, 0, NULL }
#define MT_FLAG_REAL (((uint16_t)1) << 0)
#define MT_FLAG_END (((uint16_t)1) << 1)
#define MT_FLAG_PAIRED (((uint16_t)1) << 2)
#define MT_FLAG_HL_EOL (((uint16_t)1) << 3)
// orphaned: the other side of this paired mark was deleted. this mark must be deleted very soon!
#define MT_FLAG_ORPHANED (((uint16_t)1) << 3)
#define MT_FLAG_HL_EOL (((uint16_t)1) << 4)
#define DECOR_LEVELS 4
#define MT_FLAG_DECOR_OFFSET 4
#define MT_FLAG_DECOR_OFFSET 5
#define MT_FLAG_DECOR_MASK (((uint16_t)(DECOR_LEVELS - 1)) << MT_FLAG_DECOR_OFFSET)
// next flag is (((uint16_t)1) << 6)
@ -73,39 +91,44 @@ typedef struct {
#define MT_FLAG_EXTERNAL_MASK (MT_FLAG_DECOR_MASK | MT_FLAG_RIGHT_GRAVITY | MT_FLAG_HL_EOL)
#define MARKTREE_END_FLAG (((uint64_t)1) << 63)
// this is defined so that start and end of the same range have adjacent ids
#define MARKTREE_END_FLAG ((uint64_t)1)
static inline uint64_t mt_lookup_id(uint32_t ns, uint32_t id, bool enda)
{
return (uint64_t)ns << 32 | id | (enda ? MARKTREE_END_FLAG : 0);
return (uint64_t)ns << 33 | (id <<1) | (enda ? MARKTREE_END_FLAG : 0);
}
#undef MARKTREE_END_FLAG
static inline uint64_t mt_lookup_key(mtkey_t key)
static inline uint64_t mt_lookup_key_side(MTKey key, bool end)
{
return mt_lookup_id(key.ns, key.id, end);
}
static inline uint64_t mt_lookup_key(MTKey key)
{
return mt_lookup_id(key.ns, key.id, key.flags & MT_FLAG_END);
}
static inline bool mt_paired(mtkey_t key)
static inline bool mt_paired(MTKey key)
{
return key.flags & MT_FLAG_PAIRED;
}
static inline bool mt_end(mtkey_t key)
static inline bool mt_end(MTKey key)
{
return key.flags & MT_FLAG_END;
}
static inline bool mt_start(mtkey_t key)
static inline bool mt_start(MTKey key)
{
return mt_paired(key) && !mt_end(key);
}
static inline bool mt_right(mtkey_t key)
static inline bool mt_right(MTKey key)
{
return key.flags & MT_FLAG_RIGHT_GRAVITY;
}
static inline uint8_t marktree_decor_level(mtkey_t key)
static inline uint8_t marktree_decor_level(MTKey key)
{
return (uint8_t)((key.flags&MT_FLAG_DECOR_MASK) >> MT_FLAG_DECOR_OFFSET);
}
@ -117,18 +140,27 @@ static inline uint16_t mt_flags(bool right_gravity, uint8_t decor_level)
| (decor_level << MT_FLAG_DECOR_OFFSET));
}
typedef kvec_withinit_t(uint64_t, 4) Intersection;
struct mtnode_s {
int32_t n;
int32_t level;
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_t *parent;
mtkey_t key[2 * MT_BRANCH_FACTOR - 1];
mtnode_t *ptr[];
MTNode *parent;
MTKey key[2 * MT_BRANCH_FACTOR - 1];
MTNode *ptr[];
};
static inline uint64_t mt_dbg_id(uint64_t id)
{
return (id>>1)&0xffffffff;
}
typedef struct {
mtnode_t *root;
MTNode *root;
size_t n_keys, n_nodes;
// TODO(bfredl): the pointer to node could be part of the larger
// Map(uint64_t, ExtmarkItem) essentially;

View File

@ -630,6 +630,7 @@ EXTERN unsigned rdb_flags;
#define RDB_NODELTA 0x008
#define RDB_LINE 0x010
#define RDB_FLUSH 0x020
#define RDB_INTERSECT 0x040
EXTERN long p_rdt; // 'redrawtime'
EXTERN long p_re; // 'regexpengine'

View File

@ -133,7 +133,7 @@ void init_chartabsize_arg(chartabsize_T *cts, win_T *wp, linenr_T lnum, colnr_T
if (cts->cts_row >= 0 && wp->w_buffer->b_virt_text_inline > 0) {
marktree_itr_get(wp->w_buffer->b_marktree, cts->cts_row, 0, cts->cts_iter);
mtkey_t mark = marktree_itr_current(cts->cts_iter);
MTKey mark = marktree_itr_current(cts->cts_iter);
if (mark.pos.row == cts->cts_row) {
cts->cts_has_virt_text = true;
}
@ -222,7 +222,7 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp)
int tab_size = size;
int col = (int)(s - line);
while (true) {
mtkey_t mark = marktree_itr_current(cts->cts_iter);
MTKey mark = marktree_itr_current(cts->cts_iter);
if (mark.pos.row != cts->cts_row || mark.pos.col > col) {
break;
} else if (mark.pos.col == col) {

View File

@ -7,6 +7,9 @@
// defined in version.c
extern char *Version;
extern char *longVersion;
#ifndef NDEBUG
extern char *version_cflags;
#endif
//
// Vim version number, name, etc. Patchlevel is defined in version.c.

View File

@ -753,7 +753,14 @@ describe('API/extmarks', function()
})
end)
-- TODO(bfredl): add more tests!
it('can get overlapping extmarks', function()
set_extmark(ns, 1, 0, 0, {end_row = 5, end_col=0})
set_extmark(ns, 2, 2, 5, {end_row = 2, end_col=30})
set_extmark(ns, 3, 0, 5, {end_row = 2, end_col=10})
set_extmark(ns, 4, 0, 0, {end_row = 1, end_col=0})
eq({{ 2, 2, 5 }}, get_extmarks(ns, {2, 0}, {2, -1}, { overlap=false }))
eq({{ 1, 0, 0 }, { 3, 0, 5}, {2, 2, 5}}, get_extmarks(ns, {2, 0}, {2, -1}, { overlap=true }))
end)
end)
it('replace works', function()

View File

@ -857,6 +857,11 @@ function module.testprg(name)
return ('%s/%s%s'):format(module.nvim_dir, name, ext)
end
function module.is_asan()
local version = module.eval('execute("verbose version")')
return version:match('-fsanitize=[a-z,]*address')
end
-- Returns a valid, platform-independent Nvim listen address.
-- Useful for communicating with child instances.
function module.new_pipename()

View File

@ -11,14 +11,10 @@ local load_adjust = helpers.load_adjust
local write_file = helpers.write_file
local is_os = helpers.is_os
local is_ci = helpers.is_ci
local function isasan()
local version = eval('execute("verbose version")')
return version:match('-fsanitize=[a-z,]*address')
end
local is_asan = helpers.is_asan
clear()
if isasan() then
if is_asan() then
pending('ASAN build is difficult to estimate memory usage', function() end)
return
elseif is_os('win') then

View File

@ -691,6 +691,7 @@ describe('extmark decorations', function()
[33] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGray};
[34] = {background = Screen.colors.Yellow};
[35] = {background = Screen.colors.Yellow, bold = true, foreground = Screen.colors.Blue};
[36] = {foreground = Screen.colors.Blue1, bold = true, background = Screen.colors.Red};
}
ns = meths.create_namespace 'test'
@ -1652,6 +1653,70 @@ describe('extmark decorations', function()
{24:-- VISUAL BLOCK --} |
]])
end)
it('supports multiline highlights', function()
insert(example_text)
feed 'gg'
for _,i in ipairs {1,2,3,5,6,7} do
for _,j in ipairs {2,5,10,15} do
meths.buf_set_extmark(0, ns, i, j, { end_col=j+2, hl_group = 'NonText'})
end
end
screen:expect{grid=[[
^for _,item in ipairs(items) do |
{1: }l{1:oc}al {1:te}xt,{1: h}l_id_cell, count = unpack(item) |
{1: }i{1:f }hl_{1:id}_ce{1:ll} ~= nil then |
{1: } {1: } hl{1:_i}d ={1: h}l_id_cell |
end |
{1: }f{1:or} _ {1:= }1, {1:(c}ount or 1) do |
{1: } {1: } lo{1:ca}l c{1:el}l = line[colpos] |
{1: } {1: } ce{1:ll}.te{1:xt} = text |
cell.hl_id = hl_id |
colpos = colpos+1 |
end |
end |
{1:~ }|
{1:~ }|
|
]]}
feed'5<c-e>'
screen:expect{grid=[[
^ {1: }f{1:or} _ {1:= }1, {1:(c}ount or 1) do |
{1: } {1: } lo{1:ca}l c{1:el}l = line[colpos] |
{1: } {1: } ce{1:ll}.te{1:xt} = text |
cell.hl_id = hl_id |
colpos = colpos+1 |
end |
end |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
|
]]}
meths.buf_set_extmark(0, ns, 1, 0, { end_line=8, end_col=10, hl_group = 'ErrorMsg'})
screen:expect{grid=[[
{4:^ }{36: }{4:f}{36:or}{4: _ }{36:= }{4:1, }{36:(c}{4:ount or 1) do} |
{4: }{36: }{4: }{36: }{4: lo}{36:ca}{4:l c}{36:el}{4:l = line[colpos]} |
{4: }{36: }{4: }{36: }{4: ce}{36:ll}{4:.te}{36:xt}{4: = text} |
{4: ce}ll.hl_id = hl_id |
colpos = colpos+1 |
end |
end |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
|
]]}
end)
end)
describe('decorations: inline virtual text', function()
@ -4136,7 +4201,6 @@ l5
end)
it('can add multiple signs (single extmark)', function()
pending('TODO(lewis6991): Support ranged signs')
insert(example_test3)
feed 'gg'
@ -4158,7 +4222,6 @@ l5
end)
it('can add multiple signs (multiple extmarks)', function()
pending('TODO(lewis6991): Support ranged signs')
insert(example_test3)
feed'gg'
@ -4219,7 +4282,6 @@ l5
end)
it('can add multiple signs (multiple extmarks) 3', function()
pending('TODO(lewis6991): Support ranged signs')
insert(example_test3)
feed 'gg'
@ -4289,7 +4351,6 @@ l5
end)
it('works with old signs (with range)', function()
pending('TODO(lewis6991): Support ranged signs')
insert(example_test3)
feed 'gg'
@ -4304,7 +4365,7 @@ l5
screen:expect{grid=[[
S3S4S1^l1 |
S2S3x l2 |
x S2S3l2 |
S5S3{1: }l3 |
S3{1: }l4 |
S3{1: }l5 |
@ -4317,8 +4378,6 @@ l5
end)
it('can add a ranged sign (with start out of view)', function()
pending('TODO(lewis6991): Support ranged signs')
insert(example_test3)
command 'set signcolumn=yes:2'
feed 'gg'

View File

@ -849,6 +849,16 @@ local function ptr2key(ptr)
return ffi.string(s)
end
local function is_asan()
cimport('./src/nvim/version.h')
local status, res = pcall(function() return lib.version_cflags end)
if status then
return ffi.string(res):match('-fsanitize=[a-z,]*address')
else
return false
end
end
local module = {
cimport = cimport,
cppimport = cppimport,
@ -876,6 +886,7 @@ local module = {
ptr2addr = ptr2addr,
ptr2key = ptr2key,
debug_log = debug_log,
is_asan = is_asan,
}
module = global_helpers.tbl_extend('error', module, global_helpers)
return function()

View File

@ -87,13 +87,18 @@ local function dosplice(tree, shadow, start, old_extent, new_extent)
shadowsplice(shadow, start, old_extent, new_extent)
end
local ns = 10
local last_id = nil
local function put(tree, row, col, gravitate)
local function put(tree, row, col, gravitate, end_row, end_col, end_gravitate)
last_id = last_id + 1
local my_id = last_id
lib.marktree_put_test(tree, my_id, row, col, gravitate);
end_row = end_row or -1
end_col = end_col or -1
end_gravitate = end_gravitate or false
lib.marktree_put_test(tree, ns, my_id, row, col, gravitate, end_row, end_col, end_gravitate);
return my_id
end
@ -102,7 +107,7 @@ describe('marktree', function()
last_id = 0
end)
itp('works', function()
itp('works', function()
local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit
local shadow = {}
local iter = ffi.new("MarkTreeIter[1]")
@ -129,7 +134,7 @@ describe('marktree', function()
eq({}, id2pos)
for i,ipos in pairs(shadow) do
local p = lib.marktree_lookup_ns(tree, -1, i, false, iter)
local p = lib.marktree_lookup_ns(tree, ns, i, false, iter)
eq(ipos[1], p.pos.row)
eq(ipos[2], p.pos.col)
local k = lib.marktree_itr_current(iter)
@ -210,10 +215,224 @@ describe('marktree', function()
lib.marktree_itr_get(tree, 10, 10, iter)
lib.marktree_del_itr(tree, iter, false)
eq(11, iter[0].node.key[iter[0].i].pos.col)
eq(11, iter[0].x.key[iter[0].i].pos.col)
lib.marktree_itr_get(tree, 11, 11, iter)
lib.marktree_del_itr(tree, iter, false)
eq(12, iter[0].node.key[iter[0].i].pos.col)
end)
eq(12, iter[0].x.key[iter[0].i].pos.col)
end)
itp("'intersect_mov' function works correctly", function()
local function mov(x, y, w)
local xa = ffi.new("uint64_t[?]", #x)
for i, xi in ipairs(x) do xa[i-1] = xi end
local ya = ffi.new("uint64_t[?]", #y)
for i, yi in ipairs(y) do ya[i-1] = yi end
local wa = ffi.new("uint64_t[?]", #w)
for i, wi in ipairs(w) do wa[i-1] = wi end
local dummy_size = #x + #y + #w
local wouta = ffi.new("uint64_t[?]", dummy_size)
local douta = ffi.new("uint64_t[?]", dummy_size)
local wsize = ffi.new("size_t[1]")
wsize[0] = dummy_size
local dsize = ffi.new("size_t[1]")
dsize[0] = dummy_size
local status = lib.intersect_mov_test(xa, #x, ya, #y, wa, #w, wouta, wsize, douta, dsize)
if status == 0 then error'wowza' end
local wout, dout = {}, {}
for i = 0,tonumber(wsize[0])-1 do table.insert(wout, tonumber(wouta[i])) end
for i = 0,tonumber(dsize[0])-1 do table.insert(dout, tonumber(douta[i])) end
return {wout, dout}
end
eq({{}, {}}, mov({}, {2, 3}, {2, 3}))
eq({{2, 3}, {}}, mov({}, {}, {2, 3}))
eq({{2, 3}, {}}, mov({2, 3}, {}, {}))
eq({{}, {2,3}}, mov({}, {2,3}, {}))
eq({{1, 5}, {}}, mov({1,2,5}, {2, 3}, {3}))
eq({{1, 2}, {}}, mov({1,2,5}, {5, 10}, {10}))
eq({{1, 2}, {5}}, mov({1,2}, {5, 10}, {10}))
eq({{1,3,5,7,9}, {2,4,6,8,10}}, mov({1,3,5,7,9}, {2,4,6,8,10}, {}))
eq({{1,3,5,7,9}, {2,6,10}}, mov({1,3,5,7,9}, {2,4,6,8,10}, {4, 8}))
eq({{1,4,7}, {2,5,8}}, mov({1,3,4,6,7,9}, {2,3,5,6,8,9}, {}))
eq({{1,4,7}, {}}, mov({1,3,4,6,7,9}, {2,3,5,6,8,9}, {2,5,8}))
eq({{0,1,4,7,10}, {}}, mov({1,3,4,6,7,9}, {2,3,5,6,8,9}, {0,2,5,8,10}))
end)
local function check_intersections(tree)
lib.marktree_check(tree)
-- to debug stuff disable this branch
if true == true then
ok(lib.marktree_check_intersections(tree))
return
end
local str1 = lib.mt_inspect(tree, true, true)
local dot1 = ffi.string(str1.data, str1.size)
local val = lib.marktree_check_intersections(tree)
if not val then
local str2 = lib.mt_inspect(tree, true, true)
local dot2 = ffi.string(str2.data, str2.size)
print("actual:\n\n".."Xafile.dot".."\n\nexpected:\n\n".."Xefile.dot".."\n")
print("nivå", tree[0].root.level);
io.stdout:flush()
local afil = io.open("Xafile.dot", "wb")
afil:write(dot1)
afil:close()
local efil = io.open("Xefile.dot", "wb")
efil:write(dot2)
efil:close()
ok(false)
else
ffi.C.xfree(str1.data)
end
end
itp('works with intersections', function()
local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit
local ids = {}
for i = 1,80 do
table.insert(ids, put(tree, 1, i, false, 2, 100-i, false))
check_intersections(tree)
end
for i = 1,80 do
lib.marktree_del_pair_test(tree, ns, ids[i])
check_intersections(tree)
end
ids = {}
for i = 1,80 do
table.insert(ids, put(tree, 1, i, false, 2, 100-i, false))
check_intersections(tree)
end
for i = 1,10 do
for j = 1,8 do
local ival = (j-1)*10+i
lib.marktree_del_pair_test(tree, ns, ids[ival])
check_intersections(tree)
end
end
end)
itp('works with intersections with a big tree', function()
local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit
local ids = {}
for i = 1,1000 do
table.insert(ids, put(tree, 1, i, false, 2, 1000-i, false))
if i % 10 == 1 then
check_intersections(tree)
end
end
check_intersections(tree)
eq(2000, tree[0].n_keys)
ok(tree[0].root.level >= 2)
local iter = ffi.new("MarkTreeIter[1]")
local k = 0
for i = 1,20 do
for j = 1,50 do
k = k + 1
local ival = (j-1)*20+i
if false == true then -- if there actually is a failure, this branch will fail out at the actual spot of the error
lib.marktree_lookup_ns(tree, ns, ids[ival], false, iter)
lib.marktree_del_itr(tree, iter, false)
check_intersections(tree)
lib.marktree_lookup_ns(tree, ns, ids[ival], true, iter)
lib.marktree_del_itr(tree, iter, false)
check_intersections(tree)
else
lib.marktree_del_pair_test(tree, ns, ids[ival])
if k % 5 == 1 then
check_intersections(tree)
end
end
end
end
eq(0, tree[0].n_keys)
end)
itp('works with intersections with a even bigger tree', function()
local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit
local ids = {}
-- too much overhead on ASAN
local size_factor = helpers.is_asan() and 3 or 10
local at_row = {}
for i = 1, 10 do
at_row[i] = {}
end
local size = 1000*size_factor
local k = 1
while k <= size do
for row1 = 1,9 do
for row2 = row1,10 do -- note row2 can be == row1, leads to empty ranges being tested when k > size/2
if k > size then
break
end
local id = put(tree, row1, k, false, row2, size-k, false)
table.insert(ids, id)
for i = row1+1, row2 do
table.insert(at_row[i], id)
end
--if tree[0].root.level == 4 then error("kk"..k) end
if k % 100*size_factor == 1 or (k < 2000 and k%100 == 1) then
check_intersections(tree)
end
k = k + 1
end
end
end
eq(2*size, tree[0].n_keys)
ok(tree[0].root.level >= 3)
check_intersections(tree)
local iter = ffi.new("MarkTreeIter[1]")
local pair = ffi.new("MTPair[1]")
for i = 1,10 do
-- use array as set and not {[id]=true} map, to detect duplicates
local set = {}
eq(true, ffi.C.marktree_itr_get_overlap(tree, i, 0, iter))
while ffi.C.marktree_itr_step_overlap(tree, iter, pair) do
local id = tonumber(pair[0].start.id)
table.insert(set, id)
end
table.sort(set)
eq(at_row[i], set)
end
k = 0
for i = 1,100 do
for j = 1,(10*size_factor) do
k = k + 1
local ival = (j-1)*100+i
lib.marktree_del_pair_test(tree, ns, ids[ival])
-- just a few stickprov, if there is trouble we need to check
-- everyone using the code in the "big tree" case above
if k % 100*size_factor == 0 or (k > 3000 and k % 200 == 0) then
check_intersections(tree)
end
end
end
eq(0, tree[0].n_keys)
end)
end)