Skip to content

Commit 931ee55

Browse files
authored
feat(extmarks): virtual text can be right-aligned, truncated neovim#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.
1 parent c6d2cbf commit 931ee55

File tree

10 files changed

+142
-9
lines changed

10 files changed

+142
-9
lines changed

runtime/doc/api.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2609,6 +2609,13 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts})
26092609
last).
26102610
• virt_text_pos : position of virtual text. Possible values:
26112611
• "eol": right after eol character (default).
2612+
• "eol_right_align": display right aligned in the window
2613+
unless the virtual text is longer than the space
2614+
available. If the virtual text is too long, it is
2615+
truncated to fit in the window after the EOL character.
2616+
If the line is wrapped, the virtual text is shown after
2617+
the end of the line rather than the previous screen
2618+
line.
26122619
• "overlay": display over the specified column, without
26132620
shifting the underlying text.
26142621
• "right_align": display right aligned in the window.

runtime/doc/diagnostic.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -626,8 +626,8 @@ Lua module: vim.diagnostic *diagnostic-api*
626626
• {hl_mode}? (`'replace'|'combine'|'blend'`) See
627627
|nvim_buf_set_extmark()|.
628628
• {virt_text}? (`[string,any][]`) See |nvim_buf_set_extmark()|.
629-
• {virt_text_pos}? (`'eol'|'overlay'|'right_align'|'inline'`) See
630-
|nvim_buf_set_extmark()|.
629+
• {virt_text_pos}? (`'eol'|'eol_right_align'|'inline'|'overlay'|'right_align'`)
630+
See |nvim_buf_set_extmark()|.
631631
• {virt_text_win_col}? (`integer`) See |nvim_buf_set_extmark()|.
632632
• {virt_text_hide}? (`boolean`) See |nvim_buf_set_extmark()|.
633633

runtime/doc/news.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ API
195195
|nvim_open_win()| `relative` field can be set to "laststatus" and "tabline".
196196
|nvim_buf_set_extmark()| `hl_group` field can be an array of layered groups
197197
|vim.hl.range()| now has a optional `timeout` field which allows for a timed highlight
198+
|nvim_buf_set_extmark()| virt_text_pos accepts `eol_right_align` to
199+
allow for right aligned text that truncates before covering up buffer text.
198200

199201
DEFAULTS
200202

runtime/lua/vim/_meta/api.lua

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

runtime/lua/vim/diagnostic.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ end
220220
--- @field virt_text? [string,any][]
221221
---
222222
--- See |nvim_buf_set_extmark()|.
223-
--- @field virt_text_pos? 'eol'|'overlay'|'right_align'|'inline'
223+
--- @field virt_text_pos? 'eol'|'eol_right_align'|'inline'|'overlay'|'right_align'
224224
---
225225
--- See |nvim_buf_set_extmark()|.
226226
--- @field virt_text_win_col? integer

