Fix #1697: a failed command.batch restores each document whole

The rollback undid the batch's interaction, which holds the art, the selection
and panel state only: a Save As step's new file, format and saved state stayed,
and an Undo step (which ends the interaction) left the art and the history
undone. The batch now keeps the documents as they were and puts them back,
and settles a drag or typing in progress first, as Undo does, instead of
running inside it and keeping its baseline as the batch's step. Files the
steps wrote stay written.
This commit is contained in:
bflatastic committed 2026-10-14 08:47:10 +07:00
1 parent 7aa00b41da
commit 355bb5c4f6
3 files changed
+61 -20

No files matched your search

+1 -1
View File
@@ -265,7 +265,7 @@ fn undo_group_end(s: &mut Session, p: &Value) -> Result<Value> {
/// under an interaction that would then preview on, or cancel to, the document from before (#1719).
/// A typing session also ends in the Type tool, whose caret and marked text (IME) must follow the
/// document.
fn settle_interaction(s: &mut Session) -> Result<()> {
pub(crate) fn settle_interaction(s: &mut Session) -> Result<()> {
let typing = typing_in_progress(s);
s.commit_interaction()?;
if typing {
+22 -19
View File
@@ -13,14 +13,22 @@ pub(super) fn batch(s: &mut Session, p: &Value) -> Result<Value> {
// What an error rolls back besides the documents: session-level state a step may change
// (paint defaults, drawing mode, clipboard, the Untitled-N count).
let saved = (s.paint.clone(), s.fill_active, s.draw_mode, s.draw_inside, s.clipboard.clone(), s.untitled_counter);
let (start, active): (Vec<u64>, _) = (s.docs.iter().map(|d| d.uid).collect(), s.active);
// A drag or typing still in progress is a step of its own, as before Undo: the batch's steps
// would otherwise run inside it and take its baseline as theirs (#1697).
if s.active().is_some() {
super::super::edit::settle_interaction(s)?;
}
// The documents as they are now, whole: what a failed step brings back. Their art, history,
// file, format and saved state share their data with the open ones until a step changes them.
let start = s.docs.clone();
let active = s.active;
s.batch_stash = Some(vec![]);
let r = run_steps(s, &label, artboard, &mut cmds);
let stash = s.batch_stash.take().unwrap_or_default();
let results = match r {
Ok(results) => results,
Err(e) => {
roll_back(s, &start, stash);
roll_back(s, start, &stash);
s.active = active;
(s.paint, s.fill_active, s.draw_mode, s.draw_inside, s.clipboard, s.untitled_counter) = saved;
s.pending_paint = None;
@@ -81,22 +89,17 @@ fn run_steps(s: &mut Session, label: &str, artboard: Option<u64>, cmds: &mut [Va
Ok(results)
}
/// The documents as they were before the batch (`start`: their uids in order): the ones it opened
/// close, the ones it closed or replaced (`stash`) come back, and each one's interaction is undone.
fn roll_back(s: &mut Session, start: &[u64], mut stash: Vec<DocState>) {
let mut open = std::mem::take(&mut s.docs);
for &uid in start {
// The newest revision the document reached, so the restored one redraws.
let newest = open.iter().chain(&stash).filter(|d| d.uid == uid).map(|d| d.revision).max().unwrap_or_default();
// Its state before the batch: the first one a step closed or replaced, else the open one.
let st = match stash.iter().position(|d| d.uid == uid) {
Some(i) => Some(stash.remove(i)),
None => open.iter().position(|d| d.uid == uid).map(|i| open.remove(i)),
};
if let Some(mut st) = st {
st.undo_interaction();
st.revision = newest.saturating_add(1);
s.docs.push(st);
}
/// The documents as they were before the batch (`start`), whatever the steps did to them: opened
/// ones close, closed or replaced ones (`stash`) come back, and each has its art, selection,
/// history, file, format and saved state again (#1697: undoing the batch's interaction alone left
/// a Save As step's new file and an Undo step's history in place). Files the steps wrote stay
/// written.
fn roll_back(s: &mut Session, start: Vec<DocState>, stash: &[DocState]) {
let open = std::mem::take(&mut s.docs);
s.docs = start;
for st in &mut s.docs {
// Past the newest revision the document reached, so the restored one redraws.
let newest = open.iter().chain(stash).filter(|d| d.uid == st.uid).map(|d| d.revision).max().unwrap_or(st.revision);
st.revision = newest.saturating_add(1);
}
}