Skip to content

Commit

Permalink
perf(column): keep track of number of lines that hold up the 'signcol…
Browse files Browse the repository at this point in the history
…umn'

Problem:  The entire marktree needs to be traversed each time a sign is
          removed from the sentinel line.
Solution: Remove sentinel line and instead keep track of the number of
          lines that hold up the 'signcolumn' in "max_count". Adjust this
          number for added/removed signs, and set it to 0 when the
          maximum number of signs on a line changes. Only when
          "max_count" is decremented to 0 due to sign removal do we need
          to check the entire buffer.

          Also replace "invalid_top" and "invalid_bot" with a map of
          invalid ranges, further reducing the number of lines to be
          checked.

          Also improve tree traversal when counting the number of signs.
          Instead of looping over the to be checked range and counting
          the overlap for each row, keep track of the overlap in an
          array and add this to the count.
  • Loading branch information
luukvbaal authored and lewis6991 committed Dec 7, 2023
1 parent e42f032 commit 4a34da8
Show file tree
Hide file tree
Showing 14 changed files with 198 additions and 170 deletions.
1 change: 0 additions & 1 deletion src/nvim/api/vim.c
Original file line number Diff line number Diff line change
Expand Up @@ -2189,7 +2189,6 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
int cul_id = 0;
int num_id = 0;
linenr_T lnum = statuscol_lnum;
wp->w_scwidth = win_signcol_count(wp);
decor_redraw_signs(wp, wp->w_buffer, lnum - 1, sattrs, &line_id, &cul_id, &num_id);

statuscol.sattrs = sattrs;
Expand Down
66 changes: 4 additions & 62 deletions src/nvim/buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,8 @@ void buf_clear(void)
{
linenr_T line_count = curbuf->b_ml.ml_line_count;
extmark_free_all(curbuf); // delete any extmarks
map_destroy(int, curbuf->b_signcols.invalid);
*curbuf->b_signcols.invalid = (Map(int, SignRange)) MAP_INIT;
while (!(curbuf->b_ml.ml_flags & ML_EMPTY)) {
ml_delete(1, false);
}
Expand Down Expand Up @@ -920,6 +922,8 @@ static void free_buffer_stuff(buf_T *buf, int free_flags)
}
uc_clear(&buf->b_ucmds); // clear local user commands
extmark_free_all(buf); // delete any extmarks
map_destroy(int, buf->b_signcols.invalid);
*buf->b_signcols.invalid = (Map(int, SignRange)) MAP_INIT;
map_clear_mode(buf, MAP_ALL_MODES, true, false); // clear local mappings
map_clear_mode(buf, MAP_ALL_MODES, true, true); // clear local abbrevs
XFREE_CLEAR(buf->b_start_fenc);
Expand Down Expand Up @@ -1844,7 +1848,6 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags)
buf = xcalloc(1, sizeof(buf_T));
// init b: variables
buf->b_vars = tv_dict_alloc();
buf->b_signcols.sentinel = 0;
init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE);
buf_init_changedtick(buf);
}
Expand Down Expand Up @@ -4026,67 +4029,6 @@ char *buf_spname(buf_T *buf)
return NULL;
}

/// Invalidate the signcolumn if needed after deleting a sign ranging from line1 to line2.
void buf_signcols_del_check(buf_T *buf, linenr_T line1, linenr_T line2)
{
linenr_T sent = buf->b_signcols.sentinel;
if (sent >= line1 && sent <= line2) {
// When removed sign overlaps the sentinel line, entire buffer needs to be checked.
buf->b_signcols.sentinel = buf->b_signcols.size = 0;
}
}

/// Invalidate the signcolumn if needed after adding a sign ranging from line1 to line2.
void buf_signcols_add_check(buf_T *buf, linenr_T line1, linenr_T line2)
{
if (!buf->b_signcols.sentinel) {
return;
}

linenr_T sent = buf->b_signcols.sentinel;
if (sent >= line1 && sent <= line2) {
// If added sign overlaps sentinel line, increment without invalidating.
if (buf->b_signcols.size == buf->b_signcols.max) {
buf->b_signcols.max++;
}
buf->b_signcols.size++;
return;
}

if (line1 < buf->b_signcols.invalid_top) {
buf->b_signcols.invalid_top = line1;
}
if (line2 > buf->b_signcols.invalid_bot) {
buf->b_signcols.invalid_bot = line2;
}
}

int buf_signcols(buf_T *buf, int max)
{
if (!buf->b_signs_with_text) {
buf->b_signcols.size = 0;
} else if (max <= 1 && buf->b_signs_with_text >= (size_t)max) {
buf->b_signcols.size = max;
} else {
linenr_T sent = buf->b_signcols.sentinel;
if (!sent || max > buf->b_signcols.max) {
// Recheck if the window scoped maximum 'signcolumn' is greater than the
// previous maximum or if there is no sentinel line yet.
buf->b_signcols.invalid_top = sent ? sent : 1;
buf->b_signcols.invalid_bot = sent ? sent : buf->b_ml.ml_line_count;
}

if (buf->b_signcols.invalid_bot) {
decor_validate_signcols(buf, max);
}
}

buf->b_signcols.max = max;
buf->b_signcols.invalid_top = MAXLNUM;
buf->b_signcols.invalid_bot = 0;
return buf->b_signcols.size;
}

