diff --git a/src/app/commands/count.rs b/src/app/commands/count.rs index 969872861..2dd406536 100644 --- a/src/app/commands/count.rs +++ b/src/app/commands/count.rs @@ -546,6 +546,12 @@ impl OpenCADStudio { /// its attributes and the texts of its block definition; a table, the /// cell texts of its drawing (its `*T` block). fn update_fields(&mut self, i: usize, handles: &[Handle]) { + // Every run is an undo step, as in the reference, even with nothing + // selected, no field found or no value changed. + self.push_undo_mark(i, "UPDATEFIELD"); + if handles.is_empty() { + return; + } let doc = &self.tabs[i].scene.document; let mut hosts = handles.to_vec(); for h in handles { @@ -572,25 +578,25 @@ impl OpenCADStudio { }) .flatten() .collect(); - let found = hosts - .iter() - .filter(|h| match doc.get_entity(**h) { - Some(entity) => crate::entities::field::hosts_field(doc, entity), - None => attributes.iter().any(|common| { - common.handle == **h && crate::entities::field::common_hosts_field(doc, common) - }), - }) - .count(); - self.command_line.push_output(&crate::tf!("{found} field(s) found.")); - if found == 0 { + let hosting = hosts.iter().any(|h| match doc.get_entity(*h) { + Some(entity) => crate::entities::field::hosts_field(doc, entity), + None => attributes + .iter() + .any(|common| common.handle == *h && crate::entities::field::common_hosts_field(doc, common)), + }); + if !hosting { + self.command_line.push_output(&crate::tf!("{found} field(s) found.", found = 0)); + self.command_line.push_output(&crate::tf!("{updated} field(s) updated.", updated = 0)); return; } - self.push_undo_snapshot(i, "UPDATEFIELD"); - let updated = self.tabs[i].scene.update_fields(32, Some(&hosts)); - if updated > 0 { + // The reference counts fields, not the texts holding them, and + // reports every field it evaluated as updated, changed or not. + let (changed, found) = self.tabs[i].scene.update_fields(32, Some(&hosts)); + if changed > 0 { self.tabs[i].dirty = true; } - self.command_line.push_output(&crate::tf!("{updated} field(s) updated.")); + self.command_line.push_output(&crate::tf!("{found} field(s) found.")); + self.command_line.push_output(&crate::tf!("{updated} field(s) updated.", updated = found)); } /// A count table at `point`: `Item` | `Count`, one row per block (all diff --git a/src/app/document.rs b/src/app/document.rs index bc47e8ea0..f9bef2bbb 100644 --- a/src/app/document.rs +++ b/src/app/document.rs @@ -901,4 +901,6 @@ pub(super) struct PendingHistorySnapshot { pub(super) dirty_before: bool, pub(super) structure_before: CadDocument, pub(super) recorder: Arc, + /// Kept as an undo step even when nothing changed. + pub(super) keep_empty: bool, } diff --git a/src/app/history.rs b/src/app/history.rs index ef97360a3..ffe243c3c 100644 --- a/src/app/history.rs +++ b/src/app/history.rs @@ -313,7 +313,8 @@ impl OpenCADStudio { let selected_after: Vec = self.tabs[i].scene.selected.iter().copied().collect(); let dirty_after = self.tabs[i].dirty; let active_layer_after = self.tabs[i].active_layer.clone(); - if entities.is_empty() + if !pending.keep_empty + && entities.is_empty() && !structure_changed && pending.selected_before == selected_after && pending.dirty_before == dirty_after @@ -365,9 +366,19 @@ impl OpenCADStudio { dirty_before, structure_before, recorder, + keep_empty: false, }); } + /// [`Self::push_undo_snapshot`] for a command that is an undo step even + /// when it changes nothing (UNDO and U then name it and undo nothing). + pub(super) fn push_undo_mark(&mut self, i: usize, label: impl Into) { + self.push_undo_snapshot(i, label); + if let Some(pending) = self.tabs[i].history.pending.as_mut() { + pending.keep_empty = true; + } + } + /// Begin undo capture for an entity edit that will touch `touched` entities. /// Starts a cheap Scene delta recording and returns the pre-command state to /// pass to [`OpenCADStudio::commit_undo_delta`] after the mutation. A command diff --git a/src/modules/annotate/count_cmd.rs b/src/modules/annotate/count_cmd.rs index 9ac6c3d3f..60833d8b2 100644 --- a/src/modules/annotate/count_cmd.rs +++ b/src/modules/annotate/count_cmd.rs @@ -434,9 +434,7 @@ impl CadCommand for UpdateFieldCommand { } fn on_enter(&mut self) -> CmdResult { - if self.selected.is_empty() { - return CmdResult::Cancel; - } + // An empty selection still runs (the reference keeps an undo step). let handles: Vec = self.selected.iter().map(|h| format!("{:X}", h.value())).collect(); CmdResult::Dispatch(format!("_UPDATEFIELDRUN {}", handles.join(","))) } diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 8669fd033..7cb4e6cb4 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -3419,13 +3419,14 @@ impl Scene { /// Update the fields an evaluation event re-evaluates (see /// [`crate::entities::field::update_fields`]) and redraw the hosts whose - /// text changed. Returns the number of fields the hosts hold. - pub fn update_fields(&mut self, event: i32, hosts: Option<&[Handle]>) -> usize { - let changed = crate::entities::field::update_fields(&mut self.document, event, hosts).0; + /// text changed. Returns the number of hosts whose text changed and the + /// number of fields the hosts hold. + pub fn update_fields(&mut self, event: i32, hosts: Option<&[Handle]>) -> (usize, usize) { + let (changed, found) = crate::entities::field::update_fields(&mut self.document, event, hosts); if !changed.is_empty() { self.bump_text_hosts(&changed); } - changed.len() + (changed.len(), found) } /// Redraw text hosts whose stored text changed (re-evaluated fields). An