mirror of
https://github.com/storytold/cadcraft.git
synced 2026-11-03 00:42:46 -06:00
Inside a routine, commands recorded no undo step (the routine becomes one when it ends), so U inside a routine took back the step from before the routine: after drawing a line D, ERASE B then U in one routine brought B back and removed D. As AutoCAD 2024 does (accoreconsole, the drawing read back after one routine), U inside a routine now takes back the routine's last command, with what the routine did after it (entdel, entmake); U again, UNDO n and REDO go on through its commands. With none left, U takes back the drawing's step before the routine, as before, and the routine's step then starts from there. Once the routine ends it is still one undo step. The routine's steps live in the session's LISP state (RoutineSteps), not in the drawing's undo stack, so a long routine doesn't push the drawing's earlier steps out. A command inside the routine ends what REDO could bring back; UNDO Control One and None apply. DocState::swap_in is the one restore step undo, redo and the routine share. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
129 lines
5.5 KiB
Rust
129 lines
5.5 KiB
Rust
use super::{Arc, EngineError, Result, Session, Snapshot, Value, cmd, find_command};
|
|
|
|
impl Session {
|
|
// ---------------- programmatic execution ----------------
|
|
|
|
/// Run a command with JSON parameters (no dialogs). Records undo when the drawing changes.
|
|
///
|
|
/// The command itself is guarded below, where its own panic can be told apart. This guard is
|
|
/// for everything around it: the passes after a command (constraints, associativity, groups)
|
|
/// are as able to panic as the command, and a panic there restores the drawing the same way.
|
|
pub fn execute(&mut self, id: &str, params: &Value) -> Result<Value> {
|
|
let before = self.state().ok().map(|s| (s.doc.clone(), s.selection.clone(), s.uid));
|
|
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| self.execute_guarded(id, params))) {
|
|
Ok(r) => r,
|
|
Err(p) => {
|
|
if let Some((doc, sel, uid)) = before
|
|
&& let Some(st) = self.docs.iter_mut().find(|d| d.uid == uid)
|
|
{
|
|
st.doc = doc;
|
|
st.selection = sel;
|
|
}
|
|
Err(EngineError::Internal(id.into(), crate::guard::message(&*p)))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn execute_guarded(&mut self, id: &str, params: &Value) -> Result<Value> {
|
|
let spec = find_command(id).ok_or_else(|| EngineError::UnknownCommand(id.to_string()))?;
|
|
(spec.enabled)(self).map_err(|m| EngineError::Disabled(spec.id.into(), m))?;
|
|
if let Some(why) = cmd::blocks::bedit::refused_in_editor(self, spec.id) {
|
|
return Err(EngineError::Disabled(spec.id.into(), why));
|
|
}
|
|
let before = self.state().ok().map(|s| (s.doc.clone(), s.selection.clone(), s.uid));
|
|
let editing = self.state().is_ok_and(|s| s.block_edit.is_some());
|
|
let run = spec.run;
|
|
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| run(self, params)));
|
|
let result = match result {
|
|
Ok(r) => r,
|
|
Err(p) => {
|
|
let msg = p
|
|
.downcast_ref::<&str>()
|
|
.map(ToString::to_string)
|
|
.or_else(|| p.downcast_ref::<String>().cloned())
|
|
.unwrap_or_else(|| "panic".into());
|
|
// Restore the drawing as it was.
|
|
if let Some((doc, sel, uid)) = &before
|
|
&& let Some(st) = self.docs.iter_mut().find(|d| d.uid == *uid)
|
|
{
|
|
st.doc = doc.clone();
|
|
st.selection = sel.clone();
|
|
}
|
|
return Err(EngineError::Internal(id.into(), msg));
|
|
}
|
|
};
|
|
let violation = before.as_ref().and_then(|b| cmd::blocks::refedit::after_command(self, &b.0, spec.id));
|
|
let result = match (violation, result) {
|
|
(Some(m), Ok(_)) => Err(EngineError::Other(m)),
|
|
(_, r) => r,
|
|
};
|
|
// Opening or closing the Block Editor swaps the whole drawing: nothing in it changed.
|
|
if self.state().is_ok_and(|s| s.block_edit.is_some()) == editing {
|
|
self.after_command(before.as_ref().map(|b| &b.0), before.as_ref().map(|b| b.2), spec.undoable && result.is_ok());
|
|
}
|
|
let result = match (result, &before) {
|
|
(Ok(v), Some((doc, sel, uid))) if spec.undoable => self.refuse_non_finite(Some(*uid), doc, sel, spec.id).map(|()| v),
|
|
(r, _) => r,
|
|
};
|
|
let grouped = self.lisp_groups_undo();
|
|
let mut in_routine = None;
|
|
if spec.undoable
|
|
&& let Some((doc, sel, uid)) = before
|
|
&& let Some(st) = self.docs.iter_mut().find(|d| d.uid == uid)
|
|
&& !Arc::ptr_eq(&doc, &st.doc)
|
|
{
|
|
let snap = Snapshot { label: spec.label.to_string(), doc, selection: sel };
|
|
// Inside a LISP routine the routine's single undo step records it, and the routine
|
|
// keeps it for U inside it.
|
|
if grouped {
|
|
in_routine = Some(snap);
|
|
} else {
|
|
st.push_undo(snap);
|
|
}
|
|
st.revision += 1;
|
|
}
|
|
if let Some(snap) = in_routine {
|
|
self.lisp_record_step(snap);
|
|
}
|
|
result
|
|
}
|
|
|
|
/// Bump the revision without an undo step (view-only or settings changes that redraw).
|
|
pub fn touch(&mut self) {
|
|
if let Ok(st) = self.state_mut() {
|
|
st.revision += 1;
|
|
}
|
|
}
|
|
|
|
// ---------------- undo ----------------
|
|
|
|
/// Take the last step back. Inside a LISP routine, the routine's last command first; past
|
|
/// those, the drawing's step before the routine, which then starts from there.
|
|
pub fn undo(&mut self) -> Result<Option<String>> {
|
|
if let Some(label) = self.lisp_take_back(false) {
|
|
return Ok(Some(label));
|
|
}
|
|
let st = self.state_mut()?;
|
|
let Some(snap) = st.undo.pop() else { return Ok(None) };
|
|
let cur = st.swap_in(snap);
|
|
let label = cur.label.clone();
|
|
st.redo.push(cur);
|
|
self.lisp_rebase();
|
|
Ok(Some(label))
|
|
}
|
|
|
|
/// Bring back what the last undo took back; inside a LISP routine, what U inside it took first.
|
|
pub fn redo(&mut self) -> Result<Option<String>> {
|
|
if let Some(label) = self.lisp_take_back(true) {
|
|
return Ok(Some(label));
|
|
}
|
|
let st = self.state_mut()?;
|
|
let Some(snap) = st.redo.pop() else { return Ok(None) };
|
|
let cur = st.swap_in(snap);
|
|
let label = cur.label.clone();
|
|
st.undo.push(cur);
|
|
self.lisp_rebase();
|
|
Ok(Some(label))
|
|
}
|
|
}
|