mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-29 21:26:48 -06:00
Engine: undo groups make several commands one undo step
Session::grouped(label, f) runs f as one undo step on the active document. On success the group leaves exactly one undo entry holding the workbook from before the group; on error it restores the workbook, selection, undo and redo stacks as they were and returns the error. group_begin, group_end and group_rollback do the same across separate calls, for an action that runs one step per call. While a group is open on a document, undoable commands on it add no undo steps of their own, so a long group neither grows the history nor pushes older entries past the undo limit, and Undo and Redo are disabled there. Groups nest: the outermost one makes the step, and a failing inner group rolls back only its own changes. Closing a document drops its groups, and a command that panics drops the groups it opened. history.list reports whether a group is open. Insert Copied Cells folds its nested steps into one undo step by counting revisions; inside a group the nested steps push no entries, so it now leaves the history alone there instead of removing entries from before the group.
This commit is contained in:
@@ -11,7 +11,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
cmd!(query "sheet.read", "Read Range", [], None, "{range?: \"A1:D10\" (default: used range), sheet?, formulas?: bool, formatted?: bool} → rows of values", has_doc, read),
|
||||
cmd!(query "cell.get", "Get Cell", [], None, "{cell?, sheet?} → value, formula, display text, style, comment, link", has_doc, cell_get),
|
||||
cmd!(query "selection.stats", "Selection Statistics", [], None, "{} → count, sum, average, min, max", has_doc, sel_stats),
|
||||
cmd!(query "history.list", "Undo History", [], None, "{} → undo/redo labels", has_doc, history),
|
||||
cmd!(query "history.list", "Undo History", [], None, "{} → undo/redo labels, grouping (an undo group is open)", has_doc, history),
|
||||
cmd!(query "app.commands", "List Commands", [], None, "{search?} → ids, labels, params", always, list_commands),
|
||||
]
|
||||
}
|
||||
@@ -148,9 +148,11 @@ fn sel_stats(s: &mut Session, _: &Json) -> Result<Json> {
|
||||
|
||||
fn history(s: &mut Session, _: &Json) -> Result<Json> {
|
||||
let d = s.doc()?;
|
||||
Ok(
|
||||
json!({"undo": d.undo.iter().rev().map(|e| e.label.clone()).collect::<Vec<_>>(), "redo": d.redo.iter().rev().map(|e| e.label.clone()).collect::<Vec<_>>()}),
|
||||
)
|
||||
Ok(json!({
|
||||
"undo": d.undo.iter().rev().map(|e| e.label.clone()).collect::<Vec<_>>(),
|
||||
"redo": d.redo.iter().rev().map(|e| e.label.clone()).collect::<Vec<_>>(),
|
||||
"grouping": s.in_group(d.uid),
|
||||
}))
|
||||
}
|
||||
|
||||
fn list_commands(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
|
||||
@@ -96,12 +96,19 @@ pub fn has_doc(s: &Session) -> std::result::Result<(), String> {
|
||||
}
|
||||
pub fn can_undo(s: &Session) -> std::result::Result<(), String> {
|
||||
has_doc(s)?;
|
||||
not_grouping(s)?;
|
||||
if s.active().is_some_and(|d| !d.undo.is_empty()) { Ok(()) } else { Err("nothing to undo".into()) }
|
||||
}
|
||||
pub fn can_redo(s: &Session) -> std::result::Result<(), String> {
|
||||
has_doc(s)?;
|
||||
not_grouping(s)?;
|
||||
if s.active().is_some_and(|d| !d.redo.is_empty()) { Ok(()) } else { Err("nothing to redo".into()) }
|
||||
}
|
||||
/// Undo and Redo wait while an undo group is open on the active document (an action that spans
|
||||
/// several calls is still running).
|
||||
fn not_grouping(s: &Session) -> std::result::Result<(), String> {
|
||||
if s.active().is_some_and(|d| s.in_group(d.uid)) { Err("finish or stop the running action first".into()) } else { Ok(()) }
|
||||
}
|
||||
pub fn has_clipboard(s: &Session) -> std::result::Result<(), String> {
|
||||
has_doc(s)?;
|
||||
if s.clipboard.is_some() { Ok(()) } else { Err("the clipboard is empty".into()) }
|
||||
|
||||
@@ -554,11 +554,15 @@ fn insert_copied_cells(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
}
|
||||
let revision = d.revision;
|
||||
let before = (d.wb.clone(), d.selection.clone());
|
||||
// Inside an undo group the nested steps add no undo entries of their own.
|
||||
let nested_entries = !s.in_group(d.uid);
|
||||
let result = insert_copied_steps(s, &clip, sheet, at, shift, cut);
|
||||
let d = s.doc_mut()?;
|
||||
// The nested steps collapse into this command's single undo step.
|
||||
for _ in 0..d.revision.saturating_sub(revision) {
|
||||
d.undo.pop();
|
||||
if nested_entries {
|
||||
for _ in 0..d.revision.saturating_sub(revision) {
|
||||
d.undo.pop();
|
||||
}
|
||||
}
|
||||
match result {
|
||||
Ok((final_range, out)) => {
|
||||
|
||||
Reference in New Issue
Block a user