/// Get "buf->b_fname", use "[No Name]" if it is NULL.
char *buf_get_fname(const buf_T *buf)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
Expand Down
9 changes: 4 additions & 5 deletions src/nvim/buffer_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -703,11 +703,10 @@ struct file_buffer {
// may use a different synblock_T.

struct {
int size; // last calculated number of sign columns
int max; // maximum value size is valid for.
linenr_T sentinel; // a line number which is holding up the signcolumn
linenr_T invalid_top; // first invalid line number that needs to be checked
linenr_T invalid_bot; // last invalid line number that needs to be checked
int max; // maximum number of signs on a single line
int max_count; // number of lines with max number of signs
bool resized; // whether max changed at start of redraw
Map(int, SignRange) invalid[1]; // map of invalid ranges to be checked
} b_signcols;

Terminal *terminal; // Terminal instance associated with the buffer
Expand Down
164 changes: 121 additions & 43 deletions src/nvim/decoration.c
Original file line number Diff line number Diff line change
Expand Up @@ -203,21 +203,21 @@ void buf_put_decor_virt(buf_T *buf, DecorVirtText *vt)
}

static int sign_add_id = 0;
void buf_put_decor_sh(buf_T *buf, DecorSignHighlight *sh, int row, int row2)
void buf_put_decor_sh(buf_T *buf, DecorSignHighlight *sh, int row1, int row2)
{
if (sh->flags & kSHIsSign) {
sh->sign_add_id = sign_add_id++;
buf->b_signs++;
if (sh->text.ptr) {
buf->b_signs_with_text++;
buf_signcols_add_check(buf, row + 1, row2 + 1);
buf_signcols_invalidate_range(buf, row1, row2, 1);
}
}
}

