1use crate::SheetId;
2use crate::engine::addr::GridAddr;
3use crate::engine::graph::DependencyGraph;
4use crate::engine::graph::editor::change_log::{FormulaRunAdjusted, MutationCapture};
5use crate::engine::graph::editor::reference_adjuster::{
6 MoveReferenceAdjuster, ReferenceAdjuster, ReferenceContext, RelativeReferenceAdjuster,
7 ShiftOperation,
8};
9use crate::engine::named_range::{NameScope, NamedDefinition};
10use crate::engine::{ChangeEvent, ChangeLogger, VertexId, VertexKind};
11use crate::reference::{CellRef, Coord};
12use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
13use formualizer_parse::parser::ASTNode;
14use rustc_hash::FxHashMap;
15use std::sync::atomic::{AtomicU64, Ordering};
16
17#[derive(Debug, Clone)]
19pub struct VertexMeta {
20 pub coord: GridAddr,
21 pub sheet_id: SheetId,
22 pub kind: VertexKind,
23 pub flags: u8,
24}
25
26impl VertexMeta {
27 pub fn new(row: u32, col: u32, sheet_id: SheetId, kind: VertexKind) -> Self {
28 Self {
29 coord: GridAddr::new(row, col),
30 sheet_id,
31 kind,
32 flags: 0,
33 }
34 }
35
36 pub fn with_flags(mut self, flags: u8) -> Self {
37 self.flags = flags;
38 self
39 }
40
41 pub fn dirty(mut self) -> Self {
42 self.flags |= 0x01;
43 self
44 }
45
46 pub fn volatile(mut self) -> Self {
47 self.flags |= 0x02;
48 self
49 }
50}
51
52#[derive(Debug, Clone)]
54pub struct VertexMetaPatch {
55 pub kind: Option<VertexKind>,
56 pub coord: Option<GridAddr>,
57 pub dirty: Option<bool>,
58 pub volatile: Option<bool>,
59}
60
61#[derive(Debug, Clone)]
63pub struct VertexDataPatch {
64 pub value: Option<LiteralValue>,
65 pub formula: Option<ASTNode>,
66}
67
68#[derive(Debug, Clone, Default)]
70pub struct MetaUpdateSummary {
71 pub coord_changed: bool,
72 pub kind_changed: bool,
73 pub flags_changed: bool,
74}
75
76#[derive(Debug, Clone, Default)]
78pub struct DataUpdateSummary {
79 pub value_changed: bool,
80 pub formula_changed: bool,
81 pub dependents_marked_dirty: Vec<VertexId>,
82}
83
84#[derive(Debug, Clone, Default)]
86pub struct ShiftSummary {
87 pub vertices_moved: Vec<VertexId>,
88 pub vertices_deleted: Vec<VertexId>,
89 pub references_adjusted: usize,
90 pub formulas_updated: usize,
91 #[cfg(test)]
92 pub(crate) structural_dependents_dirtied: Vec<VertexId>,
93}
94
95#[derive(Debug, Clone, Default)]
97pub struct RangeSummary {
98 pub cells_affected: usize,
99 pub vertices_created: Vec<VertexId>,
100 pub vertices_updated: Vec<VertexId>,
101 pub cells_moved: usize,
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub struct TransactionId(u64);
107
108impl TransactionId {
109 fn new() -> Self {
110 static COUNTER: AtomicU64 = AtomicU64::new(0);
111 TransactionId(COUNTER.fetch_add(1, Ordering::Relaxed))
112 }
113}
114
115#[derive(Debug)]
117struct Transaction {
118 id: TransactionId,
119 start_index: usize, }
121
122#[derive(Debug, Clone)]
124pub enum EditorError {
125 TargetOccupied { cell: CellRef },
126 OutOfBounds { row: u32, col: u32 },
127 InvalidName { name: String, reason: String },
128 TransactionFailed { reason: String },
129 TransactionUnsupported { reason: String },
130 NoActiveTransaction,
131 VertexNotFound { id: VertexId },
132 Excel(ExcelError),
133}
134
135impl From<ExcelError> for EditorError {
136 fn from(e: ExcelError) -> Self {
137 EditorError::Excel(e)
138 }
139}
140
141impl std::fmt::Display for EditorError {
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 match self {
144 EditorError::TargetOccupied { cell } => {
145 write!(
146 f,
147 "Target cell occupied at row {}, col {}",
148 cell.coord.row(),
149 cell.coord.col()
150 )
151 }
152 EditorError::OutOfBounds { row, col } => {
153 write!(f, "Cell position out of bounds: row {row}, col {col}")
154 }
155 EditorError::InvalidName { name, reason } => {
156 write!(f, "Invalid name '{name}': {reason}")
157 }
158 EditorError::TransactionFailed { reason } => {
159 write!(f, "Transaction failed: {reason}")
160 }
161 EditorError::TransactionUnsupported { reason } => {
162 write!(f, "Transaction unsupported: {reason}")
163 }
164 EditorError::NoActiveTransaction => {
165 write!(f, "No active transaction")
166 }
167 EditorError::VertexNotFound { id } => {
168 write!(f, "Vertex not found: {id:?}")
169 }
170 EditorError::Excel(e) => write!(f, "Excel error: {e:?}"),
171 }
172 }
173}
174
175impl std::error::Error for EditorError {}
176
177pub trait SpillValueReader {
212 fn read_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue>;
213}
214
215fn moved_run_holds(runs: &[crate::engine::graph::ShiftedRun], v: VertexId) -> bool {
217 runs.iter()
218 .any(|r| r.moved && v.0 >= r.first.0 && v.0 - r.first.0 < r.len)
219}
220
221enum EditorLogger<'g> {
224 Dyn(&'g mut dyn ChangeLogger),
225 Capture(&'g mut MutationCapture),
226}
227
228impl EditorLogger<'_> {
229 #[inline]
230 fn record(&mut self, event: ChangeEvent) {
231 match self {
232 EditorLogger::Dyn(l) => l.record(event),
233 EditorLogger::Capture(c) => c.record(event),
234 }
235 }
236
237 #[inline]
238 fn begin_compound(&mut self, description: String) {
239 match self {
240 EditorLogger::Dyn(l) => l.begin_compound(description),
241 EditorLogger::Capture(c) => c.begin_compound(description),
242 }
243 }
244
245 #[inline]
246 fn end_compound(&mut self) {
247 match self {
248 EditorLogger::Dyn(l) => l.end_compound(),
249 EditorLogger::Capture(c) => c.end_compound(),
250 }
251 }
252
253 fn record_formula_run(&mut self, run: FormulaRunAdjusted) {
256 match self {
257 EditorLogger::Dyn(l) => {
258 for event in run.events() {
259 l.record(event);
260 }
261 }
262 EditorLogger::Capture(c) => c.record_lazy(run),
263 }
264 }
265}
266
267pub struct VertexEditor<'g> {
268 graph: &'g mut DependencyGraph,
269 change_logger: Option<EditorLogger<'g>>,
270 spill_value_reader: Option<&'g dyn SpillValueReader>,
271 structural_occupancy: Option<crate::engine::graph::StructuralOccupancy>,
272 batch_mode: bool,
273}
274
275impl<'g> VertexEditor<'g> {
276 pub fn new(graph: &'g mut DependencyGraph) -> Self {
278 Self {
279 graph,
280 change_logger: None,
281 spill_value_reader: None,
282 structural_occupancy: None,
283 batch_mode: false,
284 }
285 }
286
287 pub(crate) fn with_structural_occupancy(
289 mut self,
290 occupancy: crate::engine::graph::StructuralOccupancy,
291 ) -> Self {
292 self.structural_occupancy = Some(occupancy);
293 self
294 }
295
296 pub(crate) fn set_structural_occupancy(
297 &mut self,
298 occupancy: crate::engine::graph::StructuralOccupancy,
299 ) {
300 self.structural_occupancy = Some(occupancy);
301 }
302
303 pub fn with_logger<L: ChangeLogger + 'g>(
305 graph: &'g mut DependencyGraph,
306 logger: &'g mut L,
307 ) -> Self {
308 Self {
309 graph,
310 change_logger: Some(EditorLogger::Dyn(logger as &'g mut dyn ChangeLogger)),
311 spill_value_reader: None,
312 structural_occupancy: None,
313 batch_mode: false,
314 }
315 }
316
317 pub fn with_logger_and_spill_reader<L: ChangeLogger + 'g>(
319 graph: &'g mut DependencyGraph,
320 logger: &'g mut L,
321 spill_value_reader: &'g dyn SpillValueReader,
322 ) -> Self {
323 Self {
324 graph,
325 change_logger: Some(EditorLogger::Dyn(logger as &'g mut dyn ChangeLogger)),
326 spill_value_reader: Some(spill_value_reader),
327 structural_occupancy: None,
328 batch_mode: false,
329 }
330 }
331
332 pub(crate) fn with_capture_and_spill_reader(
335 graph: &'g mut DependencyGraph,
336 capture: &'g mut MutationCapture,
337 spill_value_reader: &'g dyn SpillValueReader,
338 ) -> Self {
339 Self {
340 graph,
341 change_logger: Some(EditorLogger::Capture(capture)),
342 spill_value_reader: Some(spill_value_reader),
343 structural_occupancy: None,
344 batch_mode: false,
345 }
346 }
347
348 pub fn begin_batch(&mut self) {
350 if !self.batch_mode {
351 self.graph.begin_batch();
352 self.batch_mode = true;
353 }
354 }
355
356 pub fn commit_batch(&mut self) {
358 if self.batch_mode {
359 self.graph.end_batch();
360 self.batch_mode = false;
361 }
362 }
363
364 fn log_change(&mut self, event: ChangeEvent) {
366 if let Some(logger) = &mut self.change_logger {
367 logger.record(event);
368 }
369 }
370
371 fn snapshot_spill_for_anchor(
372 &self,
373 anchor: VertexId,
374 ) -> Option<crate::engine::graph::editor::change_log::SpillSnapshot> {
375 let cells = self.graph.spill_cells_for_anchor(anchor)?.to_vec();
376 if cells.is_empty() {
377 return None;
378 }
379
380 let max = self.graph.get_config().spill.max_spill_cells as usize;
382 let mut cells = cells;
383 if cells.len() > max {
384 cells.truncate(max);
385 }
386
387 let first = *cells.first().expect("non-empty spill cells");
388 let sheet_name = self.graph.sheet_name(first.sheet_id).to_string();
389 let row0 = first.coord.row();
390 let col0 = first.coord.col();
391
392 let mut max_row = row0;
393 let mut max_col = col0;
394 let mut by_coord: FxHashMap<(u32, u32), LiteralValue> = FxHashMap::default();
395 for cell in &cells {
396 max_row = max_row.max(cell.coord.row());
397 max_col = max_col.max(cell.coord.col());
398 let v = if let Some(reader) = self.spill_value_reader {
399 reader
400 .read_cell_value(&sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
401 .unwrap_or(LiteralValue::Empty)
402 } else {
403 self.graph
404 .get_cell_value(&sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
405 .unwrap_or(LiteralValue::Empty)
406 };
407 by_coord.insert((cell.coord.row(), cell.coord.col()), v);
408 }
409
410 let rows = (max_row - row0 + 1) as usize;
411 let cols = (max_col - col0 + 1) as usize;
412 let mut values: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
413 for r in 0..rows {
414 let mut row: Vec<LiteralValue> = Vec::with_capacity(cols);
415 for c in 0..cols {
416 row.push(
417 by_coord
418 .get(&(row0 + r as u32, col0 + c as u32))
419 .cloned()
420 .unwrap_or(LiteralValue::Empty),
421 );
422 }
423 values.push(row);
424 }
425
426 if self.graph.spill_may_be_intruded(anchor) {
429 cells.retain(|cell| {
430 self.graph.spill_registry_anchor_for_cell(*cell) == Some(anchor)
431 && !self.graph.is_foreign_formula_cell(cell, anchor)
432 });
433 }
434 Some(crate::engine::graph::editor::change_log::SpillSnapshot {
435 target_cells: cells,
436 values,
437 })
438 }
439
440 pub fn commit_spill_region(
442 &mut self,
443 anchor: VertexId,
444 target_cells: Vec<CellRef>,
445 values: Vec<Vec<LiteralValue>>,
446 ) -> Result<(), EditorError> {
447 let old = self.snapshot_spill_for_anchor(anchor);
448 self.graph
449 .commit_spill_region_atomic_with_fault(
450 anchor,
451 target_cells.clone(),
452 values.clone(),
453 None,
454 )
455 .map_err(EditorError::Excel)?;
456 self.log_change(ChangeEvent::SpillCommitted {
457 anchor,
458 old,
459 new: crate::engine::graph::editor::change_log::SpillSnapshot {
460 target_cells,
461 values,
462 },
463 });
464 Ok(())
465 }
466
467 pub fn clear_spill_region(&mut self, anchor: VertexId) {
469 let Some(old) = self.snapshot_spill_for_anchor(anchor) else {
470 return;
471 };
472 self.graph.clear_spill_region(anchor);
473 self.log_change(ChangeEvent::SpillCleared { anchor, old });
474 }
475
476 pub fn has_logger(&self) -> bool {
478 self.change_logger.is_some()
479 }
480
481 fn get_formula_ast(&self, id: VertexId) -> Option<ASTNode> {
482 self.graph.get_formula(id)
483 }
484
485 fn snapshot_named_definitions(&self) -> FxHashMap<(NameScope, String), NamedDefinition> {
486 let mut out: FxHashMap<(NameScope, String), NamedDefinition> = FxHashMap::default();
487 for (name, nr) in self.graph.named_ranges_iter() {
488 out.insert((NameScope::Workbook, name.clone()), nr.definition.clone());
489 }
490 for ((sheet_id, name), nr) in self.graph.sheet_named_ranges_iter() {
491 out.insert(
492 (NameScope::Sheet(*sheet_id), name.clone()),
493 nr.definition.clone(),
494 );
495 }
496 out
497 }
498
499 pub fn resize_table(
503 &mut self,
504 name: &str,
505 new_range: crate::reference::RangeRef,
506 ) -> Result<(), EditorError> {
507 self.resize_table_body(name, new_range, false)
508 }
509
510 pub fn resize_table_body(
511 &mut self,
512 name: &str,
513 new_range: crate::reference::RangeRef,
514 new_empty_body: bool,
515 ) -> Result<(), EditorError> {
516 let old_empty_body = self
517 .graph
518 .resolve_table_entry(name)
519 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Name))?
520 .empty_body;
521 let old_range = self
522 .graph
523 .resize_table_body(name, new_range, new_empty_body)?;
524 if old_range != new_range || old_empty_body != new_empty_body {
525 self.log_change(ChangeEvent::TableResized {
526 name: name.to_string(),
527 old_range,
528 new_range,
529 old_empty_body,
530 new_empty_body,
531 });
532 }
533 Ok(())
534 }
535
536 fn materialize_empty_tables(&mut self, cell: CellRef) {
537 for table in self.graph.empty_tables_at(cell) {
538 self.resize_table_body(&table.name, table.range, false)
539 .expect("existing table extent is valid");
540 }
541 }
542
543 pub(crate) fn inverse_compound_end(&mut self, description: &str) {
551 self.graph.undo_structural_extent(description);
552 }
553
554 pub fn apply_inverse(&mut self, change: ChangeEvent) -> Result<(), EditorError> {
556 match change {
557 ChangeEvent::SetValue {
558 addr,
559 old_value,
560 old_formula,
561 new: _,
562 } => {
563 if let Some(old_formula) = old_formula {
565 self.set_cell_formula(addr, old_formula);
566 } else if let Some(old_value) = old_value {
567 self.set_cell_value(addr, old_value);
568 } else {
569 self.graph.retire_cell_for_replay(addr);
574 }
575 }
576 ChangeEvent::SetFormula {
577 addr,
578 old_value,
579 old_formula,
580 new: _,
581 } => {
582 if let Some(old_formula) = old_formula {
584 self.set_cell_formula(addr, old_formula);
585 } else if let Some(old_value) = old_value {
586 self.set_cell_value(addr, old_value);
587 } else {
588 self.graph.retire_cell_for_replay(addr);
593 }
594 }
595 ChangeEvent::TableResized {
596 name,
597 old_range,
598 old_empty_body,
599 ..
600 } => {
601 self.graph
602 .resize_table_body(&name, old_range, old_empty_body)?;
603 }
604 ChangeEvent::SetRowVisibility { .. } => {
605 }
607 ChangeEvent::AddVertex { id, .. } => {
608 let _ = self.remove_vertex(id); }
611 ChangeEvent::RemoveVertex {
612 id,
613 old_value,
614 old_formula,
615 old_dependencies,
616 old_dependents,
617 coord,
618 sheet_id,
619 kind,
620 ..
621 } => {
622 if let (Some(c), Some(sid)) = (coord, sheet_id) {
623 let meta =
624 VertexMeta::new(c.row(), c.col(), sid, kind.unwrap_or(VertexKind::Cell));
625 let new_id = if self.graph.revive_vertex(id, sid, c) {
630 if self.has_logger() {
631 self.log_change(ChangeEvent::AddVertex {
632 id,
633 coord: meta.coord,
634 sheet_id: meta.sheet_id,
635 value: Some(LiteralValue::Empty),
636 formula: None,
637 kind: Some(meta.kind),
638 flags: Some(meta.flags),
639 });
640 }
641 id
642 } else {
643 self.try_add_vertex(meta)?
644 };
645 if let Some(v) = old_value {
646 let cell_ref = self.graph.make_cell_ref_internal(sid, c.row(), c.col());
647 self.set_cell_value(cell_ref, v);
648 }
649 if let Some(f) = old_formula {
650 let cell_ref = self.graph.make_cell_ref_internal(sid, c.row(), c.col());
651 self.set_cell_formula(cell_ref, f);
652 }
653 #[cfg(any(test, feature = "legacy_oracle"))]
656 {
657 for dep in old_dependencies {
658 self.graph.add_dependency_edge(new_id, dep)?;
659 }
660 for parent in old_dependents {
661 self.graph.add_dependency_edge(parent, new_id)?;
662 }
663 }
664 #[cfg(not(any(test, feature = "legacy_oracle")))]
665 let _ = (old_dependencies, old_dependents, new_id);
666 }
667 }
668 ChangeEvent::DefineName { name, scope, .. } => {
669 self.graph.delete_name(&name, scope)?;
671 }
672 ChangeEvent::UpdateName {
673 name,
674 scope,
675 old_definition,
676 ..
677 } => {
678 self.graph.update_name(&name, old_definition, scope)?;
680 }
681 ChangeEvent::DeleteName {
682 name,
683 scope,
684 old_definition,
685 } => {
686 if let Some(def) = old_definition {
687 self.graph.define_name(&name, def, scope)?;
688 } else {
689 return Err(EditorError::TransactionFailed {
690 reason: "Missing old definition for name deletion rollback".to_string(),
691 });
692 }
693 }
694 ChangeEvent::SpillCommitted { anchor, old, .. } => {
695 if let Some(old) = old {
697 self.graph
698 .restore_spill_snapshot(anchor, &old)
699 .map_err(EditorError::Excel)?;
700 } else {
701 self.graph.clear_spill_region(anchor);
702 }
703 }
704 ChangeEvent::SpillCleared { anchor, old } => {
705 self.graph
707 .restore_spill_snapshot(anchor, &old)
708 .map_err(EditorError::Excel)?;
709 }
710 ChangeEvent::StagedFormulaCellChanged { .. } => {
711 }
713 ChangeEvent::CompoundStart { description, .. } => {
715 self.graph.replay_structural_marker(&description, false);
718 }
719 ChangeEvent::CompoundEnd { .. } => {
720 }
723 ChangeEvent::VertexMoved {
724 id,
725 sheet_id: _,
726 old_coord,
727 ..
728 } => {
729 self.move_vertex(id, old_coord)?;
731 }
732 ChangeEvent::FormulaAdjusted { id, old_ast, .. } => {
733 self.graph
735 .update_vertex_formula(id, old_ast)
736 .map_err(EditorError::Excel)?;
737 self.graph.mark_vertex_dirty(id);
738 }
739 ChangeEvent::NamedRangeAdjusted {
740 name,
741 scope,
742 old_definition,
743 ..
744 } => {
745 self.graph.update_name(&name, old_definition, scope)?;
747 }
748 ChangeEvent::EdgeAdded { from, to } => {
749 return Err(EditorError::TransactionFailed {
752 reason: "Cannot rollback edge addition yet".to_string(),
753 });
754 }
755 ChangeEvent::EdgeRemoved { from, to } => {
756 return Err(EditorError::TransactionFailed {
759 reason: "Cannot rollback edge removal yet".to_string(),
760 });
761 }
762 }
763 Ok(())
764 }
765
766 pub fn add_vertex(&mut self, meta: VertexMeta) -> VertexId {
771 self.try_add_vertex(meta)
772 .unwrap_or_else(|_| VertexId::new(0))
773 }
774
775 pub fn try_add_vertex(&mut self, meta: VertexMeta) -> Result<VertexId, EditorError> {
776 let id = self
780 .graph
781 .add_empty_vertex(meta.sheet_id, meta.coord.row(), meta.coord.col())
782 .map_err(EditorError::Excel)?;
783
784 if self.has_logger() && id.0 != 0 {
785 self.log_change(ChangeEvent::AddVertex {
786 id,
787 coord: meta.coord,
788 sheet_id: meta.sheet_id,
789 value: Some(LiteralValue::Empty),
790 formula: None,
791 kind: Some(meta.kind),
792 flags: Some(meta.flags),
793 });
794 }
795 Ok(id)
796 }
797
798 pub fn remove_vertex(&mut self, id: VertexId) -> Result<(), EditorError> {
800 if !self.graph.vertex_exists(id) {
802 return Err(EditorError::Excel(
803 ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
804 ));
805 }
806 self.graph.materialize_vertex(id);
807
808 let spill_snapshot = self.snapshot_spill_for_anchor(id);
811 let did_spill_clear = spill_snapshot.is_some();
812 if let Some(old_spill) = spill_snapshot {
813 if let Some(logger) = &mut self.change_logger {
814 logger.begin_compound(format!("RemoveVertexWithSpillClear id={}", id.0));
815 }
816 self.graph.clear_spill_region(id);
817 self.log_change(ChangeEvent::SpillCleared {
818 anchor: id,
819 old: old_spill,
820 });
821 }
822
823 let dependents = self.graph.authority_in_edge_readers(id);
825
826 let (
828 old_value,
829 old_formula,
830 old_dependencies,
831 old_dependents,
832 coord,
833 sheet_id_opt,
834 kind,
835 flags,
836 ) = if self.has_logger() {
837 let coord = self.graph.get_grid_addr(id);
838 let sheet_id = self.graph.get_sheet_id(id);
839 let kind = self.graph.get_vertex_kind(id);
840 let flags = 0u8;
842 (
843 self.graph.get_value(id),
844 self.get_formula_ast(id),
845 {
847 #[cfg(any(test, feature = "legacy_oracle"))]
848 let deps = self.graph.get_dependencies(id);
849 #[cfg(not(any(test, feature = "legacy_oracle")))]
850 let deps = Vec::new();
851 deps
852 },
853 dependents.clone(), coord,
855 Some(sheet_id),
856 Some(kind),
857 Some(flags),
858 )
859 } else {
860 (None, None, vec![], vec![], None, None, None, None)
861 };
862
863 self.graph.journal_formula_left(id);
866 if let Some(cell_ref) = self.graph.get_cell_ref_for_vertex(id) {
868 self.graph.remove_cell_mapping(&cell_ref);
869 let (r, c) = (cell_ref.coord.row(), cell_ref.coord.col());
871 self.graph
872 .forget_extent_cells(cell_ref.sheet_id, (r, r), (c, c));
873 }
874
875 self.graph.forget_declared_dynamic_anchor(id);
879 self.graph.vertex_formulas.remove(&id);
880 self.graph.vertex_values.remove(&id);
881 self.graph.clear_formula_vertex_dirty(id);
882 self.graph.mark_volatile(id, false);
883 self.graph.store.set_kind(id, VertexKind::Empty);
884 self.graph.store.set_dynamic(id, false);
885
886 self.graph.remove_all_edges(id);
888
889 for dep_id in &dependents {
891 self.graph.mark_as_ref_error(*dep_id);
892 }
893
894 self.graph.mark_deleted(id, true);
896
897 self.log_change(ChangeEvent::RemoveVertex {
899 id,
900 old_value,
901 old_formula,
902 old_dependencies,
903 old_dependents,
904 coord,
905 sheet_id: sheet_id_opt,
906 kind,
907 flags,
908 });
909
910 if did_spill_clear && let Some(logger) = &mut self.change_logger {
911 logger.end_compound();
912 }
913
914 Ok(())
915 }
916
917 pub fn remove_vertex_at(&mut self, cell: CellRef) -> Result<(), EditorError> {
919 if let Some(id) = self.graph.get_vertex_for_cell(&cell) {
920 self.remove_vertex(id)
921 } else {
922 let (r, c) = (cell.coord.row(), cell.coord.col());
925 self.graph
926 .forget_extent_cells(cell.sheet_id, (r, r), (c, c));
927 Ok(())
928 }
929 }
930
931 pub fn move_vertex(&mut self, id: VertexId, new_coord: GridAddr) -> Result<(), EditorError> {
939 self.move_vertex_inner(id, new_coord, true)
940 }
941
942 fn move_vertex_inner(
945 &mut self,
946 id: VertexId,
947 new_coord: GridAddr,
948 map_cell: bool,
949 ) -> Result<(), EditorError> {
950 self.graph.authority_note_structural(false);
951 self.graph.own_formula_id(id);
954 if !self.graph.vertex_exists(id) {
956 return Err(EditorError::Excel(
957 ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
958 ));
959 }
960 if self.graph.get_grid_addr(id).is_none() {
961 return Err(EditorError::Excel(
962 ExcelError::new(ExcelErrorKind::Ref).with_message(
963 "Symbol vertices have no position and cannot be moved onto the grid",
964 ),
965 ));
966 }
967
968 let old_cell_ref = self.graph.get_cell_ref_for_vertex(id);
970
971 let sheet_id = self.graph.get_sheet_id(id);
973 let new_cell_ref = CellRef::new(
974 sheet_id,
975 Coord::new(new_coord.row(), new_coord.col(), true, true),
976 );
977
978 self.graph.set_grid_addr(id, new_coord);
980
981 self.graph.update_edge_grid_addr(id, new_coord);
983
984 if map_cell {
986 self.graph
987 .update_cell_mapping(id, old_cell_ref, new_cell_ref);
988 }
989
990 self.graph.mark_dependents_dirty(id);
992
993 Ok(())
994 }
995
996 pub fn patch_vertex_meta(
998 &mut self,
999 id: VertexId,
1000 patch: VertexMetaPatch,
1001 ) -> Result<MetaUpdateSummary, EditorError> {
1002 if !self.graph.vertex_exists(id) {
1003 return Err(EditorError::Excel(
1004 ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
1005 ));
1006 }
1007
1008 let mut summary = MetaUpdateSummary::default();
1009
1010 if let Some(coord) = patch.coord {
1011 if self.graph.get_grid_addr(id).is_none() {
1013 return Err(EditorError::Excel(
1014 ExcelError::new(ExcelErrorKind::Ref).with_message(
1015 "Symbol vertices have no position and cannot be moved onto the grid",
1016 ),
1017 ));
1018 }
1019 let _ = self.graph.own_formula_id(id);
1021 self.graph.set_grid_addr(id, coord);
1022 self.graph.update_edge_grid_addr(id, coord);
1023 summary.coord_changed = true;
1024 }
1025
1026 if let Some(kind) = patch.kind {
1027 self.graph.set_kind(id, kind);
1028 summary.kind_changed = true;
1029 }
1030
1031 if let Some(dirty) = patch.dirty {
1032 self.graph.set_dirty(id, dirty);
1033 summary.flags_changed = true;
1034 }
1035
1036 if let Some(volatile) = patch.volatile {
1037 self.graph.mark_volatile(id, volatile);
1038 summary.flags_changed = true;
1039 }
1040
1041 Ok(summary)
1042 }
1043
1044 pub fn patch_vertex_data(
1046 &mut self,
1047 id: VertexId,
1048 patch: VertexDataPatch,
1049 ) -> Result<DataUpdateSummary, EditorError> {
1050 if !self.graph.vertex_exists(id) {
1051 return Err(EditorError::Excel(
1052 ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
1053 ));
1054 }
1055
1056 let mut summary = DataUpdateSummary::default();
1057
1058 if let Some(value) = patch.value {
1059 self.graph.update_vertex_value(id, value);
1060 summary.value_changed = true;
1061
1062 let dependents = self.graph.authority_in_edge_readers(id);
1065 for dep in &dependents {
1066 self.graph.set_dirty(*dep, true);
1067 }
1068 summary.dependents_marked_dirty = dependents;
1069 }
1070
1071 if let Some(_formula) = patch.formula {
1072 summary.formula_changed = true;
1075 }
1076
1077 Ok(summary)
1078 }
1079
1080 pub fn add_edge(&mut self, from: VertexId, to: VertexId) -> bool {
1083 if from == to {
1084 return false; }
1086
1087 true
1090 }
1091
1092 pub fn remove_edge(&mut self, _from: VertexId, _to: VertexId) -> bool {
1094 true
1096 }
1097
1098 pub fn insert_rows(
1100 &mut self,
1101 sheet_id: SheetId,
1102 before: u32,
1103 count: u32,
1104 ) -> Result<ShiftSummary, EditorError> {
1105 let result = self.insert_rows_impl(sheet_id, before, count);
1106 self.graph.authority_end_structural();
1107 result
1108 }
1109
1110 fn insert_rows_impl(
1111 &mut self,
1112 sheet_id: SheetId,
1113 before: u32,
1114 count: u32,
1115 ) -> Result<ShiftSummary, EditorError> {
1116 self.graph.authority_note_structural_shift();
1117 if count == 0 {
1118 return Ok(ShiftSummary::default());
1119 }
1120
1121 let mut summary = ShiftSummary::default();
1122
1123 self.begin_batch();
1125 let conservative = crate::engine::graph::StructuralOccupancy::conservative();
1126 let occupancy = self.structural_occupancy.as_ref().unwrap_or(&conservative);
1127 let range_dependents = self.graph.authority_structural_band_readers(
1128 sheet_id,
1129 crate::engine::graph::StructuralEdit::InsertRows { before },
1130 occupancy,
1131 );
1132 #[cfg(test)]
1133 {
1134 summary.structural_dependents_dirtied = range_dependents.clone();
1135 }
1136 self.graph.mark_dirty_many(&range_dependents);
1137
1138 let vertices_to_shift: Vec<(VertexId, GridAddr)> = self
1140 .graph
1141 .grid_vertices_in_sheet(sheet_id)
1142 .filter(|(_, coord)| coord.row() >= before)
1143 .collect();
1144
1145 if let Some(logger) = &mut self.change_logger {
1146 logger.begin_compound(format!(
1147 "InsertRows sheet={sheet_id} before={before} count={count}"
1148 ));
1149 }
1150 let op = ShiftOperation::InsertRows {
1153 sheet_id,
1154 before,
1155 count,
1156 };
1157 self.shift_and_adjust(
1158 sheet_id,
1159 &op,
1160 vertices_to_shift,
1161 |old_coord| GridAddr::new(old_coord.row() + count, old_coord.col()),
1162 &mut summary,
1163 )?;
1164
1165 let old_names = if self.has_logger() {
1167 Some(self.snapshot_named_definitions())
1168 } else {
1169 None
1170 };
1171 self.graph.adjust_named_ranges(&op)?;
1172 if let Some(old_names) = old_names {
1173 let new_names = self.snapshot_named_definitions();
1174 for ((scope, name), old_definition) in old_names {
1175 if let Some(new_definition) = new_names.get(&(scope, name.clone()))
1176 && *new_definition != old_definition
1177 {
1178 self.log_change(ChangeEvent::NamedRangeAdjusted {
1179 name,
1180 scope,
1181 old_definition,
1182 new_definition: new_definition.clone(),
1183 });
1184 }
1185 }
1186 }
1187
1188 if let Some(logger) = &mut self.change_logger {
1190 logger.end_compound();
1191 }
1192
1193 self.commit_batch();
1194
1195 Ok(summary)
1196 }
1197
1198 pub fn delete_rows(
1200 &mut self,
1201 sheet_id: SheetId,
1202 start: u32,
1203 count: u32,
1204 ) -> Result<ShiftSummary, EditorError> {
1205 let result = self.delete_rows_impl(sheet_id, start, count);
1206 self.graph.authority_end_structural();
1207 result
1208 }
1209
1210 fn delete_rows_impl(
1211 &mut self,
1212 sheet_id: SheetId,
1213 start: u32,
1214 count: u32,
1215 ) -> Result<ShiftSummary, EditorError> {
1216 if count == 0 {
1217 self.graph.authority_note_structural_shift();
1218 return Ok(ShiftSummary::default());
1219 }
1220 let end = start.saturating_add(count - 1);
1225 let rects: Vec<(SheetId, u32, u32, u32, u32)> = match self
1226 .structural_occupancy
1227 .as_ref()
1228 .and_then(|o| o.occupied_column_runs())
1229 {
1230 Some(runs) => runs
1231 .into_iter()
1232 .map(|(c0, c1)| (sheet_id, start, end, c0, c1))
1233 .collect(),
1234 None => vec![(sheet_id, start, end, 0, 16_383)],
1235 };
1236 let _ = self.graph.mark_dirty_rects(&rects);
1237 self.graph.authority_note_structural_shift();
1238
1239 let mut summary = ShiftSummary::default();
1240
1241 self.begin_batch();
1242
1243 if let Some(logger) = &mut self.change_logger {
1244 logger.begin_compound(format!(
1245 "DeleteRows sheet={sheet_id} start={start} count={count}"
1246 ));
1247 }
1248
1249 let vertices_to_delete: Vec<VertexId> = self
1251 .graph
1252 .grid_vertices_in_sheet(sheet_id)
1253 .filter(|(_, coord)| coord.row() >= start && coord.row() < start + count)
1254 .map(|(id, _)| id)
1255 .collect();
1256 let conservative = crate::engine::graph::StructuralOccupancy::conservative();
1257 let occupancy = self.structural_occupancy.as_ref().unwrap_or(&conservative);
1258 let range_dependents = self.graph.authority_structural_band_readers(
1259 sheet_id,
1260 crate::engine::graph::StructuralEdit::DeleteRows {
1261 start,
1262 end: start.saturating_add(count).saturating_sub(1).max(start),
1263 },
1264 occupancy,
1265 );
1266 #[cfg(test)]
1267 {
1268 summary.structural_dependents_dirtied = range_dependents.clone();
1269 }
1270 self.graph.mark_dirty_many(&range_dependents);
1271
1272 for id in vertices_to_delete {
1273 self.remove_vertex(id)?;
1274 summary.vertices_deleted.push(id);
1275 }
1276 let vertices_to_shift: Vec<(VertexId, GridAddr)> = self
1279 .graph
1280 .grid_vertices_in_sheet(sheet_id)
1281 .filter(|(_, coord)| coord.row() >= start + count)
1282 .collect();
1283 let op = ShiftOperation::DeleteRows {
1284 sheet_id,
1285 start,
1286 count,
1287 };
1288 self.shift_and_adjust(
1289 sheet_id,
1290 &op,
1291 vertices_to_shift,
1292 |old_coord| GridAddr::new(old_coord.row() - count, old_coord.col()),
1293 &mut summary,
1294 )?;
1295
1296 let old_names = if self.has_logger() {
1298 Some(self.snapshot_named_definitions())
1299 } else {
1300 None
1301 };
1302 self.graph.adjust_named_ranges(&op)?;
1303 if let Some(old_names) = old_names {
1304 let new_names = self.snapshot_named_definitions();
1305 for ((scope, name), old_definition) in old_names {
1306 if let Some(new_definition) = new_names.get(&(scope, name.clone()))
1307 && *new_definition != old_definition
1308 {
1309 self.log_change(ChangeEvent::NamedRangeAdjusted {
1310 name,
1311 scope,
1312 old_definition,
1313 new_definition: new_definition.clone(),
1314 });
1315 }
1316 }
1317 }
1318
1319 if let Some(logger) = &mut self.change_logger {
1321 logger.end_compound();
1322 }
1323
1324 self.commit_batch();
1325
1326 Ok(summary)
1327 }
1328
1329 pub fn insert_columns(
1331 &mut self,
1332 sheet_id: SheetId,
1333 before: u32,
1334 count: u32,
1335 ) -> Result<ShiftSummary, EditorError> {
1336 let result = self.insert_columns_impl(sheet_id, before, count);
1337 self.graph.authority_end_structural();
1338 result
1339 }
1340
1341 fn insert_columns_impl(
1342 &mut self,
1343 sheet_id: SheetId,
1344 before: u32,
1345 count: u32,
1346 ) -> Result<ShiftSummary, EditorError> {
1347 self.graph.authority_note_structural_shift();
1348 if count == 0 {
1349 return Ok(ShiftSummary::default());
1350 }
1351
1352 let mut summary = ShiftSummary::default();
1353
1354 self.begin_batch();
1356 let conservative = crate::engine::graph::StructuralOccupancy::conservative();
1357 let occupancy = self.structural_occupancy.as_ref().unwrap_or(&conservative);
1358 let range_dependents = self.graph.authority_structural_band_readers(
1359 sheet_id,
1360 crate::engine::graph::StructuralEdit::InsertColumns { before },
1361 occupancy,
1362 );
1363 #[cfg(test)]
1364 {
1365 summary.structural_dependents_dirtied = range_dependents.clone();
1366 }
1367 self.graph.mark_dirty_many(&range_dependents);
1368
1369 let vertices_to_shift: Vec<(VertexId, GridAddr)> = self
1371 .graph
1372 .grid_vertices_in_sheet(sheet_id)
1373 .filter(|(_, coord)| coord.col() >= before)
1374 .collect();
1375
1376 if let Some(logger) = &mut self.change_logger {
1377 logger.begin_compound(format!(
1378 "InsertColumns sheet={sheet_id} before={before} count={count}"
1379 ));
1380 }
1381 let op = ShiftOperation::InsertColumns {
1384 sheet_id,
1385 before,
1386 count,
1387 };
1388 self.shift_and_adjust(
1389 sheet_id,
1390 &op,
1391 vertices_to_shift,
1392 |old_coord| GridAddr::new(old_coord.row(), old_coord.col() + count),
1393 &mut summary,
1394 )?;
1395
1396 let old_names = if self.has_logger() {
1398 Some(self.snapshot_named_definitions())
1399 } else {
1400 None
1401 };
1402 self.graph.adjust_named_ranges(&op)?;
1403 if let Some(old_names) = old_names {
1404 let new_names = self.snapshot_named_definitions();
1405 for ((scope, name), old_definition) in old_names {
1406 if let Some(new_definition) = new_names.get(&(scope, name.clone()))
1407 && *new_definition != old_definition
1408 {
1409 self.log_change(ChangeEvent::NamedRangeAdjusted {
1410 name,
1411 scope,
1412 old_definition,
1413 new_definition: new_definition.clone(),
1414 });
1415 }
1416 }
1417 }
1418
1419 if let Some(logger) = &mut self.change_logger {
1421 logger.end_compound();
1422 }
1423
1424 self.commit_batch();
1425
1426 Ok(summary)
1427 }
1428
1429 fn shift_and_adjust(
1442 &mut self,
1443 sheet_id: SheetId,
1444 op: &ShiftOperation,
1445 vertices_to_shift: Vec<(VertexId, GridAddr)>,
1446 shift: impl Fn(GridAddr) -> GridAddr,
1447 summary: &mut ShiftSummary,
1448 ) -> Result<(), EditorError> {
1449 let journal = self.has_logger();
1450 self.graph.shift_retired_ids(op, journal);
1451 let runs = self.graph.shift_virtual_runs(op);
1452
1453 for (id, old_coord) in vertices_to_shift {
1454 let new_coord = shift(old_coord);
1455 if self.has_logger() {
1456 self.log_change(ChangeEvent::VertexMoved {
1457 id,
1458 sheet_id,
1459 old_coord,
1460 new_coord,
1461 });
1462 }
1463 if self.graph.is_virtual_member(id) {
1464 debug_assert_eq!(self.graph.get_grid_addr(id), Some(new_coord));
1466 } else {
1467 let cell = CellRef::new(
1473 sheet_id,
1474 Coord::new(new_coord.row(), new_coord.col(), true, true),
1475 );
1476 let member_moves_later = self
1477 .graph
1478 .virtual_member_at(&cell)
1479 .is_some_and(|m| m.0 > id.0 && moved_run_holds(&runs, m));
1480 self.move_vertex_inner(id, new_coord, !member_moves_later)?;
1481 }
1482 summary.vertices_moved.push(id);
1483 }
1484 self.graph.after_shift_moves(&runs);
1485
1486 let adjuster = ReferenceAdjuster::new();
1487 let mut adjusted_runs: Vec<&crate::engine::graph::ShiftedRun> =
1488 runs.iter().filter(|r| r.adjusted.is_some()).collect();
1489 adjusted_runs.sort_unstable_by_key(|r| r.first.0);
1490 let mut next_run = 0;
1491 let formula_vertices = self.graph.materialized_formula_vertices_sorted();
1492 for id in formula_vertices {
1493 while next_run < adjusted_runs.len() && adjusted_runs[next_run].first.0 < id.0 {
1494 self.log_adjusted_run(adjusted_runs[next_run], summary);
1495 next_run += 1;
1496 }
1497 if let Some(ast) = self.get_formula_ast(id)
1498 && let Some(adjusted) = adjuster.adjust_ast_if_changed_in_context(
1499 &ast,
1500 op,
1501 &ReferenceContext::new(self.graph.get_sheet_id(id), self.graph.sheet_reg()),
1502 )
1503 {
1504 if self.has_logger() {
1505 self.log_change(ChangeEvent::FormulaAdjusted {
1506 id,
1507 addr: self.graph.get_cell_ref_for_vertex(id),
1508 old_ast: ast.clone(),
1509 new_ast: adjusted.clone(),
1510 });
1511 }
1512 self.graph.update_vertex_formula(id, adjusted)?;
1513 self.graph.mark_vertex_dirty(id);
1514 summary.formulas_updated += 1;
1515 }
1516 }
1517 for run in &adjusted_runs[next_run..] {
1518 self.log_adjusted_run(run, summary);
1519 }
1520 Ok(())
1521 }
1522
1523 fn log_adjusted_run(
1527 &mut self,
1528 run: &crate::engine::graph::ShiftedRun,
1529 summary: &mut ShiftSummary,
1530 ) {
1531 summary.formulas_updated += run.len as usize;
1532 let Some((old_first, new_first)) = &run.adjusted else {
1533 return;
1534 };
1535 if let Some(logger) = &mut self.change_logger {
1536 logger.record_formula_run(FormulaRunAdjusted {
1537 first: run.first,
1538 len: run.len,
1539 sheet_id: run.sheet,
1540 col: run.col,
1541 row0: run.row0,
1542 old_first: old_first.clone(),
1543 new_first: new_first.clone(),
1544 });
1545 }
1546 }
1547
1548 pub fn delete_columns(
1550 &mut self,
1551 sheet_id: SheetId,
1552 start: u32,
1553 count: u32,
1554 ) -> Result<ShiftSummary, EditorError> {
1555 let result = self.delete_columns_impl(sheet_id, start, count);
1556 self.graph.authority_end_structural();
1557 result
1558 }
1559
1560 fn delete_columns_impl(
1561 &mut self,
1562 sheet_id: SheetId,
1563 start: u32,
1564 count: u32,
1565 ) -> Result<ShiftSummary, EditorError> {
1566 if count == 0 {
1567 self.graph.authority_note_structural_shift();
1568 return Ok(ShiftSummary::default());
1569 }
1570 let _ = self.graph.mark_dirty_rects(&[(
1574 sheet_id,
1575 0,
1576 1_048_575,
1577 start,
1578 start.saturating_add(count - 1),
1579 )]);
1580 self.graph.authority_note_structural_shift();
1581
1582 let mut summary = ShiftSummary::default();
1583
1584 self.begin_batch();
1585
1586 if let Some(logger) = &mut self.change_logger {
1587 logger.begin_compound(format!(
1588 "DeleteColumns sheet={sheet_id} start={start} count={count}"
1589 ));
1590 }
1591
1592 let vertices_to_delete: Vec<VertexId> = self
1594 .graph
1595 .grid_vertices_in_sheet(sheet_id)
1596 .filter(|(_, coord)| coord.col() >= start && coord.col() < start + count)
1597 .map(|(id, _)| id)
1598 .collect();
1599 let conservative = crate::engine::graph::StructuralOccupancy::conservative();
1600 let occupancy = self.structural_occupancy.as_ref().unwrap_or(&conservative);
1601 let range_dependents = self.graph.authority_structural_band_readers(
1602 sheet_id,
1603 crate::engine::graph::StructuralEdit::DeleteColumns {
1604 start,
1605 end: start.saturating_add(count).saturating_sub(1).max(start),
1606 },
1607 occupancy,
1608 );
1609 #[cfg(test)]
1610 {
1611 summary.structural_dependents_dirtied = range_dependents.clone();
1612 }
1613 self.graph.mark_dirty_many(&range_dependents);
1614
1615 for id in vertices_to_delete {
1616 self.remove_vertex(id)?;
1617 summary.vertices_deleted.push(id);
1618 }
1619 let vertices_to_shift: Vec<(VertexId, GridAddr)> = self
1622 .graph
1623 .grid_vertices_in_sheet(sheet_id)
1624 .filter(|(_, coord)| coord.col() >= start + count)
1625 .collect();
1626 let op = ShiftOperation::DeleteColumns {
1627 sheet_id,
1628 start,
1629 count,
1630 };
1631 self.shift_and_adjust(
1632 sheet_id,
1633 &op,
1634 vertices_to_shift,
1635 |old_coord| GridAddr::new(old_coord.row(), old_coord.col() - count),
1636 &mut summary,
1637 )?;
1638
1639 let old_names = if self.has_logger() {
1641 Some(self.snapshot_named_definitions())
1642 } else {
1643 None
1644 };
1645 self.graph.adjust_named_ranges(&op)?;
1646 if let Some(old_names) = old_names {
1647 let new_names = self.snapshot_named_definitions();
1648 for ((scope, name), old_definition) in old_names {
1649 if let Some(new_definition) = new_names.get(&(scope, name.clone()))
1650 && *new_definition != old_definition
1651 {
1652 self.log_change(ChangeEvent::NamedRangeAdjusted {
1653 name,
1654 scope,
1655 old_definition,
1656 new_definition: new_definition.clone(),
1657 });
1658 }
1659 }
1660 }
1661
1662 if let Some(logger) = &mut self.change_logger {
1664 logger.end_compound();
1665 }
1666
1667 self.commit_batch();
1668
1669 Ok(summary)
1670 }
1671
1672 pub fn shift_rows(&mut self, sheet_id: SheetId, start_row: u32, delta: i32) {
1674 if delta == 0 {
1675 return;
1676 }
1677
1678 let change_event = ChangeEvent::SetValue {
1680 addr: CellRef {
1681 sheet_id,
1682 coord: Coord::new(start_row, 0, true, true),
1683 },
1684 old_value: None,
1685 old_formula: None,
1686 new: LiteralValue::Text(format!("Row shift: start={start_row}, delta={delta}")),
1687 };
1688 self.log_change(change_event);
1689
1690 }
1693
1694 pub fn shift_columns(&mut self, sheet_id: SheetId, start_col: u32, delta: i32) {
1696 if delta == 0 {
1697 return;
1698 }
1699
1700 let change_event = ChangeEvent::SetValue {
1702 addr: CellRef {
1703 sheet_id,
1704 coord: Coord::new(0, start_col, true, true),
1705 },
1706 old_value: None,
1707 old_formula: None,
1708 new: LiteralValue::Text(format!("Column shift: start={start_col}, delta={delta}")),
1709 };
1710 self.log_change(change_event);
1711
1712 }
1715
1716 pub fn set_cell_value(&mut self, cell_ref: CellRef, value: LiteralValue) -> VertexId {
1719 self.set_cell_value_with_old_state(cell_ref, value, None, None)
1720 }
1721
1722 pub fn set_cell_value_with_old_state(
1732 &mut self,
1733 cell_ref: CellRef,
1734 value: LiteralValue,
1735 fallback_old_value: Option<LiteralValue>,
1736 fallback_old_formula: Option<ASTNode>,
1737 ) -> VertexId {
1738 if value != LiteralValue::Empty {
1739 self.materialize_empty_tables(cell_ref);
1740 }
1741 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id).to_string();
1742
1743 let old_id = self.graph.get_vertex_id_for_address(&cell_ref);
1746 let old_value = old_id
1747 .and_then(|id| self.graph.get_value(id))
1748 .or(fallback_old_value);
1749 let old_formula = old_id
1750 .and_then(|id| self.get_formula_ast(id))
1751 .or(fallback_old_formula);
1752
1753 let spill_owner = self
1756 .graph
1757 .spill_registry_anchor_for_cell(cell_ref)
1758 .or(old_id);
1759 let spill_snapshot =
1760 spill_owner.and_then(|id| self.snapshot_spill_for_anchor(id).map(|s| (id, s)));
1761 let did_spill_clear = spill_snapshot.is_some();
1762 if let Some((anchor, old_spill)) = spill_snapshot {
1763 if let Some(logger) = &mut self.change_logger {
1764 logger.begin_compound(format!(
1765 "SetValueWithSpillClear sheet={} row={} col={}",
1766 cell_ref.sheet_id,
1767 cell_ref.coord.row(),
1768 cell_ref.coord.col()
1769 ));
1770 }
1771 self.graph.clear_spill_region(anchor);
1772 if self.graph.get_cell_ref(anchor) != Some(cell_ref) {
1773 self.graph.mark_dirty_many(&[anchor]);
1774 }
1775 self.log_change(ChangeEvent::SpillCleared {
1776 anchor,
1777 old: old_spill,
1778 });
1779 }
1780
1781 match self.graph.set_cell_value(
1784 &sheet_name,
1785 cell_ref.coord.row() + 1,
1786 cell_ref.coord.col() + 1,
1787 value.clone(),
1788 ) {
1789 Ok(_) => {
1790 let change_event = ChangeEvent::SetValue {
1792 addr: cell_ref,
1793 old_value,
1794 old_formula,
1795 new: value,
1796 };
1797 self.log_change(change_event);
1798
1799 if did_spill_clear && let Some(logger) = &mut self.change_logger {
1800 logger.end_compound();
1801 }
1802
1803 VertexId::new(0)
1805 }
1806 Err(_) => VertexId::new(0),
1807 }
1808 }
1809
1810 pub fn set_cell_formula(&mut self, cell_ref: CellRef, formula: ASTNode) -> VertexId {
1815 self.set_cell_formula_with_old_state(cell_ref, formula, None, None)
1816 }
1817
1818 pub fn try_set_cell_formula(
1820 &mut self,
1821 cell_ref: CellRef,
1822 formula: ASTNode,
1823 ) -> Result<VertexId, ExcelError> {
1824 self.try_set_cell_formula_with_old_state(cell_ref, formula, None, None)
1825 }
1826
1827 pub fn set_cell_formula_with_old_state(
1833 &mut self,
1834 cell_ref: CellRef,
1835 formula: ASTNode,
1836 fallback_old_value: Option<LiteralValue>,
1837 fallback_old_formula: Option<ASTNode>,
1838 ) -> VertexId {
1839 self.try_set_cell_formula_with_old_state(
1840 cell_ref,
1841 formula,
1842 fallback_old_value,
1843 fallback_old_formula,
1844 )
1845 .unwrap_or_else(|_| VertexId::new(0))
1846 }
1847
1848 pub fn try_set_cell_formula_with_old_state(
1851 &mut self,
1852 cell_ref: CellRef,
1853 formula: ASTNode,
1854 fallback_old_value: Option<LiteralValue>,
1855 fallback_old_formula: Option<ASTNode>,
1856 ) -> Result<VertexId, ExcelError> {
1857 self.set_cell_formula_with_old_state_and_plan(
1858 cell_ref,
1859 formula,
1860 fallback_old_value,
1861 fallback_old_formula,
1862 None,
1863 )
1864 }
1865
1866 pub(crate) fn set_cell_formula_with_prepared_plan(
1867 &mut self,
1868 cell_ref: CellRef,
1869 formula: ASTNode,
1870 fallback_old_value: Option<LiteralValue>,
1871 fallback_old_formula: Option<ASTNode>,
1872 ast_id: crate::engine::arena::AstNodeId,
1873 plan: crate::engine::ingest_pipeline::DependencyPlanRow,
1874 ) -> VertexId {
1875 self.set_cell_formula_with_old_state_and_plan(
1876 cell_ref,
1877 formula,
1878 fallback_old_value,
1879 fallback_old_formula,
1880 Some((ast_id, plan)),
1881 )
1882 .unwrap_or_else(|_| VertexId::new(0))
1883 }
1884
1885 fn set_cell_formula_with_old_state_and_plan(
1886 &mut self,
1887 cell_ref: CellRef,
1888 formula: ASTNode,
1889 fallback_old_value: Option<LiteralValue>,
1890 fallback_old_formula: Option<ASTNode>,
1891 prepared: Option<(
1892 crate::engine::arena::AstNodeId,
1893 crate::engine::ingest_pipeline::DependencyPlanRow,
1894 )>,
1895 ) -> Result<VertexId, ExcelError> {
1896 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id).to_string();
1897
1898 let old_id = self.graph.get_vertex_id_for_address(&cell_ref);
1901 let old_value = old_id
1902 .and_then(|id| self.graph.get_value(id))
1903 .or(fallback_old_value);
1904 let old_formula = old_id
1905 .and_then(|id| self.get_formula_ast(id))
1906 .or(fallback_old_formula);
1907
1908 let spill_owner = self
1911 .graph
1912 .spill_registry_anchor_for_cell(cell_ref)
1913 .or(old_id);
1914 let spill_snapshot =
1915 spill_owner.and_then(|id| self.snapshot_spill_for_anchor(id).map(|s| (id, s)));
1916 let did_spill_clear = spill_snapshot.is_some();
1917
1918 let result = if let Some((ast_id, plan)) = prepared {
1920 self.graph.set_cell_formula_with_plan(
1921 &sheet_name,
1922 cell_ref.coord.row() + 1,
1923 cell_ref.coord.col() + 1,
1924 ast_id,
1925 &plan,
1926 plan.volatile,
1927 plan.dynamic,
1928 )
1929 } else {
1930 self.graph.set_cell_formula(
1931 &sheet_name,
1932 cell_ref.coord.row() + 1,
1933 cell_ref.coord.col() + 1,
1934 formula.clone(),
1935 )
1936 };
1937 match result {
1938 Ok(summary) => {
1939 self.materialize_empty_tables(cell_ref);
1940 if let Some((anchor, old_spill)) = spill_snapshot {
1941 if let Some(logger) = &mut self.change_logger {
1942 logger.begin_compound(format!(
1943 "SetFormulaWithSpillClear sheet={} row={} col={}",
1944 cell_ref.sheet_id,
1945 cell_ref.coord.row(),
1946 cell_ref.coord.col()
1947 ));
1948 }
1949 self.graph.clear_spill_region(anchor);
1950 if self.graph.get_cell_ref(anchor) != Some(cell_ref) {
1951 self.graph.mark_dirty_many(&[anchor]);
1952 }
1953 self.log_change(ChangeEvent::SpillCleared {
1954 anchor,
1955 old: old_spill,
1956 });
1957 }
1958 let change_event = ChangeEvent::SetFormula {
1960 addr: cell_ref,
1961 old_value,
1962 old_formula,
1963 new: formula,
1964 };
1965 self.log_change(change_event);
1966
1967 if did_spill_clear && let Some(logger) = &mut self.change_logger {
1968 logger.end_compound();
1969 }
1970
1971 Ok(summary
1972 .affected_vertices
1973 .into_iter()
1974 .next()
1975 .unwrap_or(VertexId::new(0)))
1976 }
1977 Err(error) => Err(error),
1978 }
1979 }
1980
1981 pub fn set_range_values(
1985 &mut self,
1986 sheet_id: SheetId,
1987 start_row: u32,
1988 start_col: u32,
1989 values: &[Vec<LiteralValue>],
1990 ) -> Result<RangeSummary, EditorError> {
1991 let mut summary = RangeSummary::default();
1992
1993 self.begin_batch();
1994 self.graph.begin_deferred_dirty();
1998
1999 for (row_offset, row_values) in values.iter().enumerate() {
2000 for (col_offset, value) in row_values.iter().enumerate() {
2001 let row = start_row + row_offset as u32;
2002 let col = start_col + col_offset as u32;
2003 let cell_ref = self.graph.make_cell_ref_internal(sheet_id, row, col);
2004 let existing_id = self.graph.get_vertex_id_for_address(&cell_ref);
2005
2006 let id = self.set_cell_value(cell_ref, value.clone());
2007 match existing_id {
2008 Some(existing_id) => summary.vertices_updated.push(existing_id),
2009 None if id.0 != 0 => summary.vertices_created.push(id),
2010 None => {}
2011 }
2012 summary.cells_affected += 1;
2013 }
2014 }
2015
2016 let _ = self.graph.end_deferred_dirty();
2017 self.commit_batch();
2018
2019 Ok(summary)
2020 }
2021
2022 pub fn clear_range(
2024 &mut self,
2025 sheet_id: SheetId,
2026 start_row: u32,
2027 start_col: u32,
2028 end_row: u32,
2029 end_col: u32,
2030 ) -> Result<RangeSummary, EditorError> {
2031 let mut summary = RangeSummary::default();
2032
2033 self.begin_batch();
2034
2035 let vertices_in_range: Vec<_> = self
2037 .graph
2038 .grid_vertices_in_sheet(sheet_id)
2039 .filter(|(_, coord)| {
2040 let row = coord.row();
2041 let col = coord.col();
2042 row >= start_row && row <= end_row && col >= start_col && col <= end_col
2043 })
2044 .collect();
2045
2046 let mut vertex_cells = rustc_hash::FxHashSet::default();
2047 for (id, coord) in vertices_in_range {
2048 self.remove_vertex(id)?;
2049 vertex_cells.insert((coord.row(), coord.col()));
2050 summary.cells_affected += 1;
2051 }
2052 for (col, r0, r1) in
2055 self.graph
2056 .forget_extent_cells(sheet_id, (start_row, end_row), (start_col, end_col))
2057 {
2058 summary.cells_affected += (r0..=r1)
2059 .filter(|&row| !vertex_cells.contains(&(row, col)))
2060 .count();
2061 }
2062
2063 self.commit_batch();
2064
2065 Ok(summary)
2066 }
2067
2068 pub fn copy_range(
2070 &mut self,
2071 sheet_id: SheetId,
2072 from_start_row: u32,
2073 from_start_col: u32,
2074 from_end_row: u32,
2075 from_end_col: u32,
2076 to_sheet_id: SheetId,
2077 to_row: u32,
2078 to_col: u32,
2079 ) -> Result<RangeSummary, EditorError> {
2080 let row_offset = to_row as i32 - from_start_row as i32;
2081 let col_offset = to_col as i32 - from_start_col as i32;
2082
2083 let mut summary = RangeSummary::default();
2084 let mut cell_data = Vec::new();
2085
2086 let vertices_in_range: Vec<_> = self
2088 .graph
2089 .grid_vertices_in_sheet(sheet_id)
2090 .filter(|(_, coord)| {
2091 let row = coord.row();
2092 let col = coord.col();
2093 row >= from_start_row
2094 && row <= from_end_row
2095 && col >= from_start_col
2096 && col <= from_end_col
2097 })
2098 .collect();
2099
2100 for (id, coord) in vertices_in_range {
2101 let row = coord.row();
2102 let col = coord.col();
2103
2104 if let Some(formula) = self.get_formula_ast(id) {
2106 cell_data.push((
2107 row - from_start_row,
2108 col - from_start_col,
2109 CellData::Formula(formula),
2110 ));
2111 } else if let Some(value) = self.graph.get_value(id) {
2112 cell_data.push((
2113 row - from_start_row,
2114 col - from_start_col,
2115 CellData::Value(value),
2116 ));
2117 }
2118 }
2119
2120 self.begin_batch();
2121
2122 for (row_idx, col_idx, data) in cell_data {
2124 let dest_row = (to_row as i32 + row_idx as i32) as u32;
2125 let dest_col = (to_col as i32 + col_idx as i32) as u32;
2126
2127 match data {
2128 CellData::Value(value) => {
2129 let cell_ref =
2130 self.graph
2131 .make_cell_ref_internal(to_sheet_id, dest_row, dest_col);
2132
2133 if let Some(existing_id) = self.graph.get_vertex_id_for_address(&cell_ref) {
2134 self.graph.update_vertex_value(existing_id, value);
2135 self.graph.mark_vertex_dirty(existing_id);
2136 summary.vertices_updated.push(existing_id);
2137 } else {
2138 let meta =
2139 VertexMeta::new(dest_row, dest_col, to_sheet_id, VertexKind::Cell);
2140 let id = self.try_add_vertex(meta)?;
2141 self.graph.update_vertex_value(id, value);
2142 summary.vertices_created.push(id);
2143 }
2144 }
2145 CellData::Formula(formula) => {
2146 let adjuster = RelativeReferenceAdjuster::new(row_offset, col_offset);
2148 let adjusted = adjuster.adjust_formula(&formula);
2149
2150 let cell_ref =
2151 self.graph
2152 .make_cell_ref_internal(to_sheet_id, dest_row, dest_col);
2153
2154 if let Some(existing_id) = self.graph.get_vertex_id_for_address(&cell_ref) {
2155 self.graph.update_vertex_formula(existing_id, adjusted)?;
2156 summary.vertices_updated.push(existing_id);
2157 } else {
2158 let meta = VertexMeta::new(
2159 dest_row,
2160 dest_col,
2161 to_sheet_id,
2162 VertexKind::FormulaScalar,
2163 );
2164 let id = self.try_add_vertex(meta)?;
2165 self.graph.update_vertex_formula(id, adjusted)?;
2166 summary.vertices_created.push(id);
2167 }
2168 }
2169 }
2170
2171 summary.cells_affected += 1;
2172 }
2173
2174 self.commit_batch();
2175
2176 Ok(summary)
2177 }
2178
2179 pub fn move_range(
2181 &mut self,
2182 sheet_id: SheetId,
2183 from_start_row: u32,
2184 from_start_col: u32,
2185 from_end_row: u32,
2186 from_end_col: u32,
2187 to_sheet_id: SheetId,
2188 to_row: u32,
2189 to_col: u32,
2190 ) -> Result<RangeSummary, EditorError> {
2191 let result = self.move_range_impl(
2192 sheet_id,
2193 from_start_row,
2194 from_start_col,
2195 from_end_row,
2196 from_end_col,
2197 to_sheet_id,
2198 to_row,
2199 to_col,
2200 );
2201 self.graph.authority_end_structural();
2202 result
2203 }
2204
2205 fn move_range_impl(
2206 &mut self,
2207 sheet_id: SheetId,
2208 from_start_row: u32,
2209 from_start_col: u32,
2210 from_end_row: u32,
2211 from_end_col: u32,
2212 to_sheet_id: SheetId,
2213 to_row: u32,
2214 to_col: u32,
2215 ) -> Result<RangeSummary, EditorError> {
2216 self.graph.authority_note_structural(true);
2217 let mut summary = self.copy_range(
2219 sheet_id,
2220 from_start_row,
2221 from_start_col,
2222 from_end_row,
2223 from_end_col,
2224 to_sheet_id,
2225 to_row,
2226 to_col,
2227 )?;
2228
2229 let clear_summary = self.clear_range(
2231 sheet_id,
2232 from_start_row,
2233 from_start_col,
2234 from_end_row,
2235 from_end_col,
2236 )?;
2237
2238 summary.cells_moved = clear_summary.cells_affected;
2239
2240 let row_offset = to_row as i32 - from_start_row as i32;
2242 let col_offset = to_col as i32 - from_start_col as i32;
2243
2244 let all_formula_vertices: Vec<_> = self.graph.vertices_with_formulas().collect();
2246
2247 let from_sheet_name = self.graph.sheet_name(sheet_id).to_string();
2248 let to_sheet_name = self.graph.sheet_name(to_sheet_id).to_string();
2249 let adjuster = MoveReferenceAdjuster::new(
2250 sheet_id,
2251 from_sheet_name,
2252 from_start_row,
2253 from_start_col,
2254 from_end_row,
2255 from_end_col,
2256 to_sheet_id,
2257 to_sheet_name,
2258 row_offset,
2259 col_offset,
2260 );
2261
2262 for formula_id in all_formula_vertices {
2263 if let Some(formula) = self.get_formula_ast(formula_id) {
2264 let formula_sheet_id = self.graph.get_vertex_sheet_id(formula_id);
2265 if let Some(adjusted) = adjuster.adjust_if_references(&formula, formula_sheet_id) {
2266 self.graph.update_vertex_formula(formula_id, adjusted)?;
2267 }
2268 }
2269 }
2270
2271 Ok(summary)
2272 }
2273
2274 pub fn define_name(
2276 &mut self,
2277 name: &str,
2278 definition: NamedDefinition,
2279 scope: NameScope,
2280 ) -> Result<(), EditorError> {
2281 self.graph.define_name(name, definition.clone(), scope)?;
2282
2283 self.log_change(ChangeEvent::DefineName {
2284 name: name.to_string(),
2285 scope,
2286 definition,
2287 });
2288
2289 Ok(())
2290 }
2291
2292 pub fn define_name_for_cell(
2294 &mut self,
2295 name: &str,
2296 sheet_name: &str,
2297 row: u32,
2298 col: u32,
2299 scope: NameScope,
2300 ) -> Result<(), EditorError> {
2301 let sheet_id = self
2302 .graph
2303 .sheet_id(sheet_name)
2304 .ok_or_else(|| EditorError::InvalidName {
2305 name: sheet_name.to_string(),
2306 reason: "Sheet not found".to_string(),
2307 })?;
2308 let cell_ref = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
2309 self.define_name(name, NamedDefinition::Cell(cell_ref), scope)
2310 }
2311
2312 pub fn define_name_for_range(
2314 &mut self,
2315 name: &str,
2316 sheet_name: &str,
2317 start_row: u32,
2318 start_col: u32,
2319 end_row: u32,
2320 end_col: u32,
2321 scope: NameScope,
2322 ) -> Result<(), EditorError> {
2323 let sheet_id = self
2324 .graph
2325 .sheet_id(sheet_name)
2326 .ok_or_else(|| EditorError::InvalidName {
2327 name: sheet_name.to_string(),
2328 reason: "Sheet not found".to_string(),
2329 })?;
2330 let start = CellRef::new(
2331 sheet_id,
2332 Coord::from_excel(start_row, start_col, true, true),
2333 );
2334 let end = CellRef::new(sheet_id, Coord::from_excel(end_row, end_col, true, true));
2335 let range_ref = crate::reference::RangeRef::new(start, end);
2336 self.define_name(name, NamedDefinition::Range(range_ref), scope)
2337 }
2338
2339 pub fn update_name(
2341 &mut self,
2342 name: &str,
2343 new_definition: NamedDefinition,
2344 scope: NameScope,
2345 ) -> Result<(), EditorError> {
2346 let old_definition = self
2348 .graph
2349 .resolve_name(
2350 name,
2351 match scope {
2352 NameScope::Sheet(id) => id,
2353 NameScope::Workbook => 0,
2354 },
2355 )
2356 .cloned();
2357
2358 self.graph
2359 .update_name(name, new_definition.clone(), scope)?;
2360
2361 if let Some(old_def) = old_definition {
2362 self.log_change(ChangeEvent::UpdateName {
2363 name: name.to_string(),
2364 scope,
2365 old_definition: old_def,
2366 new_definition,
2367 });
2368 }
2369
2370 Ok(())
2371 }
2372
2373 pub fn delete_name(&mut self, name: &str, scope: NameScope) -> Result<(), EditorError> {
2375 let old_def = if self.has_logger() {
2377 self.graph
2378 .resolve_name(
2379 name,
2380 match scope {
2381 NameScope::Sheet(id) => id,
2382 NameScope::Workbook => 0,
2383 },
2384 )
2385 .cloned()
2386 } else {
2387 None
2388 };
2389
2390 self.graph.delete_name(name, scope)?;
2391 self.log_change(ChangeEvent::DeleteName {
2392 name: name.to_string(),
2393 scope,
2394 old_definition: old_def,
2395 });
2396
2397 Ok(())
2398 }
2399}
2400
2401enum CellData {
2403 Value(LiteralValue),
2404 Formula(ASTNode),
2405}
2406
2407impl<'g> Drop for VertexEditor<'g> {
2408 fn drop(&mut self) {
2409 if self.batch_mode {
2411 self.commit_batch();
2412 }
2413 }
2414}
2415
2416#[cfg(test)]
2417mod tests {
2418 use super::*;
2419 use crate::engine::graph::editor::change_log::{ChangeEvent, ChangeLog};
2420 use crate::reference::Coord;
2421
2422 fn create_test_graph() -> DependencyGraph {
2423 DependencyGraph::new()
2424 }
2425
2426 #[test]
2427 fn test_vertex_editor_creation() {
2428 let mut graph = create_test_graph();
2429 let editor = VertexEditor::new(&mut graph);
2430 assert!(!editor.has_logger());
2431 assert!(!editor.batch_mode);
2432 }
2433
2434 #[test]
2435 fn test_vertex_editor_with_logger() {
2436 let mut graph = create_test_graph();
2437 let mut log = ChangeLog::new();
2438 let editor = VertexEditor::with_logger(&mut graph, &mut log);
2439 assert!(editor.has_logger());
2440 assert!(!editor.batch_mode);
2441 }
2442
2443 #[test]
2444 fn test_add_vertex() {
2445 let mut graph = create_test_graph();
2446 let mut editor = VertexEditor::new(&mut graph);
2447
2448 let meta = VertexMeta::new(5, 10, 0, VertexKind::Cell).dirty();
2449 let vertex_id = editor.add_vertex(meta);
2450
2451 assert!(vertex_id.0 > 0);
2453 }
2454
2455 #[test]
2456 fn test_batch_operations() {
2457 let mut graph = create_test_graph();
2458 let mut editor = VertexEditor::new(&mut graph);
2459
2460 assert!(!editor.batch_mode);
2461 editor.begin_batch();
2462 assert!(editor.batch_mode);
2463
2464 let meta1 = VertexMeta::new(1, 1, 0, VertexKind::Cell);
2466 let meta2 = VertexMeta::new(2, 2, 0, VertexKind::Cell);
2467
2468 let id1 = editor.add_vertex(meta1);
2469 let id2 = editor.add_vertex(meta2);
2470
2471 assert!(editor.add_edge(id1, id2));
2473
2474 editor.commit_batch();
2475 assert!(!editor.batch_mode);
2476 }
2477
2478 #[test]
2479 fn test_remove_vertex() {
2480 let mut graph = create_test_graph();
2481 let mut editor = VertexEditor::new(&mut graph);
2482
2483 let meta = VertexMeta::new(3, 4, 0, VertexKind::Cell).dirty();
2484 let vertex_id = editor.add_vertex(meta);
2485
2486 assert!(editor.remove_vertex(vertex_id).is_ok());
2488 }
2489
2490 #[test]
2491 fn test_remove_vertex_clears_spill_registry_for_anchor() {
2492 let mut graph = create_test_graph();
2493 let sheet_id = graph.sheet_id_mut("Sheet1");
2494
2495 let anchor_cell = CellRef::new(sheet_id, Coord::new(0, 0, true, true));
2497 let anchor_vid = {
2498 let mut editor = VertexEditor::new(&mut graph);
2499 editor.set_cell_formula(anchor_cell, formualizer_parse::parser::parse("=0").unwrap())
2502 };
2503
2504 let target_cells = vec![
2505 CellRef::new(sheet_id, Coord::new(0, 0, true, true)),
2506 CellRef::new(sheet_id, Coord::new(0, 1, true, true)),
2507 CellRef::new(sheet_id, Coord::new(1, 0, true, true)),
2508 CellRef::new(sheet_id, Coord::new(1, 1, true, true)),
2509 ];
2510 let values = vec![
2511 vec![LiteralValue::Number(1.0), LiteralValue::Number(2.0)],
2512 vec![LiteralValue::Number(3.0), LiteralValue::Number(4.0)],
2513 ];
2514
2515 graph
2516 .commit_spill_region_atomic_with_fault(anchor_vid, target_cells.clone(), values, None)
2517 .unwrap();
2518
2519 assert!(graph.spill_registry_has_anchor(anchor_vid));
2520 for cell in &target_cells {
2521 assert_eq!(
2522 graph.spill_registry_anchor_for_cell(*cell),
2523 Some(anchor_vid)
2524 );
2525 }
2526
2527 {
2528 let mut editor = VertexEditor::new(&mut graph);
2529 editor.remove_vertex(anchor_vid).unwrap();
2530 }
2531
2532 assert!(!graph.spill_registry_has_anchor(anchor_vid));
2533 for cell in &target_cells {
2534 assert_eq!(graph.spill_registry_anchor_for_cell(*cell), None);
2535 }
2536 assert_eq!(graph.spill_registry_counts(), (0, 0));
2537 }
2538
2539 #[test]
2540 fn test_edge_operations() {
2541 let mut graph = create_test_graph();
2542 let mut editor = VertexEditor::new(&mut graph);
2543
2544 let meta1 = VertexMeta::new(1, 1, 0, VertexKind::Cell);
2545 let meta2 = VertexMeta::new(2, 2, 0, VertexKind::FormulaScalar);
2546
2547 let id1 = editor.add_vertex(meta1);
2548 let id2 = editor.add_vertex(meta2);
2549
2550 assert!(editor.add_edge(id1, id2));
2552
2553 assert!(!editor.add_edge(id1, id1));
2555
2556 assert!(editor.remove_edge(id1, id2));
2558 }
2559
2560 #[test]
2561 fn test_set_cell_value() {
2562 let mut graph = create_test_graph();
2563 let mut log = ChangeLog::new();
2564
2565 let cell_ref = CellRef {
2566 sheet_id: 0,
2567 coord: Coord::new(2, 3, true, true),
2568 };
2569 let value = LiteralValue::Number(42.0);
2570
2571 let vertex_id = {
2572 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2573 editor.set_cell_value(cell_ref, value.clone())
2574 };
2575
2576 assert_eq!(vertex_id.0, 0);
2578
2579 assert_eq!(log.len(), 1);
2581 match &log.events()[0] {
2582 ChangeEvent::SetValue { addr, new, .. } => {
2583 assert_eq!(addr.sheet_id, cell_ref.sheet_id);
2584 assert_eq!(addr.coord.row(), cell_ref.coord.row());
2585 assert_eq!(addr.coord.col(), cell_ref.coord.col());
2586 assert_eq!(new, &value);
2587 }
2588 _ => panic!("Expected SetValue event"),
2589 }
2590 }
2591
2592 #[test]
2593 fn test_set_cell_formula() {
2594 let mut graph = create_test_graph();
2595 let mut log = ChangeLog::new();
2596
2597 let cell_ref = CellRef {
2598 sheet_id: 0,
2599 coord: Coord::new(1, 1, true, true),
2600 };
2601
2602 use formualizer_parse::parser::ASTNodeType;
2603 let formula = formualizer_parse::parser::ASTNode {
2604 node_type: ASTNodeType::Literal(LiteralValue::Number(100.0)),
2605 source_token: None,
2606 contains_volatile: false,
2607 };
2608
2609 let vertex_id = {
2610 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2611 editor.set_cell_formula(cell_ref, formula.clone())
2612 };
2613
2614 assert!(vertex_id.0 > 0);
2616
2617 assert_eq!(log.len(), 1);
2619 match &log.events()[0] {
2620 ChangeEvent::SetFormula { addr, .. } => {
2621 assert_eq!(addr.sheet_id, cell_ref.sheet_id);
2622 assert_eq!(addr.coord.row(), cell_ref.coord.row());
2623 assert_eq!(addr.coord.col(), cell_ref.coord.col());
2624 }
2625 _ => panic!("Expected SetFormula event"),
2626 }
2627 }
2628
2629 #[test]
2630 fn test_shift_rows() {
2631 let mut graph = create_test_graph();
2632 let mut log = ChangeLog::new();
2633
2634 {
2635 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2636
2637 let cell1 = CellRef {
2639 sheet_id: 0,
2640 coord: Coord::new(5, 1, true, true),
2641 };
2642 let cell2 = CellRef {
2643 sheet_id: 0,
2644 coord: Coord::new(10, 1, true, true),
2645 };
2646 let cell3 = CellRef {
2647 sheet_id: 0,
2648 coord: Coord::new(15, 1, true, true),
2649 };
2650
2651 editor.set_cell_value(cell1, LiteralValue::Number(1.0));
2652 editor.set_cell_value(cell2, LiteralValue::Number(2.0));
2653 editor.set_cell_value(cell3, LiteralValue::Number(3.0));
2654 }
2655
2656 log.clear();
2658
2659 {
2660 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2661 editor.shift_rows(0, 10, 2);
2663 }
2664
2665 assert_eq!(log.len(), 1);
2667 match &log.events()[0] {
2668 ChangeEvent::SetValue { addr, new, .. } => {
2669 assert_eq!(addr.sheet_id, 0);
2670 assert_eq!(addr.coord.row(), 10);
2671 if let LiteralValue::Text(msg) = new {
2672 assert!(msg.contains("Row shift"));
2673 assert!(msg.contains("start=10"));
2674 assert!(msg.contains("delta=2"));
2675 }
2676 }
2677 _ => panic!("Expected SetValue event for row shift"),
2678 }
2679 }
2680
2681 #[test]
2682 fn test_shift_columns() {
2683 let mut graph = create_test_graph();
2684 let mut log = ChangeLog::new();
2685
2686 {
2687 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2688
2689 let cell1 = CellRef {
2691 sheet_id: 0,
2692 coord: Coord::new(1, 5, true, true),
2693 };
2694 let cell2 = CellRef {
2695 sheet_id: 0,
2696 coord: Coord::new(1, 10, true, true),
2697 };
2698
2699 editor.set_cell_value(cell1, LiteralValue::Number(1.0));
2700 editor.set_cell_value(cell2, LiteralValue::Number(2.0));
2701 }
2702
2703 log.clear();
2705
2706 {
2707 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2708 editor.shift_columns(0, 8, 3);
2710 }
2711
2712 assert_eq!(log.len(), 1);
2714 match &log.events()[0] {
2715 ChangeEvent::SetValue { addr, new, .. } => {
2716 assert_eq!(addr.sheet_id, 0);
2717 assert_eq!(addr.coord.col(), 8);
2718 if let LiteralValue::Text(msg) = new {
2719 assert!(msg.contains("Column shift"));
2720 assert!(msg.contains("start=8"));
2721 assert!(msg.contains("delta=3"));
2722 }
2723 }
2724 _ => panic!("Expected SetValue event for column shift"),
2725 }
2726 }
2727
2728 #[test]
2729 fn test_move_vertex() {
2730 let mut graph = create_test_graph();
2731 let mut editor = VertexEditor::new(&mut graph);
2732
2733 let meta = VertexMeta::new(5, 10, 0, VertexKind::Cell);
2734 let vertex_id = editor.add_vertex(meta);
2735
2736 assert!(editor.move_vertex(vertex_id, GridAddr::new(8, 12)).is_ok());
2738
2739 assert!(editor.move_vertex(vertex_id, GridAddr::new(8, 12)).is_ok());
2741 }
2742
2743 #[test]
2744 fn test_vertex_meta_builder() {
2745 let meta = VertexMeta::new(1, 2, 3, VertexKind::FormulaScalar)
2746 .dirty()
2747 .volatile()
2748 .with_flags(0x08);
2749
2750 assert_eq!(meta.coord.row(), 1);
2751 assert_eq!(meta.coord.col(), 2);
2752 assert_eq!(meta.sheet_id, 3);
2753 assert_eq!(meta.kind, VertexKind::FormulaScalar);
2754 assert_eq!(meta.flags, 0x08); }
2756
2757 #[test]
2758 fn test_change_log_management() {
2759 let mut graph = create_test_graph();
2760 let mut log = ChangeLog::new();
2761
2762 {
2763 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2764 let cell_ref = CellRef {
2765 sheet_id: 0,
2766 coord: Coord::new(0, 0, true, true),
2767 };
2768 editor.set_cell_value(cell_ref, LiteralValue::Number(1.0));
2769 editor.set_cell_value(cell_ref, LiteralValue::Number(2.0));
2770 }
2771
2772 assert_eq!(log.len(), 2);
2773
2774 log.clear();
2775 assert_eq!(log.len(), 0);
2776 }
2777
2778 #[test]
2779 fn test_editor_drop_commits_batch() {
2780 let mut graph = create_test_graph();
2781 {
2782 let mut editor = VertexEditor::new(&mut graph);
2783 editor.begin_batch();
2784
2785 let meta = VertexMeta::new(1, 1, 0, VertexKind::Cell);
2786 editor.add_vertex(meta);
2787
2788 }
2790
2791 }
2793}