feat(extmarks): virtual text can be right-aligned, truncated #31921

Problem: Right aligned virtual text can cover up buffer text if virtual
text is too long

Solution: An additional option for `virt_text_pos` called
`eol_right_align` has been added to truncate virtual text if it would
have otherwise covered up buffer text. This ensures the virtual text
extends no further left than EOL.
This commit is contained in:
georgev93 2025-01-24 22:57:45 -05:00 committed by GitHub
parent c6d2cbf8f5
commit 931ee5591f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 143 additions and 10 deletions

View File

@ -2609,6 +2609,13 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts})
last).
• virt_text_pos : position of virtual text. Possible values:
• "eol": right after eol character (default).
• "eol_right_align": display right aligned in the window
unless the virtual text is longer than the space
available. If the virtual text is too long, it is
truncated to fit in the window after the EOL character.
If the line is wrapped, the virtual text is shown after
the end of the line rather than the previous screen
line.
• "overlay": display over the specified column, without
shifting the underlying text.
• "right_align": display right aligned in the window.

View File

@ -626,8 +626,8 @@ Lua module: vim.diagnostic *diagnostic-api*
• {hl_mode}? (`'replace'|'combine'|'blend'`) See
|nvim_buf_set_extmark()|.
• {virt_text}? (`[string,any][]`) See |nvim_buf_set_extmark()|.
• {virt_text_pos}? (`'eol'|'overlay'|'right_align'|'inline'`) See
|nvim_buf_set_extmark()|.
• {virt_text_pos}? (`'eol'|'eol_right_align'|'inline'|'overlay'|'right_align'`)
See |nvim_buf_set_extmark()|.
• {virt_text_win_col}? (`integer`) See |nvim_buf_set_extmark()|.
• {virt_text_hide}? (`boolean`) See |nvim_buf_set_extmark()|.

View File

@ -195,6 +195,8 @@ API
• |nvim_open_win()| `relative` field can be set to "laststatus" and "tabline".
• |nvim_buf_set_extmark()| `hl_group` field can be an array of layered groups
• |vim.hl.range()| now has a optional `timeout` field which allows for a timed highlight
• |nvim_buf_set_extmark()| virt_text_pos accepts `eol_right_align` to
allow for right aligned text that truncates before covering up buffer text.
DEFAULTS

View File

@ -589,6 +589,15 @@ function vim.api.nvim_buf_line_count(buffer) end
--- (highest priority last).
--- - virt_text_pos : position of virtual text. Possible values:
--- - "eol": right after eol character (default).
--- - "eol_right_align": display right aligned in the window
--- unless the virtual text is longer than
--- the space available. If the virtual
--- text is too long, it is truncated to
--- fit in the window after the EOL
--- character. If the line is wrapped, the
--- virtual text is shown after the end of
--- the line rather than the previous
--- screen line.
--- - "overlay": display over the specified column, without
--- shifting the underlying text.
--- - "right_align": display right aligned in the window.

View File

@ -220,7 +220,7 @@ end
--- @field virt_text? [string,any][]
---
--- See |nvim_buf_set_extmark()|.
--- @field virt_text_pos? 'eol'|'overlay'|'right_align'|'inline'
--- @field virt_text_pos? 'eol'|'eol_right_align'|'inline'|'overlay'|'right_align'
---
--- See |nvim_buf_set_extmark()|.
--- @field virt_text_win_col? integer

View File

