Merge pull request #40113 from jreidx/feat/virt-lines-wrap

feat(extmark): virt_lines_overflow "wrap" and "auto"
This commit is contained in:
zeertzjq
2026-06-08 22:57:57 +08:00
committed by GitHub
11 changed files with 532 additions and 30 deletions

View File

@@ -3332,6 +3332,9 @@ nvim_buf_set_extmark({buf}, {ns_id}, {line}, {col}, {opts})
• "trunc": truncate virtual lines on the right (default).
• "scroll": virtual lines can scroll horizontally with
'nowrap', otherwise the same as "trunc".
• "wrap": virtual lines can wrap onto extra lines.
• "auto": virtual lines wrap with 'wrap' and scroll
horizontally with 'nowrap'.
• virt_text : *virtual-text* to link to this mark. A list of
`[text, highlight]` tuples, each representing a text chunk
with specified highlight. `highlight` element can either be

View File

@@ -127,6 +127,9 @@ API
• |nvim_get_option_info2()|
• |nvim_get_option_value()|
• |nvim_set_option_value()|
• |nvim_buf_set_extmark()| `virt_lines_overflow` accepts "wrap" to enable
wrapping onto extra rows and "auto" which enables horizontal scrolling when
'nowrap' is set and wrapping when 'wrap' is set.
BUILD

View File

@@ -696,6 +696,8 @@ function vim.api.nvim_buf_line_count(buf) end
--- - "trunc": truncate virtual lines on the right (default).
--- - "scroll": virtual lines can scroll horizontally with 'nowrap',
--- otherwise the same as "trunc".
--- - "wrap": virtual lines can wrap onto extra lines.
--- - "auto": virtual lines wrap with 'wrap' and scroll horizontally with 'nowrap'.
--- - virt_text : [](virtual-text) to link to this mark.
--- A list of `[text, highlight]` tuples, each representing a
--- text chunk with specified highlight. `highlight` element

View File

@@ -441,7 +441,7 @@ error('Cannot require a meta file')
--- @field virt_lines? any[]
--- @field virt_lines_above? boolean
--- @field virt_lines_leftcol? boolean
--- @field virt_lines_overflow? "trunc"|"scroll"
--- @field virt_lines_overflow? "trunc"|"scroll"|"wrap"|"auto"
--- @field virt_text? any[]
--- @field virt_text_hide? boolean
--- @field virt_text_pos? "eol"|"eol_right_align"|"overlay"|"right_align"|"inline"

View File

