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"
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.

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"
--- 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.

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"
/// 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.

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.
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);

View File

@ -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;

View File

@ -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.

View File

@ -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()