use serde_json::Value;
const MAX_UNDO: usize = 100;
#[derive(Debug, Clone)]
struct Snapshot {
request: Value,
rollback: usize,
parts_library: std::rc::Rc<Value>,
serial: u64,
}
static NEXT_STEP: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
fn next_step() -> u64 {
NEXT_STEP.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
}
#[derive(Debug, Clone)]
pub struct History {
document: Value,
revision: u64,
rollback: usize,
undo_stack: Vec<Snapshot>,
redo_stack: Vec<Snapshot>,
last_edit_key: Option<String>,
feature_counter: u64,
parts_library: std::rc::Rc<Value>,
pin_port_hold: Option<String>,
ports_followed: bool,
edit_serial: u64,
lock: Option<String>,
scratch: Value,
refused: u64,
refused_user: u64,
}
static NEXT_REVISION: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
fn next_revision() -> u64 {
NEXT_REVISION.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
}
impl Default for History {
fn default() -> Self {
Self {
document: empty_request(),
revision: next_revision(),
edit_serial: next_revision(),
rollback: 0,
undo_stack: Vec::new(),
redo_stack: Vec::new(),
last_edit_key: None,
feature_counter: 0,
parts_library: std::rc::Rc::new(Value::Null),
pin_port_hold: None,
ports_followed: false,
lock: None,
scratch: Value::Null,
refused: 0,
refused_user: 0,
}
}
}
fn empty_request() -> Value {
serde_json::json!({ "expressions": "", "configurator": {}, "features": [] })
}
fn trailing_number(id: &str) -> u64 {
let digit_bytes = id
.bytes()
.rev()
.take_while(u8::is_ascii_digit)
.count();
id[id.len() - digit_bytes..].parse().unwrap_or(0)
}
impl History {
pub(crate) fn fold_plugin_persistent_data(&mut self, id: &str, data: Value) {
let Some(index) = self.index_of(id) else { return; };
if self.features()[index].get("persistentData") == Some(&data) { return; }
if let Some(features) = self.document_mut().get_mut("features").and_then(Value::as_array_mut) {
features[index]["persistentData"] = data;
}
}
pub(crate) fn commit_staged(&mut self, staged: &Self) -> Result<(), String> {
if self.refuse_user() {
return Err(format!("the document is read-only: {}", self.locked().unwrap_or("locked")));
}
self.checkpoint(None);
*self.document_mut() = staged.document.clone();
self.parts_library = staged.parts_library.clone();
self.feature_counter = staged.feature_counter;
self.rollback = staged.rollback;
self.pin_port_hold = None;
Ok(())
}
pub fn from_request_json(json: &str) -> Result<Self, String> {
let mut request: Value =
serde_json::from_str(json).map_err(|e| format!("history parse: {e}"))?;
if !request.get("features").map(Value::is_array).unwrap_or(false) {
if let Some(obj) = request.as_object_mut() {
obj.insert("features".into(), Value::Array(Vec::new()));
} else {
request = empty_request();
}
}
let stored = request
.as_object_mut()
.and_then(|obj| obj.remove("featureCounter"))
.and_then(|value| value.as_u64());
let parts_library = request
.as_object_mut()
.and_then(|obj| obj.remove("partsLibrary"))
.unwrap_or(Value::Null);
let mut history = Self {
document: request,
rollback: 0,
parts_library: std::rc::Rc::new(parts_library),
..Self::default()
};
history.rollback = history.len().saturating_sub(1);
history.feature_counter = stored.unwrap_or(0).max(history.max_id_suffix());
Ok(history)
}
fn max_id_suffix(&self) -> u64 {
self.features()
.iter()
.filter_map(|f| {
f.get("inputParams")
.and_then(|p| p.get("id"))
.and_then(Value::as_str)
})
.map(trailing_number)
.max()
.unwrap_or(0)
}
fn document(&self) -> &Value {
&self.document
}
fn document_mut(&mut self) -> &mut Value {
if self.lock.is_some() {
self.refused += 1;
self.scratch = self.document.clone();
return &mut self.scratch;
}
self.revision = next_revision();
&mut self.document
}
pub fn lock(&mut self, reason: impl Into<String>) {
self.lock = Some(reason.into());
self.break_coalescing();
}
pub fn unlock(&mut self) {
self.lock = None;
self.scratch = Value::Null;
}
pub fn locked(&self) -> Option<&str> {
self.lock.as_deref()
}
pub fn refused_edits(&self) -> u64 {
self.refused
}
pub fn refused_user_edits(&self) -> u64 {
self.refused_user
}
fn refuse(&mut self) -> bool {
if self.lock.is_some() {
self.refused += 1;
return true;
}
false
}
fn refuse_user(&mut self) -> bool {
let refused = self.refuse();
if refused {
self.refused_user += 1;
}
refused
}
pub fn revision(&self) -> u64 {
self.revision
}
pub fn edit_serial(&self) -> u64 {
self.edit_serial
}
pub fn features(&self) -> &[Value] {
self.document()
.get("features")
.and_then(Value::as_array)
.map(Vec::as_slice)
.unwrap_or(&[])
}
fn features_mut(&mut self) -> &mut Vec<Value> {
let obj = self
.document_mut()
.as_object_mut()
.expect("history request is a JSON object");
obj.entry("features")
.or_insert_with(|| Value::Array(Vec::new()));
obj.get_mut("features")
.and_then(Value::as_array_mut)
.expect("features is a JSON array")
}
pub fn len(&self) -> usize {
self.features().len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn rollback(&self) -> usize {
self.rollback.min(self.len().saturating_sub(1))
}
pub fn set_rollback(&mut self, index: usize) {
self.rollback = if self.is_empty() {
0
} else {
index.min(self.len() - 1)
};
self.last_edit_key = None;
}
pub fn feature_type(&self, index: usize) -> Option<String> {
self.features()
.get(index)?
.get("type")
.and_then(Value::as_str)
.map(String::from)
}
pub fn feature_id(&self, index: usize) -> Option<String> {
self.features()
.get(index)?
.get("inputParams")
.and_then(|p| p.get("id"))
.and_then(Value::as_str)
.map(String::from)
}
pub fn index_of(&self, id: &str) -> Option<usize> {
self.features().iter().position(|f| {
f.get("inputParams")
.and_then(|p| p.get("id"))
.and_then(Value::as_str)
== Some(id)
})
}
pub fn feature_params(&self, index: usize) -> Option<Value> {
self.features().get(index)?.get("inputParams").cloned()
}
pub fn fold_param_no_undo(&mut self, id: &str, key: &str, value: Value) -> bool {
let Some(index) = self.index_of(id) else {
return false;
};
if let Some(feature) = self.features_mut().get_mut(index) {
if let Some(params) = feature
.get_mut("inputParams")
.and_then(Value::as_object_mut)
{
params.insert(key.to_string(), value);
return true;
}
}
false
}
pub fn set_feature_params(&mut self, index: usize, params: Value) {
if index >= self.len() {
return;
}
self.checkpoint(Some(&format!("param:{index}")));
if let Some(feat) = self.features_mut().get_mut(index) {
if let Some(obj) = feat.as_object_mut() {
obj.insert("inputParams".into(), params);
}
}
}
pub fn set_many_feature_params(&mut self, edits: &[(usize, Value)]) {
let len = self.len();
let mut in_range: Vec<&(usize, Value)> =
edits.iter().filter(|(index, _)| *index < len).collect();
if in_range.is_empty() {
return;
}
in_range.sort_by_key(|(index, _)| *index);
let key: Vec<String> = in_range
.iter()
.map(|(index, _)| index.to_string())
.collect();
self.checkpoint(Some(&format!("params:{}", key.join(","))));
for (index, params) in in_range {
if let Some(feature) = self.features_mut().get_mut(*index) {
if let Some(object) = feature.as_object_mut() {
object.insert("inputParams".into(), params.clone());
}
}
}
}
pub fn push_feature(&mut self, feature: Value) {
self.checkpoint(None);
self.features_mut().push(feature);
}
pub fn push_features(&mut self, features: Vec<Value>) {
if features.is_empty() {
return;
}
self.checkpoint(None);
self.features_mut().extend(features);
}
pub fn remove_feature(&mut self, index: usize) {
if index >= self.len() {
return;
}
self.checkpoint(None);
self.features_mut().remove(index);
}
pub fn swap(&mut self, a: usize, b: usize) {
let len = self.len();
if a < len && b < len && a != b {
self.checkpoint(None);
self.features_mut().swap(a, b);
}
}
pub fn next_feature_id(&mut self, base: &str) -> String {
if self.refuse_user() {
return format!("{base}{}", self.feature_counter + 1);
}
self.feature_counter += 1;
format!("{base}{}", self.feature_counter)
}
pub(crate) fn retain_feature_allocations(&mut self, staged: &Self) {
if self.refuse_user() {
return;
}
self.feature_counter = self
.feature_counter
.max(staged.feature_counter)
.max(self.max_id_suffix());
}
pub fn prefix_request(&self) -> Value {
let mut request = self.document().clone();
if let Some(id) = self.feature_id(self.rollback()) {
if let Some(obj) = request.as_object_mut() {
obj.insert("stopAtId".into(), Value::String(id));
}
}
request
}
pub fn request_before(&self, index: usize) -> Option<Value> {
let id = self.feature_id(index)?;
let mut request = self.document().clone();
request.as_object_mut()?.insert("stopBeforeId".into(), Value::String(id));
Some(request)
}
pub fn listing_json(&self) -> String {
let features: Vec<Value> = self
.features()
.iter()
.enumerate()
.map(|(index, _)| {
serde_json::json!({
"index": index,
"type": self.feature_type(index).unwrap_or_else(|| "?".into()),
"id": self.feature_id(index).unwrap_or_else(|| "(no id)".into()),
})
})
.collect();
serde_json::json!({ "step": self.rollback(), "features": features }).to_string()
}
pub fn request_json(&self) -> String {
let has_library = self
.parts_library
.as_object()
.map(|map| !map.is_empty())
.unwrap_or(false);
if self.feature_counter == 0 && !has_library {
return self.document().to_string();
}
let mut document = self.document().clone();
if let Some(obj) = document.as_object_mut() {
if self.feature_counter != 0 {
obj.insert("featureCounter".into(), Value::from(self.feature_counter));
}
if has_library {
obj.insert("partsLibrary".into(), (*self.parts_library).clone());
}
}
document.to_string()
}
pub fn request_value(&self) -> Value {
let mut document = self.document().clone();
if let Some(obj) = document.as_object_mut() {
if self.feature_counter != 0 {
obj.insert("featureCounter".into(), Value::from(self.feature_counter));
}
if self.parts_library.as_object().is_some_and(|map| !map.is_empty()) {
obj.insert("partsLibrary".into(), (*self.parts_library).clone());
}
}
document
}
pub fn request_json_without_parts_library(&self) -> String {
if self.feature_counter == 0 {
return self.document().to_string();
}
let mut document = self.document().clone();
if let Some(obj) = document.as_object_mut() {
obj.insert("featureCounter".into(), Value::from(self.feature_counter));
}
document.to_string()
}
fn snapshot(&self) -> Snapshot {
Snapshot {
request: self.document().clone(),
rollback: self.rollback,
parts_library: self.parts_library.clone(),
serial: next_step(),
}
}
fn restore(&mut self, snap: Snapshot) {
self.edit_serial = next_revision();
*self.document_mut() = snap.request;
self.parts_library = snap.parts_library;
self.pin_port_hold = None;
let last = self.len().saturating_sub(1);
self.rollback = snap.rollback.min(last);
}
fn checkpoint(&mut self, coalesce_key: Option<&str>) {
if self.lock.is_some() {
self.refused_user += 1;
return;
}
self.edit_serial = next_revision();
if coalesce_key.is_some() && coalesce_key == self.last_edit_key.as_deref() {
return;
}
self.undo_stack.push(self.snapshot());
if self.undo_stack.len() > MAX_UNDO {
self.undo_stack.remove(0);
}
self.redo_stack.clear();
self.last_edit_key = coalesce_key.map(str::to_string);
}
pub fn can_undo(&self) -> bool {
!self.undo_stack.is_empty()
}
pub fn can_redo(&self) -> bool {
!self.redo_stack.is_empty()
}
pub fn undo(&mut self) -> bool {
if self.refuse_user() {
return false;
}
let Some(prev) = self.undo_stack.pop() else {
return false;
};
self.redo_stack.push(self.snapshot());
self.restore(prev);
self.last_edit_key = None;
true
}
pub fn redo(&mut self) -> bool {
if self.refuse_user() {
return false;
}
let Some(next) = self.redo_stack.pop() else {
return false;
};
self.undo_stack.push(self.snapshot());
self.restore(next);
self.last_edit_key = None;
true
}
}
impl History {
pub fn adopt_document(&mut self, document_json: &str) -> Result<(), String> {
if let Some(reason) = &self.lock {
let error = format!("the document is read-only: {reason}");
self.refused += 1;
return Err(error);
}
let document: Value = serde_json::from_str(document_json)
.map_err(|error| format!("adopt document parse: {error}"))?;
self.adopt_document_value(document)
}
pub fn adopt_document_value(&mut self, mut document: Value) -> Result<(), String> {
if let Some(reason) = &self.lock {
let error = format!("the document is read-only: {reason}");
self.refused += 1;
return Err(error);
}
if !document.is_object() {
return Err("adopt document: not a JSON object".into());
}
let mut adopted_library = None;
if let Some(obj) = document.as_object_mut() {
obj.remove("featureCounter");
adopted_library = obj.remove("partsLibrary");
if !obj.get("features").map(Value::is_array).unwrap_or(false) {
obj.insert("features".into(), Value::Array(Vec::new()));
}
}
*self.document_mut() = document;
self.pin_port_hold = None;
if let Some(library) = adopted_library {
self.set_parts_library(library);
}
Ok(())
}
pub fn adopt_document_checkpointed(&mut self, document_json: &str) -> Result<(), String> {
let probe: Value = serde_json::from_str(document_json)
.map_err(|error| format!("adopt document parse: {error}"))?;
if !probe.is_object() {
return Err("adopt document: not a JSON object".into());
}
self.checkpoint(None);
self.adopt_document_value(probe)
}
pub fn adopt_document_value_checkpointed(&mut self, document: Value) -> Result<(), String> {
if !document.is_object() {
return Err("adopt document: not a JSON object".into());
}
self.checkpoint(None);
self.adopt_document_value(document)
}
pub fn set_parts_library(&mut self, library: Value) {
if self.refuse() {
return;
}
let empty = library.as_object().map(|m| m.is_empty()).unwrap_or(true);
self.parts_library = std::rc::Rc::new(if empty { Value::Null } else { library });
self.revision = next_revision();
}
pub fn set_parts_library_edited(&mut self, library: Value, coalesce_key: Option<&str>) {
if self.refuse_user() {
return;
}
self.checkpoint(coalesce_key);
let empty = library.as_object().map(|m| m.is_empty()).unwrap_or(true);
self.parts_library = std::rc::Rc::new(if empty { Value::Null } else { library });
self.revision = next_revision();
}
pub fn parts_library(&self) -> &Value {
&self.parts_library
}
pub fn undo_top_block(&self, key: &str) -> Option<&Value> {
self.undo_stack.last().and_then(|snapshot| snapshot.request.get(key))
}
pub fn undo_parts_libraries(&self) -> impl Iterator<Item = &Value> + '_ {
self.undo_stack.iter().rev().map(|snapshot| snapshot.parts_library.as_ref())
}
pub fn assembly_block(&self) -> Option<&Value> {
self.document().get("assembly")
}
pub fn set_assembly_block(&mut self, block: Value, checkpoint: bool) {
if checkpoint {
self.checkpoint(None);
}
if let Some(object) = self.document_mut().as_object_mut() {
object.insert("assembly".into(), block);
}
}
pub fn wire_harness_block(&self) -> Option<&Value> {
self.document().get("wireHarness")
}
pub fn set_wire_harness_block(&mut self, block: Option<Value>) {
self.checkpoint(None);
if let Some(object) = self.document_mut().as_object_mut() {
match block {
Some(block) => {
object.insert("wireHarness".into(), block);
}
None => {
object.shift_remove("wireHarness");
}
}
}
}
}
impl History {
pub fn pmi_block(&self) -> Option<&Value> {
self.document().get("pmi")
}
pub fn set_pmi_block(&mut self, block: Option<Value>, coalesce_key: Option<&str>) {
self.checkpoint(coalesce_key);
self.put_pmi_block(block);
}
pub fn set_pmi_block_no_undo(&mut self, block: Option<Value>) {
self.put_pmi_block(block);
}
fn put_pmi_block(&mut self, block: Option<Value>) {
if let Some(object) = self.document_mut().as_object_mut() {
match block {
Some(block) => {
object.insert("pmi".into(), block);
}
None => {
object.shift_remove("pmi");
}
}
}
}
pub fn break_coalescing(&mut self) {
self.last_edit_key = None;
}
}
impl History {
pub fn cam_block(&self) -> Option<&Value> {
self.document().get("cam")
}
pub fn set_cam_block(&mut self, block: Option<Value>, coalesce_key: Option<&str>) {
let coalesce_key = coalesce_key.map(|change| format!("cam:{change}"));
self.checkpoint(coalesce_key.as_deref());
self.put_cam_block(block);
}
pub fn set_cam_block_no_undo(&mut self, block: Option<Value>) {
self.put_cam_block(block);
}
fn put_cam_block(&mut self, block: Option<Value>) {
if let Some(object) = self.document_mut().as_object_mut() {
match block {
Some(block) => {
object.insert("cam".into(), block);
}
None => {
object.shift_remove("cam");
}
}
}
}
}
impl History {
pub fn sheets_block(&self) -> Option<&Value> {
self.document().get(crate::sheets::SHEETS)
}
pub fn set_sheets_block(&mut self, block: Option<Value>, coalesce_key: Option<&str>) {
self.checkpoint(coalesce_key);
if let Some(object) = self.document_mut().as_object_mut() {
match block {
Some(block) => {
object.insert(crate::sheets::SHEETS.into(), block);
}
None => {
object.shift_remove(crate::sheets::SHEETS);
}
}
}
}
}
impl History {
pub fn part_attributes_block(&self) -> Option<&Value> {
self.document().get(crate::engine_state::PART_ATTRIBUTES)
}
pub fn set_part_attributes_block(&mut self, block: Option<Value>, coalesce_key: Option<&str>) {
self.checkpoint(coalesce_key);
if let Some(object) = self.document_mut().as_object_mut() {
match block {
Some(block) => {
object.insert(crate::engine_state::PART_ATTRIBUTES.into(), block);
}
None => {
object.shift_remove(crate::engine_state::PART_ATTRIBUTES);
}
}
}
}
}
impl History {
pub fn expressions(&self) -> String {
self.document()
.get("expressions")
.and_then(Value::as_str)
.unwrap_or("")
.to_string()
}
pub fn set_expressions(&mut self, expressions: &str) {
self.checkpoint(Some("expressions"));
if let Some(obj) = self.document_mut().as_object_mut() {
obj.insert(
"expressions".into(),
Value::String(expressions.to_string()),
);
}
}
pub fn configurator(&self) -> Value {
self.document()
.get("configurator")
.cloned()
.unwrap_or_else(|| Value::Object(serde_json::Map::new()))
}
}
impl History {
pub fn feature_persistent_data(&self, index: usize) -> Option<Value> {
self.features().get(index)?.get("persistentData").cloned()
}
pub fn set_feature_persistent_field(&mut self, index: usize, key: &str, value: Value) {
self.set_feature_persistent_field_coalesced(index, key, value, None);
}
pub fn set_feature_persistent_field_coalesced(
&mut self,
index: usize,
key: &str,
value: Value,
coalesce_key: Option<&str>,
) {
if index >= self.len() {
return;
}
self.checkpoint(coalesce_key);
if let Some(feat) = self.features_mut().get_mut(index).and_then(Value::as_object_mut) {
let entry = feat
.entry("persistentData")
.or_insert_with(|| Value::Object(serde_json::Map::new()));
if !entry.is_object() {
*entry = Value::Object(serde_json::Map::new());
}
if let Some(obj) = entry.as_object_mut() {
obj.insert(key.to_string(), value);
}
}
}
pub fn set_feature_persistent_fields(&mut self, index: usize, fields: Vec<(&str, Value)>) -> bool {
let Some(feature) = self.features().get(index) else {
return false;
};
let current = feature.get("persistentData");
if fields.iter().all(|(key, value)| current.and_then(|data| data.get(*key)) == Some(value)) {
return false;
}
self.checkpoint(None);
if let Some(feat) = self.features_mut().get_mut(index).and_then(Value::as_object_mut) {
let entry = feat
.entry("persistentData")
.or_insert_with(|| Value::Object(serde_json::Map::new()));
if !entry.is_object() {
*entry = Value::Object(serde_json::Map::new());
}
if let Some(obj) = entry.as_object_mut() {
for (key, value) in fields {
obj.insert(key.to_string(), value);
}
}
}
true
}
pub fn undo_top(&self) -> Option<u64> {
self.undo_stack.last().map(|snapshot| snapshot.serial)
}
pub fn retract(&mut self, step: u64) -> bool {
if self.refuse_user() {
return false;
}
if self.undo_top() != Some(step) {
return false;
}
let Some(snapshot) = self.undo_stack.pop() else {
return false;
};
self.restore(snapshot);
self.last_edit_key = None;
true
}
pub fn set_feature_persistent_fields_in_step(
&mut self,
index: usize,
fields: Vec<(&str, Value)>,
step: u64,
) -> bool {
if self.undo_top() != Some(step) {
return self.set_feature_persistent_fields(index, fields);
}
let Some(feat) = self.features_mut().get_mut(index).and_then(Value::as_object_mut) else {
return false;
};
let entry = feat
.entry("persistentData")
.or_insert_with(|| Value::Object(serde_json::Map::new()));
if !entry.is_object() {
*entry = Value::Object(serde_json::Map::new());
}
if let Some(obj) = entry.as_object_mut() {
for (key, value) in fields {
obj.insert(key.to_string(), value);
}
}
true
}
}
pub const DIAGRAM: &str = "diagram";
pub const PCB: &str = "pcb";
pub const SYMBOL: &str = brep_kernel::SYMBOL_BLOCK;
pub const PADS: &str = brep_kernel::PADS_BLOCK;
pub const PORTS: &str = brep_kernel::PORTS_BLOCK;
impl History {
pub fn diagram_block(&self) -> Option<&Value> {
self.document().get(DIAGRAM)
}
pub fn pcb_block(&self) -> Option<&Value> {
self.document().get(PCB)
}
pub fn symbol_block(&self) -> Option<&Value> {
self.document().get(SYMBOL)
}
pub fn pads_block(&self) -> Option<&Value> {
self.document().get(PADS)
}
pub fn ports_block(&self) -> Option<&Value> {
self.document().get(PORTS)
}
pub fn declared_points(&self) -> Vec<brep_kernel::DeclaredPoint> {
brep_kernel::declared_points(self.document())
}
pub fn pin_labels(&self) -> Option<Vec<String>> {
brep_kernel::pin_labels(self.document())
}
pub fn pins(&self) -> Option<Vec<brep_kernel::DeclaredPin>> {
brep_kernel::pins(self.document())
}
pub fn port_group_for_unit(&self, unit: u32) -> Option<String> {
brep_kernel::port_group_for_unit(self.document(), unit)
}
pub fn set_diagram_block(&mut self, block: Option<Value>, coalesce_key: Option<&str>) {
self.set_ecad_block(DIAGRAM, block, coalesce_key);
}
pub fn set_pcb_block(&mut self, block: Option<Value>, coalesce_key: Option<&str>) {
self.set_ecad_block(PCB, block, coalesce_key);
}
pub fn set_symbol_block(&mut self, block: Option<Value>, coalesce_key: Option<&str>) {
self.set_ecad_block(SYMBOL, block, coalesce_key);
self.follow_pins(None);
}
pub fn set_pads_block(&mut self, block: Option<Value>, coalesce_key: Option<&str>) {
self.set_ecad_block(PADS, block, coalesce_key);
}
pub fn set_symbol_block_no_undo(&mut self, block: Option<Value>) {
let base = self.document().clone();
self.put_ecad_block(SYMBOL, block);
self.follow_pins(Some(base));
}
pub fn set_pads_block_no_undo(&mut self, block: Option<Value>) {
self.put_ecad_block(PADS, block);
}
pub fn set_ports_block(&mut self, block: Option<Value>, coalesce_key: Option<&str>) {
self.set_ecad_block(PORTS, block, coalesce_key);
self.follow_ports();
self.ports_followed = true;
}
pub fn set_ports_block_no_undo(&mut self, block: Option<Value>) {
self.put_ecad_block(PORTS, block);
self.ports_followed = true;
}
fn set_ecad_block(&mut self, key: &str, block: Option<Value>, coalesce_key: Option<&str>) {
let coalesce_key = coalesce_key.map(|change| format!("{key}:{change}"));
self.checkpoint(coalesce_key.as_deref());
self.put_ecad_block(key, block);
}
fn put_ecad_block(&mut self, key: &str, block: Option<Value>) {
if let Some(object) = self.document_mut().as_object_mut() {
match block {
Some(block) => {
object.insert(key.into(), block);
}
None => {
object.shift_remove(key);
}
}
}
}
}
impl History {
pub fn pin_port_hold(&self) -> Option<&str> {
self.pin_port_hold.as_deref()
}
pub fn pin_point_report(&self) -> Option<brep_kernel::PinPointReport> {
brep_kernel::pin_point_report(self.document())
}
pub(crate) fn take_ports_followed(&mut self) -> bool {
std::mem::take(&mut self.ports_followed)
}
pub fn ports_follow_pending(&self) -> bool {
self.ports_followed
}
fn run_base(&self) -> &Value {
self.undo_stack
.last()
.map(|snapshot| &snapshot.request)
.unwrap_or_else(|| self.document())
}
fn follow_pins(&mut self, base: Option<Value>) {
let mut document = self.document().clone();
let outcome = {
let base = base.as_ref().unwrap_or_else(|| self.run_base());
brep_kernel::follow_pin_edit(base, &mut document)
};
self.settle(outcome, document);
}
fn follow_ports(&mut self) {
if self.symbol_block().is_none() {
return;
}
if brep_kernel::declared_points(self.run_base()) == brep_kernel::declared_points(self.document()) {
return;
}
let mut document = self.document().clone();
let outcome = brep_kernel::follow_point_edit(self.run_base(), &mut document);
self.settle(outcome, document);
}
fn settle(&mut self, outcome: Result<Vec<brep_kernel::PinPointChange>, String>, document: Value) {
let changes = match outcome {
Ok(changes) => changes,
Err(reason) => {
self.pin_port_hold = Some(reason);
return;
}
};
self.pin_port_hold = None;
if changes.is_empty() {
return;
}
let moves_a_port = changes.iter().any(|change| {
use brep_kernel::PinPointChange::*;
match change {
PointRenamed { .. } | PointAdded { .. } | PointRemoved { .. } => true,
PinRelabelled { .. } | PinAdded { .. } | PinRemoved { .. }
| PadsRenumbered { .. } | PadsAmbiguous { .. } => false,
}
});
let at_end = self.rollback + 1 >= self.len();
let rolled_to = self.feature_id(self.rollback());
*self.document_mut() = document;
let len = self.len();
self.rollback = if len == 0 {
0
} else if at_end {
len - 1
} else {
rolled_to
.and_then(|id| self.index_of(&id))
.unwrap_or_else(|| self.rollback.saturating_sub(1))
.min(len - 1)
};
self.ports_followed |= moves_a_port;
}
}
impl History {
pub fn document_block(&self, key: &str) -> Option<&Value> {
self.document().get(key)
}
pub fn set_document_block(&mut self, key: &str, block: Option<Value>, coalesce_key: Option<&str>) {
let coalesce_key = coalesce_key.map(|change| format!("block:{key}:{change}"));
self.checkpoint(coalesce_key.as_deref());
self.set_document_block_no_undo(key, block);
}
pub fn set_document_block_no_undo(&mut self, key: &str, block: Option<Value>) {
if let Some(object) = self.document_mut().as_object_mut() {
match block {
Some(block) => {
object.insert(key.to_string(), block);
}
None => {
object.shift_remove(key);
}
}
}
}
}