fix(marks): wrong display after inserting/deleting lines #33389

Problem:  Lines to/from which virt_lines or inline virt_text may have
          moved are left valid. Similarly the modified region may be
          too small to account for moved decorations after inserting
          or deleting lines. `redrawOneLine()` can be replaced with
          a call to `changed_lines_redraw_buf()`.

Solution: Invalidate the line after a change if there is virt_lines, or
          inline virt_text in the buffer with 'wrap' enabled. Extend the
          modified region for inserted or deleted lines if there may be
          decorations in the buffer. Remove `redrawOneLine()`.
          Simplify the logic for `changed_lines_invalidate_win()`.

Co-authored-by: zeertzjq <zeertzjq@outlook.com>
(cherry picked from commit 064ff74cdb)
This commit is contained in:
luukvbaal
2025-04-11 13:36:49 +02:00
committed by github-actions[bot]
parent b5158e8e92
commit 9da90af0f7
2 changed files with 96 additions and 28 deletions

View File

@ -186,6 +186,14 @@ static void changed_lines_invalidate_win(win_T *wp, linenr_T lnum, colnr_T col,
approximate_botline_win(wp);
}
// If lines have been inserted/deleted and the buffer has virt_lines, or
// inline virt_text with 'wrap' enabled, invalidate the line after the changed
// lines. virt_lines may now be drawn above that line, and inline virt_text
// may cause that line to wrap.
if ((xtra < 0 && wp->w_p_wrap && buf_meta_total(wp->w_buffer, kMTMetaInline))
|| (xtra != 0 && buf_meta_total(wp->w_buffer, kMTMetaLines))) {
lnume++;
}
// Check if any w_lines[] entries have become invalid.
// For entries below the change: Correct the lnums for inserted/deleted lines.
// Makes it possible to stop displaying after the change.
@ -194,12 +202,7 @@ static void changed_lines_invalidate_win(win_T *wp, linenr_T lnum, colnr_T col,
if (wp->w_lines[i].wl_lnum >= lnum) {
// Do not change wl_lnum at index zero, it is used to compare with w_topline.
// Invalidate it instead.
// If lines haven been inserted/deleted and the buffer has virt_lines,
// invalidate the line after the changed lines as some virt_lines may
// now be drawn above a different line.
if (i == 0 || wp->w_lines[i].wl_lnum < lnume
|| (xtra != 0 && wp->w_lines[i].wl_lnum == lnume
&& buf_meta_total(wp->w_buffer, kMTMetaLines) > 0)) {
if (i == 0 || wp->w_lines[i].wl_lnum < lnume) {
// line included in change
wp->w_lines[i].wl_valid = false;
} else if (xtra != 0) {
@ -208,8 +211,7 @@ static void changed_lines_invalidate_win(win_T *wp, linenr_T lnum, colnr_T col,
wp->w_lines[i].wl_foldend += xtra;
wp->w_lines[i].wl_lastlnum += xtra;
}
} else if (wp->w_lines[i].wl_foldend >= lnum
|| wp->w_lines[i].wl_lastlnum >= lnum) {
} else if (wp->w_lines[i].wl_lastlnum >= lnum) {
// change somewhere inside this range of folded or concealed lines,
// may need to be redrawn
wp->w_lines[i].wl_valid = false;
@ -411,24 +413,6 @@ static void changed_common(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnum
}
}
static void changedOneline(buf_T *buf, linenr_T lnum)
{
if (buf->b_mod_set) {
// find the maximum area that must be redisplayed
if (lnum < buf->b_mod_top) {
buf->b_mod_top = lnum;
} else if (lnum >= buf->b_mod_bot) {
buf->b_mod_bot = lnum + 1;
}
} else {
// set the area that must be redisplayed to one line
buf->b_mod_set = true;
buf->b_mod_top = lnum;
buf->b_mod_bot = lnum + 1;
buf->b_mod_xlines = 0;
}
}
/// Changed bytes within a single line for the current buffer.
/// - marks the windows on this buffer to be redisplayed
/// - marks the buffer changed by calling changed()
@ -436,7 +420,7 @@ static void changedOneline(buf_T *buf, linenr_T lnum)
/// Careful: may trigger autocommands that reload the buffer.
void changed_bytes(linenr_T lnum, colnr_T col)
{
changedOneline(curbuf, lnum);
changed_lines_redraw_buf(curbuf, lnum, lnum + 1, 0);
changed_common(curbuf, lnum, col, lnum + 1, 0);
// When text has been changed at the end of the line, possibly the start of
// the next line may have SpellCap that should be removed or it needs to be
@ -457,7 +441,7 @@ void changed_bytes(linenr_T lnum, colnr_T col)
redraw_later(wp, UPD_VALID);
linenr_T wlnum = diff_lnum_win(lnum, wp);
if (wlnum > 0) {
changedOneline(wp->w_buffer, wlnum);
changed_lines_redraw_buf(wp->w_buffer, wlnum, wlnum + 1, 0);
}
}
}
@ -522,6 +506,14 @@ void deleted_lines_mark(linenr_T lnum, int count)
/// @param xtra number of extra lines (negative when deleting)
void changed_lines_redraw_buf(buf_T *buf, linenr_T lnum, linenr_T lnume, linenr_T xtra)
{
// If lines have been deleted and there may be decorations in the buffer, ensure
// win_update() calculates the height of, and redraws the line to which or whence
// from its mark may have moved. When lines are deleted, a virt_line mark may
// have moved be drawn two lines below so increase by one more.
if (xtra != 0 && buf->b_marktree->n_keys > 0) {
lnume += 1 + (xtra < 0 && buf_meta_total(buf, kMTMetaLines));
}
if (buf->b_mod_set) {
// find the maximum area that must be redisplayed
buf->b_mod_top = MIN(buf->b_mod_top, lnum);

View File

@ -3035,6 +3035,21 @@ describe('extmark decorations', function()
feed('<C-E><C-Y>')
eq(5, n.fn.line('w0'))
end)
it('redraws the line from which a left gravity mark has moved #27369', function()
fn.setline(1, {'aaa', 'bbb', 'ccc', 'ddd' })
api.nvim_buf_set_extmark(0, ns, 1, 0, { virt_text = {{'foo'}}, right_gravity = false })
feed('yyp')
screen:expect([[
aaa |
^aaa foo |
bbb |
ccc |
ddd |
{1:~ }|*9
|
]])
end)
end)
describe('decorations: inline virtual text', function()
@ -4760,6 +4775,67 @@ describe('decorations: inline virtual text', function()
]])
end)
it('is redrawn correctly after delete or redo #27370', function()
screen:try_resize(50, 12)
exec([[
call setline(1, ['aaa', 'bbb', 'ccc', 'ddd', 'eee', 'fff'])
call setline(3, repeat('c', winwidth(0) - 1))
]])
api.nvim_buf_set_extmark(0, ns, 1, 0, { virt_text = { { '!!!' } }, virt_text_pos = 'inline' })
feed('j')
local before_delete = [[
aaa |
!!!^bbb |
ccccccccccccccccccccccccccccccccccccccccccccccccc |
ddd |
eee |
fff |
{1:~ }|*5
|
]]
screen:expect(before_delete)
feed('dd')
local after_delete = [[
aaa |
!!!^ccccccccccccccccccccccccccccccccccccccccccccccc|
cc |
ddd |
eee |
fff |
{1:~ }|*5
|
]]
screen:expect(after_delete)
command('silent undo')
screen:expect(before_delete)
command('silent redo')
screen:expect(after_delete)
command('silent undo')
screen:expect(before_delete)
command('set report=100')
feed('yypk2P')
before_delete = [[
aaa |
^bbb |
bbb |
!!!bbb |
bbb |
ccccccccccccccccccccccccccccccccccccccccccccccccc |
ddd |
eee |
fff |
{1:~ }|*2
|
]]
screen:expect(before_delete)
feed('4dd')
screen:expect(after_delete)
command('silent undo')
screen:expect(before_delete)
command('silent redo')
screen:expect(after_delete)
end)
it('cursor position is correct with invalidated inline virt text', function()
screen:try_resize(50, 8)
api.nvim_buf_set_lines(0, 0, -1, false, { ('a'):rep(48), ('b'):rep(48) })