Engine: a rolled-back group takes what its script runs ran out of the recording too

Taking back the steps a rolled-back group recorded left what a taken-back script or macro run
ran (`Recorder::ran`) under its old index, where a later run could pick it up, and left it out of
the size count. `cut_back` now drops it with its step, moves a kept run's entry to the step's new
index and counts both again.

- Test: `a_rolled_back_script_run_takes_what_it_ran_out_of_the_recording`.
This commit is contained in:
prxnvt
2026-10-25 06:29:23 -04:00
parent 0a9305f784
commit ca2be34e8a
2 changed files with 39 additions and 8 deletions
+19 -8
View File
@@ -112,15 +112,26 @@ impl Recorder {
/// Takes back the steps recorded in the document with uid `doc` since `mark` (an undo group
/// on it was rolled back, so playing them back would make edits it no longer has). Steps in
/// other documents stay: their edits do too. Steps the buffer dropped stay counted.
/// What a step ran ([`Recorder::ran`]) goes with its step, and stays with a kept one under its
/// new index.
pub(crate) fn cut_back(&mut self, (len, _): (usize, usize), doc: u64) {
let before = self.steps.len();
let mut i = 0;
self.steps.retain(|s| {
i += 1;
i <= len || s.doc != doc
});
if self.steps.len() != before {
self.bytes = self.steps.iter().map(|s| serde_json::to_vec(s).map_or(0, |v| v.len())).sum();
if !self.steps.iter().skip(len).any(|s| s.doc == doc) {
return;
}
let mut ran = std::mem::take(&mut self.ran);
let mut steps = Vec::with_capacity(self.steps.len());
for (i, step) in std::mem::take(&mut self.steps).into_iter().enumerate() {
if i >= len && step.doc == doc {
continue;
}
if let Some(r) = ran.remove(&i) {
self.ran.insert(steps.len(), r);
}
steps.push(step);
}
self.steps = steps;
let size = |v: Result<Vec<u8>, serde_json::Error>| v.map_or(0, |v| v.len());
self.bytes = self.steps.iter().map(|s| size(serde_json::to_vec(s))).sum::<usize>()
+ self.ran.values().map(|r| size(serde_json::to_vec(r))).sum::<usize>();
}
}
+20
View File
@@ -2164,6 +2164,26 @@ fn a_script_step_that_only_opened_a_dialog_is_left_out_of_the_recording() {
assert!(recorded(&mut s, "Without").is_empty());
}
#[test]
fn a_rolled_back_script_run_takes_what_it_ran_out_of_the_recording() {
// What a recorded script run ran goes with its step when a group rollback takes the step
// back, and a kept run keeps what it ran under its new place.
let mut s = s();
s.execute("automate.newScript", json!({"name": "Mark", "commands": [{"command": "cell.set", "params": {"cell": "A1", "input": "m"}}]})).unwrap();
s.execute("macro.record", json!({"relative": false})).unwrap();
s.execute("automate.runScript", json!({"name": "Mark"})).unwrap();
let _left = s.group_begin("Left open").unwrap();
set(&mut s, "B1", "gone");
s.execute("automate.runScript", json!({"name": "Mark"})).unwrap();
s.execute("automate.stopAction", json!({})).unwrap();
let rec = s.recorder.as_ref().unwrap();
assert_eq!((rec.steps.len(), rec.ran.keys().copied().collect::<Vec<_>>()), (1, vec![0]));
s.execute("automate.runScript", json!({"name": "Mark"})).unwrap();
s.execute("macro.record", json!({"stop": true, "name": "Rec"})).unwrap();
let a1 = "cell.set {\"cell\":\"A1\",\"input\":\"m\"}";
assert_eq!(recorded(&mut s, "Rec"), [a1, a1]);
}
#[test]
fn a_script_run_records_the_state_its_toggles_set() {
let mut s = s();