neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

commit a0790558c3097f2813c56e404af30c3e2d8b8983
parent 69bdcc68234e7f228b667f95b0e65d4ebe8d1f80
Author: zeertzjq <zeertzjq@outlook.com>
Date:   Tue, 20 Feb 2024 19:53:49 +0800

fix(extmarks): priority order of inline and non-inline virt_text (#27532)


Diffstat:
Mruntime/doc/api.txt | 7++++---
Mruntime/lua/vim/_meta/api.lua | 7++++---
Msrc/nvim/api/extmark.c | 7++++---
Msrc/nvim/decoration.c | 2+-
Msrc/nvim/decoration.h | 8++++----
Msrc/nvim/drawline.c | 32++++++++++++++++++++++----------
Mtest/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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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()