commit 064ff74cdbe6699b3bdcca9c9040aaa8154a4f1d
parent 5111d66ac9bc9d69067613b610de4cecb475fd53
Author: luukvbaal <luukvbaal@gmail.com>
Date: Fri, 11 Apr 2025 13:36:49 +0200
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>
Diffstat:
2 files changed, 96 insertions(+), 28 deletions(-)
diff --git a/src/nvim/change.c b/src/nvim/change.c
@@ -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;
@@ -412,24 +414,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()
@@ -437,7 +421,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
@@ -458,7 +442,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);
}
}
}
@@ -523,6 +507,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);
diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua
@@ -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) })