'inccommand': Preserve curbuf->b_u_newhead.

Add tests for undotree().

Helped-by: Björn Linse <bjorn.linse@gmail.com>

When "curhead" points to a valid head, the value of "newhead" is
meaningless (and really should be set to null). The undo state for
a buffer is _logically_ the enum:

  enum UndoState {
    CurrentHead(head),
    NewHead(head),
    EmptyTree
  }

nvim _represents_ this as: whenever `curbuf->b_u_curhead` is nonnull it
should be used as the current head, and `curbuf->b_u_newhead` is
ignored. If the there is a current head, then this will be redoed on the
next redo, and its parent will be undone on next undo. Only if
`b_u_curhead` is NULL, `b_u_newhead` will be used as the head to undo
(and it is not possible to redo). Also both can be NULL, to indicate an
empty undotree. (To be fair, this only strictly true when calling undo.c
from the outside, in some places _within_ a function in undo.c both
values might be meaningful)

Apparently `undotree()` breaks this non-abstraction, this _cosmetic_
issue can easily be fixed by `ex_substitute` also saving and restoring
`b_u_newhead`, but is doesn't reflect any error really how
`u_undo_and_forget` manipulates the _actual_ state of the undo tree.
This commit is contained in:
Justin M. Keyes 2016-11-07 22:48:39 +01:00
parent 21dfbfbb9a
commit aa0e09d251
2 changed files with 74 additions and 19 deletions

View File

@ -6140,6 +6140,8 @@ void ex_substitute(exarg_T *eap)
char_u *save_eap = eap->arg;
save_search_patterns();
int save_changedtick = curbuf->b_changedtick;
time_t save_b_u_time_cur = curbuf->b_u_time_cur;
u_header_T *save_b_u_newhead = curbuf->b_u_newhead;
long save_b_p_ul = curbuf->b_p_ul;
curbuf->b_p_ul = LONG_MAX; // make sure we can undo all changes
block_autocmds(); // disable events before show_sub() opens window/buffer
@ -6147,15 +6149,18 @@ void ex_substitute(exarg_T *eap)
buf_T *preview_buf = do_sub(eap);
if (save_changedtick != curbuf->b_changedtick
&& !u_undo_and_forget(1)) {
abort();
if (save_changedtick != curbuf->b_changedtick) {
if (!u_undo_and_forget(1)) { abort(); }
// Restore newhead. It is meaningless when curhead is valid, but we must
// restore it so that undotree() is identical before/after the preview.
curbuf->b_u_newhead = save_b_u_newhead;
curbuf->b_u_time_cur = save_b_u_time_cur;
curbuf->b_changedtick = save_changedtick;
}
if (buf_valid(preview_buf)) {
// XXX: Must do this *after* u_undo_and_forget(), why?
close_windows(preview_buf, false);
}
curbuf->b_changedtick = save_changedtick;
curbuf->b_p_ul = save_b_p_ul;
eap->arg = save_eap;
restore_search_patterns();

View File

@ -107,9 +107,8 @@ describe(":substitute, 'inccommand' preserves", function()
]])
end)
it(':substitute with various delimiters', function()
for _, case in pairs{"", "split", "nosplit"} do
clear()
for _, case in pairs{"", "split", "nosplit"} do
it("various delimiters (inccommand="..case..")", function()
insert(default_text)
execute("set inccommand=" .. case)
@ -122,12 +121,11 @@ describe(":substitute, 'inccommand' preserves", function()
]])
execute("undo")
end
end
end)
end)
end
it("'undolevels'", function()
for _, case in pairs{"", "split", "nosplit"} do
clear()
for _, case in pairs{"", "split", "nosplit"} do
it("'undolevels' (inccommand="..case..")", function()
execute("set undolevels=139")
execute("setlocal undolevels=34")
execute("set inccommand=" .. case)
@ -135,12 +133,64 @@ describe(":substitute, 'inccommand' preserves", function()
feed(":%s/as/glork/<enter>")
eq(meths.get_option('undolevels'), 139)
eq(curbufmeths.get_option('undolevels'), 34)
end
end)
end)
end
it("b:changedtick", function()
for _, case in pairs{"", "split", "nosplit"} do
clear()
for _, case in ipairs({"", "split", "nosplit"}) do
it("empty undotree() (inccommand="..case..")", function()
execute("set undolevels=1000")
execute("set inccommand=" .. case)
local expected_undotree = eval("undotree()")
-- Start typing an incomplete :substitute command.
feed([[:%s/e/YYYY/g]])
wait()
-- Cancel the :substitute.
feed([[<C-\><C-N>]])
-- The undo tree should be unchanged.
eq(expected_undotree, eval("undotree()"))
eq({}, eval("undotree()")["entries"])
end)
end
for _, case in ipairs({"", "split", "nosplit"}) do
it("undotree() with branches (inccommand="..case..")", function()
execute("set undolevels=1000")
execute("set inccommand=" .. case)
-- Make some changes.
feed([[isome text 1<C-\><C-N>]])
feed([[osome text 2<C-\><C-N>]])
-- Add an undo branch.
feed([[u]])
-- More changes, more undo branches.
feed([[osome text 3<C-\><C-N>]])
feed([[AX<C-\><C-N>]])
feed([[...]])
feed([[uu]])
feed([[osome text 4<C-\><C-N>]])
feed([[u<C-R>u]])
feed([[osome text 5<C-\><C-N>]])
expect([[
some text 1
some text 3XX
some text 5]])
local expected_undotree = eval("undotree()")
eq(5, #expected_undotree["entries"]) -- sanity
-- Start typing an incomplete :substitute command.
feed([[:%s/e/YYYY/g]])
wait()
-- Cancel the :substitute.
feed([[<C-\><C-N>]])
-- The undo tree should be unchanged.
eq(expected_undotree, eval("undotree()"))
end)
end
for _, case in pairs{"", "split", "nosplit"} do
it("b:changedtick (inccommand="..case..")", function()
execute("set inccommand=" .. case)
feed([[isome text 1<C-\><C-N>]])
feed([[osome text 2<C-\><C-N>]])
@ -154,8 +204,8 @@ describe(":substitute, 'inccommand' preserves", function()
wait()
eq(expected_tick, eval("b:changedtick"))
end
end)
end)
end
end)