feat(extmarks): add virt_text_repeat_linebreak flag (#26625)

Problem:  Unable to predict which byte-offset to place virtual text to
          make it repeat visually in the wrapped part of a line.
Solution: Add a flag to nvim_buf_set_extmark() that causes virtual
          text to repeat in wrapped lines.
This commit is contained in:
luukvbaal 2023-12-26 00:16:03 +01:00 committed by GitHub
parent 0a598c13b1
commit bbd5c6363c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 55 additions and 12 deletions

View File

@ -2696,6 +2696,8 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {*opts})
text is selected or hidden because of scrolling with text is selected or hidden because of scrolling with
'nowrap' or 'smoothscroll'. Currently only affects 'nowrap' or 'smoothscroll'. Currently only affects
"overlay" virt_text. "overlay" virt_text.
• virt_text_repeat_linebreak : repeat the virtual text on
wrapped lines.
• hl_mode : control how highlights are combined with the • hl_mode : control how highlights are combined with the
highlights of the text. Currently only affects virt_text highlights of the text. Currently only affects virt_text
highlights, but might affect `hl_group` in later versions. highlights, but might affect `hl_group` in later versions.

View File

@ -339,11 +339,10 @@ The following changes to existing APIs or features add new behavior.
In addition, |nvim_buf_get_extmarks()| has gained an "overlap" option to In addition, |nvim_buf_get_extmarks()| has gained an "overlap" option to
return such ranges even if they started before the specified position. return such ranges even if they started before the specified position.
• Extmarks can opt-out of precise undo tracking using the new "undo_restore" • The following flags were added to |nvim_buf_set_extmark()|:
flag to |nvim_buf_set_extmark()| - "undo_restore": opt-out extmarks of precise undo tracking.
- "invalidate": automatically hide or delete extmarks.
• Extmarks can be automatically hidden or removed using the new "invalidate" - "virt_text_repeat_linebreak": repeat virtual text on wrapped lines.
flag to |nvim_buf_set_extmark()|
• LSP hover and signature help now use Treesitter for highlighting of Markdown • LSP hover and signature help now use Treesitter for highlighting of Markdown
content. content.

View File

@ -519,6 +519,8 @@ function vim.api.nvim_buf_line_count(buffer) end
--- text is selected or hidden because of scrolling with --- text is selected or hidden because of scrolling with
--- 'nowrap' or 'smoothscroll'. Currently only affects --- 'nowrap' or 'smoothscroll'. Currently only affects
--- "overlay" virt_text. --- "overlay" virt_text.
--- • virt_text_repeat_linebreak : repeat the virtual text on
--- wrapped lines.
--- • hl_mode : control how highlights are combined with the --- • hl_mode : control how highlights are combined with the
--- highlights of the text. Currently only affects virt_text --- highlights of the text. Currently only affects virt_text
--- highlights, but might affect `hl_group` in later versions. --- highlights, but might affect `hl_group` in later versions.

View File

@ -251,6 +251,7 @@ error('Cannot require a meta file')
--- @field virt_text_pos? string --- @field virt_text_pos? string
--- @field virt_text_win_col? integer --- @field virt_text_win_col? integer
--- @field virt_text_hide? boolean --- @field virt_text_hide? boolean
--- @field virt_text_repeat_linebreak? boolean
--- @field hl_eol? boolean --- @field hl_eol? boolean
--- @field hl_mode? string --- @field hl_mode? string
--- @field invalidate? boolean --- @field invalidate? boolean

View File

