mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-30 12:47:13 -06:00
Undo: a canceled undo scope drops the journal entries it cut from open undo groups
When an undo scope that joined an open undo group was canceled, the journal was cut back to where the scope began, but the group still listed the cut entries' numbers. The next command took one of those numbers, so a later cancel of the group also removed that command's entry, even when it ran in another document, and a replay of the journal no longer matched the documents. end_undo_scope now removes the cut numbers from every open undo group's journal entries and batch steps.
This commit is contained in:
@@ -2520,7 +2520,7 @@ impl Session {
|
||||
/// closes it. A scope that joined an open group leaves the group open; with `cancel`, it puts the
|
||||
/// document and its selection back as they were when the scope began and cuts the journal back to
|
||||
/// where the scope began (only while the group's step is still the newest one), and an undo step
|
||||
/// that the scope's first edit recorded goes too.
|
||||
/// that the scope's first edit recorded goes too. The open undo groups drop the cut entries.
|
||||
pub fn end_undo_scope(&mut self, scope: UndoScope, cancel: bool) {
|
||||
let Some((doc, selection, first)) = scope.joined else {
|
||||
self.end_document_undo_group(scope.uid, cancel);
|
||||
@@ -2549,6 +2549,11 @@ impl Session {
|
||||
st.history.recost_last(&st.doc);
|
||||
st.revision += 1;
|
||||
self.journal.truncate(scope.journal);
|
||||
// The next commands take the cut entries' numbers, so no open undo group keeps them.
|
||||
for g in self.docs.iter_mut().filter_map(|st| st.undo_group.as_mut()) {
|
||||
g.journal.retain(|&(n, _)| n < scope.journal);
|
||||
g.steps.retain(|&(n, _)| n < scope.journal);
|
||||
}
|
||||
}
|
||||
|
||||
/// Commands with enablement (for menus, palette, MCP `list_commands`).
|
||||
|
||||
@@ -780,6 +780,34 @@ fn an_undo_scope_in_an_open_group_takes_back_only_its_own_edits() {
|
||||
assert_eq!(objects(&s), 0, "one undo takes back the group's edits");
|
||||
}
|
||||
|
||||
/// A canceled undo scope that joined an open group forgets the journal entries it cut: when a command
|
||||
/// in another document takes the same entry number, a later cancel of the group keeps that entry, and
|
||||
/// the journal replays to the same documents.
|
||||
#[test]
|
||||
fn a_canceled_undo_scope_leaves_its_cut_entries_out_of_the_group() {
|
||||
let mut s = Session::new();
|
||||
let rect = json!({"x": 10, "y": 10, "width": 100, "height": 50});
|
||||
s.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
|
||||
s.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
|
||||
s.execute("document.activate", &json!({"index": 0})).unwrap();
|
||||
s.begin_undo_group();
|
||||
s.execute("shape.rectangle", &rect).unwrap();
|
||||
let scope = s.begin_undo_scope().unwrap();
|
||||
s.execute("shape.rectangle", &rect).unwrap();
|
||||
s.end_undo_scope(scope, true);
|
||||
let journal = s.journal.len();
|
||||
// The switch takes the entry number the scope's edit had, and the rectangle in the second document follows it.
|
||||
s.execute("document.activate", &json!({"index": 1})).unwrap();
|
||||
s.execute("shape.rectangle", &rect).unwrap();
|
||||
s.end_undo_group(true);
|
||||
let objects = s.documents().iter().map(|d| d.doc.layers[0].children().map_or(0, |c| c.len())).collect::<Vec<_>>();
|
||||
assert_eq!(objects, [0, 1]);
|
||||
assert_eq!(s.journal.len(), journal + 1, "only the group's own edit leaves the journal");
|
||||
let r = replayed(&s);
|
||||
assert_eq!(documents(&r), documents(&s));
|
||||
assert_eq!(r.active_index(), s.active_index());
|
||||
}
|
||||
|
||||
/// A move drag is named for what its last preview did: Alt pressed or released during the drag
|
||||
/// turns it into a copy or back (the Selection and Direct Selection tools).
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user