mirror of
https://github.com/neovim/neovim
synced 2025-07-17 17:51:48 +00:00
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:
@ -930,28 +930,22 @@ static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t
|
|||||||
if (wlv->skip_cells > 0) {
|
if (wlv->skip_cells > 0) {
|
||||||
int virt_text_width = (int)mb_string2cells(wlv->p_extra);
|
int virt_text_width = (int)mb_string2cells(wlv->p_extra);
|
||||||
if (virt_text_width > wlv->skip_cells) {
|
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.
|
// 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);
|
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->p_extra += c_len;
|
||||||
wlv->n_extra -= c_len;
|
wlv->n_extra -= c_len;
|
||||||
wlv->n_attr--;
|
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.
|
// Skipped cells needed to be accounted for in vcol.
|
||||||
wlv->skipped_cells += wlv->skip_cells;
|
wlv->skipped_cells += wlv->skip_cells - skip_cells_remaining;
|
||||||
wlv->skip_cells = 0;
|
wlv->skip_cells = skip_cells_remaining;
|
||||||
} else {
|
} else {
|
||||||
// The whole text is left of the window, drop
|
// The whole text is left of the window, drop
|
||||||
// it and advance to the next one.
|
// 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
|
bool in_multispace = false; // in multiple consecutive spaces
|
||||||
int multispace_pos = 0; // position in lcs-multispace string
|
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 search_attr_from_match = false; // if search_attr is from :match
|
||||||
bool has_decor = false; // this buffer has decoration
|
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) {
|
if (wlv.col >= grid->cols - 1 && schar_cells(mb_schar) == 2) {
|
||||||
mb_c = '>';
|
mb_c = '>';
|
||||||
mb_l = 1;
|
mb_l = 1;
|
||||||
(void)mb_l;
|
|
||||||
mb_schar = schar_from_ascii(mb_c);
|
mb_schar = schar_from_ascii(mb_c);
|
||||||
multi_attr = win_hl_attr(wp, HLF_AT);
|
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.n_extra -= mb_l;
|
||||||
wlv.p_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 (wlv.n_extra <= 0) {
|
||||||
if (search_attr == 0) {
|
// Only restore search_attr and area_attr when there is no "n_extra" to show.
|
||||||
search_attr = saved_search_attr;
|
if (n_extra_next <= 0) {
|
||||||
saved_search_attr = 0;
|
if (search_attr == 0) {
|
||||||
}
|
search_attr = saved_search_attr;
|
||||||
if (area_attr == 0 && *ptr != NUL) {
|
saved_search_attr = 0;
|
||||||
area_attr = saved_area_attr;
|
}
|
||||||
saved_area_attr = 0;
|
if (area_attr == 0 && *ptr != NUL) {
|
||||||
}
|
area_attr = saved_area_attr;
|
||||||
if (decor_attr == 0) {
|
saved_area_attr = 0;
|
||||||
decor_attr = saved_decor_attr;
|
}
|
||||||
saved_decor_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
|
if (wlv.extra_for_extmark) {
|
||||||
// any further.
|
// 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;
|
wlv.reset_extra_attr = true;
|
||||||
|
assert(extra_attr_next >= 0);
|
||||||
}
|
}
|
||||||
wlv.extra_for_extmark = false;
|
|
||||||
}
|
}
|
||||||
} else if (wlv.filler_todo > 0) {
|
} else if (wlv.filler_todo > 0) {
|
||||||
// Wait with reading text until filler lines are done. Still need to
|
// 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;
|
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) {
|
if (wlv.n_attr > 0 && !search_attr_from_match) {
|
||||||
wlv.char_attr = hl_combine_attr(wlv.char_attr, wlv.extra_attr);
|
wlv.char_attr = hl_combine_attr(wlv.char_attr, wlv.extra_attr);
|
||||||
if (wlv.reset_extra_attr) {
|
if (wlv.reset_extra_attr) {
|
||||||
wlv.reset_extra_attr = false;
|
wlv.reset_extra_attr = false;
|
||||||
wlv.extra_attr = 0;
|
if (extra_attr_next >= 0) {
|
||||||
// search_attr_from_match can be restored now that the extra_attr has been applied
|
wlv.extra_attr = extra_attr_next;
|
||||||
search_attr_from_match = saved_search_attr_from_match;
|
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
|
&& wlv.skip_cells <= 0
|
||||||
&& mb_schar != NUL) {
|
&& mb_schar != NUL) {
|
||||||
lcs_prec_todo = NUL;
|
lcs_prec_todo = NUL;
|
||||||
// TODO(zeertzjq): handle the n_extra > 0 case
|
if (schar_cells(mb_schar) > 1) {
|
||||||
if (schar_cells(mb_schar) > 1 && wlv.n_extra == 0) {
|
|
||||||
// Double-width character being overwritten by the "precedes"
|
// Double-width character being overwritten by the "precedes"
|
||||||
// character, need to fill up half the character.
|
// character, need to fill up half the character.
|
||||||
wlv.sc_extra = schar_from_ascii(MB_FILLER_CHAR);
|
wlv.sc_extra = schar_from_ascii(MB_FILLER_CHAR);
|
||||||
wlv.sc_final = NUL;
|
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_extra = 1;
|
||||||
wlv.n_attr = 2;
|
|
||||||
wlv.extra_attr = win_hl_attr(wp, HLF_AT);
|
wlv.extra_attr = win_hl_attr(wp, HLF_AT);
|
||||||
}
|
}
|
||||||
mb_schar = wp->w_p_lcs_chars.prec;
|
mb_schar = wp->w_p_lcs_chars.prec;
|
||||||
|
@ -3368,17 +3368,24 @@ describe('decorations: inline virtual text', function()
|
|||||||
command("set nowrap")
|
command("set nowrap")
|
||||||
api.nvim_buf_set_extmark(0, ns, 0, 2, { virt_text = { { string.rep('X', 55), 'Special' } }, virt_text_pos = 'inline' })
|
api.nvim_buf_set_extmark(0, ns, 0, 2, { virt_text = { { string.rep('X', 55), 'Special' } }, virt_text_pos = 'inline' })
|
||||||
feed('$')
|
feed('$')
|
||||||
screen:expect{grid=[[
|
screen:expect([[
|
||||||
{10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}cdefgh^i|
|
{10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}cdefgh^i|
|
||||||
{1:~ }|
|
{1:~ }|
|
||||||
|
|
|
|
||||||
]]}
|
]])
|
||||||
|
command('set list listchars+=precedes:!')
|
||||||
|
screen:expect([[
|
||||||
|
{1:!}{10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}cdefgh^i|
|
||||||
|
{1:~ }|
|
||||||
|
|
|
||||||
|
]])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('draws correctly with no wrap and multibyte virtual text', function()
|
it('draws correctly with no wrap and multibyte virtual text', function()
|
||||||
insert('12345678')
|
insert('12345678')
|
||||||
command('set nowrap')
|
command('set nowrap')
|
||||||
api.nvim_buf_set_extmark(0, ns, 0, 2, {
|
api.nvim_buf_set_extmark(0, ns, 0, 2, {
|
||||||
|
hl_mode = 'replace',
|
||||||
virt_text = { { 'α口β̳γ̲=', 'Special' }, { '❤️', 'Special' } },
|
virt_text = { { 'α口β̳γ̲=', 'Special' }, { '❤️', 'Special' } },
|
||||||
virt_text_pos = 'inline',
|
virt_text_pos = 'inline',
|
||||||
})
|
})
|
||||||
@ -3405,9 +3412,33 @@ describe('decorations: inline virtual text', function()
|
|||||||
{1:~ }|
|
{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')
|
feed('zl')
|
||||||
screen:expect([[
|
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:~ }|
|
{1:~ }|
|
||||||
|
|
|
|
||||||
]])
|
]])
|
||||||
@ -3435,9 +3466,21 @@ describe('decorations: inline virtual text', function()
|
|||||||
{1:~ }|
|
{1:~ }|
|
||||||
|
|
|
|
||||||
]])
|
]])
|
||||||
|
command('set list')
|
||||||
|
screen:expect([[
|
||||||
|
{1:!<}34567^8 |
|
||||||
|
{1:~ }|
|
||||||
|
|
|
||||||
|
]])
|
||||||
feed('zl')
|
feed('zl')
|
||||||
screen:expect([[
|
screen:expect([[
|
||||||
{10: }34567^8 |
|
{1:!}34567^8 |
|
||||||
|
{1:~ }|
|
||||||
|
|
|
||||||
|
]])
|
||||||
|
command('set nolist')
|
||||||
|
screen:expect([[
|
||||||
|
{1:<}34567^8 |
|
||||||
{1:~ }|
|
{1:~ }|
|
||||||
|
|
|
|
||||||
]])
|
]])
|
||||||
|
Reference in New Issue
Block a user