@ -400,6 +400,15 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// (highest priority last).
/// - virt_text_pos : position of virtual text. Possible values:
/// - "eol": right after eol character (default).
/// - "eol_right_align": display right aligned in the window
/// unless the virtual text is longer than
/// the space available. If the virtual
/// text is too long, it is truncated to
/// fit in the window after the EOL
/// character. If the line is wrapped, the
/// virtual text is shown after the end of
/// the line rather than the previous
/// screen line.
/// - "overlay": display over the specified column, without
/// shifting the underlying text.
/// - "right_align": display right aligned in the window.
@ -620,6 +629,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
virt_text.pos = kVPosOverlay;
} else if (strequal("right_align", str.data)) {
virt_text.pos = kVPosRightAlign;
} else if (strequal("eol_right_align", str.data)) {
virt_text.pos = kVPosEndOfLineRightAlign;
} else if (strequal("inline", str.data)) {
virt_text.pos = kVPosInline;
} else {

View File

@ -15,8 +15,8 @@
// actual Decor* data is in decoration_defs.h
/// Keep in sync with VirtTextPos in decoration_defs.h
EXTERN const char *const virt_text_pos_str[]
INIT( = { "eol", "overlay", "win_col", "right_align", "inline" });
EXTERN const char *const virt_text_pos_str[] INIT( = { "eol", "eol_right_align", "inline",
"overlay", "right_align", "win_col" });
/// Keep in sync with HlMode in decoration_defs.h
EXTERN const char *const hl_mode_str[] INIT( = { "", "replace", "combine", "blend" });

View File

@ -19,10 +19,11 @@ typedef kvec_t(VirtTextChunk) VirtText;
/// Keep in sync with virt_text_pos_str[] in decoration.h
typedef enum {
kVPosEndOfLine,
kVPosOverlay,
kVPosWinCol,
kVPosRightAlign,
kVPosEndOfLineRightAlign,
kVPosInline,
kVPosOverlay,
kVPosRightAlign,
kVPosWinCol,
} VirtTextPos;
typedef kvec_t(struct virt_line { VirtText line; bool left_col; }) VirtLines;

View File

@ -263,6 +263,9 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int
int *const indices = state->ranges_i.items;
DecorRangeSlot *const slots = state->slots.items;
/// Total width of all virtual text with "eol_right_align" alignment
int totalWidthOfEolRightAlignedVirtText = 0;
for (int i = 0; i < end; i++) {
DecorRange *item = &slots[indices[i]].range;
if (!(item->start_row == state->row && decor_virt_pos(item))) {
@ -277,7 +280,44 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int
if (decor_virt_pos(item) && item->draw_col == -1) {
bool updated = true;
VirtTextPos pos = decor_virt_pos_kind(item);
if (pos == kVPosRightAlign) {
if (do_eol && pos == kVPosEndOfLineRightAlign) {
int eolOffset = 0;
if (totalWidthOfEolRightAlignedVirtText == 0) {
// Look ahead to the remaining decor items
for (int j = i; j < end; j++) {
/// A future decor to be handled in this function's call
DecorRange *lookaheadItem = &slots[indices[j]].range;
if (lookaheadItem->start_row != state->row
|| !decor_virt_pos(lookaheadItem)
|| lookaheadItem->draw_col != -1) {
continue;
}
/// The Virtual Text of the decor item we're looking ahead to
DecorVirtText *lookaheadVt = NULL;
if (item->kind == kDecorKindVirtText) {
assert(item->data.vt);
lookaheadVt = item->data.vt;
}
if (decor_virt_pos_kind(lookaheadItem) == kVPosEndOfLineRightAlign) {
// An extra space is added for single character spacing in EOL alignment
totalWidthOfEolRightAlignedVirtText += (lookaheadVt->width + 1);
}
}
// Remove one space from the total width since there's no single space after the last entry
totalWidthOfEolRightAlignedVirtText--;
if (totalWidthOfEolRightAlignedVirtText <= (right_pos - state->eol_col)) {
eolOffset = right_pos - totalWidthOfEolRightAlignedVirtText - state->eol_col;
}
}
item->draw_col = state->eol_col + eolOffset;
} else if (pos == kVPosRightAlign) {
right_pos -= vt->width;
item->draw_col = right_pos;
} else if (pos == kVPosEndOfLine && do_eol) {
@ -304,7 +344,7 @@ 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 col = draw_virt_text_item(buf, item->draw_col, vt->data.virt_text,
vt->hl_mode, max_col, vcol);
if (vt->pos == kVPosEndOfLine && do_eol) {
if (do_eol && ((vt->pos == kVPosEndOfLine) || (vt->pos == kVPosEndOfLineRightAlign))) {
state->eol_col = col + 1;
}
*end_col = MAX(*end_col, col);

View File

@ -509,6 +509,69 @@ describe('decorations providers', function()
]]}
end)
it('can have virtual text of the style: eol_right_align', function()
insert(mulholland)
setup_provider [[
local hl = api.nvim_get_hl_id_by_name "ErrorMsg"
local test_ns = api.nvim_create_namespace "mulholland"
function on_do(event, ...)
if event == "line" then
local win, buf, line = ...
api.nvim_buf_set_extmark(buf, test_ns, line, 0, {
virt_text = {{'+'}, {'1234567890', 'ErrorMsg'}};
virt_text_pos='eol_right_align';
ephemeral = true;
})
end
end
]]
screen:expect{grid=[[
// just to see if there was an accident |
// on Mulholland Drive +{2:1234567890}|
try_start(); +{2:1234567890}|
bufref_T save_buf; +{2:1234567890}|
switch_buffer(&save_buf, buf); +{2:12345678}|
posp = getmark(mark, false); +{2:1234567890}|
restore_buffer(&save_buf);^ +{2:1234567890}|
|
]]}
end)
it('multiple eol_right_align', function()
insert(mulholland)
setup_provider [[
local hl = api.nvim_get_hl_id_by_name "ErrorMsg"
local test_ns = api.nvim_create_namespace "mulholland"
function on_do(event, ...)
if event == "line" then
local win, buf, line = ...
api.nvim_buf_set_extmark(buf, test_ns, line, 0, {
virt_text = {{'11111'}};
virt_text_pos='eol_right_align';
ephemeral = true;
})
api.nvim_buf_set_extmark(0, test_ns, line, 0, {
virt_text = {{'22222'}};
virt_text_pos='eol_right_align';
ephemeral = true;
})
end
end
]]
screen:expect{grid=[[
// just to see if there was an accident |
// on Mulholland Drive 11111 22222|
try_start(); 11111 22222|
bufref_T save_buf; 11111 22222|
switch_buffer(&save_buf, buf); 11111 222|
posp = getmark(mark, false); 11111 22222|
restore_buffer(&save_buf);^ 11111 22222|
|
]]}
end)
it('virtual text works with wrapped lines', function()
insert(mulholland)
feed('ggJj3JjJ')