void buf_decor_remove(buf_T *buf, int row, int row2, DecorInline decor, bool free)
void buf_decor_remove(buf_T *buf, int row1, int row2, DecorInline decor, bool free)
{
decor_redraw(buf, row, row2, decor);
decor_redraw(buf, row1, row2, decor);
if (decor.ext) {
DecorVirtText *vt = decor.data.ext.vt;
while (vt) {
Expand All @@ -227,7 +227,7 @@ void buf_decor_remove(buf_T *buf, int row, int row2, DecorInline decor, bool fre
uint32_t idx = decor.data.ext.sh_idx;
while (idx != DECOR_ID_INVALID) {
DecorSignHighlight *sh = &kv_A(decor_items, idx);
buf_remove_decor_sh(buf, row, row2, sh);
buf_remove_decor_sh(buf, row1, row2, sh);
idx = sh->next;
}
if (free) {
Expand All @@ -249,16 +249,16 @@ void buf_remove_decor_virt(buf_T *buf, DecorVirtText *vt)
}
}

void buf_remove_decor_sh(buf_T *buf, int row, int row2, DecorSignHighlight *sh)
void buf_remove_decor_sh(buf_T *buf, int row1, int row2, DecorSignHighlight *sh)
{
if (sh->flags & kSHIsSign) {
assert(buf->b_signs > 0);
buf->b_signs--;
if (sh->text.ptr) {
assert(buf->b_signs_with_text > 0);
buf->b_signs_with_text--;
if (row2 >= row) {
buf_signcols_del_check(buf, row + 1, row2 + 1);
if (row2 >= row1) {
buf_signcols_invalidate_range(buf, row1, row2, -1);
}
}
}
Expand Down Expand Up @@ -792,50 +792,128 @@ DecorSignHighlight *decor_find_sign(DecorInline decor)
}
}

// Increase the signcolumn size and update the sentinel line if necessary for
// the invalidated range.
void decor_validate_signcols(buf_T *buf, int max)
/// If "count" is greater than current max, set it and reset "max_count".
static void buf_signcols_validate_row(buf_T *buf, int count, int add)
{
int signcols = 0; // highest value of count
int currow = buf->b_signcols.invalid_top - 1;
// TODO(bfredl): only need to use marktree_itr_get_overlap once.
// then we can process both start and end events and update state for each row
for (; currow < buf->b_signcols.invalid_bot; currow++) {
MarkTreeIter itr[1];
if (!marktree_itr_get_overlap(buf->b_marktree, currow, 0, itr)) {
continue;
}
int del = add < 0 ? -add : 0;
if (count > buf->b_signcols.max) {
buf->b_signcols.max = count;
buf->b_signcols.max_count = 0;
buf->b_signcols.resized = true;
}
/// Add sign of "add" to "max_count"
if (count == buf->b_signcols.max - del) {
buf->b_signcols.max_count += (add > 0) - (add < 0);
}
}

int count = 0;
MTPair pair;
while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) {
if (!mt_invalid(pair.start) && (pair.start.flags & MT_FLAG_DECOR_SIGNTEXT)) {
count++;
}
/// Validate a range by counting the number of overlapping signs and adjusting
/// "b_signcols" accordingly.
static void buf_signcols_validate_range(buf_T *buf, int row1, int row2, int add)
{
int count = 0; // Number of signs on the current line
int currow = row1;
MTPair pair = { 0 };
MarkTreeIter itr[1];

// Allocate an array of integers holding the overlapping signs in the range.
assert(row2 >= row1);
int *overlap = xcalloc(sizeof(int), (size_t)(row2 + 1 - row1));

// First find the number of overlapping signs at "row1".
marktree_itr_get_overlap(buf->b_marktree, currow, 0, itr);
while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) {
if (!mt_invalid(pair.start) && pair.start.flags & MT_FLAG_DECOR_SIGNTEXT) {
overlap[0]++;
}
}

while (itr->x) {
MTKey mark = marktree_itr_current(itr);
if (mark.pos.row != currow) {
break;
}
if (!mt_invalid(mark) && !mt_end(mark) && (mark.flags & MT_FLAG_DECOR_SIGNTEXT)) {
count++;
}
marktree_itr_next(buf->b_marktree, itr);
// Continue traversing the marktree until beyond "row2". Increment "count" for
// the start of a mark, increment the overlap array until the end of a paired mark.
while (itr->x) {
MTKey mark = marktree_itr_current(itr);
if (mark.pos.row > row2) {
break;
}
// Finish the count at the previous row.
if (mark.pos.row != currow) {
buf_signcols_validate_row(buf, count + overlap[currow - row1], add);
currow = mark.pos.row;
count = 0;
}

if (count > signcols) {
if (count >= buf->b_signcols.size) {
buf->b_signcols.size = count;
buf->b_signcols.sentinel = currow + 1;
}
if (count >= max) {
return;
// Increment count and overlap array for the range of a paired sign mark.
if (!mt_invalid(mark) && !mt_end(mark) && (mark.flags & MT_FLAG_DECOR_SIGNTEXT)) {
count++;
if (mt_paired(mark)) {
MTPos end = marktree_get_altpos(buf->b_marktree, mark, NULL);
for (int i = mark.pos.row; i < MIN(row2, end.row); i++) {
overlap[row2 - i]++;
}
}
signcols = count;
}

marktree_itr_next(buf->b_marktree, itr);
}
buf_signcols_validate_row(buf, count + overlap[currow - row1], add);
xfree(overlap);
}

int buf_signcols_validate(win_T *wp, buf_T *buf, bool stc_check)
{
int start;
SignRange range;
map_foreach(buf->b_signcols.invalid, start, range, {
// Leave rest of the ranges invalid if max is already greater than
// configured maximum or resize is detected for 'statuscolumn' rebuild.
if ((!stc_check || buf->b_signcols.resized)
&& (range.add > 0 && buf->b_signcols.max >= wp->w_maxscwidth)) {
return wp->w_maxscwidth;
}
buf_signcols_validate_range(buf, start, range.end, range.add);
});

// Check if we need to scan the entire buffer.
if (buf->b_signcols.max_count == 0) {
buf->b_signcols.max = 0;
buf->b_signcols.resized = true;
buf_signcols_validate_range(buf, 0, buf->b_ml.ml_line_count, 0);
}

map_clear(int, buf->b_signcols.invalid);
return buf->b_signcols.max;
}

static void buf_signcols_invalidate_range(buf_T *buf, int row1, int row2, int add)
{
if (!buf->b_signs_with_text) {
buf->b_signcols.max = buf->b_signcols.max_count = 0;
buf->b_signcols.resized = true;
map_clear(int, buf->b_signcols.invalid);
return;
}

// Remove an invalid range if sum of added/removed signs is now 0.
SignRange *srp = map_ref(int, SignRange)(buf->b_signcols.invalid, row1, NULL);
if (srp && srp->end == row2 && srp->add + add == 0) {
map_del(int, SignRange)(buf->b_signcols.invalid, row1, NULL);
return;
}

// Merge with overlapping invalid range.
int start;
SignRange range;
map_foreach(buf->b_signcols.invalid, start, range, {
if (row1 <= range.end && start <= row2) {
row1 = MIN(row1, start);
row2 = MAX(row2, range.end);
break;
}
});

srp = map_put_ref(int, SignRange)(buf->b_signcols.invalid, row1, NULL, NULL);
srp->end = row2;
srp->add += add;
}

void decor_redraw_end(DecorState *state)
Expand Down
Loading

0 comments on commit 4a34da8

Please sign in to comment.