@@ -495,6 +495,8 @@ ArrayOf(DictAs(get_extmark_item)) nvim_buf_get_extmarks(Buffer buf, Integer ns_i
/// - "trunc": truncate virtual lines on the right (default).
/// - "scroll": virtual lines can scroll horizontally with 'nowrap',
/// otherwise the same as "trunc".
/// - "wrap": virtual lines can wrap onto extra lines.
/// - "auto": virtual lines wrap with 'wrap' and scroll horizontally with 'nowrap'.
/// - virt_text : [](virtual-text) to link to this mark.
/// A list of `[text, highlight]` tuples, each representing a
/// text chunk with specified highlight. `highlight` element
@@ -724,10 +726,15 @@ Integer nvim_buf_set_extmark(Buffer buf, Integer ns_id, Integer line, Integer co
}
int virt_lines_flags = opts->virt_lines_leftcol ? kVLLeftcol : 0;
VirtLineOverflow virt_lines_overflow = kVLOverflowTrunc;
if (HAS_KEY(opts, set_extmark, virt_lines_overflow)) {
String str = opts->virt_lines_overflow;
if (strequal("scroll", str.data)) {
virt_lines_flags |= kVLScroll;
virt_lines_overflow = kVLOverflowScroll;
} else if (strequal("wrap", str.data)) {
virt_lines_overflow = kVLOverflowWrap;
} else if (strequal("auto", str.data)) {
virt_lines_overflow = kVLOverflowAuto;
} else if (!strequal("trunc", str.data)) {
VALIDATE_S(false, "virt_lines_overflow", str.data, {
goto error;
@@ -743,7 +750,8 @@ Integer nvim_buf_set_extmark(Buffer buf, Integer ns_id, Integer line, Integer co
});
int dummig;
VirtText jtem = parse_virt_text(a.items[j].data.array, err, &dummig, false);
kv_push(virt_lines.data.virt_lines, ((struct virt_line){ jtem, virt_lines_flags }));
kv_push(virt_lines.data.virt_lines,
((struct virt_line){ jtem, virt_lines_flags, virt_lines_overflow }));
if (ERROR_SET(err)) {
goto error;
}

View File

@@ -48,7 +48,7 @@ typedef struct {
Array virt_lines;
Boolean virt_lines_above;
Boolean virt_lines_leftcol;
Enum("trunc", "scroll") virt_lines_overflow;
Enum("trunc", "scroll", "wrap", "auto") virt_lines_overflow;
Boolean strict;
String sign_text;
HLGroupID sign_hl_group;

View File

@@ -20,7 +20,9 @@
#include "nvim/grid.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
#include "nvim/marktree.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/memory_defs.h"
#include "nvim/move.h"
@@ -1122,7 +1124,66 @@ bool decor_redraw_eol(win_T *wp, DecorState *state, int *eol_attr, int eol_col)
static const uint32_t lines_filter[kMTMetaCount] = {[kMTMetaLines] = kMTFilterSelect };
static inline bool decor_virt_line_wrap(win_T *wp, VirtLineOverflow overflow)
{
return overflow == kVLOverflowWrap || (overflow == kVLOverflowAuto && wp->w_p_wrap);
}
/// Counts the number of rows occupied by a virtual line. When skip_cells is non-NULL, sets it to
/// the cell offset where target_row begins.
///
/// @param target_row Row relative to the virtual line that skip_cells is computed for.
/// @param skip_cells Cell offset for the target_row. Pass NULL when only the row count is needed.
///
/// @return Number of rows occupied by the virtual line.
int decor_virt_line_rows(win_T *wp, const struct virt_line *vl, int target_row, int *skip_cells)
{
if (skip_cells != NULL) {
*skip_cells = 0;
}
if (!decor_virt_line_wrap(wp, vl->overflow)) {
return 1;
}
int row_width = wp->w_view_width - ((vl->flags & kVLLeftcol) ? 0 : win_col_off(wp));
if (row_width <= 0) {
return 1;
}
VirtText vt = vl->line;
buf_T *buf = wp->w_buffer;
int vcol = 0;
int row_cells = 0;
int row = 0;
for (int i = 0; i < (int)kv_size(vt); i++) {
const char *virt_str = kv_A(vt, i).text;
if (virt_str == NULL) {
continue;
}
while (*virt_str != NUL) {
int bytes_to_next_char = utfc_ptr2len(virt_str);
// Need to compute cell width due to TAB & double width cell chars
int cells = (*virt_str == TAB)
? tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array)
: utf_ptr2cells(virt_str);
virt_str += bytes_to_next_char;
// Wrap before total row cell length would exceed the window size
// to protect against double width cell chars being split at the boundary
if (row_cells + cells > row_width) {
row++;
if (skip_cells != NULL && row == target_row) {
*skip_cells = vcol;
}
row_cells = 0;
}
row_cells += cells;
vcol += cells;
}
}
return row + 1;
}
/// @param apply_folds Only count virtual lines that are not in folds.
/// @return Number of rows occupied by the virtual lines.
int decor_virt_lines(win_T *wp, int start_row, int end_row, int *num_below, VirtLines *lines,
bool apply_folds)
{
@@ -1141,25 +1202,40 @@ int decor_virt_lines(win_T *wp, int start_row, int end_row, int *num_below, Virt
assert(start_row >= 0);
int virt_lines = 0;
int n_virt_lines = 0;
while (true) {
MTKey mark = marktree_itr_current(itr);
DecorVirtText *vt = mt_decor_virt(mark);
if (!mt_invalid(mark) && ns_in_win(mark.ns, wp)) {
while (vt) {
if (vt->flags & kVTIsLines) {
VirtLines virt_lines = vt->data.virt_lines;
if ((vt->flags & kVTIsLines) && kv_size(virt_lines) > 0) {
bool above = vt->flags & kVTLinesAbove;
int mrow = mark.pos.row;
int draw_row = mrow + (above ? 0 : 1);
if (draw_row >= start_row && draw_row < end_row
&& (!apply_folds || !(hasFolding(wp, mrow + 1, NULL, NULL)
|| decor_conceal_line(wp, mrow, false)))) {
virt_lines += (int)kv_size(vt->data.virt_lines);
if (lines) {
kv_splice(*lines, vt->data.virt_lines);
// All virtual lines from the same extmark have the same overflow flag.
if (decor_virt_line_wrap(wp, kv_A(virt_lines, 0).overflow)) {
// Iterates over each virtual line summing number of rows.
// Rows belonging to previous line are accumulated in num_below.
for (int i = 0; i < (int)kv_size(virt_lines); i++) {
int rows = decor_virt_line_rows(wp, &kv_A(virt_lines, i), 0, NULL);
n_virt_lines += rows;
if (num_below && !above) {
(*num_below) += rows;
}
}
} else {
// If virtual lines don't wrap there is no need to iterate over them.
n_virt_lines += (int)kv_size(virt_lines);
if (num_below && !above) {
(*num_below) += (int)kv_size(virt_lines);
}
}
if (num_below && !above) {
(*num_below) += (int)kv_size(vt->data.virt_lines);
if (lines) {
kv_splice(*lines, virt_lines);
}
}
}
@@ -1172,7 +1248,7 @@ int decor_virt_lines(win_T *wp, int start_row, int end_row, int *num_below, Virt
}
}
return virt_lines;
return n_virt_lines;
}
/// This assumes maximum one entry of each kind, which will not always be the case.
@@ -1262,16 +1338,27 @@ void decor_to_dict_legacy(Dict *dict, DecorInline decor, bool hl_name, Arena *ar
if (virt_lines) {
Array all_chunks = arena_array(arena, kv_size(virt_lines->data.virt_lines));
int virt_lines_flags = 0;
VirtLineOverflow virt_lines_overflow = kVLOverflowTrunc;
for (size_t i = 0; i < kv_size(virt_lines->data.virt_lines); i++) {
virt_lines_flags = kv_A(virt_lines->data.virt_lines, i).flags;
virt_lines_overflow = kv_A(virt_lines->data.virt_lines, i).overflow;
Array chunks = virt_text_to_array(kv_A(virt_lines->data.virt_lines, i).line, hl_name, arena);
ADD(all_chunks, ARRAY_OBJ(chunks));
}
PUT_C(*dict, "virt_lines", ARRAY_OBJ(all_chunks));
PUT_C(*dict, "virt_lines_above", BOOLEAN_OBJ(virt_lines->flags & kVTLinesAbove));
PUT_C(*dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_flags & kVLLeftcol));
PUT_C(*dict, "virt_lines_overflow",
CSTR_AS_OBJ(virt_lines_flags & kVLScroll ? "scroll" : "trunc"));
char *overflow = "trunc";
if (virt_lines_overflow == kVLOverflowScroll) {
overflow = "scroll";
} else if (virt_lines_overflow == kVLOverflowWrap) {
overflow = "wrap";
} else if (virt_lines_overflow == kVLOverflowAuto) {
overflow = "auto";
}
PUT_C(*dict, "virt_lines_overflow", CSTR_AS_OBJ(overflow));
priority = virt_lines->priority;
}

View File

@@ -29,11 +29,16 @@ typedef enum {
/// Flags for virtual lines
enum {
kVLLeftcol = 1, ///< Start at left window edge, ignoring number column, etc.
kVLScroll = 2, ///< Can scroll horizontally with 'nowrap'
// kVLWrap = 4,
};
typedef kvec_t(struct virt_line { VirtText line; int flags; }) VirtLines;
typedef enum {
kVLOverflowTrunc, ///< Truncate with 'nowrap'
kVLOverflowScroll, ///< Scroll horizontally with 'nowrap'
kVLOverflowWrap, ///< Wrap onto extra rows
kVLOverflowAuto, ///< Scroll with 'nowrap'; wrap with 'wrap'
} VirtLineOverflow;
typedef kvec_t(struct virt_line { VirtText line; int flags; VirtLineOverflow overflow; }) VirtLines;
typedef uint16_t DecorPriority;
typedef uint32_t DecorPriorityInternal;

View File

@@ -1322,6 +1322,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
}
VirtLines virt_lines = KV_INITIAL_VALUE;
wlv.n_virt_lines = decor_virt_lines(wp, lnum - 1, lnum, &wlv.n_virt_below, &virt_lines, true);
// Preserving count of virt_lines for topline visibility
int total_virt_rows = wlv.n_virt_lines;
wlv.filler_lines += wlv.n_virt_lines;
if (lnum == wp->w_topline) {
wlv.filler_lines = wp->w_topfill;
@@ -1674,8 +1676,13 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
const bool may_have_inline_virt
= !has_foldtext && buf_meta_total(wp->w_buffer, kMTMetaInline) > 0;
int virt_line_index = -1;
bool has_virt_line = false;
int virt_line_flags = 0;
// Index into virt_lines and the starting row of that line (virt_line_start_row).
// Both persist across iterations to avoid traversing previously visited rows.
int virt_line_index = 0;
int virt_line_start_row = 0;
int virt_line_skip_cells = 0;
// Repeat for each cell in the displayed line.
while (true) {
@@ -1715,15 +1722,29 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
}
if (wlv.filler_todo > 0) {
int index = wlv.filler_todo - (wlv.filler_lines - wlv.n_virt_lines);
if (index > 0) {
virt_line_index = (int)kv_size(virt_lines) - index;
assert(virt_line_index >= 0);
virt_line_flags = kv_A(virt_lines, virt_line_index).flags;
// nvim_buf_set_extmark: virt_lines_overflow
int virt_rows_todo = wlv.filler_todo - (wlv.filler_lines - wlv.n_virt_lines);
if (virt_rows_todo > 0) {
int target_row = total_virt_rows - virt_rows_todo;
// Count number of rows spanned by virtual line.
// Find the virtual line containing the target row.
// Set skip cells with the same row-counting function so offset calculation stays in sync
for (; virt_line_index < (int)kv_size(virt_lines); virt_line_index++) {
int line_rows = decor_virt_line_rows(wp, &kv_A(virt_lines, virt_line_index), 0, NULL);
if (target_row < virt_line_start_row + line_rows) {
has_virt_line = true;
virt_line_flags = kv_A(virt_lines, virt_line_index).flags;
decor_virt_line_rows(wp, &kv_A(virt_lines, virt_line_index),
target_row - virt_line_start_row,
&virt_line_skip_cells);
break;
}
virt_line_start_row += line_rows;
}
}
}
if (virt_line_index >= 0 && (virt_line_flags & kVLLeftcol)) {
if (has_virt_line && (virt_line_flags & kVLLeftcol)) {
// skip columns
} else if (statuscol.draw) {
// Draw 'statuscolumn' if it is set.
@@ -1768,7 +1789,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
break;
}
wlv.filler_todo--;
virt_line_index = -1;
has_virt_line = false;
if (wlv.filler_todo == 0 && (wp->w_botfill || !draw_text)) {
break;
}
@@ -3149,14 +3170,20 @@ end_check:
}
}
if (virt_line_index >= 0) {
if (has_virt_line) {
VirtLineOverflow overflow = kv_A(virt_lines, virt_line_index).overflow;
bool virt_row_scroll = (overflow == kVLOverflowScroll)
|| ((overflow == kVLOverflowAuto) && !wp->w_p_wrap);
if (virt_row_scroll) {
virt_line_skip_cells = wp->w_leftcol;
}
draw_virt_text_item(buf,
virt_line_flags & kVLLeftcol ? 0 : win_col_offset,
(virt_line_flags & kVLLeftcol) ? 0 : win_col_offset,
kv_A(virt_lines, virt_line_index).line,
kHlModeReplace,
view_width,
0,
virt_line_flags & kVLScroll ? wp->w_leftcol : 0);
virt_line_skip_cells);
} else if (wlv.filler_todo <= 0) {
draw_virt_text(wp, buf, win_col_offset, &draw_col, wlv.row);
}
@@ -3205,7 +3232,7 @@ end_check:
statuscol.draw = false; // don't draw status column if "n" is in 'cpo'
}
wlv.filler_todo--;
virt_line_index = -1;
has_virt_line = false;
virt_line_flags = 0;
// When the filler lines are actually below the last line of the
// file, or we are not drawing text for this line, break here.

View File

@@ -1738,6 +1738,50 @@ describe('API/extmarks', function()
},
}, get_extmark_by_id(ns, marks[3], { details = true }))
-- verify 'wrap' is returned through get_extmark_by_id to validate flag comparison
set_extmark(ns, marks[4], 0, 0, {
priority = 0,
ui_watched = true,
virt_lines = { { { '', 'Macro' }, { '' }, { '', '' } } },
virt_lines_overflow = 'wrap',
})
eq({
0,
0,
{
ns_id = ns,
right_gravity = true,
ui_watched = true,
priority = 0,
virt_lines = { { { '', 'Macro' }, { '' }, { '', '' } } },
virt_lines_above = false,
virt_lines_leftcol = false,
virt_lines_overflow = 'wrap',
},
}, get_extmark_by_id(ns, marks[4], { details = true }))
-- verify 'auto' is returned through get_extmark_by_id to validate flag comparison
set_extmark(ns, marks[5], 0, 0, {
priority = 0,
ui_watched = true,
virt_lines = { { { '', 'Macro' }, { '' }, { '', '' } } },
virt_lines_overflow = 'auto',
})
eq({
0,
0,
{
ns_id = ns,
right_gravity = true,
ui_watched = true,
priority = 0,
virt_lines = { { { '', 'Macro' }, { '' }, { '', '' } } },
virt_lines_above = false,
virt_lines_leftcol = false,
virt_lines_overflow = 'auto',
},
}, get_extmark_by_id(ns, marks[5], { details = true }))
set_extmark(ns, marks[4], 0, 0, { cursorline_hl_group = 'Statement' })
eq({
0,

View File

@@ -6759,7 +6759,7 @@ if (h->n_buckets < new_n_buckets) { // expand
}
end)
it('scrolls horizontally with virt_lines_overflow = "scroll" #31000', function()
it('scrolls horizontally with virt_lines_overflow=scroll #31000', function()
command('set nowrap signcolumn=yes')
insert('abcdefghijklmnopqrstuvwxyz')
api.nvim_buf_set_extmark(0, ns, 0, 0, {
@@ -6893,6 +6893,329 @@ if (h->n_buckets < new_n_buckets) { // expand
]])
end)
it('virt_lines_overflow=wrap', function()
command('set wrap signcolumn=yes')
insert('line1\nline2\nline3\n')
api.nvim_buf_set_extmark(0, ns, 0, 0, {
virt_lines = {
{
{
string.rep(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean non felis dapibus, semper sapien vitae, fringilla orci.',
5
),
'Special',
},
},
},
virt_lines_overflow = 'wrap',
})
screen:expect([[
{7: }{16:rem ipsum dolor sit amet, consectetur adipiscing}|
{7: }{16: elit. Aenean non felis dapibus, semper sapien v}|
{7: }{16:itae, fringilla orci.Lorem ipsum dolor sit amet,}|
{7: }{16: consectetur adipiscing elit. Aenean non felis d}|
{7: }{16:apibus, semper sapien vitae, fringilla orci.Lore}|
{7: }{16:m ipsum dolor sit amet, consectetur adipiscing e}|
{7: }{16:lit. Aenean non felis dapibus, semper sapien vit}|
{7: }{16:ae, fringilla orci.} |
{7: }line2 |
{7: }line3 |
{7: }^ |
|
]])
feed('<C-e>')
screen:expect([[
{7: }{16: elit. Aenean non felis dapibus, semper sapien v}|
{7: }{16:itae, fringilla orci.Lorem ipsum dolor sit amet,}|
{7: }{16: consectetur adipiscing elit. Aenean non felis d}|
{7: }{16:apibus, semper sapien vitae, fringilla orci.Lore}|
{7: }{16:m ipsum dolor sit amet, consectetur adipiscing e}|
{7: }{16:lit. Aenean non felis dapibus, semper sapien vit}|
{7: }{16:ae, fringilla orci.} |
{7: }line2 |
{7: }line3 |
{7: }^ |
{1:~ }|
|
]])
feed('<C-e>')
screen:expect([[
{7: }{16:itae, fringilla orci.Lorem ipsum dolor sit amet,}|
{7: }{16: consectetur adipiscing elit. Aenean non felis d}|
{7: }{16:apibus, semper sapien vitae, fringilla orci.Lore}|
{7: }{16:m ipsum dolor sit amet, consectetur adipiscing e}|
{7: }{16:lit. Aenean non felis dapibus, semper sapien vit}|
{7: }{16:ae, fringilla orci.} |
{7: }line2 |
{7: }line3 |
{7: }^ |
{1:~ }|*2
|
]])
end)
it('virt_lines_overflow=wrap with 2 cell character', function()
command('set wrap signcolumn=yes')
insert('line1\nline2\nline3\n')
api.nvim_buf_set_extmark(0, ns, 0, 0, {
virt_lines = {
{ { string.rep('12345678古', 5), 'Special' } },
},
virt_lines_overflow = 'wrap',
})
screen:expect([[
{7: }line1 |
{7: }{16:12345678古12345678古12345678古12345678古12345678}|
{7: }{16:古} |
{7: }line2 |
{7: }line3 |
{7: }^ |
{1:~ }|*5
|
]])
feed('<C-e>')
screen:expect([[
{7: }{16:12345678古12345678古12345678古12345678古12345678}|
{7: }{16:古} |
{7: }line2 |
{7: }line3 |
{7: }^ |
{1:~ }|*6
|
]])
feed('<C-e>')
screen:expect([[
{7: }{16:古} |
{7: }line2 |
{7: }line3 |
{7: }^ |
{1:~ }|*7
|
]])
end)
it('virt_lines_overflow=wrap with tabs', function()
command('set wrap signcolumn=yes')
api.nvim_buf_set_extmark(0, ns, 0, 0, {
virt_lines = {
{ { string.rep('1234\t\t\t5\t6\t78古', 10), 'Special' } },
},
virt_lines_overflow = 'wrap',
})
screen:expect([[
{7: }^ |
{7: }{16:1234 5 6 78古1234}|
{7: }{16: 5 6 78古1234}|*8
{7: }{16: 5 6 78古} |
|
]])
end)
it('virt_lines_overflow=wrap with 2 cell character at the boundary', function()
insert('x')
api.nvim_buf_set_extmark(0, ns, 0, 0, {
virt_lines = { { { string.rep('a', 49) .. string.rep('', 4), 'Special' } } },
virt_lines_overflow = 'wrap',
})
screen:expect([[
^x |
{16:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa }|
{16:古古古古} |
{1:~ }|*8
|
]])
api.nvim_buf_clear_namespace(0, ns, 0, -1)
screen:try_resize(13, 10)
api.nvim_buf_set_extmark(0, ns, 0, 0, {
virt_lines = {
{ { 'AB', 'Special' } },
{ { string.rep('', 13), 'Special' } },
},
virt_lines_overflow = 'wrap',
})
screen:expect([[
^x |
{16:AB} |
{16:古古古古古古 }|*2
{16:古} |
{1:~ }|*4
|
]])
end)
it('virt_lines_overflow=wrap with nowrap', function()
command('set nowrap signcolumn=yes')
insert(string.rep('abcdefghij', 6) .. 'line1\nline2\nline3\n')
api.nvim_buf_set_extmark(0, ns, 0, 0, {
virt_lines = {
{ { string.rep('12345678古', 5), 'Special' } },
},
virt_lines_overflow = 'wrap',
})
feed('ggzlzl')
screen:expect([[
{7: }^cdefghijabcdefghijabcdefghijabcdefghijabcdefghij|
{7: }{16:12345678古12345678古12345678古12345678古12345678}|
{7: }{16:古} |
{7: }ne2 |
{7: }ne3 |
{7: } |
{1:~ }|*5
|
]])
end)
it('virt_lines_overflow=auto with wrap', function()
command('set wrap signcolumn=yes')
insert('line1\nline2\nline3\n')
api.nvim_buf_set_extmark(0, ns, 0, 0, {
virt_lines = {
{ { string.rep('Thequickbrownfoxjumpsoverthelazydog. The quick brown fox jumps over the lazy dog', 20), 'Special' } },
},
virt_lines_overflow = 'auto',
})
screen:expect([[
{7: }{16:rown fox jumps over the lazy dogThequickbrownfox}|
{7: }{16:jumpsoverthelazydog. The quick brown fox jumps o}|
{7: }{16:ver the lazy dogThequickbrownfoxjumpsoverthelazy}|
{7: }{16:dog. The quick brown fox jumps over the lazy dog}|
{7: }{16:Thequickbrownfoxjumpsoverthelazydog. The quick b}|
{7: }{16:rown fox jumps over the lazy dogThequickbrownfox}|
{7: }{16:jumpsoverthelazydog. The quick brown fox jumps o}|
{7: }{16:ver the lazy dog} |
{7: }line2 |
{7: }line3 |
{7: }^ |
|
]])
end)
it('virt_lines_overflow=auto and nowrap scrolls horizontally', function()
command('set nowrap signcolumn=yes')
insert('abcdefghijklmnopqrstuvwxyz')
api.nvim_buf_set_extmark(0, ns, 0, 0, {
virt_lines = {
{ { '12α口β̳γ̲=', 'Special' }, { '345678', 'Special' } },
{ { '123\t45\t678', 'NonText' } },
},
virt_lines_overflow = 'auto',
})
screen:expect([[
{7: }abcdefghijklmnopqrstuvwxy^z |
{7: }{16:12α口β̳γ̲=❤345678} |
{7: }{1:123 45 678} |
{1:~ }|*8
|
]])
feed('zl')
screen:expect([[
{7: }bcdefghijklmnopqrstuvwxy^z |
{7: }{16:2α口β̳γ̲=❤345678} |
{7: }{1:23 45 678} |
{1:~ }|*8
|
]])
end)
it('virt_lines_overflow=wrap with virt_lines_above', function()
command('set wrap signcolumn=yes')
insert('line1\nline2\nline3\n')
feed('2gg')
api.nvim_buf_set_extmark(0, ns, 1, 0, {
virt_lines = {
{ { string.rep('Lorem ipsum dolor sit amet, consectetur. ', 4), 'Special' } },
},
virt_lines_overflow = 'wrap',
virt_lines_above = true,
})
screen:expect([[
{7: }line1 |
{7: }{16:Lorem ipsum dolor sit amet, consectetur. Lorem i}|
{7: }{16:psum dolor sit amet, consectetur. Lorem ipsum do}|
{7: }{16:lor sit amet, consectetur. Lorem ipsum dolor sit}|
{7: }{16: amet, consectetur. } |
{7: }^line2 |
{7: }line3 |
{7: } |
{1:~ }|*3
|
]])
end)
it('virt_lines_overflow=wrap with wrapped rows for statuscolumn', function()
command('set wrap statuscolumn=[%{v:lnum}]')
insert('line1\nline2\nline3\n')
feed('2gg')
api.nvim_buf_set_extmark(0, ns, 0, 0, {
virt_lines = {
{ { 'BELOW ' .. string.rep('Lorem ipsum dolor sit amet, consectetur. ', 4), 'Special' } },
},
virt_lines_overflow = 'wrap',
})
api.nvim_buf_set_extmark(0, ns, 1, 0, {
virt_lines = {
{ { 'ABOVE ' .. string.rep('Lorem ipsum dolor sit amet, consectetur. ', 4), 'Special' } },
},
virt_lines_overflow = 'wrap',
virt_lines_above = true,
})
screen:expect([[
{8:[1]}line1 |
{8:[1]}{16:BELOW Lorem ipsum dolor sit amet, consectetur. }|
{8:[1]}{16:Lorem ipsum dolor sit amet, consectetur. Lorem }|
{8:[1]}{16:ipsum dolor sit amet, consectetur. Lorem ipsum }|
{8:[1]}{16:dolor sit amet, consectetur. } |
{8:[2]}{16:ABOVE Lorem ipsum dolor sit amet, consectetur. }|
{8:[2]}{16:Lorem ipsum dolor sit amet, consectetur. Lorem }|
{8:[2]}{16:ipsum dolor sit amet, consectetur. Lorem ipsum }|
{8:[2]}{16:dolor sit amet, consectetur. } |
{8:[2]}^line2 |
{8:[3]}line3 |
|
]])
end)
it('mixing virt_lines_overflow=wrap and virt_lines_overflow=scroll', function()
command('set nowrap signcolumn=yes')
insert('line1\nline2\nline3\n')
api.nvim_buf_set_extmark(0, ns, 0, 0, {
virt_lines = { { { 'BELOW ' .. string.rep('a', 60), 'Special' } } },
virt_lines_overflow = 'wrap',
})
api.nvim_buf_set_extmark(0, ns, 1, 0, {
virt_lines = { { { 'ABOVE ' .. string.rep('b', 60), 'Special' } } },
virt_lines_overflow = 'scroll',
virt_lines_above = true,
})
api.nvim_buf_set_extmark(0, ns, 1, 0, {
virt_lines = { { { 'BELOW ' .. string.rep('c', 60), 'Special' } } },
virt_lines_overflow = 'scroll',
})
api.nvim_buf_set_extmark(0, ns, 2, 0, {
virt_lines = { { { 'ABOVE ' .. string.rep('d', 60), 'Special' } } },
virt_lines_overflow = 'wrap',
virt_lines_above = true,
})
feed('2ggzlzl')
screen:expect([[
{7: }ne1 |
{7: }{16:BELOW aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
{7: }{16:aaaaaaaaaaaaaaaaaa} |
{7: }{16:OVE bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb}|
{7: }^ne2 |
{7: }{16:LOW cccccccccccccccccccccccccccccccccccccccccccc}|
{7: }{16:ABOVE dddddddddddddddddddddddddddddddddddddddddd}|
{7: }{16:dddddddddddddddddd} |
{7: }ne3 |
{7: } |
{1:~ }|
|
]])
end)
it('does not show twice if end_row or end_col is specified #18622', function()
screen:try_resize(50, 8)
insert([[