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

This commit is contained in:
zeertzjq 2024-02-20 19:53:49 +08:00 committed by GitHub
parent 69bdcc6823
commit a0790558c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 126 additions and 24 deletions

View File

@ -2754,9 +2754,10 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {*opts})
hidden marks, an "invalid" key is added to the "details" hidden marks, an "invalid" key is added to the "details"
array of |nvim_buf_get_extmarks()| and family. If array of |nvim_buf_get_extmarks()| and family. If
"undo_restore" is false, the extmark is deleted instead. "undo_restore" is false, the extmark is deleted instead.
• priority: a priority value for the highlight group or sign • priority: a priority value for the highlight group, sign
attribute. For example treesitter highlighting uses a attribute or virtual text. For virtual text, item with
value of 100. highest priority is drawn last. For example treesitter
highlighting uses a value of 100.
• strict: boolean that indicates extmark should not be • strict: boolean that indicates extmark should not be
placed if the line or column value is past the end of the placed if the line or column value is past the end of the
buffer or end of the line respectively. Defaults to true. buffer or end of the line respectively. Defaults to true.

View File

@ -576,9 +576,10 @@ function vim.api.nvim_buf_line_count(buffer) end
--- hidden marks, an "invalid" key is added to the "details" --- hidden marks, an "invalid" key is added to the "details"
--- array of `nvim_buf_get_extmarks()` and family. If --- array of `nvim_buf_get_extmarks()` and family. If
--- "undo_restore" is false, the extmark is deleted instead. --- "undo_restore" is false, the extmark is deleted instead.
--- • priority: a priority value for the highlight group or sign --- • priority: a priority value for the highlight group, sign
--- attribute. For example treesitter highlighting uses a --- attribute or virtual text. For virtual text, item with
--- value of 100. --- highest priority is drawn last. For example treesitter
--- highlighting uses a value of 100.
--- • strict: boolean that indicates extmark should not be --- • strict: boolean that indicates extmark should not be
--- placed if the line or column value is past the end of the --- placed if the line or column value is past the end of the
--- buffer or end of the line respectively. Defaults to true. --- buffer or end of the line respectively. Defaults to true.

View File

@ -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" /// hidden marks, an "invalid" key is added to the "details"
/// array of |nvim_buf_get_extmarks()| and family. If /// array of |nvim_buf_get_extmarks()| and family. If
/// "undo_restore" is false, the extmark is deleted instead. /// "undo_restore" is false, the extmark is deleted instead.
/// - priority: a priority value for the highlight group or sign /// - priority: a priority value for the highlight group, sign
/// attribute. For example treesitter highlighting uses a /// attribute or virtual text. For virtual text, item with
/// value of 100. /// highest priority is drawn last. For example treesitter
/// highlighting uses a value of 100.
/// - strict: boolean that indicates extmark should not be placed /// - strict: boolean that indicates extmark should not be placed
/// if the line or column value is past the end of the /// if the line or column value is past the end of the
/// buffer or end of the line respectively. Defaults to true. /// buffer or end of the line respectively. Defaults to true.

View File

@ -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. /// 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; DecorVirtText *vt = item->kind == kDecorKindVirtText ? item->data.vt : NULL;
VirtTextPos pos = decor_virt_pos_kind(item); VirtTextPos pos = decor_virt_pos_kind(item);

View File

@ -52,10 +52,10 @@ typedef struct {
///< Reflects the order of patterns/captures in the query file. ///< Reflects the order of patterns/captures in the query file.
DecorRangeKind kind; DecorRangeKind kind;
/// Screen column to draw the virtual text. /// Screen column to draw the virtual text.
/// When -1, the virtual text may be drawn after deciding where. /// When -1, it should be drawn on the current screen line after deciding where.
/// When -3, the virtual text should be drawn on the next screen line. /// When -3, it may be drawn at a position yet to be assigned.
/// When -10, the virtual text has just been added. /// When -10, it has just been added.
/// When INT_MIN, the virtual text should no longer be drawn. /// When INT_MIN, it should no longer be drawn.
int draw_col; int draw_col;
} DecorRange; } DecorRange;

