fix(marks): truncate double-width inline virt_text consistently (#32560)

- Fix wrong cursor position with 'listchars' "precedes".
- Always show the '<' truncation character.
This commit is contained in:
zeertzjq
2025-02-22 06:35:10 +08:00
committed by GitHub
parent 4ff813e5c6
commit cdedfc3743
2 changed files with 124 additions and 46 deletions

View File

@ -930,28 +930,22 @@ static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t
if (wlv->skip_cells > 0) {
int virt_text_width = (int)mb_string2cells(wlv->p_extra);
if (virt_text_width > wlv->skip_cells) {
int cells_to_skip = wlv->skip_cells;
int skip_cells_remaining = wlv->skip_cells;
// Skip cells in the text.
while (cells_to_skip > 0) {
while (skip_cells_remaining > 0) {
int cells = utf_ptr2cells(wlv->p_extra);
if (cells > skip_cells_remaining) {
break;
}
int c_len = utfc_ptr2len(wlv->p_extra);
cells_to_skip -= utf_ptr2cells(wlv->p_extra);
skip_cells_remaining -= cells;
wlv->p_extra += c_len;
wlv->n_extra -= c_len;
wlv->n_attr--;
}
// If a double-width char doesn't fit, pad with space.
if (cells_to_skip < 0) {
int pad_len = -cells_to_skip;
char *padded = get_extra_buf((size_t)(wlv->n_extra + pad_len) + 1);
memset(padded, ' ', (size_t)pad_len);
xmemcpyz(padded + pad_len, wlv->p_extra, (size_t)wlv->n_extra);
wlv->p_extra = padded;
wlv->n_extra += pad_len;
wlv->n_attr += pad_len;
}
// Skipped cells needed to be accounted for in vcol.
wlv->skipped_cells += wlv->skip_cells;
wlv->skip_cells = 0;
wlv->skipped_cells += wlv->skip_cells - skip_cells_remaining;
wlv->skip_cells = skip_cells_remaining;
} else {
// The whole text is left of the window, drop
// it and advance to the next one.
@ -1076,6 +1070,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
bool in_multispace = false; // in multiple consecutive spaces
int multispace_pos = 0; // position in lcs-multispace string
int n_extra_next = 0; // n_extra to use after current extra chars
int extra_attr_next = -1; // extra_attr to use after current extra chars
bool search_attr_from_match = false; // if search_attr is from :match
bool has_decor = false; // this buffer has decoration
@ -1950,7 +1947,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
if (wlv.col >= grid->cols - 1 && schar_cells(mb_schar) == 2) {
mb_c = '>';
mb_l = 1;
(void)mb_l;
mb_schar = schar_from_ascii(mb_c);
multi_attr = win_hl_attr(wp, HLF_AT);
@ -1963,30 +1959,58 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
wlv.n_extra -= mb_l;
wlv.p_extra += mb_l;
}
// If a double-width char doesn't fit at the left side display a '<'.
if (wlv.filler_todo <= 0 && wlv.skip_cells > 0 && mb_l > 1) {
if (wlv.n_extra > 0) {
n_extra_next = wlv.n_extra;
extra_attr_next = wlv.extra_attr;
}
wlv.n_extra = 1;
wlv.sc_extra = schar_from_ascii(MB_FILLER_CHAR);
wlv.sc_final = NUL;
mb_schar = schar_from_ascii(' ');
mb_c = ' ';
mb_l = 1;
(void)mb_l;
wlv.n_attr++;
wlv.extra_attr = win_hl_attr(wp, HLF_AT);
}
}
// Only restore search_attr and area_attr after "n_extra" in
// the next screen line is also done.
if (wlv.n_extra <= 0) {
if (search_attr == 0) {
search_attr = saved_search_attr;
saved_search_attr = 0;
}
if (area_attr == 0 && *ptr != NUL) {
area_attr = saved_area_attr;
saved_area_attr = 0;
}
if (decor_attr == 0) {
decor_attr = saved_decor_attr;
saved_decor_attr = 0;
}
if (wlv.extra_for_extmark) {
// wlv.extra_attr should be used at this position but not
// any further.
// Only restore search_attr and area_attr when there is no "n_extra" to show.
if (n_extra_next <= 0) {
if (search_attr == 0) {
search_attr = saved_search_attr;
saved_search_attr = 0;
}
if (area_attr == 0 && *ptr != NUL) {
area_attr = saved_area_attr;
saved_area_attr = 0;
}
if (decor_attr == 0) {
decor_attr = saved_decor_attr;
saved_decor_attr = 0;
}
if (wlv.extra_for_extmark) {
// wlv.extra_attr should be used at this position but not any further.
wlv.reset_extra_attr = true;
extra_attr_next = -1;
}
wlv.extra_for_extmark = false;
} else {
assert(wlv.sc_extra != NUL || wlv.sc_final != NUL);
assert(wlv.p_extra != NULL);
wlv.sc_extra = NUL;
wlv.sc_final = NUL;
wlv.n_extra = n_extra_next;
n_extra_next = 0;
// wlv.extra_attr should be used at this position, but extra_attr_next
// should be used after that.
wlv.reset_extra_attr = true;
assert(extra_attr_next >= 0);
}
wlv.extra_for_extmark = false;
}
} else if (wlv.filler_todo > 0) {
// Wait with reading text until filler lines are done. Still need to
@ -2563,14 +2587,19 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL;
}
// Don't override visual selection highlighting.
// Use "wlv.extra_attr", but don't override visual selection highlighting.
if (wlv.n_attr > 0 && !search_attr_from_match) {
wlv.char_attr = hl_combine_attr(wlv.char_attr, wlv.extra_attr);
if (wlv.reset_extra_attr) {
wlv.reset_extra_attr = false;
wlv.extra_attr = 0;
// search_attr_from_match can be restored now that the extra_attr has been applied
search_attr_from_match = saved_search_attr_from_match;
if (extra_attr_next >= 0) {
wlv.extra_attr = extra_attr_next;
extra_attr_next = -1;
} else {
wlv.extra_attr = 0;
// search_attr_from_match can be restored now that the extra_attr has been applied
search_attr_from_match = saved_search_attr_from_match;
}
}
}
@ -2584,14 +2613,20 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
&& wlv.skip_cells <= 0
&& mb_schar != NUL) {
lcs_prec_todo = NUL;
// TODO(zeertzjq): handle the n_extra > 0 case
if (schar_cells(mb_schar) > 1 && wlv.n_extra == 0) {
if (schar_cells(mb_schar) > 1) {
// Double-width character being overwritten by the "precedes"
// character, need to fill up half the character.
wlv.sc_extra = schar_from_ascii(MB_FILLER_CHAR);
wlv.sc_final = NUL;
if (wlv.n_extra > 0) {
assert(wlv.p_extra != NULL);
n_extra_next = wlv.n_extra;
extra_attr_next = wlv.extra_attr;
wlv.n_attr = MAX(wlv.n_attr + 1, 2);
} else {
wlv.n_attr = 2;
}
wlv.n_extra = 1;
wlv.n_attr = 2;
wlv.extra_attr = win_hl_attr(wp, HLF_AT);
}
mb_schar = wp->w_p_lcs_chars.prec;

View File

@ -3368,17 +3368,24 @@ describe('decorations: inline virtual text', function()
command("set nowrap")
api.nvim_buf_set_extmark(0, ns, 0, 2, { virt_text = { { string.rep('X', 55), 'Special' } }, virt_text_pos = 'inline' })
feed('$')
screen:expect{grid=[[
screen:expect([[
{10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}cdefgh^i|
{1:~ }|
|
]]}
]])
command('set list listchars+=precedes:!')
screen:expect([[
{1:!}{10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}cdefgh^i|
{1:~ }|
|
]])
end)
it('draws correctly with no wrap and multibyte virtual text', function()
insert('12345678')
command('set nowrap')
api.nvim_buf_set_extmark(0, ns, 0, 2, {
hl_mode = 'replace',
virt_text = { { 'α口β̳γ̲=', 'Special' }, { '❤️', 'Special' } },
virt_text_pos = 'inline',
})
@ -3405,9 +3412,33 @@ describe('decorations: inline virtual text', function()
{1:~ }|
|
]])
feed('V')
screen:expect([[
{10:口β̳γ̲=❤️}{7:34567}^8 |
{1:~ }|
{8:-- VISUAL LINE --} |
]])
command('set list listchars+=precedes:!')
screen:expect([[
{1:!<}{10:β̳γ̲=❤️}{7:34567}^8 |
{1:~ }|
{8:-- VISUAL LINE --} |
]])
feed('zl')
screen:expect([[
{10: β̳γ̲=❤️}34567^8 |
{1:!}{10:β̳γ̲=❤️}{7:34567}^8 |
{1:~ }|
{8:-- VISUAL LINE --} |
]])
command('set nolist')
screen:expect([[
{1:<}{10:β̳γ̲=❤️}{7:34567}^8 |
{1:~ }|
{8:-- VISUAL LINE --} |
]])
feed('<Esc>')
screen:expect([[
{1:<}{10:β̳γ̲=❤️}34567^8 |
{1:~ }|
|
]])
@ -3435,9 +3466,21 @@ describe('decorations: inline virtual text', function()
{1:~ }|
|
]])
command('set list')
screen:expect([[
{1:!<}34567^8 |
{1:~ }|
|
]])
feed('zl')
screen:expect([[
{10: }34567^8 |
{1:!}34567^8 |
{1:~ }|
|
]])
command('set nolist')
screen:expect([[
{1:<}34567^8 |
{1:~ }|
|
]])