Engine: stopping a recording waits while a macro in its workbook is being debugged

Stopping a recording saved the macro with a nested macro.save while a debugged run's undo group could be open on the same workbook. Stopping the run without keeping its changes then deleted the macro that macro.record had reported as saved. Stopping now fails with "Finish or stop the macro first" under macro.record before anything changes, and the recording goes on, as when a save fails for another reason, so stopping again after the run ends saves it.
This commit is contained in:
prxnvt committed 2026-10-22 02:26:33 -04:00
1 parent ea691f703c
commit bbf1bc441b
3 files changed
+37 -13

No files matched your search

+7 -2
View File
@@ -185,8 +185,9 @@ fn start_recording(s: &mut Session, relative: bool) -> Result<Json> {
}
/// Stops recording and saves what was recorded as a macro, in the workbook where recording
/// started. When saving fails (a bad name, a full workbook) the recording goes on, so stopping
/// again keeps what was recorded; when that workbook was closed, the recording ends unsaved.
/// started. When saving fails (a bad name, a full workbook, a macro being debugged there) the
/// recording goes on, so stopping again keeps what was recorded; when that workbook was closed,
/// the recording ends unsaved.
fn stop_recording(s: &mut Session, p: &Json) -> Result<Json> {
let Some(rec) = s.stop_recording() else { return Ok(json!({"recording": false})) };
let start = s.macro_recording.take();
@@ -209,6 +210,10 @@ fn stop_recording(s: &mut Session, p: &Json) -> Result<Json> {
/// Saves the recording into the workbook at index `at`.
fn save_recording(s: &mut Session, p: &Json, rec: &crate::record::Recorder, start: Option<RecordingStart>, at: usize) -> Result<Json> {
let d = s.docs.get(at).ok_or(EngineError::NoDocument)?;
// A macro being debugged there could still roll the saved macro back with its run.
if s.macro_debug.as_ref().is_some_and(|m| m.locks(d.uid)) {
return Err(EngineError::Disabled("macro.record".into(), crate::macros::debug::BUSY.into()));
}
let start = start.unwrap_or_else(|| RecordingStart {
doc: d.uid,
sheet: d.wb.active_sheet,
+19
View File
@@ -1163,3 +1163,22 @@ fn steps_past_the_macro_limit_are_counted_as_dropped() {
assert_eq!(r["steps"], gridcraft_model::MAX_MACRO_STEPS);
assert_eq!(r["dropped"], 8, "{r}");
}
#[test]
fn a_recording_waits_while_a_macro_in_its_workbook_is_debugged() {
let mut s = s();
stepper(&mut s);
s.execute("macro.record", json!({"relative": false})).unwrap();
set(&mut s, "Z1", "typed");
debug(&mut s, json!({"name": "Walk", "inputs": "B2:C5"}));
debug(&mut s, json!({"action": "step"}));
// Saved now, the macro would go into the run's undo group, and stopping the run without
// keeping its changes would delete it again.
let r = s.execute("macro.record", json!({"stop": true, "name": "Typed"}));
assert!(matches!(&r, Err(EngineError::Disabled(id, why)) if id == "macro.record" && why == crate::macros::debug::BUSY), "{r:?}");
assert!(s.is_recording());
debug(&mut s, json!({"action": "stop"}));
let r = s.execute("macro.record", json!({"stop": true, "name": "Typed"})).unwrap();
assert_eq!(r["saved"], "Typed");
assert_eq!(recorded(&mut s, "Typed"), ["cell.set {\"cell\":\"Z1\",\"input\":\"typed\"}"]);
}
+11 -11
View File
@@ -122,17 +122,17 @@ of the step that just ran, and the cells changed so far.
`description` when stopping; a name that another macro has gets a suffix, such as `Tidy (2)`, so
stopping never replaces a macro). The macro is saved in the workbook where recording started, even if
another one is active; if that workbook was closed, stopping ends the recording without saving it.
When saving the recording fails (a name the check refuses, a workbook at its macro limit),
recording goes on, so stopping again keeps what was recorded. A recording keeps up to 100,000
commands and a macro up to 10,000 steps; steps past either limit are left out and counted in
`dropped`. With relative references the selection when recording starts is the example input:
references in and near it on its sheet become tokens (on another sheet `C4` stays that sheet's
`C4`), and the input's shape, header and column types are inferred. Toggles such as Bold are recorded with the state they set
(`"on": true`), so playing them back sets them rather than flipping them. Commands a macro may not
run are left out and listed in the result, also from the steps of a macro or script run while
recording; a macro run while recording is replaced by its steps when it has no inputs, no output
and only plain commands (otherwise it is left out and listed), and an Automate script run by the
script's steps.
When saving the recording fails (a name the check refuses, a workbook at its macro limit, a
macro being debugged in that workbook), recording goes on, so stopping again keeps what was
recorded. A recording keeps up to 100,000 commands and a macro up to 10,000 steps; steps past
either limit are left out and counted in `dropped`. With relative references the selection when
recording starts is the example input: references in and near it on its sheet become tokens (on
another sheet `C4` stays that sheet's `C4`), and the input's shape, header and column types are
inferred. Toggles such as Bold are recorded with the state they set (`"on": true`), so playing
them back sets them rather than flipping them. Commands a macro may not run are left out and
listed in the result, also from the steps of a macro or script run while recording; a macro run
while recording is replaced by its steps when it has no inputs, no output and only plain commands
(otherwise it is left out and listed), and an Automate script run by the script's steps.
## Other commands and storage