From 228684d2fbb6262f761b2b5d7001033bd69880c1 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 5 Feb 2023 23:49:43 +0000 Subject: fix(decoration): don't show signcolumn for non-sign_text extmark (#22135) Fixes: #22127 --- src/nvim/decoration.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 63c55ec602..c98ffbeefb 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -100,9 +100,13 @@ void decor_remove(buf_T *buf, int row, int row2, Decoration *decor) if (decor_has_sign(decor)) { assert(buf->b_signs > 0); buf->b_signs--; - } - if (row2 >= row && decor->sign_text) { - buf_signcols_del_check(buf, row + 1, row2 + 1); + if (decor->sign_text) { + assert(buf->b_signs_with_text > 0); + buf->b_signs_with_text--; + if (row2 >= row) { + buf_signcols_del_check(buf, row + 1, row2 + 1); + } + } } } decor_free(decor); @@ -445,11 +449,11 @@ int decor_signcols(buf_T *buf, DecorState *state, int row, int end_row, int max) int signcols = 0; // highest value of count int currow = -1; // current row - if (max <= 1 && buf->b_signs >= (size_t)max) { + if (max <= 1 && buf->b_signs_with_text >= (size_t)max) { return max; } - if (buf->b_signs == 0) { + if (buf->b_signs_with_text == 0) { return 0; } -- cgit From 39842be8cd8808c7da2638a6cc84d7c3fe40b996 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 5 Mar 2023 07:09:28 +0800 Subject: fix(extmarks): don't leak memory on error (#22507) --- src/nvim/decoration.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index c98ffbeefb..7e47565247 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -112,15 +112,20 @@ void decor_remove(buf_T *buf, int row, int row2, Decoration *decor) decor_free(decor); } +void decor_clear(Decoration *decor) +{ + clear_virttext(&decor->virt_text); + for (size_t i = 0; i < kv_size(decor->virt_lines); i++) { + clear_virttext(&kv_A(decor->virt_lines, i).line); + } + kv_destroy(decor->virt_lines); + xfree(decor->sign_text); +} + void decor_free(Decoration *decor) { if (decor) { - clear_virttext(&decor->virt_text); - for (size_t i = 0; i < kv_size(decor->virt_lines); i++) { - clear_virttext(&kv_A(decor->virt_lines, i).line); - } - kv_destroy(decor->virt_lines); - xfree(decor->sign_text); + decor_clear(decor); xfree(decor); } } -- cgit From 8021300806e2ccf04b3ec33970b682ee3c7a9cc3 Mon Sep 17 00:00:00 2001 From: bfredl Date: Thu, 16 Mar 2023 13:56:05 +0100 Subject: refactor(extmarks): some minor internal API changes extranges and a bunch of other improvements are coming for 0.10 This gets in some minor surrounding API changes to avoid rebase conflicts until then. - decorations will be able to be specific to windows - adjust deletion API to fit with extranges --- src/nvim/decoration.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 7e47565247..217544175d 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -166,10 +166,10 @@ next_mark: return NULL; } -bool decor_redraw_reset(buf_T *buf, DecorState *state) +bool decor_redraw_reset(win_T *wp, DecorState *state) { state->row = -1; - state->buf = buf; + state->win = wp; for (size_t i = 0; i < kv_size(state->active); i++) { DecorRange item = kv_A(state->active, i); if (item.virt_text_owned) { @@ -177,7 +177,7 @@ bool decor_redraw_reset(buf_T *buf, DecorState *state) } } kv_size(state->active) = 0; - return buf->b_marktree->n_keys; + return wp->w_buffer->b_marktree->n_keys; } Decoration get_decor(mtkey_t mark) @@ -198,8 +198,9 @@ static bool decor_virt_pos(Decoration decor) return kv_size(decor.virt_text) || decor.ui_watched; } -bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state) +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) { @@ -250,10 +251,10 @@ next_mark: return true; // TODO(bfredl): check if available in the region } -bool decor_redraw_line(buf_T *buf, int row, DecorState *state) +bool decor_redraw_line(win_T *wp, int row, DecorState *state) { if (state->row == -1) { - decor_redraw_start(buf, row, state); + decor_redraw_start(wp, row, state); } state->row = row; state->col_until = -1; @@ -282,8 +283,9 @@ static void decor_add(DecorState *state, int start_row, int start_col, int end_r kv_A(state->active, index) = range; } -int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden, DecorState *state) +int decor_redraw_col(win_T *wp, int col, int win_col, bool hidden, DecorState *state) { + buf_T *buf = wp->w_buffer; if (col <= state->col_until) { return state->current; } @@ -529,12 +531,12 @@ next_mark: void decor_redraw_end(DecorState *state) { - state->buf = NULL; + state->win = NULL; } -bool decor_redraw_eol(buf_T *buf, DecorState *state, int *eol_attr, int eol_col) +bool decor_redraw_eol(win_T *wp, DecorState *state, int *eol_attr, int eol_col) { - decor_redraw_col(buf, MAXCOL, MAXCOL, false, state); + decor_redraw_col(wp, MAXCOL, MAXCOL, false, state); state->eol_col = eol_col; bool has_virttext = false; for (size_t i = 0; i < kv_size(state->active); i++) { -- cgit From eb3fcf652bbcab01cd6d55a0e2c120c09cbe69d3 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 17 Mar 2023 21:19:34 +0800 Subject: vim-patch:9.0.0194: cursor displayed in wrong position after removing text prop (#22706) Problem: Cursor displayed in wrong position after removing text prop. (Ben Jackson) Solution: Invalidate the cursor position. (closes vim/vim#10898) https://github.com/vim/vim/commit/326c5d36e7cb8526330565109c17b4a13ff790ae Co-authored-by: Bram Moolenaar --- src/nvim/decoration.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 217544175d..f45e13b42a 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -11,6 +11,7 @@ #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/memory.h" +#include "nvim/move.h" #include "nvim/pos.h" #include "nvim/sign_defs.h" @@ -86,6 +87,7 @@ void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) if (decor && kv_size(decor->virt_lines)) { redraw_buf_line_later(buf, row1 + 1 + (decor->virt_lines_above?0:1), true); + changed_line_display_buf(buf); } } -- cgit From f0ac91c58b42ed4f38dea7352d89fd39a88142f4 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Sat, 1 Apr 2023 14:58:52 +0200 Subject: feat(api): evaluate 'statuscolumn' with nvim_eval_statusline() --- src/nvim/decoration.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index f45e13b42a..ea54554c46 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -386,7 +386,7 @@ next_mark: } void decor_redraw_signs(buf_T *buf, int row, int *num_signs, SignTextAttrs sattrs[], - HlPriAttr *num_attrs, HlPriAttr *line_attrs, HlPriAttr *cul_attrs) + HlPriId *num_id, HlPriId *line_id, HlPriId *cul_id) { if (!buf->b_signs) { return; @@ -422,23 +422,23 @@ void decor_redraw_signs(buf_T *buf, int row, int *num_signs, SignTextAttrs sattr if (j < SIGN_SHOW_MAX) { sattrs[j] = (SignTextAttrs) { .text = decor->sign_text, - .hl_attr_id = decor->sign_hl_id == 0 ? 0 : syn_id2attr(decor->sign_hl_id), + .hl_id = decor->sign_hl_id, .priority = decor->priority }; (*num_signs)++; } } - struct { HlPriAttr *dest; int hl; } cattrs[] = { - { line_attrs, decor->line_hl_id }, - { num_attrs, decor->number_hl_id }, - { cul_attrs, decor->cursorline_hl_id }, + 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 = (HlPriAttr) { - .attr_id = syn_id2attr(cattrs[i].hl), + *cattrs[i].dest = (HlPriId) { + .hl_id = cattrs[i].hl, .priority = decor->priority }; } -- cgit From 3b0df1780e2c8526bda5dead18ee7cc45925caba Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Wed, 26 Apr 2023 23:23:44 +0200 Subject: refactor: uncrustify Notable changes: replace all infinite loops to `while(true)` and remove `int` from `unsigned int`. --- src/nvim/decoration.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index ea54554c46..980be0282d 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -86,7 +86,7 @@ void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) } if (decor && kv_size(decor->virt_lines)) { - redraw_buf_line_later(buf, row1 + 1 + (decor->virt_lines_above?0:1), true); + redraw_buf_line_later(buf, row1 + 1 + (decor->virt_lines_above ? 0 : 1), true); changed_line_display_buf(buf); } } -- cgit From eb4676c67f5dd54bcda473783315901a3444b40b Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 27 Apr 2023 17:30:22 +0100 Subject: fix: disallow removing extmarks in on_lines callbacks (#23219) fix(extmarks): disallow removing extmarks in on_lines callbacks decor_redraw_start (which runs before decor_providers_invoke_lines) gets references for the extmarks on a specific line. If these extmarks are deleted in on_lines callbacks then this results in a heap-use-after-free error. Fixes #22801 --- src/nvim/decoration.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 980be0282d..87e4441f32 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -65,7 +65,7 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start } extmark_set(buf, (uint32_t)src_id, NULL, (int)lnum - 1, hl_start, (int)lnum - 1 + end_off, hl_end, - &decor, true, false, kExtmarkNoUndo); + &decor, true, false, kExtmarkNoUndo, NULL); } } -- cgit From 0e1f3b5acf74e82ea778f3c0871a804a9ae89d4e Mon Sep 17 00:00:00 2001 From: Ibby <33922797+SleepySwords@users.noreply.github.com> Date: Sun, 19 Mar 2023 18:32:44 +1100 Subject: vim-patch:9.0.0130: cursor position wrong when inserting around virtual text Problem: Cursor position wrong when inserting around virtual text. Solution: Update the cursor position properly. https://github.com/vim/vim/commit/1f4ee19eefecd8f70b7cbe8ee9db8ace6352e23e Co-authored-by: tom-anders <13141438+tom-anders@users.noreply.github.com> --- src/nvim/decoration.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 87e4441f32..ec11c20b3e 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -95,6 +95,10 @@ void decor_remove(buf_T *buf, int row, int row2, Decoration *decor) { decor_redraw(buf, row, row2, decor); if (decor) { + if (kv_size(decor->virt_text) && decor->virt_text_pos == kVTInline) { + assert(buf->b_virt_text_inline > 0); + buf->b_virt_text_inline--; + } if (kv_size(decor->virt_lines)) { assert(buf->b_virt_line_blocks > 0); buf->b_virt_line_blocks--; -- cgit From 389f5ca39d278173dc0446dc339f7ac1ff573329 Mon Sep 17 00:00:00 2001 From: Ibby <33922797+SleepySwords@users.noreply.github.com> Date: Sun, 19 Mar 2023 18:50:45 +1100 Subject: fix(ui): adjust the cursor when inserting virtual text Credit to: Jesse Bakker https://github.com/neovim/neovim/pull/20130#issuecomment-1369652743 Co-authored-by: Jesse Bakker --- src/nvim/decoration.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index ec11c20b3e..ce1af290c1 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -83,6 +83,9 @@ void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) if (decor && decor_virt_pos(*decor)) { redraw_buf_line_later(buf, row1 + 1, false); + if (decor->virt_text_pos == kVTInline) { + changed_line_display_buf(buf); + } } if (decor && kv_size(decor->virt_lines)) { -- cgit From 510e1f131b56e0423342f597178459a63eb0b810 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 27 May 2023 21:36:16 +0800 Subject: fix(extmarks): make right_align and win_col work on wrapped line (#23759) --- src/nvim/decoration.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index ce1af290c1..81e1cb617c 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -81,7 +81,7 @@ void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) } } - if (decor && decor_virt_pos(*decor)) { + if (decor && decor_virt_pos(decor)) { redraw_buf_line_later(buf, row1 + 1, false); if (decor->virt_text_pos == kVTInline) { changed_line_display_buf(buf); @@ -202,9 +202,9 @@ Decoration get_decor(mtkey_t mark) } /// @return true if decor has a virtual position (virtual text or ui_watched) -static bool decor_virt_pos(Decoration decor) +bool decor_virt_pos(const Decoration *const decor) { - return kv_size(decor.virt_text) || decor.ui_watched; + return kv_size(decor->virt_text) || decor->ui_watched; } bool decor_redraw_start(win_T *wp, int top_row, DecorState *state) @@ -232,7 +232,7 @@ bool decor_redraw_start(win_T *wp, int top_row, DecorState *state) // 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)) + if ((mt_start(mark) && altpos.row < top_row && !decor_virt_pos(&decor)) || (mt_end(mark) && altpos.row >= top_row)) { goto next_mark; } @@ -342,7 +342,7 @@ next_mark: bool active = false, keep = true; if (item.end_row < state->row || (item.end_row == state->row && item.end_col <= col)) { - if (!(item.start_row >= state->row && decor_virt_pos(item.decor))) { + if (!(item.start_row >= state->row && decor_virt_pos(&item.decor))) { keep = false; } } else { @@ -372,10 +372,19 @@ next_mark: if (active && item.decor.spell != kNone) { spell = item.decor.spell; } - if ((item.start_row == state->row && item.start_col <= col) - && decor_virt_pos(item.decor) - && item.decor.virt_text_pos == kVTOverlay && item.win_col == -1) { - item.win_col = (item.decor.virt_text_hide && hidden) ? -2 : win_col; + if (item.start_row == state->row && decor_virt_pos(&item.decor) + && item.draw_col != INT_MIN) { + if (item.start_col <= col) { + if (item.decor.virt_text_pos == kVTOverlay && item.draw_col == -1) { + item.draw_col = (item.decor.virt_text_hide && hidden) ? INT_MIN : win_col; + } else if (item.draw_col == -3) { + item.draw_col = -1; + } + } else if (wp->w_p_wrap + && (item.decor.virt_text_pos == kVTRightAlign + || item.decor.virt_text_pos == kVTWinCol)) { + item.draw_col = -3; + } } if (keep) { kv_A(state->active, j++) = item; @@ -550,7 +559,7 @@ bool decor_redraw_eol(win_T *wp, DecorState *state, int *eol_attr, int eol_col) bool has_virttext = 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.decor)) { + if (item.start_row == state->row && decor_virt_pos(&item.decor)) { has_virttext = true; } -- cgit From 4dd43e31db8fa23b5189e074cff94f1491035aac Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 28 May 2023 17:22:25 +0800 Subject: fix(extmarks): don't show virt lines for end mark (#23792) --- src/nvim/decoration.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 81e1cb617c..677bc25127 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -603,7 +603,7 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fo mtkey_t mark = marktree_itr_current(itr); if (mark.pos.row < 0 || mark.pos.row >= end_row) { break; - } else if (marktree_decor_level(mark) < kDecorLevelVirtLine) { + } else if (mt_end(mark) || marktree_decor_level(mark) < kDecorLevelVirtLine) { goto next_mark; } bool above = mark.pos.row > (lnum - 2); -- cgit From 2bdef6dd2a7572602aeb2efec76812769bcee246 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 1 Jun 2023 16:20:31 +0800 Subject: fix(column): don't overflow sign column with extmark signs (#23854) --- src/nvim/decoration.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 677bc25127..2027848ccf 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -433,7 +433,9 @@ void decor_redraw_signs(buf_T *buf, int row, int *num_signs, SignTextAttrs sattr if (sattrs[j - 1].priority >= decor->priority) { break; } - sattrs[j] = sattrs[j - 1]; + if (j < SIGN_SHOW_MAX) { + sattrs[j] = sattrs[j - 1]; + } } if (j < SIGN_SHOW_MAX) { sattrs[j] = (SignTextAttrs) { -- cgit From 19fb573ad992b6f658b58159314eeea0c2f30953 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 11 Jul 2023 08:57:55 +0800 Subject: perf(extmarks): avoid unnecessary marktree traversal with folds (#24306) Extreme testcase: ```lua vim.cmd([[ call setline(1, ['', '', '']) 2,3fold ]]) local ns = vim.api.nvim_create_namespace('') for _ = 1, 100000 do vim.api.nvim_buf_set_extmark(0, ns, 1, 0, { virt_lines = {{{ '' }}} }) end local start_time = vim.uv.hrtime() vim.api.nvim_win_text_height(0, {}) local stop_time = vim.uv.hrtime() print(stop_time - start_time) ``` Before this PR: 21542011 After this PR: 43874 --- src/nvim/decoration.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 2027848ccf..59af433b35 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -592,26 +592,34 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fo return 0; } - int virt_lines = 0; - int row = MAX(lnum - 2, 0); - int end_row = (int)lnum; - MarkTreeIter itr[1] = { 0 }; - marktree_itr_get(buf->b_marktree, row, 0, itr); + assert(lnum > 0); bool below_fold = lnum > 1 && hasFoldingWin(wp, lnum - 1, NULL, NULL, true, NULL); if (has_fold == kNone) { has_fold = hasFoldingWin(wp, lnum, NULL, NULL, true, NULL); } + + const int row = lnum - 1; + const int start_row = below_fold ? row : MAX(row - 1, 0); + const int end_row = has_fold ? row : row + 1; + if (start_row >= end_row) { + return 0; + } + + int virt_lines = 0; + MarkTreeIter itr[1] = { 0 }; + marktree_itr_get(buf->b_marktree, start_row, 0, itr); while (true) { mtkey_t mark = marktree_itr_current(itr); if (mark.pos.row < 0 || mark.pos.row >= end_row) { break; - } else if (mt_end(mark) || marktree_decor_level(mark) < kDecorLevelVirtLine) { + } else if (mt_end(mark) + || marktree_decor_level(mark) < kDecorLevelVirtLine + || !mark.decor_full) { goto next_mark; } - bool above = mark.pos.row > (lnum - 2); - bool has_fold_cur = above ? has_fold : below_fold; - Decoration *decor = mark.decor_full; - if (!has_fold_cur && decor && decor->virt_lines_above == above) { + Decoration *const decor = mark.decor_full; + const int draw_row = mark.pos.row + (decor->virt_lines_above ? 0 : 1); + if (draw_row == row) { virt_lines += (int)kv_size(decor->virt_lines); if (lines) { kv_splice(*lines, decor->virt_lines); -- cgit From 5a6c7c805b8bb1d1ed9fe829ed33f18ffa6f4f47 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 19 Aug 2023 03:55:11 +0800 Subject: fix(extmarks): make empty "conceal" respect &conceallevel = 1 (#24785) This treats extmark conceal more like matchadd() conceal. --- src/nvim/decoration.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 59af433b35..d9d1417d2a 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -332,7 +332,7 @@ next_mark: int attr = 0; size_t j = 0; - bool conceal = 0; + int conceal = 0; int conceal_char = 0; int conceal_attr = 0; TriState spell = kNone; @@ -362,8 +362,9 @@ next_mark: attr = hl_combine_attr(attr, item.attr_id); } if (active && item.decor.conceal) { - conceal = true; - if (item.start_row == state->row && item.start_col == col && item.decor.conceal_char) { + conceal = 1; + if (item.start_row == state->row && item.start_col == col) { + conceal = 2; conceal_char = item.decor.conceal_char; state->col_until = MIN(state->col_until, item.start_col); conceal_attr = item.attr_id; -- cgit From b04286a187d57c50f01cd36cd4668b7a69026579 Mon Sep 17 00:00:00 2001 From: bfredl Date: Sun, 22 Nov 2020 10:10:37 +0100 Subject: 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. --- src/nvim/decoration.c | 159 +++++++++++++++++++++++++------------------------- 1 file changed, 80 insertions(+), 79 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index d9d1417d2a..265bc11661 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -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; - Decoration decor = get_decor(mark); - - 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; + while (marktree_itr_step_overlap(buf->b_marktree, state->itr, &pair)) { + if (marktree_decor_level(pair.start) < kDecorLevelVisible) { + continue; } - 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); - } + Decoration decor = get_decor(pair.start); -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,43 +423,49 @@ 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]; - } + 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] = (SignTextAttrs) { - .text = decor->sign_text, - .hl_id = decor->sign_hl_id, - .priority = decor->priority - }; - (*num_signs)++; + sattrs[j] = sattrs[j - 1]; } } - - 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 - }; - } + if (j < SIGN_SHOW_MAX) { + sattrs[j] = (SignTextAttrs) { + .text = decor->sign_text, + .hl_id = decor->sign_hl_id, + .priority = decor->priority + }; + (*num_signs)++; } + } -next_mark: - marktree_itr_next(buf->b_marktree, itr); + 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 + }; + } } } @@ -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) -- cgit From b52bd8a2dea829f23721c15ee9ce51ccf766df43 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 15 Sep 2023 12:35:27 +0800 Subject: fix(extmarks): properly handle virt_text on next screen line (#25166) TODO: virt_text_hide doesn't work for the first char on a wrapped screen line, and it's not clear how to fix that. --- src/nvim/decoration.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 265bc11661..0181cc8983 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -348,18 +348,12 @@ next_mark: if (active && item.decor.spell != kNone) { spell = item.decor.spell; } - if (item.start_row == state->row && decor_virt_pos(&item.decor) - && item.draw_col != INT_MIN) { - if (item.start_col <= col) { - if (item.decor.virt_text_pos == kVTOverlay && item.draw_col == -1) { - item.draw_col = (item.decor.virt_text_hide && hidden) ? INT_MIN : win_col; - } else if (item.draw_col == -3) { - item.draw_col = -1; - } - } else if (wp->w_p_wrap - && (item.decor.virt_text_pos == kVTRightAlign - || item.decor.virt_text_pos == kVTWinCol)) { - item.draw_col = -3; + if (item.start_row == state->row && item.start_col <= col + && decor_virt_pos(&item.decor) && item.draw_col == -1) { + if (item.decor.virt_text_pos == kVTOverlay) { + item.draw_col = (item.decor.virt_text_hide && hidden) ? INT_MIN : win_col; + } else if (win_col < 0 && item.decor.virt_text_pos != kVTInline) { + item.draw_col = win_col; } } if (keep) { -- cgit From 35e50d79c630b05f67a840ebe21b4043ba9a6066 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 15 Sep 2023 20:30:50 +0800 Subject: fix(extmarks): overlay virt_text position after 'showbreak' (#25175) Also make virt_text_hide work properly. --- src/nvim/decoration.c | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 0181cc8983..f4ca31040a 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -268,6 +268,28 @@ static void decor_add(DecorState *state, int start_row, int start_col, int end_r kv_A(state->active, index) = range; } +/// Initialize the draw_col of a newly-added non-inline virtual text item. +static void decor_init_draw_col(int win_col, bool hidden, DecorRange *item) +{ + if (win_col < 0) { + item->draw_col = win_col; + } else if (item->decor.virt_text_pos == kVTOverlay) { + item->draw_col = (item->decor.virt_text_hide && hidden) ? INT_MIN : win_col; + } else { + item->draw_col = -1; + } +} + +void decor_recheck_draw_col(int win_col, bool hidden, DecorState *state) +{ + for (size_t i = 0; i < kv_size(state->active); i++) { + DecorRange *item = &kv_A(state->active, i); + if (item->draw_col == -3) { + decor_init_draw_col(win_col, hidden, item); + } + } +} + int decor_redraw_col(win_T *wp, int col, int win_col, bool hidden, DecorState *state) { buf_T *buf = wp->w_buffer; @@ -349,12 +371,9 @@ next_mark: spell = item.decor.spell; } if (item.start_row == state->row && item.start_col <= col - && decor_virt_pos(&item.decor) && item.draw_col == -1) { - if (item.decor.virt_text_pos == kVTOverlay) { - item.draw_col = (item.decor.virt_text_hide && hidden) ? INT_MIN : win_col; - } else if (win_col < 0 && item.decor.virt_text_pos != kVTInline) { - item.draw_col = win_col; - } + && decor_virt_pos(&item.decor) && item.draw_col == -1 + && item.decor.virt_text_pos != kVTInline) { + decor_init_draw_col(win_col, hidden, &item); } if (keep) { kv_A(state->active, j++) = item; -- cgit From 818d7f6daf306c6ad7bed0d2ee5c8b9c89f625f4 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 20 Sep 2023 21:48:12 +0800 Subject: fix(extmarks): fix win_col virt_text drawn on wrong screen line (#25264) --- src/nvim/decoration.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index f4ca31040a..b70f070a51 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -254,7 +254,7 @@ static void decor_add(DecorState *state, int start_row, int start_col, int end_r DecorRange range = { start_row, start_col, end_row, end_col, *decor, attr_id, - kv_size(decor->virt_text) && owned, -1, ns_id, mark_id }; + kv_size(decor->virt_text) && owned, -10, ns_id, mark_id }; kv_pushp(state->active); size_t index; @@ -268,10 +268,10 @@ static void decor_add(DecorState *state, int start_row, int start_col, int end_r kv_A(state->active, index) = range; } -/// Initialize the draw_col of a newly-added non-inline virtual text item. +/// Initialize the draw_col of a newly-added virtual text item. static void decor_init_draw_col(int win_col, bool hidden, DecorRange *item) { - if (win_col < 0) { + if (win_col < 0 && item->decor.virt_text_pos != kVTInline) { item->draw_col = win_col; } else if (item->decor.virt_text_pos == kVTOverlay) { item->draw_col = (item->decor.virt_text_hide && hidden) ? INT_MIN : win_col; @@ -371,8 +371,7 @@ next_mark: spell = item.decor.spell; } if (item.start_row == state->row && item.start_col <= col - && decor_virt_pos(&item.decor) && item.draw_col == -1 - && item.decor.virt_text_pos != kVTInline) { + && decor_virt_pos(&item.decor) && item.draw_col == -10) { decor_init_draw_col(win_col, hidden, &item); } if (keep) { -- cgit From 64e8a3c4d19eab40888fbac36b96e97bd9d68c42 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 22 Sep 2023 15:36:24 +0800 Subject: fix(ui): handle virtual text with multiple hl in more cases (#25304) --- src/nvim/decoration.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index b70f070a51..9d391cde8c 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -153,6 +153,24 @@ void clear_virttext(VirtText *text) *text = (VirtText)KV_INITIAL_VALUE; } +/// Get the next chunk of a virtual text item. +/// +/// @param[in] vt The virtual text item +/// @param[in,out] pos Position in the virtual text item +/// @param[in,out] attr Highlight attribute +/// +/// @return The text of the chunk, or NULL if there are no more chunks +char *next_virt_text_chunk(VirtText vt, size_t *pos, int *attr) +{ + char *text = NULL; + for (; text == NULL && *pos < kv_size(vt); (*pos)++) { + text = kv_A(vt, *pos).text; + int hl_id = kv_A(vt, *pos).hl_id; + *attr = hl_combine_attr(*attr, hl_id > 0 ? syn_id2attr(hl_id) : 0); + } + return text; +} + Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) { MarkTreeIter itr[1] = { 0 }; -- cgit From cf8b2c0e74fd5e723b0c15c2ce84e6900fd322d3 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 30 Sep 2023 12:05:28 +0800 Subject: build(iwyu): add a few more _defs.h mappings (#25435) --- src/nvim/decoration.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 9d391cde8c..59e0711906 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -2,6 +2,7 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include +#include #include "nvim/buffer.h" #include "nvim/decoration.h" -- cgit From 68cb4a7405ea9f8841d1f25ee8997c49e77fa679 Mon Sep 17 00:00:00 2001 From: bfredl Date: Fri, 3 Nov 2023 12:26:38 +0100 Subject: feat(extmarks): add "undo_restore" flag to opt out of undo-restoring It is a design goal of extmarks that they allow precise tracking of changes across undo/redo, including restore the exact positions after a do/undo or undo/redo cycle. However this behavior is not useful for all usecases. Many plugins won't keep marks around for long after text changes, but uses them more like a cache until some external source (like LSP semantic highlights) has fully updated to changed text and then will explicitly readjust/replace extmarks as needed. Add a "undo_restore" flag which is true by default (matches existing behavior) but can be set to false to opt-out of this behavior. Delete dead u_extmark_set() code. --- src/nvim/decoration.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 59e0711906..24f6693fe2 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -66,7 +66,7 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start } extmark_set(buf, (uint32_t)src_id, NULL, (int)lnum - 1, hl_start, (int)lnum - 1 + end_off, hl_end, - &decor, true, false, kExtmarkNoUndo, NULL); + &decor, true, false, true, NULL); } } -- cgit From 4e6f559b8c5f77924fdbe2e5abd9c6aa8efad13f Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Tue, 24 Oct 2023 13:32:00 +0200 Subject: feat(extmarks): add 'invalidate' property to extmarks Problem: No way to have extmarks automatically removed when the range it is attached to is deleted. Solution: Add new 'invalidate' property that will hide a mark when the entirety of its range is deleted. When "undo_restore" is set to false, delete the mark from the buffer instead. --- src/nvim/decoration.c | 60 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 18 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 24f6693fe2..8341f29410 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -66,7 +66,7 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start } extmark_set(buf, (uint32_t)src_id, NULL, (int)lnum - 1, hl_start, (int)lnum - 1 + end_off, hl_end, - &decor, true, false, true, NULL); + &decor, true, false, true, false, NULL); } } @@ -95,7 +95,30 @@ void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) } } -void decor_remove(buf_T *buf, int row, int row2, Decoration *decor) +void decor_add(buf_T *buf, int row, int row2, Decoration *decor, bool hl_id) +{ + if (decor) { + if (kv_size(decor->virt_text) && decor->virt_text_pos == kVTInline) { + buf->b_virt_text_inline++; + } + if (kv_size(decor->virt_lines)) { + buf->b_virt_line_blocks++; + } + if (decor_has_sign(decor)) { + buf->b_signs++; + } + if (decor->sign_text) { + buf->b_signs_with_text++; + // TODO(lewis6991): smarter invalidation + buf_signcols_add_check(buf, NULL); + } + } + if (decor || hl_id) { + decor_redraw(buf, row, row2 > -1 ? row2 : row, decor); + } +} + +void decor_remove(buf_T *buf, int row, int row2, Decoration *decor, bool invalidate) { decor_redraw(buf, row, row2, decor); if (decor) { @@ -119,7 +142,9 @@ void decor_remove(buf_T *buf, int row, int row2, Decoration *decor) } } } - decor_free(decor); + if (!invalidate) { + decor_free(decor); + } } void decor_clear(Decoration *decor) @@ -180,7 +205,7 @@ Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) MTKey mark = marktree_itr_current(itr); if (mark.pos.row < 0 || mark.pos.row > row) { break; - } else if (marktree_decor_level(mark) < kDecorLevelVisible) { + } else if (mt_invalid(mark) || marktree_decor_level(mark) < kDecorLevelVisible) { goto next_mark; } Decoration *decor = mark.decor_full; @@ -236,14 +261,14 @@ bool decor_redraw_start(win_T *wp, int top_row, DecorState *state) MTPair pair; while (marktree_itr_step_overlap(buf->b_marktree, state->itr, &pair)) { - if (marktree_decor_level(pair.start) < kDecorLevelVisible) { + if (mt_invalid(pair.start) || marktree_decor_level(pair.start) < kDecorLevelVisible) { continue; } Decoration decor = get_decor(pair.start); - 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); + decor_push(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 @@ -266,8 +291,8 @@ bool decor_redraw_line(win_T *wp, int row, DecorState *state) 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, - Decoration *decor, bool owned, uint64_t ns_id, uint64_t mark_id) +static void decor_push(DecorState *state, int start_row, int start_col, int end_row, int end_col, + Decoration *decor, bool owned, uint64_t ns_id, uint64_t mark_id) { int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; @@ -327,8 +352,7 @@ int decor_redraw_col(win_T *wp, int col, int win_col, bool hidden, DecorState *s break; } - if (mt_end(mark) - || marktree_decor_level(mark) < kDecorLevelVisible) { + if (mt_invalid(mark) || mt_end(mark) || marktree_decor_level(mark) < kDecorLevelVisible) { goto next_mark; } @@ -339,8 +363,8 @@ int decor_redraw_col(win_T *wp, int col, int win_col, bool hidden, DecorState *s endpos = mark.pos; } - decor_add(state, mark.pos.row, mark.pos.col, endpos.row, endpos.col, - &decor, false, mark.ns, mark.id); + decor_push(state, mark.pos.row, mark.pos.col, endpos.row, endpos.col, + &decor, false, mark.ns, mark.id); next_mark: marktree_itr_next(buf->b_marktree, state->itr); @@ -425,7 +449,7 @@ void decor_redraw_signs(buf_T *buf, int row, int *num_signs, SignTextAttrs sattr MTPair pair; while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { - if (marktree_decor_level(pair.start) < kDecorLevelVisible) { + if (mt_invalid(pair.start) || marktree_decor_level(pair.start) < kDecorLevelVisible) { continue; } @@ -444,7 +468,7 @@ void decor_redraw_signs(buf_T *buf, int row, int *num_signs, SignTextAttrs sattr break; } - if (mt_end(mark) || marktree_decor_level(mark) < kDecorLevelVisible) { + if (mt_end(mark) || mt_invalid(mark) || marktree_decor_level(mark) < kDecorLevelVisible) { goto next_mark; } @@ -605,14 +629,14 @@ bool decor_redraw_eol(win_T *wp, DecorState *state, int *eol_attr, int eol_col) return has_virttext; } -void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col, Decoration *decor, - uint64_t ns_id, uint64_t mark_id) +void decor_push_ephemeral(int start_row, int start_col, int end_row, int end_col, Decoration *decor, + uint64_t ns_id, uint64_t mark_id) { if (end_row == -1) { end_row = start_row; end_col = start_col; } - decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true, ns_id, mark_id); + decor_push(&decor_state, start_row, start_col, end_row, end_col, decor, true, ns_id, mark_id); } /// @param has_fold whether line "lnum" has a fold, or kNone when not calculated yet -- cgit From 353a4be7e84fdc101318215bdcc8a7e780d737fe Mon Sep 17 00:00:00 2001 From: dundargoc Date: Sun, 12 Nov 2023 13:13:58 +0100 Subject: build: remove PVS We already have an extensive suite of static analysis tools we use, which causes a fair bit of redundancy as we get duplicate warnings. PVS is also prone to give false warnings which creates a lot of work to identify and disable. --- src/nvim/decoration.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 8341f29410..b111b01fe9 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -1,6 +1,3 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - #include #include -- cgit From c4afb9788c4f139eb2e3b7aa4d6a6a20b67ba156 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Sat, 11 Nov 2023 00:52:50 +0100 Subject: refactor(sign): move legacy signs to extmarks Problem: The legacy signlist data structures and associated functions are redundant since the introduction of extmark signs. Solution: Store signs defined through the legacy commands in a hashmap, placed signs in the extmark tree. Replace signlist associated functions. Usage of the legacy sign commands should yield no change in behavior with the exception of: - "orphaned signs" are now always removed when the line it is placed on is deleted. This used to depend on the value of 'signcolumn'. - It is no longer possible to place multiple signs with the same identifier in a single group on multiple lines. This will now move the sign instead. Moreover, both signs placed through the legacy sign commands and through |nvim_buf_set_extmark()|: - Will show up in both |sign-place| and |nvim_buf_get_extmarks()|. - Are displayed by increasing sign identifier, left to right. Extmark signs used to be ordered decreasingly as opposed to legacy signs. --- src/nvim/decoration.c | 200 ++++++++++++++++++-------------------------------- 1 file changed, 72 insertions(+), 128 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index b111b01fe9..48dffea24b 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -11,7 +11,7 @@ #include "nvim/memory.h" #include "nvim/move.h" #include "nvim/pos.h" -#include "nvim/sign_defs.h" +#include "nvim/sign.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "decoration.c.generated.h" @@ -92,6 +92,8 @@ void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) } } +static int sign_add_id = 0; + void decor_add(buf_T *buf, int row, int row2, Decoration *decor, bool hl_id) { if (decor) { @@ -102,12 +104,12 @@ void decor_add(buf_T *buf, int row, int row2, Decoration *decor, bool hl_id) buf->b_virt_line_blocks++; } if (decor_has_sign(decor)) { + decor->sign_add_id = sign_add_id++; buf->b_signs++; } if (decor->sign_text) { buf->b_signs_with_text++; - // TODO(lewis6991): smarter invalidation - buf_signcols_add_check(buf, NULL); + buf_signcols_add_check(buf, row + 1); } } if (decor || hl_id) { @@ -152,6 +154,7 @@ void decor_clear(Decoration *decor) } kv_destroy(decor->virt_lines); xfree(decor->sign_text); + xfree(decor->sign_name); } void decor_free(Decoration *decor) @@ -429,107 +432,73 @@ next_mark: return attr; } -void decor_redraw_signs(buf_T *buf, int row, int *num_signs, SignTextAttrs sattrs[], - HlPriId *num_id, HlPriId *line_id, HlPriId *cul_id) +/// Return the sign attributes on the currently refreshed row. +/// +/// @param[out] sattrs Output array for sign text and texthl id +/// @param[out] line_attr Highest priority linehl id +/// @param[out] cul_attr Highest priority culhl id +/// @param[out] num_attr Highest priority numhl id +void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[], int *line_id, + int *cul_id, int *num_id) { - if (!buf->b_signs) { - return; - } - - 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)) { + MarkTreeIter itr[1]; + if (!buf->b_signs || !marktree_itr_get_overlap(buf->b_marktree, row, 0, itr)) { return; } MTPair pair; + int num_text = 0; + kvec_t(MTKey) signs = KV_INITIAL_VALUE; + // TODO(bfredl): integrate with main decor loop. while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { - if (mt_invalid(pair.start) || marktree_decor_level(pair.start) < kDecorLevelVisible) { - continue; - } - - Decoration *decor = pair.start.decor_full; - - if (!decor || !decor_has_sign(decor)) { - continue; + if (!mt_invalid(pair.start) && pair.start.decor_full && decor_has_sign(pair.start.decor_full)) { + pair.start.pos.row = row; + num_text += (pair.start.decor_full->sign_text != NULL); + kv_push(signs, pair.start); } - - decor_to_sign(decor, num_signs, sattrs, num_id, line_id, cul_id); } - while (true) { + while (itr->x) { MTKey mark = marktree_itr_current(itr); - if (mark.pos.row < 0 || mark.pos.row > row) { + if (mark.pos.row != row) { break; } - - if (mt_end(mark) || mt_invalid(mark) || marktree_decor_level(mark) < kDecorLevelVisible) { - goto next_mark; - } - - Decoration *decor = mark.decor_full; - - if (!decor || !decor_has_sign(decor)) { - goto next_mark; + if (!mt_end(mark) && !mt_invalid(mark) && mark.decor_full && decor_has_sign(mark.decor_full)) { + num_text += (mark.decor_full->sign_text != NULL); + kv_push(signs, mark); } - - 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 (kv_size(signs)) { + int width = (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u') ? 1 : wp->w_scwidth; + int idx = MIN(width, num_text) - 1; + qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_cmp); + + for (size_t i = 0; i < kv_size(signs); i++) { + Decoration *decor = kv_A(signs, i).decor_full; + if (idx >= 0 && decor->sign_text) { + sattrs[idx].text = decor->sign_text; + sattrs[idx--].hl_id = decor->sign_hl_id; } - if (j < SIGN_SHOW_MAX) { - sattrs[j] = sattrs[j - 1]; + if (*num_id == 0) { + *num_id = decor->number_hl_id; + } + if (*line_id == 0) { + *line_id = decor->line_hl_id; + } + if (*cul_id == 0) { + *cul_id = decor->cursorline_hl_id; } } - 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 - }; - } + kv_destroy(signs); } } // 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) +int decor_signcols(buf_T *buf, int row, int end_row, int max) { - int count = 0; // count for the number of signs on a given row - int count_remove = 0; // how much to decrement count by when iterating marks for a new row - int signcols = 0; // highest value of count - int currow = -1; // current row - if (max <= 1 && buf->b_signs_with_text >= (size_t)max) { return max; } @@ -538,66 +507,41 @@ int decor_signcols(buf_T *buf, DecorState *state, int row, int end_row, int max) return 0; } - MarkTreeIter itr[1] = { 0 }; - marktree_itr_get(buf->b_marktree, 0, -1, itr); - while (true) { - MTKey mark = marktree_itr_current(itr); - if (mark.pos.row < 0 || mark.pos.row > end_row) { - break; - } - - if ((mark.pos.row < row && mt_end(mark)) - || marktree_decor_level(mark) < kDecorLevelVisible - || !mark.decor_full) { - goto next_mark; - } - - Decoration decor = get_decor(mark); - - if (!decor.sign_text) { - goto next_mark; - } - - if (mark.pos.row > currow) { - count -= count_remove; - count_remove = 0; - currow = mark.pos.row; + int signcols = 0; // highest value of count + for (int currow = row; currow <= end_row; currow++) { + MarkTreeIter itr[1]; + if (!marktree_itr_get_overlap(buf->b_marktree, currow, 0, itr)) { + continue; } - if (!mt_paired(mark)) { - if (mark.pos.row >= row) { + int count = 0; + MTPair pair; + while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { + if (!mt_invalid(pair.start) && pair.start.decor_full && pair.start.decor_full->sign_text) { count++; - if (count > signcols) { - signcols = count; - if (signcols >= max) { - return max; - } - } - count_remove++; } - goto next_mark; } - MTPos altpos = marktree_get_altpos(buf->b_marktree, mark, NULL); - - if (mt_end(mark)) { - if (mark.pos.row >= row && altpos.row <= end_row) { - count_remove++; + while (itr->x) { + MTKey mark = marktree_itr_current(itr); + if (mark.pos.row != currow) { + break; } - } else { - if (altpos.row >= row) { + if (!mt_invalid(mark) && !mt_end(mark) && mark.decor_full && mark.decor_full->sign_text) { count++; - if (count > signcols) { - signcols = count; - if (signcols >= max) { - return max; - } - } } + marktree_itr_next(buf->b_marktree, itr); } -next_mark: - marktree_itr_next(buf->b_marktree, itr); + if (count > signcols) { + if (row != end_row) { + buf->b_signcols.sentinel = currow + 1; + } + if (count >= max) { + return max; + } + signcols = count; + } } return signcols; -- cgit From 585eeacb24e1aa0fed978e46063de100b16b8bdf Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Mon, 20 Nov 2023 02:27:16 +0100 Subject: refactor(sign): store 'signcolumn' width range when it is set Problem: Minimum and maximum signcolumn width is determined each redraw. Solution: Determine and store 'signcolumn' range when option is set. --- src/nvim/decoration.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 48dffea24b..b784d8bea3 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -442,7 +442,9 @@ void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[], int *cul_id, int *num_id) { MarkTreeIter itr[1]; - if (!buf->b_signs || !marktree_itr_get_overlap(buf->b_marktree, row, 0, itr)) { + if (!buf->b_signs + || wp->w_minscwidth == SCL_NO + || !marktree_itr_get_overlap(buf->b_marktree, row, 0, itr)) { return; } @@ -471,7 +473,7 @@ void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[], } if (kv_size(signs)) { - int width = (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u') ? 1 : wp->w_scwidth; + int width = wp->w_minscwidth == SCL_NUM ? 1 : wp->w_scwidth; int idx = MIN(width, num_text) - 1; qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_cmp); -- cgit From 0b38fe4dbb77c15ae6f5779174855acab25fc86c Mon Sep 17 00:00:00 2001 From: bfredl Date: Wed, 8 Mar 2023 15:18:02 +0100 Subject: refactor(decorations): break up Decoration struct into smaller pieces Remove the monolithic Decoration struct. Before this change, each extmark could either represent just a hl_id + priority value as a inline decoration, or it would take a pointer to this monolitic 112 byte struct which has to be allocated. This change separates the decorations into two pieces: DecorSignHighlight for signs, highlights and simple set-flag decorations (like spell, ui-watched), and DecorVirtText for virtual text and lines. The main separation here is whether they are expected to allocate more memory. Currently this is not really true as sign text has to be an allocated string, but the plan is to get rid of this eventually (it can just be an array of two schar_T:s). Further refactors are expected to improve the representation of each decoration kind individually. The goal of this particular PR is to get things started by cutting the Gordian knot which was the monolithic struct Decoration. Now, each extmark can either contain chained indicies/pointers to these kinds of objects, or it can fit a subset of DecorSignHighlight inline. The point of this change is not only to make decorations smaller in memory. In fact, the main motivation is to later allow them to grow _larger_, but on a dynamic, on demand fashion. As a simple example, it would be possible to augment highlights to take a list of multiple `hl_group`:s, which then would trivially map to a chain of multiple DecorSignHighlight entries. One small feature improvement included with this refactor itself, is that the restriction that extmarks cannot be removed inside a decoration provider has been lifted. These are instead safely lifetime extended on a "to free" list until the current iteration of screen drawing is done. NB: flags is a mess. but DecorLevel is useless, this slightly less so --- src/nvim/decoration.c | 745 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 592 insertions(+), 153 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index b784d8bea3..91d5bfcc54 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -1,6 +1,7 @@ #include #include +#include "nvim/api/extmark.h" #include "nvim/buffer.h" #include "nvim/decoration.h" #include "nvim/drawscreen.h" @@ -17,6 +18,17 @@ # include "decoration.c.generated.h" #endif +// TODO(bfredl): These should maybe be per-buffer, so that all resources +// asssociated with a buffer can be freed when the buffer is unloaded. +kvec_t(DecorSignHighlight) decor_items = KV_INITIAL_VALUE; +uint32_t decor_freelist = UINT32_MAX; + +// Decorations might be requested to be deleted in a callback in the middle of redrawing. +// In this case, there might still be live references to the memory allocated for the decoration. +// Keep a "to free" list which can be safely processed when redrawing is done. +DecorVirtText *to_free_virt = NULL; +uint32_t to_free_sh = UINT32_MAX; + /// Add highlighting to a buffer, bounded by two cursor positions, /// with an offset. /// @@ -35,8 +47,8 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start { colnr_T hl_start = 0; colnr_T hl_end = 0; - Decoration decor = DECORATION_INIT; - decor.hl_id = hl_id; + DecorInline decor = DECOR_INLINE_INIT; + decor.data.hl.hl_id = hl_id; // TODO(bfredl): if decoration had blocky mode, we could avoid this loop for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum++) { @@ -61,113 +73,271 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start hl_start = pos_start.col + offset; hl_end = pos_end.col + offset; } + extmark_set(buf, (uint32_t)src_id, NULL, (int)lnum - 1, hl_start, (int)lnum - 1 + end_off, hl_end, - &decor, true, false, true, false, NULL); + decor, MT_FLAG_DECOR_HL, true, false, true, false, NULL); } } -void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) +void decor_redraw(buf_T *buf, int row1, int row2, DecorInline decor) { if (row2 >= row1) { - if (!decor - || decor->hl_id - || decor_has_sign(decor) - || decor->conceal - || decor->spell != kNone) { - redraw_buf_range_later(buf, row1 + 1, row2 + 1); + redraw_buf_range_later(buf, row1 + 1, row2 + 1); + } + + if (decor.ext) { + DecorVirtText *vt = decor.data.ext.vt; + while (vt) { + if (vt->flags & kVTIsLines) { + redraw_buf_line_later(buf, row1 + 1 + ((vt->flags & kVTLinesAbove) ? 0 : 1), true); + changed_line_display_buf(buf); + } else { + if (vt->pos == kVPosInline) { + changed_line_display_buf(buf); + } + } + vt = vt->next; } + + uint32_t idx = decor.data.ext.sh_idx; + while (idx != DECOR_ID_INVALID) { + DecorSignHighlight *sh = &kv_A(decor_items, idx); + decor_redraw_sh(buf, row1, row2, *sh); + idx = sh->next; + } + } else { + decor_redraw_sh(buf, row1, row2, decor_sh_from_inline(decor.data.hl, (String)STRING_INIT)); } +} - if (decor && decor_virt_pos(decor)) { - redraw_buf_line_later(buf, row1 + 1, false); - if (decor->virt_text_pos == kVTInline) { - changed_line_display_buf(buf); +void decor_redraw_sh(buf_T *buf, int row1, int row2, DecorSignHighlight sh) +{ + if (sh.hl_id || (sh.flags & (kSHIsSign|kSHSpellOn|kSHSpellOff))) { + if (row2 >= row1) { + redraw_buf_range_later(buf, row1 + 1, row2 + 1); } } + if (sh.flags & kSHUIWatched) { + redraw_buf_line_later(buf, row1 + 1, false); + } +} - if (decor && kv_size(decor->virt_lines)) { - redraw_buf_line_later(buf, row1 + 1 + (decor->virt_lines_above ? 0 : 1), true); - changed_line_display_buf(buf); +uint32_t decor_put_sh(DecorSignHighlight item) +{ + if (decor_freelist != UINT32_MAX) { + uint32_t pos = decor_freelist; + decor_freelist = kv_A(decor_items, decor_freelist).next; + kv_A(decor_items, pos) = item; + return pos; + } else { + uint32_t pos = (uint32_t)kv_size(decor_items); + kv_push(decor_items, item); + return pos; } } -static int sign_add_id = 0; +DecorVirtText *decor_put_vt(DecorVirtText vt, DecorVirtText *next) +{ + DecorVirtText *decor_alloc = xmalloc(sizeof *decor_alloc); + *decor_alloc = vt; + decor_alloc->next = next; + return decor_alloc; +} -void decor_add(buf_T *buf, int row, int row2, Decoration *decor, bool hl_id) +DecorSignHighlight decor_sh_from_inline(DecorHighlightInline item, String conceal_large) { - if (decor) { - if (kv_size(decor->virt_text) && decor->virt_text_pos == kVTInline) { - buf->b_virt_text_inline++; + // TODO(bfredl): Eventually simple signs will be inlinable as well + assert(!(item.flags & kSHIsSign)); + DecorSignHighlight conv = { + .flags = item.flags, + .priority = item.priority, + .text.data = { 0 }, + .hl_id = item.hl_id, + .number_hl_id = 0, + .line_hl_id = 0, + .cursorline_hl_id = 0, + .next = DECOR_ID_INVALID, + }; + + // TODO(bfredl): 'tis a little bullshit. Won't need it once conceals and signs to use schar_T + if (conceal_large.size) { + String c = conceal_large; + if (c.size <= 8) { + memcpy(conv.text.data, c.data, c.size + (c.size < 8 ? 1 : 0)); + } else { + conv.flags |= kSHConcealAlloc; + conv.text.ptr = xstrdup(conceal_large.data); + } + } else { + memcpy(conv.text.data, item.conceal_char, 4); + conv.text.data[4] = NUL; + } + return conv; +} + +void buf_put_decor(buf_T *buf, DecorInline decor, int row) +{ + if (decor.ext) { + if (decor.data.ext.vt) { + buf_put_decor_virt(buf, decor.data.ext.vt); } - if (kv_size(decor->virt_lines)) { - buf->b_virt_line_blocks++; + if (decor.data.ext.sh_idx != DECOR_ID_INVALID) { + buf_put_decor_sh(buf, &kv_A(decor_items, decor.data.ext.sh_idx), row); } - if (decor_has_sign(decor)) { - decor->sign_add_id = sign_add_id++; - buf->b_signs++; + } +} + +void buf_put_decor_virt(buf_T *buf, DecorVirtText *vt) +{ + if (vt->flags &kVTIsLines) { + buf->b_virt_line_blocks++; + } else { + if (vt->pos == kVPosInline) { + buf->b_virt_text_inline++; } - if (decor->sign_text) { + } + if (vt->next) { + buf_put_decor_virt(buf, vt->next); + } +} + +static int sign_add_id = 0; +void buf_put_decor_sh(buf_T *buf, DecorSignHighlight *sh, int row) +{ + 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); } } - if (decor || hl_id) { - decor_redraw(buf, row, row2 > -1 ? row2 : row, decor); - } } -void decor_remove(buf_T *buf, int row, int row2, Decoration *decor, bool invalidate) +void buf_decor_remove(buf_T *buf, int row, int row2, DecorInline decor, bool free) { decor_redraw(buf, row, row2, decor); - if (decor) { - if (kv_size(decor->virt_text) && decor->virt_text_pos == kVTInline) { + if (decor.ext) { + DecorVirtText *vt = decor.data.ext.vt; + while (vt) { + buf_remove_decor_virt(buf, vt); + vt = vt->next; + } + uint32_t idx = decor.data.ext.sh_idx; + while (idx != DECOR_ID_INVALID) { + DecorSignHighlight *sh = &kv_A(decor_items, idx); + buf_remove_decor_sh(buf, row, row2, sh); + idx = sh->next; + } + if (free) { + decor_free(decor); + } + } +} + +void buf_remove_decor_virt(buf_T *buf, DecorVirtText *vt) +{ + if (vt->flags &kVTIsLines) { + assert(buf->b_virt_line_blocks > 0); + buf->b_virt_line_blocks--; + } else { + if (vt->pos == kVPosInline) { assert(buf->b_virt_text_inline > 0); buf->b_virt_text_inline--; } - if (kv_size(decor->virt_lines)) { - assert(buf->b_virt_line_blocks > 0); - buf->b_virt_line_blocks--; - } - if (decor_has_sign(decor)) { - assert(buf->b_signs > 0); - buf->b_signs--; - if (decor->sign_text) { - assert(buf->b_signs_with_text > 0); - buf->b_signs_with_text--; - if (row2 >= row) { - buf_signcols_del_check(buf, row + 1, row2 + 1); - } + } +} + +void buf_remove_decor_sh(buf_T *buf, int row, 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 (!invalidate) { - decor_free(decor); - } } -void decor_clear(Decoration *decor) +void decor_free(DecorInline decor) { - clear_virttext(&decor->virt_text); - for (size_t i = 0; i < kv_size(decor->virt_lines); i++) { - clear_virttext(&kv_A(decor->virt_lines, i).line); + if (!decor.ext) { + return; + } + DecorVirtText *vt = decor.data.ext.vt; + uint32_t idx = decor.data.ext.sh_idx; + + if (decor_state.running_decor_provider) { + while (vt) { + if (vt->next == NULL) { + vt->next = to_free_virt; + to_free_virt = decor.data.ext.vt; + break; + } + vt = vt->next; + } + while (idx != DECOR_ID_INVALID) { + DecorSignHighlight *sh = &kv_A(decor_items, idx); + if (sh->next == DECOR_ID_INVALID) { + sh->next = to_free_sh; + to_free_sh = decor.data.ext.sh_idx; + break; + } + idx = sh->next; + } + } else { + // safe to delete right now + decor_free_inner(vt, idx); } - kv_destroy(decor->virt_lines); - xfree(decor->sign_text); - xfree(decor->sign_name); } -void decor_free(Decoration *decor) +void decor_free_inner(DecorVirtText *vt, uint32_t first_idx) { - if (decor) { - decor_clear(decor); - xfree(decor); + while (vt) { + if (vt->flags & kVTIsLines) { + clear_virtlines(&vt->data.virt_lines); + } else { + clear_virttext(&vt->data.virt_text); + } + DecorVirtText *tofree = vt; + vt = vt->next; + xfree(tofree); + } + + uint32_t idx = first_idx; + while (idx != DECOR_ID_INVALID) { + DecorSignHighlight *sh = &kv_A(decor_items, idx); + if (sh->flags & (kSHIsSign | kSHConcealAlloc)) { + xfree(sh->text.ptr); + } + if (sh->flags & kSHIsSign) { + xfree(sh->sign_name); + } + if (sh->next == DECOR_ID_INVALID) { + sh->next = decor_freelist; + decor_freelist = first_idx; + break; + } + idx = sh->next; } } +void decor_check_to_be_deleted(void) +{ + assert(!decor_state.running_decor_provider); + decor_free_inner(to_free_virt, to_free_sh); + to_free_virt = NULL; + to_free_sh = DECOR_ID_INVALID; +} + void decor_state_free(DecorState *state) { - xfree(state->active.items); + kv_destroy(state->active); } void clear_virttext(VirtText *text) @@ -179,6 +349,15 @@ void clear_virttext(VirtText *text) *text = (VirtText)KV_INITIAL_VALUE; } +void clear_virtlines(VirtLines *lines) +{ + for (size_t i = 0; i < kv_size(*lines); i++) { + clear_virttext(&kv_A(*lines, i).line); + } + kv_destroy(*lines); + *lines = (VirtLines)KV_INITIAL_VALUE; +} + /// Get the next chunk of a virtual text item. /// /// @param[in] vt The virtual text item @@ -197,7 +376,7 @@ char *next_virt_text_chunk(VirtText vt, size_t *pos, int *attr) return text; } -Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) +DecorVirtText *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); @@ -205,12 +384,14 @@ Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) MTKey mark = marktree_itr_current(itr); if (mark.pos.row < 0 || mark.pos.row > row) { break; - } else if (mt_invalid(mark) || marktree_decor_level(mark) < kDecorLevelVisible) { + } else if (mt_invalid(mark) || !(mark.flags & MT_FLAG_DECOR_EXT)) { goto next_mark; } - Decoration *decor = mark.decor_full; - if ((ns_id == 0 || ns_id == mark.ns) - && decor && kv_size(decor->virt_text)) { + DecorVirtText *decor = mark.decor_data.ext.vt; + while (decor && (decor->flags & kVTIsLines)) { + decor = decor->next; + } + if ((ns_id == 0 || ns_id == mark.ns) && decor) { return decor; } next_mark: @@ -225,30 +406,30 @@ bool decor_redraw_reset(win_T *wp, DecorState *state) state->win = wp; for (size_t i = 0; i < kv_size(state->active); i++) { DecorRange item = kv_A(state->active, i); - if (item.virt_text_owned) { - clear_virttext(&item.decor.virt_text); + if (item.owned && item.kind == kDecorKindVirtText) { + clear_virttext(&item.data.vt->data.virt_text); + xfree(item.data.vt); } } kv_size(state->active) = 0; return wp->w_buffer->b_marktree->n_keys; } -Decoration get_decor(MTKey mark) +/// @return true if decor has a virtual position (virtual text or ui_watched) +bool decor_virt_pos(const DecorRange *decor) { - if (mark.decor_full) { - return *mark.decor_full; - } - Decoration fake = DECORATION_INIT; - fake.hl_id = mark.hl_id; - fake.priority = mark.priority; - fake.hl_eol = (mark.flags & MT_FLAG_HL_EOL); - return fake; + return (decor->kind == kDecorKindVirtText || decor->kind == kDecorKindUIWatched); } -/// @return true if decor has a virtual position (virtual text or ui_watched) -bool decor_virt_pos(const Decoration *const decor) +VirtTextPos decor_virt_pos_kind(const DecorRange *decor) { - return kv_size(decor->virt_text) || decor->ui_watched; + if (decor->kind == kDecorKindVirtText) { + return decor->data.vt->pos; + } + if (decor->kind == kDecorKindUIWatched) { + return decor->data.ui.pos; + } + return kVPosEndOfLine; // not used; return whatever } bool decor_redraw_start(win_T *wp, int top_row, DecorState *state) @@ -261,14 +442,14 @@ bool decor_redraw_start(win_T *wp, int top_row, DecorState *state) MTPair pair; while (marktree_itr_step_overlap(buf->b_marktree, state->itr, &pair)) { - if (mt_invalid(pair.start) || marktree_decor_level(pair.start) < kDecorLevelVisible) { + MTKey m = pair.start; + if (mt_invalid(m) || !mt_decor_any(m)) { continue; } - Decoration decor = get_decor(pair.start); - - decor_push(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); + decor_range_add_from_inline(state, pair.start.pos.row, pair.start.pos.col, pair.end_pos.row, + pair.end_pos.col, + mt_decor(m), false, m.ns, m.id); } return true; // TODO(bfredl): check if available in the region @@ -291,20 +472,35 @@ bool decor_redraw_line(win_T *wp, int row, DecorState *state) return (k.pos.row >= 0 && k.pos.row <= row); } -static void decor_push(DecorState *state, int start_row, int start_col, int end_row, int end_col, - Decoration *decor, bool owned, uint64_t ns_id, uint64_t mark_id) +static void decor_range_add_from_inline(DecorState *state, int start_row, int start_col, + int end_row, int end_col, DecorInline decor, bool owned, + uint32_t ns, uint32_t mark_id) { - int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0; - - DecorRange range = { start_row, start_col, end_row, end_col, - *decor, attr_id, - kv_size(decor->virt_text) && owned, -10, ns_id, mark_id }; + if (decor.ext) { + DecorVirtText *vt = decor.data.ext.vt; + while (vt) { + decor_range_add_virt(state, start_row, start_col, end_row, end_col, vt, owned); + vt = vt->next; + } + uint32_t idx = decor.data.ext.sh_idx; + while (idx != DECOR_ID_INVALID) { + DecorSignHighlight *sh = &kv_A(decor_items, idx); + decor_range_add_sh(state, start_row, start_col, end_row, end_col, sh, owned, ns, mark_id); + idx = sh->next; + } + } else { + DecorSignHighlight sh = decor_sh_from_inline(decor.data.hl, (String)STRING_INIT); + decor_range_add_sh(state, start_row, start_col, end_row, end_col, &sh, owned, ns, mark_id); + } +} +static void decor_range_insert(DecorState *state, DecorRange range) +{ kv_pushp(state->active); size_t index; for (index = kv_size(state->active) - 1; index > 0; index--) { DecorRange item = kv_A(state->active, index - 1); - if (item.decor.priority <= range.decor.priority) { + if (item.priority <= range.priority) { break; } kv_A(state->active, index) = kv_A(state->active, index - 1); @@ -312,13 +508,60 @@ static void decor_push(DecorState *state, int start_row, int start_col, int end_ kv_A(state->active, index) = range; } +void decor_range_add_virt(DecorState *state, int start_row, int start_col, int end_row, int end_col, + DecorVirtText *vt, bool owned) +{ + bool is_lines = vt->flags & kVTIsLines; + DecorRange range = { + .start_row = start_row, .start_col = start_col, .end_row = end_row, .end_col = end_col, + .kind = is_lines ? kDecorKindVirtLines : kDecorKindVirtText, + .data.vt = vt, + .attr_id = 0, + .owned = owned, + .priority = vt->priority, + .draw_col = -10, + }; + decor_range_insert(state, range); +} + +void decor_range_add_sh(DecorState *state, int start_row, int start_col, int end_row, int end_col, + DecorSignHighlight *sh, bool owned, uint32_t ns, uint32_t mark_id) +{ + DecorRange range = { + .start_row = start_row, .start_col = start_col, .end_row = end_row, .end_col = end_col, + .kind = kDecorKindHighlight, + .data.sh = *sh, + .attr_id = 0, + .owned = owned, + .priority = sh->priority, + .draw_col = -10, + }; + + if (sh->hl_id || (sh->flags & (kSHIsSign | kSHConceal | kSHSpellOn | kSHSpellOff))) { + if (sh->hl_id) { + range.attr_id = syn_id2attr(sh->hl_id); + } + decor_range_insert(state, range); + } + + if (sh->flags & (kSHUIWatched)) { + range.kind = kDecorKindUIWatched; + range.data.ui.ns_id = ns; + range.data.ui.mark_id = mark_id; + range.data.ui.pos = (sh->flags & kSHUIWatchedOverlay) ? kVPosOverlay : kVPosEndOfLine; + decor_range_insert(state, range); + } +} + /// Initialize the draw_col of a newly-added virtual text item. static void decor_init_draw_col(int win_col, bool hidden, DecorRange *item) { - if (win_col < 0 && item->decor.virt_text_pos != kVTInline) { + DecorVirtText *vt = item->kind == kDecorKindVirtText ? item->data.vt : NULL; + VirtTextPos pos = decor_virt_pos_kind(item); + if (win_col < 0 && pos != kVPosInline) { item->draw_col = win_col; - } else if (item->decor.virt_text_pos == kVTOverlay) { - item->draw_col = (item->decor.virt_text_hide && hidden) ? INT_MIN : win_col; + } else if (pos == kVPosOverlay) { + item->draw_col = (vt && (vt->flags & kVTHide) && hidden) ? INT_MIN : win_col; } else { item->draw_col = -1; } @@ -352,19 +595,17 @@ int decor_redraw_col(win_T *wp, int col, int win_col, bool hidden, DecorState *s break; } - if (mt_invalid(mark) || mt_end(mark) || marktree_decor_level(mark) < kDecorLevelVisible) { + if (mt_invalid(mark) || mt_end(mark) || !mt_decor_any(mark)) { goto next_mark; } - Decoration decor = get_decor(mark); - MTPos endpos = marktree_get_altpos(buf->b_marktree, mark, NULL); if (endpos.row == -1) { endpos = mark.pos; } - decor_push(state, mark.pos.row, mark.pos.col, endpos.row, endpos.col, - &decor, false, mark.ns, mark.id); + decor_range_add_from_inline(state, mark.pos.row, mark.pos.col, endpos.row, endpos.col, + mt_decor(mark), false, mark.ns, mark.id); next_mark: marktree_itr_next(buf->b_marktree, state->itr); @@ -382,7 +623,7 @@ next_mark: bool active = false, keep = true; if (item.end_row < state->row || (item.end_row == state->row && item.end_col <= col)) { - if (!(item.start_row >= state->row && decor_virt_pos(&item.decor))) { + if (!(item.start_row >= state->row && decor_virt_pos(&item))) { keep = false; } } else { @@ -401,26 +642,35 @@ next_mark: if (active && item.attr_id > 0) { attr = hl_combine_attr(attr, item.attr_id); } - if (active && item.decor.conceal) { + if (active && item.kind == kDecorKindHighlight && (item.data.sh.flags & kSHConceal)) { conceal = 1; if (item.start_row == state->row && item.start_col == col) { + DecorSignHighlight *sh = &item.data.sh; conceal = 2; - conceal_char = item.decor.conceal_char; + char *text = (sh->flags & kSHConcealAlloc) ? sh->text.ptr : sh->text.data; + // TODO(bfredl): kSHConcealAlloc is obviously a waste unless we change + // `conceal_char` to schar_T + conceal_char = utf_ptr2char(text); state->col_until = MIN(state->col_until, item.start_col); conceal_attr = item.attr_id; } } - if (active && item.decor.spell != kNone) { - spell = item.decor.spell; + if (active && item.kind == kDecorKindHighlight) { + if (item.data.sh.flags & kSHSpellOn) { + spell = kTrue; + } else if (item.data.sh.flags & kSHSpellOff) { + spell = kFalse; + } } if (item.start_row == state->row && item.start_col <= col - && decor_virt_pos(&item.decor) && item.draw_col == -10) { + && decor_virt_pos(&item) && item.draw_col == -10) { decor_init_draw_col(win_col, hidden, &item); } if (keep) { kv_A(state->active, j++) = item; - } else if (item.virt_text_owned) { - clear_virttext(&item.decor.virt_text); + } else if (item.owned && item.kind == kDecorKindVirtText) { + clear_virttext(&item.data.vt->data.virt_text); + xfree(item.data.vt); } } kv_size(state->active) = j; @@ -432,6 +682,21 @@ next_mark: return attr; } +typedef struct { + DecorSignHighlight *sh; + uint32_t id; +} SignItem; + +int sign_item_cmp(const void *p1, const void *p2) +{ + const SignItem *s1 = (SignItem *)p1; + const SignItem *s2 = (SignItem *)p2; + int n = s2->sh->priority - s1->sh->priority; + + return n ? n : (n = (int)(s2->id - s1->id)) + ? n : (s2->sh->sign_add_id - s1->sh->sign_add_id); +} + /// Return the sign attributes on the currently refreshed row. /// /// @param[out] sattrs Output array for sign text and texthl id @@ -450,13 +715,15 @@ void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[], MTPair pair; int num_text = 0; - kvec_t(MTKey) signs = KV_INITIAL_VALUE; + kvec_t(SignItem) signs = KV_INITIAL_VALUE; // TODO(bfredl): integrate with main decor loop. while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { - if (!mt_invalid(pair.start) && pair.start.decor_full && decor_has_sign(pair.start.decor_full)) { - pair.start.pos.row = row; - num_text += (pair.start.decor_full->sign_text != NULL); - kv_push(signs, pair.start); + if (!mt_invalid(pair.start) && mt_decor_sign(pair.start)) { + DecorSignHighlight *sh = decor_find_sign(mt_decor(pair.start)); + if (sh) { + num_text += (sh->text.ptr != NULL); + kv_push(signs, ((SignItem){ sh, pair.start.id })); + } } } @@ -465,38 +732,60 @@ void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[], if (mark.pos.row != row) { break; } - if (!mt_end(mark) && !mt_invalid(mark) && mark.decor_full && decor_has_sign(mark.decor_full)) { - num_text += (mark.decor_full->sign_text != NULL); - kv_push(signs, mark); + if (!mt_end(mark) && !mt_invalid(mark) && mt_decor_sign(mark)) { + DecorSignHighlight *sh = decor_find_sign(mt_decor(mark)); + if (sh) { + num_text += (sh->text.ptr != NULL); + kv_push(signs, ((SignItem){ sh, mark.id })); + } } + marktree_itr_next(buf->b_marktree, itr); } if (kv_size(signs)) { int width = wp->w_minscwidth == SCL_NUM ? 1 : wp->w_scwidth; int idx = MIN(width, num_text) - 1; - qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_cmp); + qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(kv_A(signs, 0)), sign_item_cmp); for (size_t i = 0; i < kv_size(signs); i++) { - Decoration *decor = kv_A(signs, i).decor_full; - if (idx >= 0 && decor->sign_text) { - sattrs[idx].text = decor->sign_text; - sattrs[idx--].hl_id = decor->sign_hl_id; + DecorSignHighlight *sh = kv_A(signs, i).sh; + if (idx >= 0 && sh->text.ptr) { + sattrs[idx].text = sh->text.ptr; + sattrs[idx--].hl_id = sh->hl_id; } if (*num_id == 0) { - *num_id = decor->number_hl_id; + *num_id = sh->number_hl_id; } if (*line_id == 0) { - *line_id = decor->line_hl_id; + *line_id = sh->line_hl_id; } if (*cul_id == 0) { - *cul_id = decor->cursorline_hl_id; + *cul_id = sh->cursorline_hl_id; } } kv_destroy(signs); } } +DecorSignHighlight *decor_find_sign(DecorInline decor) +{ + if (!decor.ext) { + return NULL; + } + uint32_t decor_id = decor.data.ext.sh_idx; + while (true) { + if (decor_id == DECOR_ID_INVALID) { + return NULL; + } + DecorSignHighlight *sh = &kv_A(decor_items, decor_id); + if (sh->flags & kSHIsSign) { + return sh; + } + decor_id = sh->next; + } +} + // Get the maximum required amount of sign columns needed between row and // end_row. int decor_signcols(buf_T *buf, int row, int end_row, int max) @@ -510,6 +799,8 @@ int decor_signcols(buf_T *buf, int row, int end_row, int max) } int signcols = 0; // highest value of count + // 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 (int currow = row; currow <= end_row; currow++) { MarkTreeIter itr[1]; if (!marktree_itr_get_overlap(buf->b_marktree, currow, 0, itr)) { @@ -519,7 +810,7 @@ int decor_signcols(buf_T *buf, int row, int end_row, int max) int count = 0; MTPair pair; while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { - if (!mt_invalid(pair.start) && pair.start.decor_full && pair.start.decor_full->sign_text) { + if (!mt_invalid(pair.start) && (pair.start.flags & MT_FLAG_DECOR_SIGNTEXT)) { count++; } } @@ -529,8 +820,11 @@ int decor_signcols(buf_T *buf, int row, int end_row, int max) if (mark.pos.row != currow) { break; } - if (!mt_invalid(mark) && !mt_end(mark) && mark.decor_full && mark.decor_full->sign_text) { - count++; + if (!mt_invalid(mark) && !mt_end(mark) && (mark.flags & MT_FLAG_DECOR_SIGNTEXT)) { + DecorSignHighlight *sh = decor_find_sign(mt_decor(mark)); + if (sh && sh->text.ptr) { + count++; + } } marktree_itr_next(buf->b_marktree, itr); } @@ -558,28 +852,19 @@ bool decor_redraw_eol(win_T *wp, DecorState *state, int *eol_attr, int eol_col) { decor_redraw_col(wp, MAXCOL, MAXCOL, false, state); state->eol_col = eol_col; - bool has_virttext = false; + 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.decor)) { - has_virttext = true; + if (item.start_row == state->row && decor_virt_pos(&item)) { + has_virt_pos = true; } - if (item.decor.hl_eol && item.start_row <= state->row) { + if (item.kind == kDecorKindHighlight + && (item.data.sh.flags & kSHHlEol) && item.start_row <= state->row) { *eol_attr = hl_combine_attr(*eol_attr, item.attr_id); } } - return has_virttext; -} - -void decor_push_ephemeral(int start_row, int start_col, int end_row, int end_col, Decoration *decor, - uint64_t ns_id, uint64_t mark_id) -{ - if (end_row == -1) { - end_row = start_row; - end_col = start_col; - } - decor_push(&decor_state, start_row, start_col, end_row, end_col, decor, true, ns_id, mark_id); + return has_virt_pos; } /// @param has_fold whether line "lnum" has a fold, or kNone when not calculated yet @@ -612,18 +897,22 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fo MTKey mark = marktree_itr_current(itr); if (mark.pos.row < 0 || mark.pos.row >= end_row) { break; - } else if (mt_end(mark) - || marktree_decor_level(mark) < kDecorLevelVirtLine - || !mark.decor_full) { + } else if (mt_end(mark) || !(mark.flags & MT_FLAG_DECOR_VIRT_LINES)) { goto next_mark; } - Decoration *const decor = mark.decor_full; - const int draw_row = mark.pos.row + (decor->virt_lines_above ? 0 : 1); - if (draw_row == row) { - virt_lines += (int)kv_size(decor->virt_lines); - if (lines) { - kv_splice(*lines, decor->virt_lines); + DecorVirtText *vt = mark.decor_data.ext.vt; + while (vt) { + if (vt->flags & kVTIsLines) { + bool above = vt->flags & kVTLinesAbove; + int draw_row = mark.pos.row + (above ? 0 : 1); + if (draw_row == row) { + virt_lines += (int)kv_size(vt->data.virt_lines); + if (lines) { + kv_splice(*lines, vt->data.virt_lines); + } + } } + vt = vt->next; } next_mark: marktree_itr_next(buf->b_marktree, itr); @@ -631,3 +920,153 @@ next_mark: return virt_lines; } + +/// This assumes maximum one entry of each kind, which will not always be the case. +void decor_to_dict_legacy(Dictionary *dict, DecorInline decor, bool hl_name) +{ + DecorSignHighlight sh_hl = DECOR_SIGN_HIGHLIGHT_INIT; + DecorSignHighlight sh_sign = DECOR_SIGN_HIGHLIGHT_INIT; + DecorVirtText *virt_text = NULL; + DecorVirtText *virt_lines = NULL; + int32_t priority = -1; // sentinel value which cannot actually be set + + if (decor.ext) { + DecorVirtText *vt = decor.data.ext.vt; + while (vt) { + if (vt->flags & kVTIsLines) { + virt_lines = vt; + } else { + virt_text = vt; + } + vt = vt->next; + } + + uint32_t idx = decor.data.ext.sh_idx; + while (idx != DECOR_ID_INVALID) { + DecorSignHighlight *sh = &kv_A(decor_items, idx); + if (sh->flags & (kSHIsSign)) { + sh_sign = *sh; + } else { + sh_hl = *sh; + } + idx = sh->next; + } + } else { + sh_hl = decor_sh_from_inline(decor.data.hl, (String)STRING_INIT); + } + + if (sh_hl.hl_id) { + PUT(*dict, "hl_group", hl_group_name(sh_hl.hl_id, hl_name)); + PUT(*dict, "hl_eol", BOOLEAN_OBJ(sh_hl.flags & kSHHlEol)); + if (sh_hl.flags & kSHConceal) { + String name; + if (sh_hl.flags & kSHConcealAlloc) { + name = cstr_to_string(sh_hl.text.ptr); + } else { + name = cbuf_to_string(sh_hl.text.data, strnlen(sh_hl.text.data, 8)); + } + PUT(*dict, "conceal", STRING_OBJ(name)); + } + + if (sh_hl.flags & kSHSpellOn) { + PUT(*dict, "spell", BOOLEAN_OBJ(true)); + } else if (sh_hl.flags & kSHSpellOff) { + PUT(*dict, "spell", BOOLEAN_OBJ(false)); + } + + priority = sh_hl.priority; + } + + if (sh_hl.flags & kSHUIWatched) { + PUT(*dict, "ui_watched", BOOLEAN_OBJ(true)); + } + + if (virt_text) { + if (virt_text->hl_mode) { + PUT(*dict, "hl_mode", CSTR_TO_OBJ(hl_mode_str[virt_text->hl_mode])); + } + + Array chunks = virt_text_to_array(virt_text->data.virt_text, hl_name); + PUT(*dict, "virt_text", ARRAY_OBJ(chunks)); + PUT(*dict, "virt_text_hide", BOOLEAN_OBJ(virt_text->flags & kVTHide)); + if (virt_text->pos == kVPosWinCol) { + PUT(*dict, "virt_text_win_col", INTEGER_OBJ(virt_text->col)); + } + PUT(*dict, "virt_text_pos", + CSTR_TO_OBJ(virt_text_pos_str[virt_text->pos])); + priority = virt_text->priority; + } + + if (virt_lines) { + Array all_chunks = ARRAY_DICT_INIT; + bool virt_lines_leftcol = false; + for (size_t i = 0; i < kv_size(virt_lines->data.virt_lines); i++) { + virt_lines_leftcol = kv_A(virt_lines->data.virt_lines, i).left_col; + Array chunks = virt_text_to_array(kv_A(virt_lines->data.virt_lines, i).line, hl_name); + ADD(all_chunks, ARRAY_OBJ(chunks)); + } + PUT(*dict, "virt_lines", ARRAY_OBJ(all_chunks)); + PUT(*dict, "virt_lines_above", BOOLEAN_OBJ(virt_lines->flags & kVTLinesAbove)); + PUT(*dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol)); + priority = virt_lines->priority; + } + + if (sh_sign.flags & kSHIsSign) { + if (sh_sign.text.ptr) { + PUT(*dict, "sign_text", CSTR_TO_OBJ(sh_sign.text.ptr)); + } + + // uncrustify:off + + struct { char *name; const int val; } hls[] = { + { "sign_hl_group" , sh_sign.hl_id }, + { "number_hl_group" , sh_sign.number_hl_id }, + { "line_hl_group" , sh_sign.line_hl_id }, + { "cursorline_hl_group", sh_sign.cursorline_hl_id }, + { NULL, 0 }, + }; + + // uncrustify:on + + for (int j = 0; hls[j].name; j++) { + if (hls[j].val) { + PUT(*dict, hls[j].name, hl_group_name(hls[j].val, hl_name)); + } + } + priority = sh_sign.priority; + } + + if (priority != -1) { + PUT(*dict, "priority", INTEGER_OBJ(priority)); + } +} + +uint16_t decor_type_flags(DecorInline decor) +{ + if (decor.ext) { + uint16_t type_flags = kExtmarkNone; + DecorVirtText *vt = decor.data.ext.vt; + while (vt) { + type_flags |= (vt->flags & kVTIsLines) ? kExtmarkVirtLines : kExtmarkVirtText; + vt = vt->next; + } + uint32_t idx = decor.data.ext.sh_idx; + while (idx != DECOR_ID_INVALID) { + DecorSignHighlight *sh = &kv_A(decor_items, idx); + type_flags |= (sh->flags & kSHIsSign) ? kExtmarkSign : kExtmarkHighlight; + idx = sh->next; + } + return type_flags; + } else { + return (decor.data.hl.flags & kSHIsSign) ? kExtmarkSign : kExtmarkHighlight; + } +} + +Object hl_group_name(int hl_id, bool hl_name) +{ + if (hl_name) { + return CSTR_TO_OBJ(syn_id2name(hl_id)); + } else { + return INTEGER_OBJ(hl_id); + } +} -- cgit From fba17d5b882e2903ba7a40eabc6f557e3cf24658 Mon Sep 17 00:00:00 2001 From: bfredl Date: Wed, 22 Nov 2023 11:30:36 +0100 Subject: fix(decorations): fix imbalanced sign count --- src/nvim/decoration.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 91d5bfcc54..2ef689680d 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -179,11 +179,17 @@ DecorSignHighlight decor_sh_from_inline(DecorHighlightInline item, String concea void buf_put_decor(buf_T *buf, DecorInline decor, int row) { if (decor.ext) { - if (decor.data.ext.vt) { - buf_put_decor_virt(buf, decor.data.ext.vt); + DecorVirtText *vt = decor.data.ext.vt; + while (vt) { + buf_put_decor_virt(buf, vt); + vt = vt->next; } - if (decor.data.ext.sh_idx != DECOR_ID_INVALID) { - buf_put_decor_sh(buf, &kv_A(decor_items, decor.data.ext.sh_idx), row); + + uint32_t idx = decor.data.ext.sh_idx; + while (idx != DECOR_ID_INVALID) { + DecorSignHighlight *sh = &kv_A(decor_items, idx); + buf_put_decor_sh(buf, sh, row); + idx = sh->next; } } } -- cgit From c249058758af6919f718750b2f4acb96bafeb2dd Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Sat, 18 Nov 2023 23:49:11 +0100 Subject: feat(extmarks): add sign name to extmark "details" array Problem: Unable to identify legacy signs when fetching extmarks with `nvim_buf_get_extmarks()`. Solution: Add "sign_name" to the extmark detail array. Add some misc. changes as follow-up to #25724 --- src/nvim/decoration.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 2ef689680d..0f4bbaaac7 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -1022,6 +1022,10 @@ void decor_to_dict_legacy(Dictionary *dict, DecorInline decor, bool hl_name) PUT(*dict, "sign_text", CSTR_TO_OBJ(sh_sign.text.ptr)); } + if (sh_sign.sign_name) { + PUT(*dict, "sign_name", CSTR_TO_OBJ(sh_sign.sign_name)); + } + // uncrustify:off struct { char *name; const int val; } hls[] = { -- cgit From c126a3756a09716ed64fd2acb9eee5d411c4aa7b Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Thu, 23 Nov 2023 12:58:17 +0100 Subject: fix(column): apply numhl signs when 'signcolumn' is "no" (#26167) --- src/nvim/decoration.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 0f4bbaaac7..43b07501a0 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -713,9 +713,7 @@ void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[], int *cul_id, int *num_id) { MarkTreeIter itr[1]; - if (!buf->b_signs - || wp->w_minscwidth == SCL_NO - || !marktree_itr_get_overlap(buf->b_marktree, row, 0, itr)) { + if (!buf->b_signs || !marktree_itr_get_overlap(buf->b_marktree, row, 0, itr)) { return; } -- cgit From a827003e3052c6d9ee7bdb71518182e9bd76317d Mon Sep 17 00:00:00 2001 From: dundargoc Date: Sat, 25 Nov 2023 11:32:32 +0100 Subject: build: rework IWYU mapping files Create mapping to most of the C spec and some POSIX specific functions. This is more robust than relying files shipped with IWYU. --- src/nvim/decoration.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 43b07501a0..f860b65c93 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -1,7 +1,13 @@ #include #include +#include +#include +#include #include "nvim/api/extmark.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/decoration.h" #include "nvim/drawscreen.h" @@ -9,8 +15,10 @@ #include "nvim/fold.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/move.h" +#include "nvim/option_vars.h" #include "nvim/pos.h" #include "nvim/sign.h" -- cgit From 2c16c6a6c42f46e290df5441c37572f296aeb09f Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Mon, 27 Nov 2023 10:43:13 +0100 Subject: docs: small fixes (#26154) --- src/nvim/decoration.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index f860b65c93..e0fde50d8d 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -27,7 +27,7 @@ #endif // TODO(bfredl): These should maybe be per-buffer, so that all resources -// asssociated with a buffer can be freed when the buffer is unloaded. +// associated with a buffer can be freed when the buffer is unloaded. kvec_t(DecorSignHighlight) decor_items = KV_INITIAL_VALUE; uint32_t decor_freelist = UINT32_MAX; -- cgit From f4aedbae4cb1f206f5b7c6142697b71dd473059b Mon Sep 17 00:00:00 2001 From: dundargoc Date: Mon, 27 Nov 2023 18:39:38 +0100 Subject: build(IWYU): fix includes for undo_defs.h --- src/nvim/decoration.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index e0fde50d8d..503d81f742 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -19,7 +19,7 @@ #include "nvim/memory.h" #include "nvim/move.h" #include "nvim/option_vars.h" -#include "nvim/pos.h" +#include "nvim/pos_defs.h" #include "nvim/sign.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -- cgit From ae3685798deaf51f14422c568998998c03f91f2c Mon Sep 17 00:00:00 2001 From: bfredl Date: Sun, 26 Nov 2023 21:07:29 +0100 Subject: feat(decoration): allow conceal_char to be a composing char decor->text.str pointer must go. This removes it for conceal char, in preparation for a larger PR which will also handle the sign case. By actually allowing composing chars for a conceal chars, this becomes a feature and not just a refactor, as a bonus. --- src/nvim/decoration.c | 54 ++++++++++++++++++++++----------------------------- 1 file changed, 23 insertions(+), 31 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 503d81f742..3a8a9c74bb 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -13,6 +13,7 @@ #include "nvim/drawscreen.h" #include "nvim/extmark.h" #include "nvim/fold.h" +#include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/mbyte.h" @@ -115,7 +116,7 @@ void decor_redraw(buf_T *buf, int row1, int row2, DecorInline decor) idx = sh->next; } } else { - decor_redraw_sh(buf, row1, row2, decor_sh_from_inline(decor.data.hl, (String)STRING_INIT)); + decor_redraw_sh(buf, row1, row2, decor_sh_from_inline(decor.data.hl)); } } @@ -153,14 +154,14 @@ DecorVirtText *decor_put_vt(DecorVirtText vt, DecorVirtText *next) return decor_alloc; } -DecorSignHighlight decor_sh_from_inline(DecorHighlightInline item, String conceal_large) +DecorSignHighlight decor_sh_from_inline(DecorHighlightInline item) { // TODO(bfredl): Eventually simple signs will be inlinable as well assert(!(item.flags & kSHIsSign)); DecorSignHighlight conv = { .flags = item.flags, .priority = item.priority, - .text.data = { 0 }, + .text.sc[0] = item.conceal_char, .hl_id = item.hl_id, .number_hl_id = 0, .line_hl_id = 0, @@ -168,19 +169,6 @@ DecorSignHighlight decor_sh_from_inline(DecorHighlightInline item, String concea .next = DECOR_ID_INVALID, }; - // TODO(bfredl): 'tis a little bullshit. Won't need it once conceals and signs to use schar_T - if (conceal_large.size) { - String c = conceal_large; - if (c.size <= 8) { - memcpy(conv.text.data, c.data, c.size + (c.size < 8 ? 1 : 0)); - } else { - conv.flags |= kSHConcealAlloc; - conv.text.ptr = xstrdup(conceal_large.data); - } - } else { - memcpy(conv.text.data, item.conceal_char, 4); - conv.text.data[4] = NUL; - } return conv; } @@ -326,12 +314,13 @@ void decor_free_inner(DecorVirtText *vt, uint32_t first_idx) uint32_t idx = first_idx; while (idx != DECOR_ID_INVALID) { DecorSignHighlight *sh = &kv_A(decor_items, idx); - if (sh->flags & (kSHIsSign | kSHConcealAlloc)) { + if (sh->flags & kSHIsSign) { xfree(sh->text.ptr); } if (sh->flags & kSHIsSign) { xfree(sh->sign_name); } + sh->flags = 0; if (sh->next == DECOR_ID_INVALID) { sh->next = decor_freelist; decor_freelist = first_idx; @@ -372,6 +361,16 @@ void clear_virtlines(VirtLines *lines) *lines = (VirtLines)KV_INITIAL_VALUE; } +void decor_check_invalid_glyphs(void) +{ + for (size_t i = 0; i < kv_size(decor_items); i++) { + DecorSignHighlight *it = &kv_A(decor_items, i); + if ((it->flags & kSHConceal) && schar_high(it->text.sc[0])) { + it->text.sc[0] = schar_from_char(schar_get_first_codepoint(it->text.sc[0])); + } + } +} + /// Get the next chunk of a virtual text item. /// /// @param[in] vt The virtual text item @@ -503,7 +502,7 @@ static void decor_range_add_from_inline(DecorState *state, int start_row, int st idx = sh->next; } } else { - DecorSignHighlight sh = decor_sh_from_inline(decor.data.hl, (String)STRING_INIT); + DecorSignHighlight sh = decor_sh_from_inline(decor.data.hl); decor_range_add_sh(state, start_row, start_col, end_row, end_col, &sh, owned, ns, mark_id); } } @@ -628,7 +627,7 @@ next_mark: int attr = 0; size_t j = 0; int conceal = 0; - int conceal_char = 0; + schar_T conceal_char = 0; int conceal_attr = 0; TriState spell = kNone; @@ -661,10 +660,7 @@ next_mark: if (item.start_row == state->row && item.start_col == col) { DecorSignHighlight *sh = &item.data.sh; conceal = 2; - char *text = (sh->flags & kSHConcealAlloc) ? sh->text.ptr : sh->text.data; - // TODO(bfredl): kSHConcealAlloc is obviously a waste unless we change - // `conceal_char` to schar_T - conceal_char = utf_ptr2char(text); + conceal_char = sh->text.sc[0]; state->col_until = MIN(state->col_until, item.start_col); conceal_attr = item.attr_id; } @@ -964,20 +960,16 @@ void decor_to_dict_legacy(Dictionary *dict, DecorInline decor, bool hl_name) idx = sh->next; } } else { - sh_hl = decor_sh_from_inline(decor.data.hl, (String)STRING_INIT); + sh_hl = decor_sh_from_inline(decor.data.hl); } if (sh_hl.hl_id) { PUT(*dict, "hl_group", hl_group_name(sh_hl.hl_id, hl_name)); PUT(*dict, "hl_eol", BOOLEAN_OBJ(sh_hl.flags & kSHHlEol)); if (sh_hl.flags & kSHConceal) { - String name; - if (sh_hl.flags & kSHConcealAlloc) { - name = cstr_to_string(sh_hl.text.ptr); - } else { - name = cbuf_to_string(sh_hl.text.data, strnlen(sh_hl.text.data, 8)); - } - PUT(*dict, "conceal", STRING_OBJ(name)); + char buf[MAX_SCHAR_SIZE]; + schar_get(buf, sh_hl.text.sc[0]); + PUT(*dict, "conceal", CSTR_TO_OBJ(buf)); } if (sh_hl.flags & kSHSpellOn) { -- cgit From c9f53d0e40815644bbf7c57a0792f2c793c954aa Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 28 Nov 2023 19:00:14 +0800 Subject: refactor: iwyu (#26269) --- src/nvim/decoration.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 3a8a9c74bb..29993bffa5 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -2,12 +2,10 @@ #include #include #include -#include #include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/decoration.h" #include "nvim/drawscreen.h" @@ -16,7 +14,6 @@ #include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" -#include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/move.h" #include "nvim/option_vars.h" -- cgit From 35cec0de4acd351119230330f54b0a45f9823695 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Mon, 27 Nov 2023 16:22:19 +0100 Subject: fix(column): redraw and update signcols for paired extmark Problem: Signcolumn width does not increase when ranged sign does not start at sentinel line. Solution: Handle paired range of added sign when checking signcols. --- src/nvim/decoration.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 29993bffa5..c2bef5920c 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -169,7 +169,7 @@ DecorSignHighlight decor_sh_from_inline(DecorHighlightInline item) return conv; } -void buf_put_decor(buf_T *buf, DecorInline decor, int row) +void buf_put_decor(buf_T *buf, DecorInline decor, int row, int row2) { if (decor.ext) { DecorVirtText *vt = decor.data.ext.vt; @@ -181,7 +181,7 @@ void buf_put_decor(buf_T *buf, DecorInline decor, int row) uint32_t idx = decor.data.ext.sh_idx; while (idx != DECOR_ID_INVALID) { DecorSignHighlight *sh = &kv_A(decor_items, idx); - buf_put_decor_sh(buf, sh, row); + buf_put_decor_sh(buf, sh, row, row2); idx = sh->next; } } @@ -202,14 +202,14 @@ 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) +void buf_put_decor_sh(buf_T *buf, DecorSignHighlight *sh, int row, 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); + buf_signcols_add_check(buf, row + 1, row2 + 1); } } } @@ -835,7 +835,7 @@ int decor_signcols(buf_T *buf, int row, int end_row, int max) } if (count > signcols) { - if (row != end_row) { + if (count > buf->b_signcols.size) { buf->b_signcols.sentinel = currow + 1; } if (count >= max) { -- cgit From 898f8d1f0b9f7f1f32ebefbd557b21a148616c2c Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Tue, 28 Nov 2023 19:23:57 +0100 Subject: refactor(decor): remove sign conditions that are always true --- src/nvim/decoration.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index c2bef5920c..15d2869c48 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -725,10 +725,8 @@ void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[], while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { if (!mt_invalid(pair.start) && mt_decor_sign(pair.start)) { DecorSignHighlight *sh = decor_find_sign(mt_decor(pair.start)); - if (sh) { - num_text += (sh->text.ptr != NULL); - kv_push(signs, ((SignItem){ sh, pair.start.id })); - } + num_text += (sh->text.ptr != NULL); + kv_push(signs, ((SignItem){ sh, pair.start.id })); } } @@ -739,10 +737,8 @@ void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[], } if (!mt_end(mark) && !mt_invalid(mark) && mt_decor_sign(mark)) { DecorSignHighlight *sh = decor_find_sign(mt_decor(mark)); - if (sh) { - num_text += (sh->text.ptr != NULL); - kv_push(signs, ((SignItem){ sh, mark.id })); - } + num_text += (sh->text.ptr != NULL); + kv_push(signs, ((SignItem){ sh, mark.id })); } marktree_itr_next(buf->b_marktree, itr); @@ -826,10 +822,7 @@ int decor_signcols(buf_T *buf, int row, int end_row, int max) break; } if (!mt_invalid(mark) && !mt_end(mark) && (mark.flags & MT_FLAG_DECOR_SIGNTEXT)) { - DecorSignHighlight *sh = decor_find_sign(mt_decor(mark)); - if (sh && sh->text.ptr) { - count++; - } + count++; } marktree_itr_next(buf->b_marktree, itr); } -- cgit From a0e9ef09d7af8274c754ca6c368ef4a6f7411510 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Wed, 29 Nov 2023 02:17:16 +0100 Subject: fix(decorations): do not apply sign highlight id as range attr id --- src/nvim/decoration.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 15d2869c48..407b54d133 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -537,6 +537,10 @@ void decor_range_add_virt(DecorState *state, int start_row, int start_col, int e void decor_range_add_sh(DecorState *state, int start_row, int start_col, int end_row, int end_col, DecorSignHighlight *sh, bool owned, uint32_t ns, uint32_t mark_id) { + if (sh->flags & kSHIsSign) { + return; + } + DecorRange range = { .start_row = start_row, .start_col = start_col, .end_row = end_row, .end_col = end_col, .kind = kDecorKindHighlight, @@ -547,7 +551,7 @@ void decor_range_add_sh(DecorState *state, int start_row, int start_col, int end .draw_col = -10, }; - if (sh->hl_id || (sh->flags & (kSHIsSign | kSHConceal | kSHSpellOn | kSHSpellOff))) { + if (sh->hl_id || (sh->flags & (kSHConceal | kSHSpellOn | kSHSpellOff))) { if (sh->hl_id) { range.attr_id = syn_id2attr(sh->hl_id); } -- cgit From f4001d27efae44c6c07678ad2c72eed5f1a25ea8 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Tue, 28 Nov 2023 05:40:18 +0100 Subject: perf(column): only invalidate lines affected by added sign --- src/nvim/decoration.c | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) (limited to 'src/nvim/decoration.c') diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 407b54d133..11204a1b31 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -791,22 +791,15 @@ DecorSignHighlight *decor_find_sign(DecorInline decor) } } -// Get the maximum required amount of sign columns needed between row and -// end_row. -int decor_signcols(buf_T *buf, int row, int end_row, int max) +// 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 (max <= 1 && buf->b_signs_with_text >= (size_t)max) { - return max; - } - - if (buf->b_signs_with_text == 0) { - return 0; - } - 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 (int currow = row; currow <= end_row; currow++) { + for (; currow < buf->b_signcols.invalid_bot; currow++) { MarkTreeIter itr[1]; if (!marktree_itr_get_overlap(buf->b_marktree, currow, 0, itr)) { continue; @@ -832,17 +825,16 @@ int decor_signcols(buf_T *buf, int row, int end_row, int max) } if (count > signcols) { - if (count > buf->b_signcols.size) { + if (count >= buf->b_signcols.size) { + buf->b_signcols.size = count; buf->b_signcols.sentinel = currow + 1; } if (count >= max) { - return max; + return; } signcols = count; } } - - return signcols; } void decor_redraw_end(DecorState *state) -- cgit