diff options
author | zeertzjq <zeertzjq@outlook.com> | 2025-02-20 21:47:12 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-20 21:47:12 +0800 |
commit | 51cf84daf9612574978731e66db45a52136b8899 (patch) | |
tree | 5ac6f72c40e5219356ac198e1c20ca4df55354cf | |
parent | 574ea6a1911b740bb611f0b658a83f606b6837bc (diff) | |
download | rneovim-51cf84daf9612574978731e66db45a52136b8899.tar.gz rneovim-51cf84daf9612574978731e66db45a52136b8899.tar.bz2 rneovim-51cf84daf9612574978731e66db45a52136b8899.zip |
feat(marks): virtual lines support horizontal scrolling (#32497)
Add a new field `virt_lines_overflow` that enables horizontal scrolling
for virtual lines when set to "scroll".
-rw-r--r-- | runtime/doc/api.txt | 6 | ||||
-rw-r--r-- | runtime/doc/news.txt | 7 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/api.lua | 6 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/api_keysets.lua | 1 | ||||
-rw-r--r-- | src/nvim/api/extmark.c | 20 | ||||
-rw-r--r-- | src/nvim/api/keysets_defs.h | 1 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.h | 4 | ||||
-rw-r--r-- | src/nvim/decoration.c | 8 | ||||
-rw-r--r-- | src/nvim/decoration_defs.h | 9 | ||||
-rw-r--r-- | src/nvim/drawline.c | 67 | ||||
-rw-r--r-- | test/functional/api/extmark_spec.lua | 56 | ||||
-rw-r--r-- | test/functional/ui/decorations_spec.lua | 135 |
12 files changed, 274 insertions, 46 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index d3d18e9d6b..ec3dfebbc0 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -2651,6 +2651,12 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts}) • virt_lines_above: place virtual lines above instead. • virt_lines_leftcol: Place virtual lines in the leftmost column of the window, bypassing sign and number columns. + • virt_lines_overflow: controls how to handle virtual lines + wider than the window. Currently takes the one of the + following values: + • "trunc": truncate virtual lines on the right (default). + • "scroll": virtual lines can scroll horizontally with + 'nowrap', otherwise the same as "trunc". • ephemeral : for use with |nvim_set_decoration_provider()| callbacks. The mark will only be used for the current redraw cycle, and not be permantently stored in the diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index da672f449c..f743bfc78b 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -202,9 +202,12 @@ API • |nvim_echo()| `err` field to print error messages and `chunks` accepts highlight group IDs. • |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 +• |nvim_buf_set_extmark()| new field `virt_lines_overflow` accepts value `scroll` to + enable horizontal scrolling for virtual lines with 'nowrap'. + right aligned text that truncates before covering up buffer text. +• |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 +• |nvim_buf_set_extmark()| `virt_text_pos` field accepts value `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 96ee376882..a6ffb43146 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -641,7 +641,11 @@ function vim.api.nvim_buf_line_count(buffer) end --- - virt_lines_leftcol: Place virtual lines in the leftmost --- column of the window, bypassing --- sign and number columns. ---- +--- - virt_lines_overflow: controls how to handle virtual lines wider +--- than the window. Currently takes the one of the following values: +--- - "trunc": truncate virtual lines on the right (default). +--- - "scroll": virtual lines can scroll horizontally with 'nowrap', +--- otherwise the same as "trunc". --- - ephemeral : for use with `nvim_set_decoration_provider()` --- callbacks. The mark will only be used for the current --- redraw cycle, and not be permantently stored in the diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua index 4d0665872b..a66e373851 100644 --- a/runtime/lua/vim/_meta/api_keysets.lua +++ b/runtime/lua/vim/_meta/api_keysets.lua @@ -258,6 +258,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? string --- @field strict? boolean --- @field sign_text? string --- @field sign_hl_group? integer|string diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 2cf9221e52..ef09dbb0aa 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -448,7 +448,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// - virt_lines_leftcol: Place virtual lines in the leftmost /// column of the window, bypassing /// sign and number columns. -/// +/// - virt_lines_overflow: controls how to handle virtual lines wider +/// than the window. Currently takes the one of the following values: +/// - "trunc": truncate virtual lines on the right (default). +/// - "scroll": virtual lines can scroll horizontally with 'nowrap', +/// otherwise the same as "trunc". /// - ephemeral : for use with |nvim_set_decoration_provider()| /// callbacks. The mark will only be used for the current /// redraw cycle, and not be permantently stored in the @@ -669,7 +673,17 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer } } - bool virt_lines_leftcol = opts->virt_lines_leftcol; + int virt_lines_flags = opts->virt_lines_leftcol ? kVLLeftcol : 0; + if (HAS_KEY(opts, set_extmark, virt_lines_overflow)) { + String str = opts->virt_lines_overflow; + if (strequal("scroll", str.data)) { + virt_lines_flags |= kVLScroll; + } else if (!strequal("trunc", str.data)) { + VALIDATE_S(false, "virt_lines_overflow", str.data, { + goto error; + }); + } + } if (HAS_KEY(opts, set_extmark, virt_lines)) { Array a = opts->virt_lines; @@ -679,7 +693,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer }); int dummig; VirtText jtem = parse_virt_text(a.items[j].data.array, err, &dummig); - kv_push(virt_lines.data.virt_lines, ((struct virt_line){ jtem, virt_lines_leftcol })); + kv_push(virt_lines.data.virt_lines, ((struct virt_line){ jtem, virt_lines_flags })); if (ERROR_SET(err)) { goto error; } diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h index 6625908cda..b3015911f9 100644 --- a/src/nvim/api/keysets_defs.h +++ b/src/nvim/api/keysets_defs.h @@ -45,6 +45,7 @@ typedef struct { Array virt_lines; Boolean virt_lines_above; Boolean virt_lines_leftcol; + String virt_lines_overflow; Boolean strict; String sign_text; HLGroupID sign_hl_group; diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index d581c6bc10..5cf1ca4d34 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -64,7 +64,7 @@ #define NIL ((Object)OBJECT_INIT) #define NULL_STRING ((String)STRING_INIT) -#define HAS_KEY(d, typ, key) (((d)->is_set__##typ##_ & (1 << KEYSET_OPTIDX_##typ##__##key)) != 0) +#define HAS_KEY(d, typ, key) (((d)->is_set__##typ##_ & (1ULL << KEYSET_OPTIDX_##typ##__##key)) != 0) #define GET_BOOL_OR_TRUE(d, typ, key) (HAS_KEY(d, typ, key) ? (d)->key : true) @@ -75,7 +75,7 @@ kv_push_c(dict, ((KeyValuePair) { .key = cstr_as_string(k), .value = v })) #define PUT_KEY(d, typ, key, v) \ - do { (d).is_set__##typ##_ |= (1 << KEYSET_OPTIDX_##typ##__##key); (d).key = v; } while (0) + do { (d).is_set__##typ##_ |= (1ULL << KEYSET_OPTIDX_##typ##__##key); (d).key = v; } while (0) #define ADD(array, item) \ kv_push(array, item) diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 3ca3f2904b..d41e7e32be 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -1169,15 +1169,17 @@ 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)); - bool virt_lines_leftcol = false; + int virt_lines_flags = 0; for (size_t i = 0; i < kv_size(virt_lines->data.virt_lines); i++) { - virt_lines_leftcol = kv_A(virt_lines->data.virt_lines, i).left_col; + virt_lines_flags = kv_A(virt_lines->data.virt_lines, i).flags; 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_leftcol)); + 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")); priority = virt_lines->priority; } diff --git a/src/nvim/decoration_defs.h b/src/nvim/decoration_defs.h index 36ad6df7a0..8be988cd82 100644 --- a/src/nvim/decoration_defs.h +++ b/src/nvim/decoration_defs.h @@ -26,7 +26,14 @@ typedef enum { kVPosWinCol, } VirtTextPos; -typedef kvec_t(struct virt_line { VirtText line; bool left_col; }) VirtLines; +/// 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 uint16_t DecorPriority; #define DECOR_PRIORITY_BASE 0x1000 diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 0988f668d5..c2a14bdfd5 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -231,7 +231,8 @@ static int line_putchar(buf_T *buf, const char **pp, schar_T *dest, int maxcells } if (*p == TAB) { - cells = MIN(tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array), maxcells); + cells = tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array); + cells = MIN(cells, maxcells); } // When overwriting the left half of a double-width char, clear the right half. @@ -345,7 +346,7 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int if (vt) { 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); + vt->hl_mode, max_col, vcol, 0); if (do_eol && ((vt->pos == kVPosEndOfLine) || (vt->pos == kVPosEndOfLineRightAlign))) { state->eol_col = col + 1; } @@ -358,32 +359,45 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int } static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, int max_col, - int vcol) + int vcol, int skip_cells) { - const char *p = ""; + const char *virt_str = ""; int virt_attr = 0; size_t virt_pos = 0; while (col < max_col) { - if (!*p) { + if (skip_cells >= 0 && *virt_str == NUL) { if (virt_pos >= kv_size(vt)) { break; } virt_attr = 0; - p = next_virt_text_chunk(vt, &virt_pos, &virt_attr); - if (p == NULL) { + virt_str = next_virt_text_chunk(vt, &virt_pos, &virt_attr); + if (virt_str == NULL) { break; } } - if (*p == NUL) { + // Skip cells in the text. + while (skip_cells > 0 && *virt_str != NUL) { + int c_len = utfc_ptr2len(virt_str); + int cells = *virt_str == TAB + ? tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array) + : utf_ptr2cells(virt_str); + skip_cells -= cells; + vcol += cells; + virt_str += c_len; + } + // If a double-width char or TAB doesn't fit, pad with spaces. + const char *draw_str = skip_cells < 0 ? " " : virt_str; + if (*draw_str == NUL) { continue; } + assert(skip_cells <= 0); int attr; bool through = false; if (hl_mode == kHlModeCombine) { attr = hl_combine_attr(linebuf_attr[col], virt_attr); } else if (hl_mode == kHlModeBlend) { - through = (*p == ' '); + through = (*draw_str == ' '); attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through); } else { attr = virt_attr; @@ -397,13 +411,18 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, // Clear the right half as well for the assertion in line_putchar(). linebuf_char[col] = schar_from_ascii(' '); } - int cells = line_putchar(buf, &p, through ? dummy : &linebuf_char[col], + int cells = line_putchar(buf, &draw_str, through ? dummy : &linebuf_char[col], maxcells, vcol); for (int c = 0; c < cells; c++) { linebuf_attr[col] = attr; col++; } - vcol += cells; + if (skip_cells < 0) { + skip_cells++; + } else { + vcol += cells; + virt_str = draw_str; + } } return col; } @@ -1587,8 +1606,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s const bool may_have_inline_virt = !has_foldtext && buf_meta_total(wp->w_buffer, kMTMetaInline) > 0; - int virt_line_index; - int virt_line_offset = -1; + int virt_line_index = -1; + int virt_line_flags = 0; // Repeat for the whole displayed line. while (true) { int has_match_conc = 0; ///< match wants to conceal @@ -1616,11 +1635,11 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s if (index > 0) { virt_line_index = (int)kv_size(virt_lines) - index; assert(virt_line_index >= 0); - virt_line_offset = kv_A(virt_lines, virt_line_index).left_col ? 0 : win_col_off(wp); + virt_line_flags = kv_A(virt_lines, virt_line_index).flags; } } - if (virt_line_offset == 0) { + if (virt_line_index >= 0 && (virt_line_flags & kVLLeftcol)) { // skip columns } else if (statuscol.draw) { // Draw 'statuscolumn' if it is set. @@ -2715,7 +2734,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s } if (kv_size(fold_vt) > 0) { - draw_virt_text_item(buf, win_col_offset, fold_vt, kHlModeCombine, grid->cols, 0); + draw_virt_text_item(buf, win_col_offset, fold_vt, kHlModeCombine, grid->cols, 0, 0); } draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row); // Set increasing virtual columns in grid->vcols[] to set correct curswant @@ -2923,7 +2942,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s end_check: // At end of screen line and there is more to come: Display the line // so far. If there is no more to display it is caught above. - if (wlv.col >= grid->cols && (!has_foldtext || virt_line_offset >= 0) + if (wlv.col >= grid->cols && (!has_foldtext || virt_line_index >= 0) && (wlv.col <= leftcols_width || *ptr != NUL || wlv.filler_todo > 0 @@ -2956,9 +2975,14 @@ end_check: } } - if (virt_line_offset >= 0) { - draw_virt_text_item(buf, virt_line_offset, kv_A(virt_lines, virt_line_index).line, - kHlModeReplace, grid->cols, 0); + if (virt_line_index >= 0) { + draw_virt_text_item(buf, + virt_line_flags & kVLLeftcol ? 0 : win_col_offset, + kv_A(virt_lines, virt_line_index).line, + kHlModeReplace, + grid->cols, + 0, + virt_line_flags & kVLScroll ? wp->w_leftcol : 0); } else if (wlv.filler_todo <= 0) { draw_virt_text(wp, buf, win_col_offset, &draw_col, wlv.row); } @@ -3008,7 +3032,8 @@ end_check: statuscol.draw = false; // don't draw status column if "n" is in 'cpo' } wlv.filler_todo--; - virt_line_offset = -1; + virt_line_index = -1; + virt_line_flags = 0; // When the filler lines are actually below the last line of the // file, don't draw the line itself, break here. if (wlv.filler_todo == 0 && (wp->w_botfill || end_fill)) { diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index 8a4aea1efe..fd392d479d 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -124,6 +124,10 @@ describe('API/extmarks', function() ) eq("Invalid 'hl_mode': 'foo'", pcall_err(set_extmark, ns, marks[2], 0, 0, { hl_mode = 'foo' })) eq( + "Invalid 'virt_lines_overflow': 'foo'", + pcall_err(set_extmark, ns, marks[2], 0, 0, { virt_lines_overflow = 'foo' }) + ) + eq( "Invalid 'id': expected Integer, got Array", pcall_err(set_extmark, ns, {}, 0, 0, { end_col = 1, end_row = 1 }) ) @@ -1576,11 +1580,6 @@ describe('API/extmarks', function() virt_text_hide = true, virt_text_pos = 'right_align', }) - set_extmark(ns, marks[2], 0, 0, { - priority = 0, - virt_text = { { '', 'Macro' }, { '', { 'Type', 'Search' } }, { '' } }, - virt_text_win_col = 1, - }) eq({ 0, 0, @@ -1607,12 +1606,20 @@ describe('API/extmarks', function() }, virt_lines_above = true, virt_lines_leftcol = true, + virt_lines_overflow = 'trunc', virt_text = { { 'text', 'Macro' }, { '???' }, { 'stack', { 'Type', 'Search' } } }, virt_text_repeat_linebreak = false, virt_text_hide = true, virt_text_pos = 'right_align', }, }, get_extmark_by_id(ns, marks[1], { details = true })) + + set_extmark(ns, marks[2], 0, 0, { + priority = 0, + virt_text = { { '', 'Macro' }, { '', { 'Type', 'Search' } }, { '' } }, + virt_text_repeat_linebreak = true, + virt_text_win_col = 1, + }) eq({ 0, 0, @@ -1621,13 +1628,35 @@ describe('API/extmarks', function() right_gravity = true, priority = 0, virt_text = { { '', 'Macro' }, { '', { 'Type', 'Search' } }, { '' } }, - virt_text_repeat_linebreak = false, + virt_text_repeat_linebreak = true, virt_text_hide = false, virt_text_pos = 'win_col', virt_text_win_col = 1, }, }, get_extmark_by_id(ns, marks[2], { details = true })) - set_extmark(ns, marks[3], 0, 0, { cursorline_hl_group = 'Statement' }) + + set_extmark(ns, marks[3], 0, 0, { + priority = 0, + ui_watched = true, + virt_lines = { { { '', 'Macro' }, { '' }, { '', '' } } }, + virt_lines_overflow = 'scroll', + }) + eq({ + 0, + 0, + { + ns_id = 1, + right_gravity = true, + ui_watched = true, + priority = 0, + virt_lines = { { { '', 'Macro' }, { '' }, { '', '' } } }, + virt_lines_above = false, + virt_lines_leftcol = false, + virt_lines_overflow = 'scroll', + }, + }, get_extmark_by_id(ns, marks[3], { details = true })) + + set_extmark(ns, marks[4], 0, 0, { cursorline_hl_group = 'Statement' }) eq({ 0, 0, @@ -1637,8 +1666,9 @@ describe('API/extmarks', function() priority = 4096, right_gravity = true, }, - }, get_extmark_by_id(ns, marks[3], { details = true })) - set_extmark(ns, marks[4], 0, 0, { + }, get_extmark_by_id(ns, marks[4], { details = true })) + + set_extmark(ns, marks[5], 0, 0, { end_col = 1, conceal = 'a', spell = true, @@ -1655,8 +1685,9 @@ describe('API/extmarks', function() right_gravity = true, spell = true, }, - }, get_extmark_by_id(ns, marks[4], { details = true })) - set_extmark(ns, marks[5], 0, 0, { + }, get_extmark_by_id(ns, marks[5], { details = true })) + + set_extmark(ns, marks[6], 0, 0, { end_col = 1, spell = false, }) @@ -1671,7 +1702,8 @@ describe('API/extmarks', function() right_gravity = true, spell = false, }, - }, get_extmark_by_id(ns, marks[5], { details = true })) + }, get_extmark_by_id(ns, marks[6], { details = true })) + api.nvim_buf_clear_namespace(0, ns, 0, -1) -- legacy sign mark includes sign name command('sign define sign1 text=s1 texthl=Title linehl=LineNR numhl=Normal culhl=CursorLine') diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 2f83e7b97a..0f0aa3a5ed 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -4949,7 +4949,6 @@ if (h->n_buckets < new_n_buckets) { // expand ]]} end) - it('works with hard TABs', function() insert(example_text2) feed 'gg' @@ -5020,6 +5019,140 @@ if (h->n_buckets < new_n_buckets) { // expand ]]} end) + 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, { + virt_lines = { + { { '12αβ̳γ̲口=', 'Special' }, { '❤️345678', 'Special' } }, + { { '123\t45\t678', 'NonText' } }, + }, + virt_lines_overflow = 'scroll', + }) + 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 + | + ]]) + feed('zl') + screen:expect([[ + {7: }cdefghijklmnopqrstuvwxy^z | + {7: }{16:αβ̳γ̲口=❤️345678} | + {7: }{1:3 45 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }defghijklmnopqrstuvwxy^z | + {7: }{16:β̳γ̲口=❤️345678} | + {7: }{1: 45 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }efghijklmnopqrstuvwxy^z | + {7: }{16:γ̲口=❤️345678} | + {7: }{1: 45 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }fghijklmnopqrstuvwxy^z | + {7: }{16:口=❤️345678} | + {7: }{1: 45 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }ghijklmnopqrstuvwxy^z | + {7: }{16: =❤️345678} | + {7: }{1: 45 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }hijklmnopqrstuvwxy^z | + {7: }{16:=❤️345678} | + {7: }{1: 45 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }ijklmnopqrstuvwxy^z | + {7: }{16:❤️345678} | + {7: }{1:45 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }jklmnopqrstuvwxy^z | + {7: }{16: 345678} | + {7: }{1:5 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }klmnopqrstuvwxy^z | + {7: }{16:345678} | + {7: }{1: 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }lmnopqrstuvwxy^z | + {7: }{16:45678} | + {7: }{1: 678} | + {1:~ }|*8 + | + ]]) + feed('zl') + screen:expect([[ + {7: }mnopqrstuvwxy^z | + {7: }{16:5678} | + {7: }{1: 678} | + {1:~ }|*8 + | + ]]) + api.nvim_buf_set_extmark(0, ns, 0, 1, { + virt_lines = { { { '123\t45\t67', 'NonText' } } }, + virt_lines_leftcol = true, + virt_lines_overflow = 'trunc', + }) + api.nvim_buf_set_extmark(0, ns, 0, 2, { + virt_lines = { { { '123\t45\t6', 'NonText' } } }, + virt_lines_leftcol = false, + virt_lines_overflow = 'trunc', + }) + screen:expect([[ + {7: }mnopqrstuvwxy^z | + {7: }{16:5678} | + {7: }{1: 678} | + {1:123 45 67} | + {7: }{1:123 45 6} | + {1:~ }|*6 + | + ]]) + end) + it('does not show twice if end_row or end_col is specified #18622', function() screen:try_resize(50, 8) insert([[ |