@ -391,6 +391,8 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// text is selected or hidden because of /// text is selected or hidden because of
/// scrolling with 'nowrap' or 'smoothscroll'. /// scrolling with 'nowrap' or 'smoothscroll'.
/// Currently only affects "overlay" virt_text. /// Currently only affects "overlay" virt_text.
/// - virt_text_repeat_linebreak : repeat the virtual text on
/// wrapped lines.
/// - hl_mode : control how highlights are combined with the /// - hl_mode : control how highlights are combined with the
/// highlights of the text. Currently only affects /// highlights of the text. Currently only affects
/// virt_text highlights, but might affect `hl_group` /// virt_text highlights, but might affect `hl_group`
@ -613,7 +615,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
} }
hl.flags |= opts->hl_eol ? kSHHlEol : 0; hl.flags |= opts->hl_eol ? kSHHlEol : 0;
virt_text.flags |= opts->virt_text_hide ? kVTHide : 0; virt_text.flags |= ((opts->virt_text_hide ? kVTHide : 0)
| (opts->virt_text_repeat_linebreak ? kVTRepeatLinebreak : 0));
if (HAS_KEY(opts, set_extmark, hl_mode)) { if (HAS_KEY(opts, set_extmark, hl_mode)) {
String str = opts->hl_mode; String str = opts->hl_mode;

View File

@ -33,6 +33,7 @@ typedef struct {
String virt_text_pos; String virt_text_pos;
Integer virt_text_win_col; Integer virt_text_win_col;
Boolean virt_text_hide; Boolean virt_text_hide;
Boolean virt_text_repeat_linebreak;
Boolean hl_eol; Boolean hl_eol;
String hl_mode; String hl_mode;
Boolean invalidate; Boolean invalidate;

View File

@ -1061,11 +1061,11 @@ void decor_to_dict_legacy(Dictionary *dict, DecorInline decor, bool hl_name)
Array chunks = virt_text_to_array(virt_text->data.virt_text, hl_name); Array chunks = virt_text_to_array(virt_text->data.virt_text, hl_name);
PUT(*dict, "virt_text", ARRAY_OBJ(chunks)); PUT(*dict, "virt_text", ARRAY_OBJ(chunks));
PUT(*dict, "virt_text_hide", BOOLEAN_OBJ(virt_text->flags & kVTHide)); PUT(*dict, "virt_text_hide", BOOLEAN_OBJ(virt_text->flags & kVTHide));
PUT(*dict, "virt_text_repeat_linebreak", BOOLEAN_OBJ(virt_text->flags & kVTRepeatLinebreak));
if (virt_text->pos == kVPosWinCol) { if (virt_text->pos == kVPosWinCol) {
PUT(*dict, "virt_text_win_col", INTEGER_OBJ(virt_text->col)); PUT(*dict, "virt_text_win_col", INTEGER_OBJ(virt_text->col));
} }
PUT(*dict, "virt_text_pos", PUT(*dict, "virt_text_pos", CSTR_TO_OBJ(virt_text_pos_str[virt_text->pos]));
CSTR_TO_OBJ(virt_text_pos_str[virt_text->pos]));
priority = virt_text->priority; priority = virt_text->priority;
} }

View File

@ -77,6 +77,7 @@ enum {
kVTIsLines = 1, kVTIsLines = 1,
kVTHide = 2, kVTHide = 2,
kVTLinesAbove = 4, kVTLinesAbove = 4,
kVTRepeatLinebreak = 8,
}; };
typedef struct DecorVirtText DecorVirtText; typedef struct DecorVirtText DecorVirtText;

View File

@ -286,10 +286,12 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int
int vcol = item->draw_col - col_off; int vcol = item->draw_col - col_off;
col = draw_virt_text_item(buf, item->draw_col, vt->data.virt_text, col = draw_virt_text_item(buf, item->draw_col, vt->data.virt_text,
vt->hl_mode, max_col, vcol); vt->hl_mode, max_col, vcol);
if (vt->pos == kVPosEndOfLine && do_eol) {
state->eol_col = col + 1;
}
} }
item->draw_col = INT_MIN; // deactivate if (!vt || !(vt->flags & kVTRepeatLinebreak)) {
if (vt && vt->pos == kVPosEndOfLine && do_eol) { item->draw_col = INT_MIN; // deactivate
state->eol_col = col + 1;
} }
*end_col = MAX(*end_col, col); *end_col = MAX(*end_col, col);
@ -1588,7 +1590,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
&& wlv.vcol >= wp->w_virtcol) && wlv.vcol >= wp->w_virtcol)
|| number_only) || number_only)
&& wlv.filler_todo <= 0) { && wlv.filler_todo <= 0) {
draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row); if (!number_only) {
draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row);
}
// don't clear anything after wlv.col // don't clear anything after wlv.col
win_put_linebuf(wp, wlv.row, 0, wlv.col, wlv.col, bg_attr, false); win_put_linebuf(wp, wlv.row, 0, wlv.col, wlv.col, bg_attr, false);
// Pretend we have finished updating the window. Except when // Pretend we have finished updating the window. Except when

