aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgeorgev93 <39860568+georgev93@users.noreply.github.com>2025-01-24 22:57:45 -0500
committerGitHub <noreply@github.com>2025-01-24 19:57:45 -0800
commit931ee5591fa764a769946318e05062098baf7c21 (patch)
tree82dd7f486cc47d1e7a2d44d204ab12f967a5e0da
parentc6d2cbf8f51abfa0c9d244ef384a15b0b69e16c6 (diff)
downloadrneovim-931ee5591fa764a769946318e05062098baf7c21.tar.gz
rneovim-931ee5591fa764a769946318e05062098baf7c21.tar.bz2
rneovim-931ee5591fa764a769946318e05062098baf7c21.zip
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.
-rw-r--r--runtime/doc/api.txt7
-rw-r--r--runtime/doc/diagnostic.txt4
-rw-r--r--runtime/doc/news.txt2
-rw-r--r--runtime/lua/vim/_meta/api.lua9
-rw-r--r--runtime/lua/vim/diagnostic.lua2
-rw-r--r--src/nvim/api/extmark.c11
-rw-r--r--src/nvim/decoration.h4
-rw-r--r--src/nvim/decoration_defs.h5
-rw-r--r--src/nvim/drawline.c44
-rw-r--r--test/functional/ui/decorations_spec.lua63
10 files changed, 142 insertions, 9 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index c5ade72f93..1a5df18f6c 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -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.
diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt
index d4939d8cc7..6b1456d5a6 100644
--- a/runtime/doc/diagnostic.txt
+++ b/runtime/doc/diagnostic.txt
@@ -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()|.
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 099fc17c5d..1d53f168ff 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -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
diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua
index 6d9a17ea2b..50fb7e4f9d 100644
--- a/runtime/lua/vim/_meta/api.lua
+++ b/runtime/lua/vim/_meta/api.lua
@@ -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.
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index ead75f7d51..04118999cf 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -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
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index e66140da5a..778e857057 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -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 {
diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h
index a2f4fefd45..bdbb1795cb 100644
--- a/src/nvim/decoration.h
+++ b/src/nvim/decoration.h
@@ -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" });
diff --git a/src/nvim/decoration_defs.h b/src/nvim/decoration_defs.h
index 58ba93a7ba..36ad6df7a0 100644
--- a/src/nvim/decoration_defs.h
+++ b/src/nvim/decoration_defs.h
@@ -19,10 +19,11 @@ typedef kvec_t(VirtTextChunk) VirtText;
/// Keep in sync with virt_text_pos_str[] in decoration.h
typedef enum {
kVPosEndOfLine,
+ kVPosEndOfLineRightAlign,
+ kVPosInline,
kVPosOverlay,
- kVPosWinCol,
kVPosRightAlign,
- kVPosInline,
+ kVPosWinCol,
} VirtTextPos;
typedef kvec_t(struct virt_line { VirtText line; bool left_col; }) VirtLines;
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
index 4a7bc9170a..37a42917b0 100644
--- a/src/nvim/drawline.c
+++ b/src/nvim/drawline.c
@@ -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);
diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua
index c2030b9527..7969dd5d3b 100644
--- a/test/functional/ui/decorations_spec.lua
+++ b/test/functional/ui/decorations_spec.lua
@@ -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')