From a0790558c3097f2813c56e404af30c3e2d8b8983 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 20 Feb 2024 19:53:49 +0800 Subject: [PATCH] fix(extmarks): priority order of inline and non-inline virt_text (#27532) --- runtime/doc/api.txt | 7 +- runtime/lua/vim/_meta/api.lua | 7 +- src/nvim/api/extmark.c | 7 +- src/nvim/decoration.c | 2 +- src/nvim/decoration.h | 8 +-- src/nvim/drawline.c | 32 ++++++--- test/functional/ui/decorations_spec.lua | 87 +++++++++++++++++++++++++ 7 files changed, 126 insertions(+), 24 deletions(-) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 83b50dcdbe..503529dd01 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -2754,9 +2754,10 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {*opts}) hidden marks, an "invalid" key is added to the "details" array of |nvim_buf_get_extmarks()| and family. If "undo_restore" is false, the extmark is deleted instead. - • priority: a priority value for the highlight group or sign - attribute. For example treesitter highlighting uses a - value of 100. + • priority: a priority value for the highlight group, sign + attribute or virtual text. For virtual text, item with + highest priority is drawn last. For example treesitter + highlighting uses a value of 100. • strict: boolean that indicates extmark should not be placed if the line or column value is past the end of the buffer or end of the line respectively. Defaults to true. diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 0978f0acf5..31ef3dd64b 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -576,9 +576,10 @@ function vim.api.nvim_buf_line_count(buffer) end --- hidden marks, an "invalid" key is added to the "details" --- array of `nvim_buf_get_extmarks()` and family. If --- "undo_restore" is false, the extmark is deleted instead. ---- • priority: a priority value for the highlight group or sign ---- attribute. For example treesitter highlighting uses a ---- value of 100. +--- • priority: a priority value for the highlight group, sign +--- attribute or virtual text. For virtual text, item with +--- highest priority is drawn last. For example treesitter +--- highlighting uses a value of 100. --- • strict: boolean that indicates extmark should not be --- placed if the line or column value is past the end of the --- buffer or end of the line respectively. Defaults to true. diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index f6f7d332ec..0b5f3471fb 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -452,9 +452,10 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// hidden marks, an "invalid" key is added to the "details" /// array of |nvim_buf_get_extmarks()| and family. If /// "undo_restore" is false, the extmark is deleted instead. -/// - priority: a priority value for the highlight group or sign -/// attribute. For example treesitter highlighting uses a -/// value of 100. +/// - priority: a priority value for the highlight group, sign +/// attribute or virtual text. For virtual text, item with +/// highest priority is drawn last. For example treesitter +/// highlighting uses a value of 100. /// - strict: boolean that indicates extmark should not be placed /// if the line or column value is past the end of the /// buffer or end of the line respectively. Defaults to true. diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 792204bdbc..aaa435fb45 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -541,7 +541,7 @@ void decor_range_add_sh(DecorState *state, int start_row, int start_col, int end } /// Initialize the draw_col of a newly-added virtual text item. -static void decor_init_draw_col(int win_col, bool hidden, DecorRange *item) +void decor_init_draw_col(int win_col, bool hidden, DecorRange *item) { DecorVirtText *vt = item->kind == kDecorKindVirtText ? item->data.vt : NULL; VirtTextPos pos = decor_virt_pos_kind(item); diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index e70c588806..a63cc7afc0 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -52,10 +52,10 @@ typedef struct { ///< Reflects the order of patterns/captures in the query file. DecorRangeKind kind; /// Screen column to draw the virtual text. - /// When -1, the virtual text may be drawn after deciding where. - /// When -3, the virtual text should be drawn on the next screen line. - /// When -10, the virtual text has just been added. - /// When INT_MIN, the virtual text should no longer be drawn. + /// When -1, it should be drawn on the current screen line after deciding where. + /// When -3, it may be drawn at a position yet to be assigned. + /// When -10, it has just been added. + /// When INT_MIN, it should no longer be drawn. int draw_col; } DecorRange; diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index c275fcd7d4..d7c8110d6d 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -769,7 +769,7 @@ static bool has_more_inline_virt(winlinevars_T *wlv, ptrdiff_t v) return false; } -static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t v) +static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t v, bool selected) { while (wlv->n_extra == 0) { if (wlv->virt_inline_i >= kv_size(wlv->virt_inline)) { @@ -779,6 +779,11 @@ static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t DecorState *state = &decor_state; for (size_t i = 0; i < kv_size(state->active); i++) { DecorRange *item = &kv_A(state->active, i); + if (item->draw_col == -3) { + // No more inline virtual text before this non-inline virtual text item, + // so its position can be decided now. + decor_init_draw_col(wlv->off, selected, item); + } if (item->start_row != state->row || item->kind != kDecorKindVirtText || item->data.vt->pos != kVPosInline @@ -1493,6 +1498,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s extra_check = true; } + const bool may_have_inline_virt + = !has_foldtext && buf_meta_total(wp->w_buffer, kMTMetaInline) > 0; int virt_line_index; int virt_line_offset = -1; // Repeat for the whole displayed line. @@ -1656,17 +1663,21 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s bool selected = (area_active || (area_highlighting && noinvcur && wlv.vcol == wp->w_virtcol)); + // When there may be inline virtual text, position of non-inline virtual text + // can only be decided after drawing inline virtual text with lower priority. if (decor_need_recheck) { - decor_recheck_draw_col(wlv.off, selected, &decor_state); + if (!may_have_inline_virt) { + decor_recheck_draw_col(wlv.off, selected, &decor_state); + } decor_need_recheck = false; } if (wlv.filler_todo <= 0) { - extmark_attr = decor_redraw_col(wp, (colnr_T)(ptr - line), wlv.off, selected, - &decor_state); + extmark_attr = decor_redraw_col(wp, (colnr_T)(ptr - line), + may_have_inline_virt ? -3 : wlv.off, + selected, &decor_state); } - - if (!has_foldtext && buf_meta_total(wp->w_buffer, kMTMetaInline) > 0) { - handle_inline_virtual_text(wp, &wlv, ptr - line); + if (may_have_inline_virt) { + handle_inline_virtual_text(wp, &wlv, ptr - line, selected); if (wlv.n_extra > 0 && wlv.virt_inline_hl_mode <= kHlModeReplace) { // restore search_attr and area_attr when n_extra is down to zero // TODO(bfredl): this is ugly as fuck. look if we can do this some other way. @@ -2665,12 +2676,12 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s && !has_foldtext) { if (has_decor && *ptr == NUL && lcs_eol == 0 && lcs_eol_todo) { // Tricky: there might be a virtual text just _after_ the last char - decor_redraw_col(wp, (colnr_T)(ptr - line), wlv.off, false, &decor_state); + decor_redraw_col(wp, (colnr_T)(ptr - line), -1, false, &decor_state); } if (*ptr != NUL || (lcs_eol > 0 && lcs_eol_todo) || (wlv.n_extra > 0 && (wlv.sc_extra != NUL || *wlv.p_extra != NUL)) - || has_more_inline_virt(&wlv, ptr - line)) { + || (may_have_inline_virt && has_more_inline_virt(&wlv, ptr - line))) { mb_schar = wp->w_p_lcs_chars.ext; wlv.char_attr = win_hl_attr(wp, HLF_AT); mb_c = schar_get_first_codepoint(mb_schar); @@ -2819,6 +2830,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s } else if (!is_wrapped) { // Without wrapping, we might need to display right_align and win_col // virt_text for the entire text line. + decor_recheck_draw_col(-1, true, &decor_state); decor_redraw_col(wp, MAXCOL, -1, true, &decor_state); } } @@ -2831,7 +2843,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL && wlv.p_extra != at_end_str) || (wlv.n_extra != 0 && (wlv.sc_extra != NUL || *wlv.p_extra != NUL)) - || has_more_inline_virt(&wlv, ptr - line))) { + || (may_have_inline_virt && has_more_inline_virt(&wlv, ptr - line)))) { const bool wrap = is_wrapped // Wrapping enabled (not a folded line). && wlv.filler_todo <= 0 // Not drawing diff filler lines. && lcs_eol_todo // Haven't printed the lcs_eol character. diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 9bbae0dae0..0ba33ec3d4 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -2401,6 +2401,93 @@ describe('extmark decorations', function() helpers.assert_alive() end) + + it('priority ordering of overlay or win_col virtual text at same position', function() + api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = {{'A'}}, virt_text_pos = 'overlay', priority = 100 }) + api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = {{'A'}}, virt_text_win_col = 30, priority = 100 }) + api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = {{'BB'}}, virt_text_pos = 'overlay', priority = 90 }) + api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = {{'BB'}}, virt_text_win_col = 30, priority = 90 }) + api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = {{'CCC'}}, virt_text_pos = 'overlay', priority = 80 }) + api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text = {{'CCC'}}, virt_text_win_col = 30, priority = 80 }) + screen:expect([[ + ^ABC ABC | + {1:~ }|*13 + | + ]]) + end) + + it('priority ordering of inline and non-inline virtual text at same char', function() + insert(('?'):rep(40) .. ('!'):rep(30)) + api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = {{'A'}}, virt_text_pos = 'overlay', priority = 10 }) + api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = {{'a'}}, virt_text_win_col = 15, priority = 10 }) + api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = {{'BBBB'}}, virt_text_pos = 'inline', priority = 15 }) + api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = {{'C'}}, virt_text_pos = 'overlay', priority = 20 }) + api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = {{'c'}}, virt_text_win_col = 17, priority = 20 }) + api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = {{'DDDD'}}, virt_text_pos = 'inline', priority = 25 }) + api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = {{'E'}}, virt_text_pos = 'overlay', priority = 30 }) + api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = {{'e'}}, virt_text_win_col = 19, priority = 30 }) + api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = {{'FFFF'}}, virt_text_pos = 'inline', priority = 35 }) + api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = {{'G'}}, virt_text_pos = 'overlay', priority = 40 }) + api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = {{'g'}}, virt_text_win_col = 21, priority = 40 }) + api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = {{'HHHH'}}, virt_text_pos = 'inline', priority = 45 }) + api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = {{'I'}}, virt_text_pos = 'overlay', priority = 50 }) + api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = {{'i'}}, virt_text_win_col = 23, priority = 50 }) + api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = {{'JJJJ'}}, virt_text_pos = 'inline', priority = 55 }) + api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = {{'K'}}, virt_text_pos = 'overlay', priority = 60 }) + api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = {{'k'}}, virt_text_win_col = 25, priority = 60 }) + screen:expect([[ + ???????????????a?c?e????????????????????ABBBCDDDEF| + FFGHHHIJJJK!!!!!!!!!!g!i!k!!!!!!!!!!!!!^! | + {1:~ }|*12 + | + ]]) + feed('02x$') + screen:expect([[ + ???????????????a?c?e??????????????????ABBBCDDDEFFF| + GHHHIJJJK!!!!!!!!!!!!g!i!k!!!!!!!!!!!^! | + {1:~ }|*12 + | + ]]) + feed('02x$') + screen:expect([[ + ???????????????a?c?e?g??????????????ABBBCDDDEFFFGH| + HHIJJJK!!!!!!!!!!!!!!!!i!k!!!!!!!!!^! | + {1:~ }|*12 + | + ]]) + feed('02x$') + screen:expect([[ + ???????????????a?c?e?g????????????ABBBCDDDEFFFGHHH| + IJJJK!!!!!!!!!!!!!!!!!!i!k!!!!!!!^! | + {1:~ }|*12 + | + ]]) + command('set nowrap') + feed('0') + screen:expect([[ + ^???????????????a?c?e?g?i?k????????ABBBCDDDEFFFGHHH| + {1:~ }|*13 + | + ]]) + feed('2x') + screen:expect([[ + ^???????????????a?c?e?g?i?k??????ABBBCDDDEFFFGHHHIJ| + {1:~ }|*13 + | + ]]) + feed('2x') + screen:expect([[ + ^???????????????a?c?e?g?i?k????ABBBCDDDEFFFGHHHIJJJ| + {1:~ }|*13 + | + ]]) + feed('2x') + screen:expect([[ + ^???????????????a?c?e?g?i?k??ABBBCDDDEFFFGHHHIJJJK!| + {1:~ }|*13 + | + ]]) + end) end) describe('decorations: inline virtual text', function()