View File

@ -1549,6 +1549,7 @@ describe('API/extmarks', function()
virt_lines_above = true, virt_lines_above = true,
virt_lines_leftcol = true, virt_lines_leftcol = true,
virt_text = { { "text", "Macro" }, { "???" }, { "stack", { "Type", "Search" } } }, virt_text = { { "text", "Macro" }, { "???" }, { "stack", { "Type", "Search" } } },
virt_text_repeat_linebreak = false,
virt_text_hide = true, virt_text_hide = true,
virt_text_pos = "right_align", virt_text_pos = "right_align",
} }, get_extmark_by_id(ns, marks[1], { details = true })) } }, get_extmark_by_id(ns, marks[1], { details = true }))
@ -1557,6 +1558,7 @@ describe('API/extmarks', function()
right_gravity = true, right_gravity = true,
priority = 0, priority = 0,
virt_text = { { "", "Macro" }, { "", { "Type", "Search" } }, { "" } }, virt_text = { { "", "Macro" }, { "", { "Type", "Search" } }, { "" } },
virt_text_repeat_linebreak = false,
virt_text_hide = false, virt_text_hide = false,
virt_text_pos = "win_col", virt_text_pos = "win_col",
virt_text_win_col = 1, virt_text_win_col = 1,

View File

@ -644,6 +644,7 @@ describe('Buffer highlighting', function()
virt_text = s1, virt_text = s1,
-- other details -- other details
right_gravity = true, right_gravity = true,
virt_text_repeat_linebreak = false,
virt_text_pos = 'eol', virt_text_pos = 'eol',
virt_text_hide = false, virt_text_hide = false,
}}}, get_extmarks(id1, {0,0}, {0, -1}, {details=true})) }}}, get_extmarks(id1, {0,0}, {0, -1}, {details=true}))
@ -656,6 +657,7 @@ describe('Buffer highlighting', function()
virt_text = s2, virt_text = s2,
-- other details -- other details
right_gravity = true, right_gravity = true,
virt_text_repeat_linebreak = false,
virt_text_pos = 'eol', virt_text_pos = 'eol',
virt_text_hide = false, virt_text_hide = false,
}}}, get_extmarks(id1, {lastline,0}, {lastline, -1}, {details=true})) }}}, get_extmarks(id1, {lastline,0}, {lastline, -1}, {details=true}))

View File

@ -2094,6 +2094,32 @@ describe('extmark decorations', function()
| |
]]} ]]}
end) end)
it('virt_text_repeat_linebreak repeats virtual text on wrapped lines', function()
screen:try_resize(40, 5)
meths.set_option_value('breakindent', true, {})
insert(example_text)
meths.buf_set_extmark(0, ns, 1, 0, { virt_text = {{'', 'NonText'}}, virt_text_pos = 'overlay', virt_text_repeat_linebreak = true })
meths.buf_set_extmark(0, ns, 1, 3, { virt_text = {{'', 'NonText'}}, virt_text_pos = 'overlay', virt_text_repeat_linebreak = true })
command('norm gg')
screen:expect{grid=[[
^for _,item in ipairs(items) do |
{1:} {1:}local text, hl_id_cell, count = unpa|
{1:} {1:}ck(item) |
if hl_id_cell ~= nil then |
|
]]}
meths.buf_clear_namespace(0, ns, 0, -1)
meths.buf_set_extmark(0, ns, 1, 0, { virt_text = {{'', 'NonText'}}, virt_text_repeat_linebreak = true, virt_text_win_col = 0 })
meths.buf_set_extmark(0, ns, 1, 0, { virt_text = {{'', 'NonText'}}, virt_text_repeat_linebreak = true, virt_text_win_col = 2 })
screen:expect{grid=[[
^for _,item in ipairs(items) do |
{1:} {1:} local text, hl_id_cell, count = unpa|
{1:} {1:} ck(item) |
if hl_id_cell ~= nil then |
|
]]}
end)
end) end)
describe('decorations: inline virtual text', function() describe('decorations: inline virtual text', function()