mirror of
https://github.com/neovim/neovim.git
synced 2026-09-13 01:21:06 +00:00
fix(multicursor): undo corrupted by insert that changes line-count #41835
Problem:
Undo "u" is wrong after cascading an insert that adds a line (`A{<CR>`, `o`, …).
The cascade replays the insert as a per-cursor `edit()`, where `u_save`
stores a line multiple times into the undo block at different line
counts; then `getbot` computes conflicting entries that undo each other.
Solution:
`u_save` the whole affected line-range as one entry at session start,
the same approach used by Vim for linewise ops (`op_shift`, `ex_sort`).
The "enclosing" `u_save` wins and the invalid entries are overridden.
This commit is contained in:
@@ -601,6 +601,24 @@ void mc_ins_cascade_start(bool cascade, varnumber_T tick)
|
||||
mc_ins_span.tick = tick;
|
||||
mc_ins_span.region = 0;
|
||||
mc_ins_regions_clear();
|
||||
|
||||
if (mc_ins_span.active) {
|
||||
// Cover the line-range of all cursors with one undo-entry. #41822
|
||||
//
|
||||
// Undo entries apply in reverse save-order, so this entry (saved first) "wins", even though
|
||||
// per-cursor newline-shifting edits may record conflicting undo entries. Same approach is used
|
||||
// by Vim for linewise-op/range-command (see u_save in op_shift, ex_sort, …).
|
||||
linenr_T lo = curwin->w_cursor.lnum;
|
||||
linenr_T hi = curwin->w_cursor.lnum;
|
||||
for (size_t i = 0; i < kv_size(mc_cursors); i++) {
|
||||
pos_T pos;
|
||||
if (mc_ctx_resolve(&kv_A(mc_cursors, i), &pos)) {
|
||||
lo = MIN(lo, pos.lnum);
|
||||
hi = MAX(hi, pos.lnum);
|
||||
}
|
||||
}
|
||||
(void)u_save(lo - 1, hi + 1); // Ignore FAIL result: only relevant only if undo is unavailable.
|
||||
}
|
||||
}
|
||||
|
||||
/// True during a span replay. The replay's synthetic <Esc> does not end the primary insert-session,
|
||||
|
||||
@@ -2359,6 +2359,24 @@ describe('multicursor', function()
|
||||
eq(3, fn.line('.')) -- primary placement after undoing a live insert
|
||||
end)
|
||||
|
||||
it('one undo reverts a per-cursor newline-insert #41822', function()
|
||||
-- A newline shifts the line count between the per-cursor replays; single "u" restores it.
|
||||
cursors({ 'aa', 'bb', 'cc' }, 'Qjj')
|
||||
feed('A{<CR><Esc>') -- Append + newline at cursor (line 1) and primary (line 3).
|
||||
eq({ 'aa{', '', 'bb', 'cc{', '' }, get_lines())
|
||||
feed('u')
|
||||
eq({ 'aa', 'bb', 'cc' }, get_lines())
|
||||
feed('<C-r>')
|
||||
eq({ 'aa{', '', 'bb', 'cc{', '' }, get_lines())
|
||||
-- "o" opens the line before Insert starts: the range still covers it.
|
||||
clear_cursors()
|
||||
cursors({ 'aa', 'bb', 'cc' }, 'Qjj')
|
||||
feed('ox<Esc>')
|
||||
eq({ 'aa', 'x', 'bb', 'cc', 'x' }, get_lines())
|
||||
feed('u')
|
||||
eq({ 'aa', 'bb', 'cc' }, get_lines())
|
||||
end)
|
||||
|
||||
it('a mapped undo/redo (vim-repeat "nmap u") does not cascade', function()
|
||||
-- vim-repeat maps u/U/<C-R> to undo/redo wrappers. Such a mapping changes the buffer, but an
|
||||
-- undo/redo is buffer-global, not a per-cursor edit: it must NOT cascade, or every cursor
|
||||
|
||||
Reference in New Issue
Block a user