Merge pull request #13243 from bfredl/intersection

feat(extmark): support highlighting and querying multiline ranges
This commit is contained in:
bfredl 2023-09-12 11:16:35 +02:00 committed by GitHub
commit 1c4a93b591
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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)