mirror of
https://github.com/neovim/neovim.git
synced 2026-08-28 01:51:52 +00:00
fix(marks): undo reverts a mark set after the change #41330
Problem:
A named mark updated after a change is moved back (treated as the
original mark) by undo:
:1mark d
:$
dw
:2mark d " 'd is on line 2
:undo " 'd is back on line 1
The undo header snapshots `b_namedm` when the change is recorded, and
`u_undoredo()` restores that snapshot indiscriminately.
Solution:
Update the pending header's snapshot when a mark is set explicitly.
Marks that the change itself moved go through mark_adjust(), not
setmark_pos(), so those are still reverted.
Similar to 2546741d1b (for extmarks): an explicit set inside an undo
block is confused with an edit-driven adjustment. But the extmarks case
is dealing with mid-edit moves, whereas named/regular marks only need
the stale snapshot dropped.
This commit is contained in:
@@ -514,6 +514,9 @@ These existing features changed their behavior.
|
||||
`opts.plain=true` and now does not expand leading tildes ("~") in addition
|
||||
to environment variables ("expand_env" is still accepted, for backwards
|
||||
compatibility).
|
||||
• |undo| no longer restores the old (wrong) position of a mark that you moved
|
||||
(|m|, |:mark|) later. The mark shifts with the text it was moved to, the
|
||||
same as a mark the change never touched.
|
||||
|
||||
==============================================================================
|
||||
REMOVED FEATURES *news-removed*
|
||||
|
||||
@@ -682,6 +682,12 @@ Mappings:
|
||||
- "#" followed by a digit doesn't stand for a function key at the start of the
|
||||
lhs of a mapping.
|
||||
|
||||
Marks:
|
||||
- |undo| does not restore a mark that you moved (|m|, |:mark|) after the change
|
||||
being undone; the mark shifts with the text it was moved to, the same as a
|
||||
mark the change never touched. Vim restores its old (wrong) position. Marks
|
||||
moved by the change itself are still restored.
|
||||
|
||||
Motion:
|
||||
- The |jumplist| avoids useless/phantom jumps.
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
#include "nvim/tag.h"
|
||||
#include "nvim/textobject.h"
|
||||
#include "nvim/types_defs.h"
|
||||
#include "nvim/undo.h"
|
||||
#include "nvim/vim_defs.h"
|
||||
|
||||
// This file contains routines to maintain and manipulate marks.
|
||||
@@ -184,6 +185,8 @@ int setmark_pos(int c, pos_T *pos, int fnum, fmarkv_T *view_pt)
|
||||
if (ASCII_ISLOWER(c)) {
|
||||
i = c - 'a';
|
||||
RESET_FMARK(buf->b_namedm + i, *pos, fnum, view);
|
||||
// Moving a mark is not part of the pending change, so undo must not revert it. #5754
|
||||
u_update_named_mark(buf, i);
|
||||
do_markset_autocmd((char)c, pos, buf);
|
||||
return OK;
|
||||
}
|
||||
|
||||
@@ -3235,6 +3235,19 @@ void f_undotree(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(buf, buf->b_u_oldhead));
|
||||
}
|
||||
|
||||
/// Drops named mark `idx` from the pending undo snapshot (if the user moved that mark after the
|
||||
/// change was recorded, undo must not put it back).
|
||||
///
|
||||
/// The mark is then left to mark_adjust(), so undo shifts it with its text, like other marks not
|
||||
/// touched by the change.
|
||||
void u_update_named_mark(buf_T *buf, int idx)
|
||||
{
|
||||
u_header_T *uhp = buf->b_u_curhead != NULL ? buf->b_u_curhead : buf->b_u_newhead;
|
||||
if (uhp != NULL) {
|
||||
uhp->uh_namedm[idx].mark.lnum = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Given the buffer, Return the undo header. If none is set, set one first.
|
||||
// NULL will be returned if e.g undolevels = -1 (undo disabled)
|
||||
u_header_T *u_force_get_undo_header(buf_T *buf)
|
||||
|
||||
@@ -41,6 +41,35 @@ describe('named marks', function()
|
||||
eq({ 4, 0 }, api.nvim_buf_get_mark(0, 'c'))
|
||||
end)
|
||||
|
||||
it('moved after a change follow their text through undo #5754', function()
|
||||
command('edit ' .. file1)
|
||||
command('1mark d')
|
||||
command('$')
|
||||
feed('dw')
|
||||
command('2mark d')
|
||||
eq({ 2, 0 }, api.nvim_buf_get_mark(0, 'd'))
|
||||
command('undo')
|
||||
eq({ 2, 0 }, api.nvim_buf_get_mark(0, 'd'))
|
||||
command('redo')
|
||||
eq({ 2, 0 }, api.nvim_buf_get_mark(0, 'd'))
|
||||
|
||||
-- A mark on a line the change deletes is cleared by the change; undo restores it.
|
||||
api.nvim_buf_set_mark(0, 'e', 2, 2, {})
|
||||
command('2delete')
|
||||
eq({ 0, 2 }, api.nvim_buf_get_mark(0, 'e'))
|
||||
command('undo')
|
||||
eq({ 2, 2 }, api.nvim_buf_get_mark(0, 'e'))
|
||||
eq('1test2', fn.getline(2))
|
||||
|
||||
-- A mark moved after a line-count change shifts with the text it was moved to.
|
||||
command('2delete')
|
||||
eq('1test3', fn.getline(2))
|
||||
command('2mark f')
|
||||
command('undo')
|
||||
eq({ 3, 0 }, api.nvim_buf_get_mark(0, 'f'))
|
||||
eq('1test3', fn.getline(3))
|
||||
end)
|
||||
|
||||
it('errors when set out of range with :mark', function()
|
||||
command('edit ' .. file1)
|
||||
local err = pcall_err(n.exec_capture, '1000mark x')
|
||||
|
||||
Reference in New Issue
Block a user