View File

@ -769,7 +769,7 @@ static bool has_more_inline_virt(winlinevars_T *wlv, ptrdiff_t v)
return false; 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) { while (wlv->n_extra == 0) {
if (wlv->virt_inline_i >= kv_size(wlv->virt_inline)) { 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; DecorState *state = &decor_state;
for (size_t i = 0; i < kv_size(state->active); i++) { for (size_t i = 0; i < kv_size(state->active); i++) {
DecorRange *item = &kv_A(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 if (item->start_row != state->row
|| item->kind != kDecorKindVirtText || item->kind != kDecorKindVirtText
|| item->data.vt->pos != kVPosInline || 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; 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_index;
int virt_line_offset = -1; int virt_line_offset = -1;
// Repeat for the whole displayed line. // 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 bool selected = (area_active || (area_highlighting && noinvcur
&& wlv.vcol == wp->w_virtcol)); && 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) { 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; decor_need_recheck = false;
} }
if (wlv.filler_todo <= 0) { if (wlv.filler_todo <= 0) {
extmark_attr = decor_redraw_col(wp, (colnr_T)(ptr - line), wlv.off, selected, extmark_attr = decor_redraw_col(wp, (colnr_T)(ptr - line),
&decor_state); may_have_inline_virt ? -3 : wlv.off,
selected, &decor_state);
} }
if (may_have_inline_virt) {
if (!has_foldtext && buf_meta_total(wp->w_buffer, kMTMetaInline) > 0) { handle_inline_virtual_text(wp, &wlv, ptr - line, selected);
handle_inline_virtual_text(wp, &wlv, ptr - line);
if (wlv.n_extra > 0 && wlv.virt_inline_hl_mode <= kHlModeReplace) { if (wlv.n_extra > 0 && wlv.virt_inline_hl_mode <= kHlModeReplace) {
// restore search_attr and area_attr when n_extra is down to zero // 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. // 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) { && !has_foldtext) {
if (has_decor && *ptr == NUL && lcs_eol == 0 && lcs_eol_todo) { if (has_decor && *ptr == NUL && lcs_eol == 0 && lcs_eol_todo) {
// Tricky: there might be a virtual text just _after_ the last char // 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 if (*ptr != NUL
|| (lcs_eol > 0 && lcs_eol_todo) || (lcs_eol > 0 && lcs_eol_todo)
|| (wlv.n_extra > 0 && (wlv.sc_extra != NUL || *wlv.p_extra != NUL)) || (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; mb_schar = wp->w_p_lcs_chars.ext;
wlv.char_attr = win_hl_attr(wp, HLF_AT); wlv.char_attr = win_hl_attr(wp, HLF_AT);
mb_c = schar_get_first_codepoint(mb_schar); 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) { } else if (!is_wrapped) {
// Without wrapping, we might need to display right_align and win_col // Without wrapping, we might need to display right_align and win_col
// virt_text for the entire text line. // virt_text for the entire text line.
decor_recheck_draw_col(-1, true, &decor_state);
decor_redraw_col(wp, MAXCOL, -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 || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL
&& wlv.p_extra != at_end_str) && wlv.p_extra != at_end_str)
|| (wlv.n_extra != 0 && (wlv.sc_extra != NUL || *wlv.p_extra != NUL)) || (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). const bool wrap = is_wrapped // Wrapping enabled (not a folded line).
&& wlv.filler_todo <= 0 // Not drawing diff filler lines. && wlv.filler_todo <= 0 // Not drawing diff filler lines.
&& lcs_eol_todo // Haven't printed the lcs_eol character. && lcs_eol_todo // Haven't printed the lcs_eol character.

View File

@ -2401,6 +2401,93 @@ describe('extmark decorations', function()
helpers.assert_alive() helpers.assert_alive()
end) 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) end)
describe('decorations: inline virtual text', function() describe('decorations: inline virtual text', function()