mirror of
https://github.com/neovim/neovim.git
synced 2026-08-14 03:04:54 +00:00
fix(extmarks): undo-redo of a mark explicitly moved during an edit #41252
Problem:
A mark moved by nvim_buf_set_extmark() during an edit is misplaced by
undo and redo. Only splices ("edits") are recorded, and replaying them
reproduces the shifts they caused, never the explicit set: the mark ends
up wherever the text pushed it.
Solution:
When an open undo block moves an existing mark, record both positions.
Undo restores the pre-set position, redo re-applies the set.
Partially reverts 18334a4a0c ; ExtmarkSavePos.row/col were unused
because nothing recorded an explicit move, but now `extmark_set()` does.
This commit is contained in:
@@ -64,6 +64,25 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col
|
||||
MarkTreeIter itr[1] = { 0 };
|
||||
MTKey old_mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr);
|
||||
if (old_mark.id) {
|
||||
// Moved inside an open undo block: record BOTH positions, because replaying the surrounding
|
||||
// splices reproduces only the shifts they caused, not an explicit extmark_set().
|
||||
if (!no_undo && !buf->b_u_synced
|
||||
&& (old_mark.pos.row != row || old_mark.pos.col != col)) {
|
||||
u_header_T *uhp = u_force_get_undo_header(buf);
|
||||
if (uhp) {
|
||||
kv_push(uhp->uh_extmark, ((ExtmarkUndoObject){
|
||||
.type = kExtmarkSavePos,
|
||||
.data.savepos = {
|
||||
.mark = mt_lookup_key(old_mark),
|
||||
.undo_row = old_mark.pos.row,
|
||||
.undo_col = old_mark.pos.col,
|
||||
.redo_row = row,
|
||||
.redo_col = col,
|
||||
.invalidated = false,
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
if (mt_paired(old_mark) || end_row > -1) {
|
||||
extmark_del_id(buf, ns_id, id);
|
||||
} else {
|
||||
@@ -419,8 +438,10 @@ void extmark_splice_delete(buf_T *buf, int l_row, colnr_T l_col, int u_row, coln
|
||||
ExtmarkSavePos pos = {
|
||||
.mark = mt_lookup_key(mark),
|
||||
.invalidated = invalidated,
|
||||
.old_row = mark.pos.row,
|
||||
.old_col = mark.pos.col
|
||||
.undo_row = mark.pos.row,
|
||||
.undo_col = mark.pos.col,
|
||||
.redo_row = -1,
|
||||
.redo_col = -1,
|
||||
};
|
||||
undo.data.savepos = pos;
|
||||
undo.type = kExtmarkSavePos;
|
||||
@@ -454,10 +475,12 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo)
|
||||
// kExtmarkSavePos
|
||||
} else if (undo_info.type == kExtmarkSavePos) {
|
||||
ExtmarkSavePos pos = undo_info.data.savepos;
|
||||
if (undo && pos.old_row >= 0) {
|
||||
extmark_setraw(curbuf, pos.mark, pos.old_row, pos.old_col, pos.invalidated);
|
||||
if (undo && pos.undo_row >= 0) {
|
||||
extmark_setraw(curbuf, pos.mark, pos.undo_row, pos.undo_col, pos.invalidated);
|
||||
} else if (!undo && pos.redo_row >= 0) {
|
||||
// Redo an explicit extmark_set (kExtmarkSplice only redo's the shifts).
|
||||
extmark_setraw(curbuf, pos.mark, pos.redo_row, pos.redo_col, false);
|
||||
}
|
||||
// No Redo since kExtmarkSplice will move marks back
|
||||
} else if (undo_info.type == kExtmarkMove) {
|
||||
ExtmarkMove move = undo_info.data.move;
|
||||
if (undo) {
|
||||
|
||||
@@ -43,8 +43,10 @@ typedef struct {
|
||||
// extmark was updated
|
||||
typedef struct {
|
||||
uint64_t mark; // raw mark id of the marktree
|
||||
int old_row;
|
||||
colnr_T old_col;
|
||||
int undo_row;
|
||||
colnr_T undo_col;
|
||||
int redo_row; // -1: nothing to redo (the mark was not explicitly moved).
|
||||
colnr_T redo_col;
|
||||
bool invalidated;
|
||||
} ExtmarkSavePos;
|
||||
|
||||
|
||||
@@ -983,6 +983,25 @@ describe('API/extmarks', function()
|
||||
eq(2, #rv)
|
||||
end)
|
||||
|
||||
it('undo and redo of a mark explicitly moved during an edit', function()
|
||||
feed('ggdGiabcdef<esc>')
|
||||
set_extmark(ns, marks[1], 0, 4)
|
||||
-- Insert at col 0; while the undo block is still open, explicitly
|
||||
-- move the mark.
|
||||
feed('0i!!')
|
||||
set_extmark(ns, marks[1], 0, 1)
|
||||
feed('<esc>')
|
||||
eq({ 0, 1 }, get_extmark_by_id(ns, marks[1]))
|
||||
feed('u')
|
||||
-- Back where it was before the edit (the recorded set-time position,
|
||||
-- shifted back by the splice reversal).
|
||||
eq({ 0, 4 }, get_extmark_by_id(ns, marks[1]))
|
||||
feed('<c-r>')
|
||||
-- Redo restores the explicit position: splice adjustment alone would
|
||||
-- leave the mark at col 6.
|
||||
eq({ 0, 1 }, get_extmark_by_id(ns, marks[1]))
|
||||
end)
|
||||
|
||||
it('undo and redo of marks deleted during edits', function()
|
||||
-- test extmark_adjust
|
||||
feed('A<cr>12345<esc>')
|
||||
|
||||
Reference in New Issue
Block a user