src/nvim/api/extmark.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,15 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
400400
/// (highest priority last).
401401
/// - virt_text_pos : position of virtual text. Possible values:
402402
/// - "eol": right after eol character (default).
403+
/// - "eol_right_align": display right aligned in the window
404+
/// unless the virtual text is longer than
405+
/// the space available. If the virtual
406+
/// text is too long, it is truncated to
407+
/// fit in the window after the EOL
408+
/// character. If the line is wrapped, the
409+
/// virtual text is shown after the end of
410+
/// the line rather than the previous
411+
/// screen line.
403412
/// - "overlay": display over the specified column, without
404413
/// shifting the underlying text.
405414
/// - "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
620629
virt_text.pos = kVPosOverlay;
621630
} else if (strequal("right_align", str.data)) {
622631
virt_text.pos = kVPosRightAlign;
632+
} else if (strequal("eol_right_align", str.data)) {
633+
virt_text.pos = kVPosEndOfLineRightAlign;
623634
} else if (strequal("inline", str.data)) {
624635
virt_text.pos = kVPosInline;
625636
} else {

src/nvim/decoration.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
// actual Decor* data is in decoration_defs.h
1616

1717
/// Keep in sync with VirtTextPos in decoration_defs.h
18-
EXTERN const char *const virt_text_pos_str[]
19-
INIT( = { "eol", "overlay", "win_col", "right_align", "inline" });
18+
EXTERN const char *const virt_text_pos_str[] INIT( = { "eol", "eol_right_align", "inline",
19+
"overlay", "right_align", "win_col" });
2020

2121
/// Keep in sync with HlMode in decoration_defs.h
2222
EXTERN const char *const hl_mode_str[] INIT( = { "", "replace", "combine", "blend" });

src/nvim/decoration_defs.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ typedef kvec_t(VirtTextChunk) VirtText;
1919
/// Keep in sync with virt_text_pos_str[] in decoration.h
2020
typedef enum {
2121
kVPosEndOfLine,
22+
kVPosEndOfLineRightAlign,
23+
kVPosInline,
2224
kVPosOverlay,
23-
kVPosWinCol,
2425
kVPosRightAlign,
25-
kVPosInline,
26+
kVPosWinCol,
2627
} VirtTextPos;
2728

2829
typedef kvec_t(struct virt_line { VirtText line; bool left_col; }) VirtLines;

src/nvim/drawline.c

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,9 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int
263263
int *const indices = state->ranges_i.items;
264264
DecorRangeSlot *const slots = state->slots.items;
265265

266+
/// Total width of all virtual text with "eol_right_align" alignment
267+
int totalWidthOfEolRightAlignedVirtText = 0;
268+
266269
for (int i = 0; i < end; i++) {
267270
DecorRange *item = &slots[indices[i]].range;
268271
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
277280
if (decor_virt_pos(item) && item->draw_col == -1) {
278281
bool updated = true;
279282
VirtTextPos pos = decor_virt_pos_kind(item);
280-
if (pos == kVPosRightAlign) {
283+
284+
if (do_eol && pos == kVPosEndOfLineRightAlign) {
285+
int eolOffset = 0;
286+
if (totalWidthOfEolRightAlignedVirtText == 0) {
287+
// Look ahead to the remaining decor items
288+
for (int j = i; j < end; j++) {
289+
/// A future decor to be handled in this function's call
290+
DecorRange *lookaheadItem = &slots[indices[j]].range;
291+
292+
if (lookaheadItem->start_row != state->row
293+
|| !decor_virt_pos(lookaheadItem)
294+
|| lookaheadItem->draw_col != -1) {
295+
continue;
296+
}
297+
298+
/// The Virtual Text of the decor item we're looking ahead to
299+
DecorVirtText *lookaheadVt = NULL;
300+
if (item->kind == kDecorKindVirtText) {
301+
assert(item->data.vt);
302+
lookaheadVt = item->data.vt;
303+
}
304+
305+
if (decor_virt_pos_kind(lookaheadItem) == kVPosEndOfLineRightAlign) {
306+
// An extra space is added for single character spacing in EOL alignment
307+
totalWidthOfEolRightAlignedVirtText += (lookaheadVt->width + 1);
308+
}
309+
}
310+
311+
// Remove one space from the total width since there's no single space after the last entry
312+
totalWidthOfEolRightAlignedVirtText--;
313+
314+
if (totalWidthOfEolRightAlignedVirtText <= (right_pos - state->eol_col)) {
315+
eolOffset = right_pos - totalWidthOfEolRightAlignedVirtText - state->eol_col;
316+
}
317+
}
318+
319+
item->draw_col = state->eol_col + eolOffset;
320+
} else if (pos == kVPosRightAlign) {
281321
right_pos -= vt->width;
282322
item->draw_col = right_pos;
283323
} 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
304344
int vcol = item->draw_col - col_off;
305345
int col = draw_virt_text_item(buf, item->draw_col, vt->data.virt_text,
306346
vt->hl_mode, max_col, vcol);
307-
if (vt->pos == kVPosEndOfLine && do_eol) {
347+
if (do_eol && ((vt->pos == kVPosEndOfLine) || (vt->pos == kVPosEndOfLineRightAlign))) {
308348
state->eol_col = col + 1;
309349
}
310350
*end_col = MAX(*end_col, col);

test/functional/ui/decorations_spec.lua

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,69 @@ describe('decorations providers', function()
509509
]]}
510510
end)
511511

512+
it('can have virtual text of the style: eol_right_align', function()
513+
insert(mulholland)
514+
setup_provider [[
515+
local hl = api.nvim_get_hl_id_by_name "ErrorMsg"
516+
local test_ns = api.nvim_create_namespace "mulholland"
517+
function on_do(event, ...)
518+
if event == "line" then
519+
local win, buf, line = ...
520+
api.nvim_buf_set_extmark(buf, test_ns, line, 0, {
521+
virt_text = {{'+'}, {'1234567890', 'ErrorMsg'}};
522+
virt_text_pos='eol_right_align';
523+
ephemeral = true;
524+
})
525+
end
526+
end
527+
]]
528+
529+
screen:expect{grid=[[
530+
// just to see if there was an accident |
531+
// on Mulholland Drive +{2:1234567890}|
532+
try_start(); +{2:1234567890}|
533+
bufref_T save_buf; +{2:1234567890}|
534+
switch_buffer(&save_buf, buf); +{2:12345678}|
535+
posp = getmark(mark, false); +{2:1234567890}|
536+
restore_buffer(&save_buf);^ +{2:1234567890}|
537+
|
538+
]]}
539+
end)
540+
541+
it('multiple eol_right_align', function()
542+
insert(mulholland)
543+
setup_provider [[
544+
local hl = api.nvim_get_hl_id_by_name "ErrorMsg"
545+
local test_ns = api.nvim_create_namespace "mulholland"
546+
function on_do(event, ...)
547+
if event == "line" then
548+
local win, buf, line = ...
549+
api.nvim_buf_set_extmark(buf, test_ns, line, 0, {
550+
virt_text = {{'11111'}};
551+
virt_text_pos='eol_right_align';
552+
ephemeral = true;
553+
})
554+
api.nvim_buf_set_extmark(0, test_ns, line, 0, {
555+
virt_text = {{'22222'}};
556+
virt_text_pos='eol_right_align';
557+
ephemeral = true;
558+
})
559+
end
560+
end
561+
]]
562+
563+
screen:expect{grid=[[
564+
// just to see if there was an accident |
565+
// on Mulholland Drive 11111 22222|
566+
try_start(); 11111 22222|
567+
bufref_T save_buf; 11111 22222|
568+
switch_buffer(&save_buf, buf); 11111 222|
569+
posp = getmark(mark, false); 11111 22222|
570+
restore_buffer(&save_buf);^ 11111 22222|
571+
|
572+
]]}
573+
end)
574+
512575
it('virtual text works with wrapped lines', function()
513576
insert(mulholland)
514577
feed('ggJj3JjJ')

0 commit comments

Comments
 (0)