mirror of
https://github.com/neovim/neovim.git
synced 2024-12-19 18:55:14 -07:00
Merge pull request #30869 from vanaigr/decor_long_lines_perf
perf(decor): improve performance for long lines
This commit is contained in:
commit
c87ca1e2eb
@ -254,7 +254,8 @@ OPTIONS
|
|||||||
|
|
||||||
PERFORMANCE
|
PERFORMANCE
|
||||||
|
|
||||||
• TODO
|
• Significantly reduced redraw time for long lines with treesitter
|
||||||
|
highlighting.
|
||||||
|
|
||||||
PLUGINS
|
PLUGINS
|
||||||
|
|
||||||
|
@ -314,7 +314,8 @@ void decor_check_to_be_deleted(void)
|
|||||||
|
|
||||||
void decor_state_free(DecorState *state)
|
void decor_state_free(DecorState *state)
|
||||||
{
|
{
|
||||||
kv_destroy(state->active);
|
kv_destroy(state->slots);
|
||||||
|
kv_destroy(state->ranges_i);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear_virttext(VirtText *text)
|
void clear_virttext(VirtText *text)
|
||||||
@ -399,14 +400,30 @@ bool decor_redraw_reset(win_T *wp, DecorState *state)
|
|||||||
{
|
{
|
||||||
state->row = -1;
|
state->row = -1;
|
||||||
state->win = wp;
|
state->win = wp;
|
||||||
for (size_t i = 0; i < kv_size(state->active); i++) {
|
|
||||||
DecorRange item = kv_A(state->active, i);
|
int *const indices = state->ranges_i.items;
|
||||||
if (item.owned && item.kind == kDecorKindVirtText) {
|
DecorRangeSlot *const slots = state->slots.items;
|
||||||
clear_virttext(&item.data.vt->data.virt_text);
|
|
||||||
xfree(item.data.vt);
|
int const beg_pos[] = { 0, state->future_begin };
|
||||||
|
int const end_pos[] = { state->current_end, (int)kv_size(state->ranges_i) };
|
||||||
|
|
||||||
|
for (int pos_i = 0; pos_i < 2; pos_i++) {
|
||||||
|
for (int i = beg_pos[pos_i]; i < end_pos[pos_i]; i++) {
|
||||||
|
DecorRange *const r = &slots[indices[i]].range;
|
||||||
|
if (r->owned && r->kind == kDecorKindVirtText) {
|
||||||
|
clear_virttext(&r->data.vt->data.virt_text);
|
||||||
|
xfree(r->data.vt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
kv_size(state->active) = 0;
|
}
|
||||||
|
|
||||||
|
kv_size(state->slots) = 0;
|
||||||
|
kv_size(state->ranges_i) = 0;
|
||||||
|
state->free_slot_i = -1;
|
||||||
|
state->current_end = 0;
|
||||||
|
state->future_begin = 0;
|
||||||
|
state->new_range_ordering = 0;
|
||||||
|
|
||||||
return wp->w_buffer->b_marktree->n_keys;
|
return wp->w_buffer->b_marktree->n_keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -452,6 +469,25 @@ bool decor_redraw_start(win_T *wp, int top_row, DecorState *state)
|
|||||||
|
|
||||||
bool decor_redraw_line(win_T *wp, int row, DecorState *state)
|
bool decor_redraw_line(win_T *wp, int row, DecorState *state)
|
||||||
{
|
{
|
||||||
|
int count = (int)kv_size(state->ranges_i);
|
||||||
|
int const cur_end = state->current_end;
|
||||||
|
int fut_beg = state->future_begin;
|
||||||
|
|
||||||
|
// Move future ranges to start right after current ranges.
|
||||||
|
// Otherwise future ranges will grow forward indefinitely.
|
||||||
|
if (fut_beg == count) {
|
||||||
|
fut_beg = count = cur_end;
|
||||||
|
} else if (fut_beg != cur_end) {
|
||||||
|
int *const indices = state->ranges_i.items;
|
||||||
|
memmove(indices + cur_end, indices + fut_beg, (size_t)(count - fut_beg) * sizeof(indices[0]));
|
||||||
|
|
||||||
|
count = cur_end + (count - fut_beg);
|
||||||
|
fut_beg = cur_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
kv_size(state->ranges_i) = (size_t)count;
|
||||||
|
state->future_begin = fut_beg;
|
||||||
|
|
||||||
if (state->row == -1) {
|
if (state->row == -1) {
|
||||||
decor_redraw_start(wp, row, state);
|
decor_redraw_start(wp, row, state);
|
||||||
}
|
}
|
||||||
@ -459,7 +495,7 @@ bool decor_redraw_line(win_T *wp, int row, DecorState *state)
|
|||||||
state->col_until = -1;
|
state->col_until = -1;
|
||||||
state->eol_col = -1;
|
state->eol_col = -1;
|
||||||
|
|
||||||
if (kv_size(state->active)) {
|
if (cur_end != 0 || fut_beg != count) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -489,18 +525,51 @@ static void decor_range_add_from_inline(DecorState *state, int start_row, int st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void decor_range_insert(DecorState *state, DecorRange range)
|
static void decor_range_insert(DecorState *state, DecorRange *range)
|
||||||
{
|
{
|
||||||
kv_pushp(state->active);
|
range->ordering = state->new_range_ordering++;
|
||||||
size_t index;
|
|
||||||
for (index = kv_size(state->active) - 1; index > 0; index--) {
|
int index;
|
||||||
DecorRange item = kv_A(state->active, index - 1);
|
// Get space for a new `DecorRange` from the freelist or allocate.
|
||||||
if (item.priority <= range.priority) {
|
if (state->free_slot_i >= 0) {
|
||||||
|
index = state->free_slot_i;
|
||||||
|
DecorRangeSlot *slot = &kv_A(state->slots, index);
|
||||||
|
state->free_slot_i = slot->next_free_i;
|
||||||
|
slot->range = *range;
|
||||||
|
} else {
|
||||||
|
index = (int)kv_size(state->slots);
|
||||||
|
kv_pushp(state->slots)->range = *range;
|
||||||
|
}
|
||||||
|
|
||||||
|
int const row = range->start_row;
|
||||||
|
int const col = range->start_col;
|
||||||
|
|
||||||
|
int const count = (int)kv_size(state->ranges_i);
|
||||||
|
int *const indices = state->ranges_i.items;
|
||||||
|
DecorRangeSlot *const slots = state->slots.items;
|
||||||
|
|
||||||
|
int begin = state->future_begin;
|
||||||
|
int end = count;
|
||||||
|
while (begin < end) {
|
||||||
|
int const mid = begin + ((end - begin) >> 1);
|
||||||
|
DecorRange *const mr = &slots[indices[mid]].range;
|
||||||
|
|
||||||
|
int const mrow = mr->start_row;
|
||||||
|
int const mcol = mr->start_col;
|
||||||
|
if (mrow < row || (mrow == row && mcol <= col)) {
|
||||||
|
begin = mid + 1;
|
||||||
|
if (mrow == row && mcol == col) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
kv_A(state->active, index) = kv_A(state->active, index - 1);
|
} else {
|
||||||
|
end = mid;
|
||||||
}
|
}
|
||||||
kv_A(state->active, index) = range;
|
}
|
||||||
|
|
||||||
|
kv_pushp(state->ranges_i);
|
||||||
|
int *const item = &kv_A(state->ranges_i, begin);
|
||||||
|
memmove(item + 1, item, (size_t)(count - begin) * sizeof(*item));
|
||||||
|
*item = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
void decor_range_add_virt(DecorState *state, int start_row, int start_col, int end_row, int end_col,
|
void decor_range_add_virt(DecorState *state, int start_row, int start_col, int end_row, int end_col,
|
||||||
@ -516,7 +585,7 @@ void decor_range_add_virt(DecorState *state, int start_row, int start_col, int e
|
|||||||
.priority = vt->priority,
|
.priority = vt->priority,
|
||||||
.draw_col = -10,
|
.draw_col = -10,
|
||||||
};
|
};
|
||||||
decor_range_insert(state, range);
|
decor_range_insert(state, &range);
|
||||||
}
|
}
|
||||||
|
|
||||||
void decor_range_add_sh(DecorState *state, int start_row, int start_col, int end_row, int end_col,
|
void decor_range_add_sh(DecorState *state, int start_row, int start_col, int end_row, int end_col,
|
||||||
@ -541,7 +610,7 @@ void decor_range_add_sh(DecorState *state, int start_row, int start_col, int end
|
|||||||
if (sh->hl_id) {
|
if (sh->hl_id) {
|
||||||
range.attr_id = syn_id2attr(sh->hl_id);
|
range.attr_id = syn_id2attr(sh->hl_id);
|
||||||
}
|
}
|
||||||
decor_range_insert(state, range);
|
decor_range_insert(state, &range);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sh->flags & (kSHUIWatched)) {
|
if (sh->flags & (kSHUIWatched)) {
|
||||||
@ -549,7 +618,7 @@ void decor_range_add_sh(DecorState *state, int start_row, int start_col, int end
|
|||||||
range.data.ui.ns_id = ns;
|
range.data.ui.ns_id = ns;
|
||||||
range.data.ui.mark_id = mark_id;
|
range.data.ui.mark_id = mark_id;
|
||||||
range.data.ui.pos = (sh->flags & kSHUIWatchedOverlay) ? kVPosOverlay : kVPosEndOfLine;
|
range.data.ui.pos = (sh->flags & kSHUIWatchedOverlay) ? kVPosOverlay : kVPosEndOfLine;
|
||||||
decor_range_insert(state, range);
|
decor_range_insert(state, &range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -569,29 +638,32 @@ void decor_init_draw_col(int win_col, bool hidden, DecorRange *item)
|
|||||||
|
|
||||||
void decor_recheck_draw_col(int win_col, bool hidden, DecorState *state)
|
void decor_recheck_draw_col(int win_col, bool hidden, DecorState *state)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < kv_size(state->active); i++) {
|
int const end = state->current_end;
|
||||||
DecorRange *item = &kv_A(state->active, i);
|
int *const indices = state->ranges_i.items;
|
||||||
if (item->draw_col == -3) {
|
DecorRangeSlot *const slots = state->slots.items;
|
||||||
decor_init_draw_col(win_col, hidden, item);
|
|
||||||
|
for (int i = 0; i < end; i++) {
|
||||||
|
DecorRange *const r = &slots[indices[i]].range;
|
||||||
|
if (r->draw_col == -3) {
|
||||||
|
decor_init_draw_col(win_col, hidden, r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int decor_redraw_col(win_T *wp, int col, int win_col, bool hidden, DecorState *state)
|
int decor_redraw_col_impl(win_T *wp, int col, int win_col, bool hidden, DecorState *state)
|
||||||
{
|
{
|
||||||
buf_T *buf = wp->w_buffer;
|
buf_T *const buf = wp->w_buffer;
|
||||||
if (col <= state->col_until) {
|
int const row = state->row;
|
||||||
return state->current;
|
int col_until = MAXCOL;
|
||||||
}
|
|
||||||
state->col_until = MAXCOL;
|
|
||||||
while (true) {
|
while (true) {
|
||||||
// TODO(bfredl): check duplicate entry in "intersection"
|
// TODO(bfredl): check duplicate entry in "intersection"
|
||||||
// branch
|
// branch
|
||||||
MTKey mark = marktree_itr_current(state->itr);
|
MTKey mark = marktree_itr_current(state->itr);
|
||||||
if (mark.pos.row < 0 || mark.pos.row > state->row) {
|
if (mark.pos.row < 0 || mark.pos.row > row) {
|
||||||
break;
|
break;
|
||||||
} else if (mark.pos.row == state->row && mark.pos.col > col) {
|
} else if (mark.pos.row == row && mark.pos.col > col) {
|
||||||
state->col_until = mark.pos.col - 1;
|
col_until = mark.pos.col - 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -607,73 +679,132 @@ next_mark:
|
|||||||
marktree_itr_next(buf->b_marktree, state->itr);
|
marktree_itr_next(buf->b_marktree, state->itr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int *const indices = state->ranges_i.items;
|
||||||
|
DecorRangeSlot *const slots = state->slots.items;
|
||||||
|
|
||||||
|
int count = (int)kv_size(state->ranges_i);
|
||||||
|
int cur_end = state->current_end;
|
||||||
|
int fut_beg = state->future_begin;
|
||||||
|
|
||||||
|
// Promote future ranges before the cursor to active.
|
||||||
|
for (; fut_beg < count; fut_beg++) {
|
||||||
|
int const index = indices[fut_beg];
|
||||||
|
DecorRange *const r = &slots[index].range;
|
||||||
|
if (r->start_row > row || (r->start_row == row && r->start_col > col)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int const ordering = r->ordering;
|
||||||
|
DecorPriority const priority = r->priority;
|
||||||
|
|
||||||
|
int begin = 0;
|
||||||
|
int end = cur_end;
|
||||||
|
while (begin < end) {
|
||||||
|
int mid = begin + ((end - begin) >> 1);
|
||||||
|
int mi = indices[mid];
|
||||||
|
DecorRange *mr = &slots[mi].range;
|
||||||
|
if (mr->priority < priority || (mr->priority == priority && mr->ordering < ordering)) {
|
||||||
|
begin = mid + 1;
|
||||||
|
} else {
|
||||||
|
end = mid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int *const item = indices + begin;
|
||||||
|
memmove(item + 1, item, (size_t)(cur_end - begin) * sizeof(*item));
|
||||||
|
*item = index;
|
||||||
|
cur_end++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fut_beg < count) {
|
||||||
|
DecorRange *r = &slots[indices[fut_beg]].range;
|
||||||
|
if (r->start_row == row) {
|
||||||
|
col_until = MIN(col_until, r->start_col - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int new_cur_end = 0;
|
||||||
|
|
||||||
int attr = 0;
|
int attr = 0;
|
||||||
size_t j = 0;
|
|
||||||
int conceal = 0;
|
int conceal = 0;
|
||||||
schar_T conceal_char = 0;
|
schar_T conceal_char = 0;
|
||||||
int conceal_attr = 0;
|
int conceal_attr = 0;
|
||||||
TriState spell = kNone;
|
TriState spell = kNone;
|
||||||
|
|
||||||
for (size_t i = 0; i < kv_size(state->active); i++) {
|
for (int i = 0; i < cur_end; i++) {
|
||||||
DecorRange item = kv_A(state->active, i);
|
int const index = indices[i];
|
||||||
bool active = false, keep = true;
|
DecorRangeSlot *const slot = slots + index;
|
||||||
if (item.end_row < state->row
|
DecorRange *const r = &slot->range;
|
||||||
|| (item.end_row == state->row && item.end_col <= col)) {
|
|
||||||
if (!(item.start_row >= state->row && decor_virt_pos(&item))) {
|
bool keep;
|
||||||
keep = false;
|
if (r->end_row < row || (r->end_row == row && r->end_col <= col)) {
|
||||||
}
|
keep = r->start_row >= row && decor_virt_pos(r);
|
||||||
} else {
|
} else {
|
||||||
if (item.start_row < state->row
|
keep = true;
|
||||||
|| (item.start_row == state->row && item.start_col <= col)) {
|
|
||||||
active = true;
|
if (r->end_row == row && r->end_col > col) {
|
||||||
if (item.end_row == state->row && item.end_col > col) {
|
col_until = MIN(col_until, r->end_col - 1);
|
||||||
state->col_until = MIN(state->col_until, item.end_col - 1);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (item.start_row == state->row) {
|
if (r->attr_id > 0) {
|
||||||
state->col_until = MIN(state->col_until, item.start_col - 1);
|
attr = hl_combine_attr(attr, r->attr_id);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
if (r->kind == kDecorKindHighlight && (r->data.sh.flags & kSHConceal)) {
|
||||||
if (active && item.attr_id > 0) {
|
|
||||||
attr = hl_combine_attr(attr, item.attr_id);
|
|
||||||
}
|
|
||||||
if (active && item.kind == kDecorKindHighlight && (item.data.sh.flags & kSHConceal)) {
|
|
||||||
conceal = 1;
|
conceal = 1;
|
||||||
if (item.start_row == state->row && item.start_col == col) {
|
if (r->start_row == row && r->start_col == col) {
|
||||||
DecorSignHighlight *sh = &item.data.sh;
|
DecorSignHighlight *sh = &r->data.sh;
|
||||||
conceal = 2;
|
conceal = 2;
|
||||||
conceal_char = sh->text[0];
|
conceal_char = sh->text[0];
|
||||||
state->col_until = MIN(state->col_until, item.start_col);
|
col_until = MIN(col_until, r->start_col);
|
||||||
conceal_attr = item.attr_id;
|
conceal_attr = r->attr_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (active && item.kind == kDecorKindHighlight) {
|
|
||||||
if (item.data.sh.flags & kSHSpellOn) {
|
if (r->kind == kDecorKindHighlight) {
|
||||||
|
if (r->data.sh.flags & kSHSpellOn) {
|
||||||
spell = kTrue;
|
spell = kTrue;
|
||||||
} else if (item.data.sh.flags & kSHSpellOff) {
|
} else if (r->data.sh.flags & kSHSpellOff) {
|
||||||
spell = kFalse;
|
spell = kFalse;
|
||||||
}
|
}
|
||||||
if (item.data.sh.url != NULL) {
|
if (r->data.sh.url != NULL) {
|
||||||
attr = hl_add_url(attr, item.data.sh.url);
|
attr = hl_add_url(attr, r->data.sh.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (item.start_row == state->row && item.start_col <= col
|
|
||||||
&& decor_virt_pos(&item) && item.draw_col == -10) {
|
|
||||||
decor_init_draw_col(win_col, hidden, &item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (r->start_row == row && r->start_col <= col
|
||||||
|
&& decor_virt_pos(r) && r->draw_col == -10) {
|
||||||
|
decor_init_draw_col(win_col, hidden, r);
|
||||||
|
}
|
||||||
|
|
||||||
if (keep) {
|
if (keep) {
|
||||||
kv_A(state->active, j++) = item;
|
indices[new_cur_end++] = index;
|
||||||
} else if (item.owned) {
|
} else {
|
||||||
if (item.kind == kDecorKindVirtText) {
|
if (r->owned) {
|
||||||
clear_virttext(&item.data.vt->data.virt_text);
|
if (r->kind == kDecorKindVirtText) {
|
||||||
xfree(item.data.vt);
|
clear_virttext(&r->data.vt->data.virt_text);
|
||||||
} else if (item.kind == kDecorKindHighlight) {
|
xfree(r->data.vt);
|
||||||
xfree((void *)item.data.sh.url);
|
} else if (r->kind == kDecorKindHighlight) {
|
||||||
|
xfree((void *)r->data.sh.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int *fi = &state->free_slot_i;
|
||||||
|
slot->next_free_i = *fi;
|
||||||
|
*fi = index;
|
||||||
}
|
}
|
||||||
kv_size(state->active) = j;
|
}
|
||||||
|
cur_end = new_cur_end;
|
||||||
|
|
||||||
|
if (fut_beg == count) {
|
||||||
|
fut_beg = count = cur_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
kv_size(state->ranges_i) = (size_t)count;
|
||||||
|
state->future_begin = fut_beg;
|
||||||
|
state->current_end = cur_end;
|
||||||
|
state->col_until = col_until;
|
||||||
|
|
||||||
state->current = attr;
|
state->current = attr;
|
||||||
state->conceal = conceal;
|
state->conceal = conceal;
|
||||||
state->conceal_char = conceal_char;
|
state->conceal_char = conceal_char;
|
||||||
@ -870,16 +1001,18 @@ bool decor_redraw_eol(win_T *wp, DecorState *state, int *eol_attr, int eol_col)
|
|||||||
{
|
{
|
||||||
decor_redraw_col(wp, MAXCOL, MAXCOL, false, state);
|
decor_redraw_col(wp, MAXCOL, MAXCOL, false, state);
|
||||||
state->eol_col = eol_col;
|
state->eol_col = eol_col;
|
||||||
bool has_virt_pos = false;
|
|
||||||
for (size_t i = 0; i < kv_size(state->active); i++) {
|
|
||||||
DecorRange item = kv_A(state->active, i);
|
|
||||||
if (item.start_row == state->row && decor_virt_pos(&item)) {
|
|
||||||
has_virt_pos = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.kind == kDecorKindHighlight
|
int const count = state->current_end;
|
||||||
&& (item.data.sh.flags & kSHHlEol) && item.start_row <= state->row) {
|
int *const indices = state->ranges_i.items;
|
||||||
*eol_attr = hl_combine_attr(*eol_attr, item.attr_id);
|
DecorRangeSlot *const slots = state->slots.items;
|
||||||
|
|
||||||
|
bool has_virt_pos = false;
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
DecorRange *r = &slots[indices[i]].range;
|
||||||
|
has_virt_pos |= r->start_row == state->row && decor_virt_pos(r);
|
||||||
|
|
||||||
|
if (r->kind == kDecorKindHighlight && (r->data.sh.flags & kSHHlEol)) {
|
||||||
|
*eol_attr = hl_combine_attr(*eol_attr, r->attr_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return has_virt_pos;
|
return has_virt_pos;
|
||||||
|
@ -27,13 +27,19 @@ typedef enum {
|
|||||||
kDecorKindVirtText,
|
kDecorKindVirtText,
|
||||||
kDecorKindVirtLines,
|
kDecorKindVirtLines,
|
||||||
kDecorKindUIWatched,
|
kDecorKindUIWatched,
|
||||||
} DecorRangeKind;
|
} DecorRangeKindEnum;
|
||||||
|
|
||||||
|
typedef uint8_t DecorRangeKind;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int start_row;
|
int start_row;
|
||||||
int start_col;
|
int start_col;
|
||||||
int end_row;
|
int end_row;
|
||||||
int end_col;
|
int end_col;
|
||||||
|
int ordering; ///< range insertion order
|
||||||
|
DecorPriority priority;
|
||||||
|
bool owned; ///< ephemeral decoration, free memory immediately
|
||||||
|
DecorRangeKind kind;
|
||||||
// next pointers MUST NOT be used, these are separate ranges
|
// next pointers MUST NOT be used, these are separate ranges
|
||||||
// vt->next could be pointing to freelist memory at this point
|
// vt->next could be pointing to freelist memory at this point
|
||||||
union {
|
union {
|
||||||
@ -46,9 +52,6 @@ typedef struct {
|
|||||||
} ui;
|
} ui;
|
||||||
} data;
|
} data;
|
||||||
int attr_id; ///< cached lookup of inl.hl_id if it was a highlight
|
int attr_id; ///< cached lookup of inl.hl_id if it was a highlight
|
||||||
bool owned; ///< ephemeral decoration, free memory immediately
|
|
||||||
DecorPriority priority;
|
|
||||||
DecorRangeKind kind;
|
|
||||||
/// Screen column to draw the virtual text.
|
/// Screen column to draw the virtual text.
|
||||||
/// When -1, it should be drawn on the current screen line after deciding where.
|
/// When -1, it should be drawn on the current screen line after deciding where.
|
||||||
/// When -3, it may be drawn at a position yet to be assigned.
|
/// When -3, it may be drawn at a position yet to be assigned.
|
||||||
@ -57,9 +60,28 @@ typedef struct {
|
|||||||
int draw_col;
|
int draw_col;
|
||||||
} DecorRange;
|
} DecorRange;
|
||||||
|
|
||||||
|
/// DecorRange can be removed from `DecorState` list in any order,
|
||||||
|
/// so we track available slots using a freelist (with `next_free_i`).
|
||||||
|
/// The list head is in `DecorState.free_slot_i`.
|
||||||
|
typedef union {
|
||||||
|
DecorRange range;
|
||||||
|
int next_free_i;
|
||||||
|
} DecorRangeSlot;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MarkTreeIter itr[1];
|
MarkTreeIter itr[1];
|
||||||
kvec_t(DecorRange) active;
|
kvec_t(DecorRangeSlot) slots;
|
||||||
|
kvec_t(int) ranges_i;
|
||||||
|
/// Indices in [0; current_end) of `ranges_i` point to ranges that start
|
||||||
|
/// before current position. Sorted by priority and order of insertion.
|
||||||
|
int current_end;
|
||||||
|
/// Indices in [future_begin, kv_size(ranges_i)) of `ranges_i` point to
|
||||||
|
/// ranges that start after current position. Sorted by starting position.
|
||||||
|
int future_begin;
|
||||||
|
/// Head of DecorRangeSlot freelist. -1 if none are freed.
|
||||||
|
int free_slot_i;
|
||||||
|
/// Index for keeping track of range insertion order.
|
||||||
|
int new_range_ordering;
|
||||||
win_T *win;
|
win_T *win;
|
||||||
int top_row;
|
int top_row;
|
||||||
int row;
|
int row;
|
||||||
@ -83,4 +105,14 @@ EXTERN kvec_t(DecorSignHighlight) decor_items INIT( = KV_INITIAL_VALUE);
|
|||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
# include "decoration.h.generated.h"
|
# include "decoration.h.generated.h"
|
||||||
|
# include "decoration.h.inline.generated.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static inline int decor_redraw_col(win_T *wp, int col, int win_col, bool hidden, DecorState *state)
|
||||||
|
FUNC_ATTR_ALWAYS_INLINE
|
||||||
|
{
|
||||||
|
if (col <= state->col_until) {
|
||||||
|
return state->current;
|
||||||
|
}
|
||||||
|
return decor_redraw_col_impl(wp, col, win_col, hidden, state);
|
||||||
|
}
|
||||||
|
@ -120,7 +120,8 @@ void decor_providers_invoke_win(win_T *wp)
|
|||||||
{
|
{
|
||||||
// this might change in the future
|
// this might change in the future
|
||||||
// then we would need decor_state.running_decor_provider just like "on_line" below
|
// then we would need decor_state.running_decor_provider just like "on_line" below
|
||||||
assert(kv_size(decor_state.active) == 0);
|
assert(decor_state.current_end == 0
|
||||||
|
&& decor_state.future_begin == (int)kv_size(decor_state.ranges_i));
|
||||||
|
|
||||||
if (kv_size(decor_providers) > 0) {
|
if (kv_size(decor_providers) > 0) {
|
||||||
validate_botline(wp);
|
validate_botline(wp);
|
||||||
|
@ -251,12 +251,17 @@ static int line_putchar(buf_T *buf, const char **pp, schar_T *dest, int maxcells
|
|||||||
|
|
||||||
static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int win_row)
|
static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int win_row)
|
||||||
{
|
{
|
||||||
DecorState *state = &decor_state;
|
DecorState *const state = &decor_state;
|
||||||
const int max_col = wp->w_grid.cols;
|
int const max_col = wp->w_grid.cols;
|
||||||
int right_pos = max_col;
|
int right_pos = max_col;
|
||||||
bool do_eol = state->eol_col > -1;
|
bool const do_eol = state->eol_col > -1;
|
||||||
for (size_t i = 0; i < kv_size(state->active); i++) {
|
|
||||||
DecorRange *item = &kv_A(state->active, i);
|
int const end = state->current_end;
|
||||||
|
int *const indices = state->ranges_i.items;
|
||||||
|
DecorRangeSlot *const slots = state->slots.items;
|
||||||
|
|
||||||
|
for (int i = 0; i < end; i++) {
|
||||||
|
DecorRange *item = &slots[indices[i]].range;
|
||||||
if (!(item->start_row == state->row && decor_virt_pos(item))) {
|
if (!(item->start_row == state->row && decor_virt_pos(item))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -756,10 +761,20 @@ static bool has_more_inline_virt(winlinevars_T *wlv, ptrdiff_t v)
|
|||||||
if (wlv->virt_inline_i < kv_size(wlv->virt_inline)) {
|
if (wlv->virt_inline_i < kv_size(wlv->virt_inline)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
DecorState *state = &decor_state;
|
|
||||||
for (size_t i = 0; i < kv_size(state->active); i++) {
|
int const count = (int)kv_size(decor_state.ranges_i);
|
||||||
DecorRange *item = &kv_A(state->active, i);
|
int const cur_end = decor_state.current_end;
|
||||||
if (item->start_row != state->row
|
int const fut_beg = decor_state.future_begin;
|
||||||
|
int *const indices = decor_state.ranges_i.items;
|
||||||
|
DecorRangeSlot *const slots = decor_state.slots.items;
|
||||||
|
|
||||||
|
int const beg_pos[] = { 0, fut_beg };
|
||||||
|
int const end_pos[] = { cur_end, count };
|
||||||
|
|
||||||
|
for (int pos_i = 0; pos_i < 2; pos_i++) {
|
||||||
|
for (int i = beg_pos[pos_i]; i < end_pos[pos_i]; i++) {
|
||||||
|
DecorRange *item = &slots[indices[i]].range;
|
||||||
|
if (item->start_row != decor_state.row
|
||||||
|| item->kind != kDecorKindVirtText
|
|| item->kind != kDecorKindVirtText
|
||||||
|| item->data.vt->pos != kVPosInline
|
|| item->data.vt->pos != kVPosInline
|
||||||
|| item->data.vt->width == 0) {
|
|| item->data.vt->width == 0) {
|
||||||
@ -769,6 +784,7 @@ static bool has_more_inline_virt(winlinevars_T *wlv, ptrdiff_t v)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -780,8 +796,12 @@ static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t
|
|||||||
wlv->virt_inline = VIRTTEXT_EMPTY;
|
wlv->virt_inline = VIRTTEXT_EMPTY;
|
||||||
wlv->virt_inline_i = 0;
|
wlv->virt_inline_i = 0;
|
||||||
DecorState *state = &decor_state;
|
DecorState *state = &decor_state;
|
||||||
for (size_t i = 0; i < kv_size(state->active); i++) {
|
int const end = state->current_end;
|
||||||
DecorRange *item = &kv_A(state->active, i);
|
int *const indices = state->ranges_i.items;
|
||||||
|
DecorRangeSlot *const slots = state->slots.items;
|
||||||
|
|
||||||
|
for (int i = 0; i < end; i++) {
|
||||||
|
DecorRange *item = &slots[indices[i]].range;
|
||||||
if (item->draw_col == -3) {
|
if (item->draw_col == -3) {
|
||||||
// No more inline virtual text before this non-inline virtual text item,
|
// No more inline virtual text before this non-inline virtual text item,
|
||||||
// so its position can be decided now.
|
// so its position can be decided now.
|
||||||
|
102
test/benchmark/decor_spec.lua
Normal file
102
test/benchmark/decor_spec.lua
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
local n = require('test.functional.testnvim')()
|
||||||
|
local Screen = require('test.functional.ui.screen')
|
||||||
|
local exec_lua = n.exec_lua
|
||||||
|
|
||||||
|
describe('decor perf', function()
|
||||||
|
before_each(n.clear)
|
||||||
|
|
||||||
|
it('can handle long lines', function()
|
||||||
|
local screen = Screen.new(100, 101)
|
||||||
|
screen:attach()
|
||||||
|
|
||||||
|
local result = exec_lua [==[
|
||||||
|
local ephemeral_pattern = {
|
||||||
|
{ 0, 4, 'Comment', 11 },
|
||||||
|
{ 0, 3, 'Keyword', 12 },
|
||||||
|
{ 1, 2, 'Label', 12 },
|
||||||
|
{ 0, 1, 'String', 21 },
|
||||||
|
{ 1, 3, 'Function', 21 },
|
||||||
|
{ 2, 10, 'Label', 8 },
|
||||||
|
}
|
||||||
|
|
||||||
|
local regular_pattern = {
|
||||||
|
{ 4, 5, 'String', 12 },
|
||||||
|
{ 1, 4, 'Function', 2 },
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, list in ipairs({ ephemeral_pattern, regular_pattern }) do
|
||||||
|
for _, p in ipairs(list) do
|
||||||
|
p[3] = vim.api.nvim_get_hl_id_by_name(p[3])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local text = ('abcdefghijklmnopqrstuvwxyz0123'):rep(333)
|
||||||
|
local line_len = #text
|
||||||
|
vim.api.nvim_buf_set_lines(0, 0, 0, false, { text })
|
||||||
|
|
||||||
|
local ns = vim.api.nvim_create_namespace('decor_spec.lua')
|
||||||
|
vim.api.nvim_buf_clear_namespace(0, ns, 0, -1)
|
||||||
|
vim.api.nvim_win_set_cursor(0, { 1, 0 })
|
||||||
|
|
||||||
|
local ps, pe
|
||||||
|
local function add_pattern(pattern, ephemeral)
|
||||||
|
ps = vim.uv.hrtime()
|
||||||
|
local i = 0
|
||||||
|
while i < line_len - 10 do
|
||||||
|
for _, p in ipairs(pattern) do
|
||||||
|
vim.api.nvim_buf_set_extmark(0, ns, 0, i + p[1], {
|
||||||
|
end_row = 0,
|
||||||
|
end_col = i + p[2],
|
||||||
|
hl_group = p[3],
|
||||||
|
priority = p[4],
|
||||||
|
ephemeral = ephemeral,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
i = i + 5
|
||||||
|
end
|
||||||
|
pe = vim.uv.hrtime()
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.api.nvim_set_decoration_provider(ns, {
|
||||||
|
on_win = function()
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
on_line = function()
|
||||||
|
add_pattern(ephemeral_pattern, true)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
add_pattern(regular_pattern, false)
|
||||||
|
|
||||||
|
local total = {}
|
||||||
|
local provider = {}
|
||||||
|
for i = 1, 100 do
|
||||||
|
local tic = vim.uv.hrtime()
|
||||||
|
vim.cmd'redraw!'
|
||||||
|
local toc = vim.uv.hrtime()
|
||||||
|
table.insert(total, toc - tic)
|
||||||
|
table.insert(provider, pe - ps)
|
||||||
|
end
|
||||||
|
|
||||||
|
return { total, provider }
|
||||||
|
]==]
|
||||||
|
|
||||||
|
local total, provider = unpack(result)
|
||||||
|
table.sort(total)
|
||||||
|
table.sort(provider)
|
||||||
|
|
||||||
|
local ms = 1 / 1000000
|
||||||
|
local function fmt(stats)
|
||||||
|
return string.format(
|
||||||
|
'min, 25%%, median, 75%%, max:\n\t%0.1fms,\t%0.1fms,\t%0.1fms,\t%0.1fms,\t%0.1fms',
|
||||||
|
stats[1] * ms,
|
||||||
|
stats[1 + math.floor(#stats * 0.25)] * ms,
|
||||||
|
stats[1 + math.floor(#stats * 0.5)] * ms,
|
||||||
|
stats[1 + math.floor(#stats * 0.75)] * ms,
|
||||||
|
stats[#stats] * ms
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
print('\nTotal ' .. fmt(total) .. '\nDecoration provider: ' .. fmt(provider))
|
||||||
|
end)
|
||||||
|
end)
|
Loading…
Reference in New Issue
Block a user