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 Some(crate::engine::graph::editor::change_log::SpillSnapshot {
427 target_cells: cells,
428 values,
429 })
430 }
431
432 pub fn commit_spill_region(
434 &mut self,
435 anchor: VertexId,
436 target_cells: Vec<CellRef>,
437 values: Vec<Vec<LiteralValue>>,
438 ) -> Result<(), EditorError> {
439 let old = self.snapshot_spill_for_anchor(anchor);
440 self.graph
441 .commit_spill_region_atomic_with_fault(
442 anchor,
443 target_cells.clone(),
444 values.clone(),
445 None,
446 )
447 .map_err(EditorError::Excel)?;
448 self.log_change(ChangeEvent::SpillCommitted {
449 anchor,
450 old,
451 new: crate::engine::graph::editor::change_log::SpillSnapshot {
452 target_cells,
453 values,
454 },
455 });
456 Ok(())
457 }
458
459 pub fn clear_spill_region(&mut self, anchor: VertexId) {
461 let Some(old) = self.snapshot_spill_for_anchor(anchor) else {
462 return;
463 };
464 self.graph.clear_spill_region(anchor);
465 self.log_change(ChangeEvent::SpillCleared { anchor, old });
466 }
467
468 pub fn has_logger(&self) -> bool {
470 self.change_logger.is_some()
471 }
472
473 fn get_formula_ast(&self, id: VertexId) -> Option<ASTNode> {
474 self.graph.get_formula(id)
475 }
476
477 fn snapshot_named_definitions(&self) -> FxHashMap<(NameScope, String), NamedDefinition> {
478 let mut out: FxHashMap<(NameScope, String), NamedDefinition> = FxHashMap::default();
479 for (name, nr) in self.graph.named_ranges_iter() {
480 out.insert((NameScope::Workbook, name.clone()), nr.definition.clone());
481 }
482 for ((sheet_id, name), nr) in self.graph.sheet_named_ranges_iter() {
483 out.insert(
484 (NameScope::Sheet(*sheet_id), name.clone()),
485 nr.definition.clone(),
486 );
487 }
488 out
489 }
490
491 pub(crate) fn inverse_compound_end(&mut self, description: &str) {
501 self.graph.undo_structural_extent(description);
502 }
503
504 pub fn apply_inverse(&mut self, change: ChangeEvent) -> Result<(), EditorError> {
506 match change {
507 ChangeEvent::SetValue {
508 addr,
509 old_value,
510 old_formula,
511 new: _,
512 } => {
513 if let Some(old_formula) = old_formula {
515 self.set_cell_formula(addr, old_formula);
516 } else if let Some(old_value) = old_value {
517 self.set_cell_value(addr, old_value);
518 } else {
519 self.graph.retire_cell_for_replay(addr);
524 }
525 }
526 ChangeEvent::SetFormula {
527 addr,
528 old_value,
529 old_formula,
530 new: _,
531 } => {
532 if let Some(old_formula) = old_formula {
534 self.set_cell_formula(addr, old_formula);
535 } else if let Some(old_value) = old_value {
536 self.set_cell_value(addr, old_value);
537 } else {
538 self.graph.retire_cell_for_replay(addr);
543 }
544 }
545 ChangeEvent::SetRowVisibility { .. } => {
546 }
548 ChangeEvent::AddVertex { id, .. } => {
549 let _ = self.remove_vertex(id); }
552 ChangeEvent::RemoveVertex {
553 id,
554 old_value,
555 old_formula,
556 old_dependencies,
557 old_dependents,
558 coord,
559 sheet_id,
560 kind,
561 ..
562 } => {
563 if let (Some(c), Some(sid)) = (coord, sheet_id) {
564 let meta =
565 VertexMeta::new(c.row(), c.col(), sid, kind.unwrap_or(VertexKind::Cell));
566 let new_id = if self.graph.revive_vertex(id, sid, c) {
571 if self.has_logger() {
572 self.log_change(ChangeEvent::AddVertex {
573 id,
574 coord: meta.coord,
575 sheet_id: meta.sheet_id,
576 value: Some(LiteralValue::Empty),
577 formula: None,
578 kind: Some(meta.kind),
579 flags: Some(meta.flags),
580 });
581 }
582 id
583 } else {
584 self.try_add_vertex(meta)?
585 };
586 if let Some(v) = old_value {
587 let cell_ref = self.graph.make_cell_ref_internal(sid, c.row(), c.col());
588 self.set_cell_value(cell_ref, v);
589 }
590 if let Some(f) = old_formula {
591 let cell_ref = self.graph.make_cell_ref_internal(sid, c.row(), c.col());
592 self.set_cell_formula(cell_ref, f);
593 }
594 #[cfg(any(test, feature = "legacy_oracle"))]
597 {
598 for dep in old_dependencies {
599 self.graph.add_dependency_edge(new_id, dep)?;
600 }
601 for parent in old_dependents {
602 self.graph.add_dependency_edge(parent, new_id)?;
603 }
604 }
605 #[cfg(not(any(test, feature = "legacy_oracle")))]
606 let _ = (old_dependencies, old_dependents, new_id);
607 }
608 }
609 ChangeEvent::DefineName { name, scope, .. } => {
610 self.graph.delete_name(&name, scope)?;
612 }
613 ChangeEvent::UpdateName {
614 name,
615 scope,
616 old_definition,
617 ..
618 } => {
619 self.graph.update_name(&name, old_definition, scope)?;
621 }
622 ChangeEvent::DeleteName {
623 name,
624 scope,
625 old_definition,
626 } => {
627 if let Some(def) = old_definition {
628 self.graph.define_name(&name, def, scope)?;
629 } else {
630 return Err(EditorError::TransactionFailed {
631 reason: "Missing old definition for name deletion rollback".to_string(),
632 });
633 }
634 }
635 ChangeEvent::SpillCommitted { anchor, old, .. } => {
636 if let Some(old) = old {
638 self.graph
639 .commit_spill_region_atomic_with_fault(
640 anchor,
641 old.target_cells,
642 old.values,
643 None,
644 )
645 .map_err(EditorError::Excel)?;
646 } else {
647 self.graph.clear_spill_region(anchor);
648 }
649 }
650 ChangeEvent::SpillCleared { anchor, old } => {
651 self.graph
653 .commit_spill_region_atomic_with_fault(
654 anchor,
655 old.target_cells,
656 old.values,
657 None,
658 )
659 .map_err(EditorError::Excel)?;
660 }
661 ChangeEvent::StagedFormulaCellChanged { .. } => {
662 }
664 ChangeEvent::CompoundStart { description, .. } => {
666 self.graph.replay_structural_marker(&description, false);
669 }
670 ChangeEvent::CompoundEnd { .. } => {
671 }
674 ChangeEvent::VertexMoved {
675 id,
676 sheet_id: _,
677 old_coord,
678 ..
679 } => {
680 self.move_vertex(id, old_coord)?;
682 }
683 ChangeEvent::FormulaAdjusted { id, old_ast, .. } => {
684 self.graph
686 .update_vertex_formula(id, old_ast)
687 .map_err(EditorError::Excel)?;
688 self.graph.mark_vertex_dirty(id);
689 }
690 ChangeEvent::NamedRangeAdjusted {
691 name,
692 scope,
693 old_definition,
694 ..
695 } => {
696 self.graph.update_name(&name, old_definition, scope)?;
698 }
699 ChangeEvent::EdgeAdded { from, to } => {
700 return Err(EditorError::TransactionFailed {
703 reason: "Cannot rollback edge addition yet".to_string(),
704 });
705 }
706 ChangeEvent::EdgeRemoved { from, to } => {
707 return Err(EditorError::TransactionFailed {
710 reason: "Cannot rollback edge removal yet".to_string(),
711 });
712 }
713 }
714 Ok(())
715 }
716
717 pub fn add_vertex(&mut self, meta: VertexMeta) -> VertexId {
722 self.try_add_vertex(meta)
723 .unwrap_or_else(|_| VertexId::new(0))
724 }
725
726 pub fn try_add_vertex(&mut self, meta: VertexMeta) -> Result<VertexId, EditorError> {
727 let id = self
731 .graph
732 .add_empty_vertex(meta.sheet_id, meta.coord.row(), meta.coord.col())
733 .map_err(EditorError::Excel)?;
734
735 if self.has_logger() && id.0 != 0 {
736 self.log_change(ChangeEvent::AddVertex {
737 id,
738 coord: meta.coord,
739 sheet_id: meta.sheet_id,
740 value: Some(LiteralValue::Empty),
741 formula: None,
742 kind: Some(meta.kind),
743 flags: Some(meta.flags),
744 });
745 }
746 Ok(id)
747 }
748
749 pub fn remove_vertex(&mut self, id: VertexId) -> Result<(), EditorError> {
751 if !self.graph.vertex_exists(id) {
753 return Err(EditorError::Excel(
754 ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
755 ));
756 }
757 self.graph.materialize_vertex(id);
758
759 let spill_snapshot = self.snapshot_spill_for_anchor(id);
762 let did_spill_clear = spill_snapshot.is_some();
763 if let Some(old_spill) = spill_snapshot {
764 if let Some(logger) = &mut self.change_logger {
765 logger.begin_compound(format!("RemoveVertexWithSpillClear id={}", id.0));
766 }
767 self.graph.clear_spill_region(id);
768 self.log_change(ChangeEvent::SpillCleared {
769 anchor: id,
770 old: old_spill,
771 });
772 }
773
774 let dependents = self.graph.authority_in_edge_readers(id);
776
777 let (
779 old_value,
780 old_formula,
781 old_dependencies,
782 old_dependents,
783 coord,
784 sheet_id_opt,
785 kind,
786 flags,
787 ) = if self.has_logger() {
788 let coord = self.graph.get_grid_addr(id);
789 let sheet_id = self.graph.get_sheet_id(id);
790 let kind = self.graph.get_vertex_kind(id);
791 let flags = 0u8;
793 (
794 self.graph.get_value(id),
795 self.get_formula_ast(id),
796 {
798 #[cfg(any(test, feature = "legacy_oracle"))]
799 let deps = self.graph.get_dependencies(id);
800 #[cfg(not(any(test, feature = "legacy_oracle")))]
801 let deps = Vec::new();
802 deps
803 },
804 dependents.clone(), coord,
806 Some(sheet_id),
807 Some(kind),
808 Some(flags),
809 )
810 } else {
811 (None, None, vec![], vec![], None, None, None, None)
812 };
813
814 self.graph.journal_formula_left(id);
817 if let Some(cell_ref) = self.graph.get_cell_ref_for_vertex(id) {
819 self.graph.remove_cell_mapping(&cell_ref);
820 let (r, c) = (cell_ref.coord.row(), cell_ref.coord.col());
822 self.graph
823 .forget_extent_cells(cell_ref.sheet_id, (r, r), (c, c));
824 }
825
826 self.graph.vertex_formulas.remove(&id);
830 self.graph.vertex_values.remove(&id);
831 self.graph.clear_formula_vertex_dirty(id);
832 self.graph.mark_volatile(id, false);
833 self.graph.store.set_kind(id, VertexKind::Empty);
834 self.graph.store.set_dynamic(id, false);
835
836 self.graph.remove_all_edges(id);
838
839 for dep_id in &dependents {
841 self.graph.mark_as_ref_error(*dep_id);
842 }
843
844 self.graph.mark_deleted(id, true);
846
847 self.log_change(ChangeEvent::RemoveVertex {
849 id,
850 old_value,
851 old_formula,
852 old_dependencies,
853 old_dependents,
854 coord,
855 sheet_id: sheet_id_opt,
856 kind,
857 flags,
858 });
859
860 if did_spill_clear && let Some(logger) = &mut self.change_logger {
861 logger.end_compound();
862 }
863
864 Ok(())
865 }
866
867 pub fn remove_vertex_at(&mut self, cell: CellRef) -> Result<(), EditorError> {
869 if let Some(id) = self.graph.get_vertex_for_cell(&cell) {
870 self.remove_vertex(id)
871 } else {
872 let (r, c) = (cell.coord.row(), cell.coord.col());
875 self.graph
876 .forget_extent_cells(cell.sheet_id, (r, r), (c, c));
877 Ok(())
878 }
879 }
880
881 pub fn move_vertex(&mut self, id: VertexId, new_coord: GridAddr) -> Result<(), EditorError> {
889 self.move_vertex_inner(id, new_coord, true)
890 }
891
892 fn move_vertex_inner(
895 &mut self,
896 id: VertexId,
897 new_coord: GridAddr,
898 map_cell: bool,
899 ) -> Result<(), EditorError> {
900 self.graph.authority_note_structural(false);
901 self.graph.own_formula_id(id);
904 if !self.graph.vertex_exists(id) {
906 return Err(EditorError::Excel(
907 ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
908 ));
909 }
910 if self.graph.get_grid_addr(id).is_none() {
911 return Err(EditorError::Excel(
912 ExcelError::new(ExcelErrorKind::Ref).with_message(
913 "Symbol vertices have no position and cannot be moved onto the grid",
914 ),
915 ));
916 }
917
918 let old_cell_ref = self.graph.get_cell_ref_for_vertex(id);
920
921 let sheet_id = self.graph.get_sheet_id(id);
923 let new_cell_ref = CellRef::new(
924 sheet_id,
925 Coord::new(new_coord.row(), new_coord.col(), true, true),
926 );
927
928 self.graph.set_grid_addr(id, new_coord);
930
931 self.graph.update_edge_grid_addr(id, new_coord);
933
934 if map_cell {
936 self.graph
937 .update_cell_mapping(id, old_cell_ref, new_cell_ref);
938 }
939
940 self.graph.mark_dependents_dirty(id);
942
943 Ok(())
944 }
945
946 pub fn patch_vertex_meta(
948 &mut self,
949 id: VertexId,
950 patch: VertexMetaPatch,
951 ) -> Result<MetaUpdateSummary, EditorError> {
952 if !self.graph.vertex_exists(id) {
953 return Err(EditorError::Excel(
954 ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
955 ));
956 }
957
958 let mut summary = MetaUpdateSummary::default();
959
960 if let Some(coord) = patch.coord {
961 if self.graph.get_grid_addr(id).is_none() {
963 return Err(EditorError::Excel(
964 ExcelError::new(ExcelErrorKind::Ref).with_message(
965 "Symbol vertices have no position and cannot be moved onto the grid",
966 ),
967 ));
968 }
969 let _ = self.graph.own_formula_id(id);
971 self.graph.set_grid_addr(id, coord);
972 self.graph.update_edge_grid_addr(id, coord);
973 summary.coord_changed = true;
974 }
975
976 if let Some(kind) = patch.kind {
977 self.graph.set_kind(id, kind);
978 summary.kind_changed = true;
979 }
980
981 if let Some(dirty) = patch.dirty {
982 self.graph.set_dirty(id, dirty);
983 summary.flags_changed = true;
984 }
985
986 if let Some(volatile) = patch.volatile {
987 self.graph.mark_volatile(id, volatile);
988 summary.flags_changed = true;
989 }
990
991 Ok(summary)
992 }
993
994 pub fn patch_vertex_data(
996 &mut self,
997 id: VertexId,
998 patch: VertexDataPatch,
999 ) -> Result<DataUpdateSummary, EditorError> {
1000 if !self.graph.vertex_exists(id) {
1001 return Err(EditorError::Excel(
1002 ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
1003 ));
1004 }
1005
1006 let mut summary = DataUpdateSummary::default();
1007
1008 if let Some(value) = patch.value {
1009 self.graph.update_vertex_value(id, value);
1010 summary.value_changed = true;
1011
1012 let dependents = self.graph.authority_in_edge_readers(id);
1015 for dep in &dependents {
1016 self.graph.set_dirty(*dep, true);
1017 }
1018 summary.dependents_marked_dirty = dependents;
1019 }
1020
1021 if let Some(_formula) = patch.formula {
1022 summary.formula_changed = true;
1025 }
1026
1027 Ok(summary)
1028 }
1029
1030 pub fn add_edge(&mut self, from: VertexId, to: VertexId) -> bool {
1033 if from == to {
1034 return false; }
1036
1037 true
1040 }
1041
1042 pub fn remove_edge(&mut self, _from: VertexId, _to: VertexId) -> bool {
1044 true
1046 }
1047
1048 pub fn insert_rows(
1050 &mut self,
1051 sheet_id: SheetId,
1052 before: u32,
1053 count: u32,
1054 ) -> Result<ShiftSummary, EditorError> {
1055 let result = self.insert_rows_impl(sheet_id, before, count);
1056 self.graph.authority_end_structural();
1057 result
1058 }
1059
1060 fn insert_rows_impl(
1061 &mut self,
1062 sheet_id: SheetId,
1063 before: u32,
1064 count: u32,
1065 ) -> Result<ShiftSummary, EditorError> {
1066 self.graph.authority_note_structural_shift();
1067 if count == 0 {
1068 return Ok(ShiftSummary::default());
1069 }
1070
1071 let mut summary = ShiftSummary::default();
1072
1073 self.begin_batch();
1075 let conservative = crate::engine::graph::StructuralOccupancy::conservative();
1076 let occupancy = self.structural_occupancy.as_ref().unwrap_or(&conservative);
1077 let range_dependents = self.graph.authority_structural_band_readers(
1078 sheet_id,
1079 crate::engine::graph::StructuralEdit::InsertRows { before },
1080 occupancy,
1081 );
1082 #[cfg(test)]
1083 {
1084 summary.structural_dependents_dirtied = range_dependents.clone();
1085 }
1086 self.graph.mark_dirty_many(&range_dependents);
1087
1088 let vertices_to_shift: Vec<(VertexId, GridAddr)> = self
1090 .graph
1091 .grid_vertices_in_sheet(sheet_id)
1092 .filter(|(_, coord)| coord.row() >= before)
1093 .collect();
1094
1095 if let Some(logger) = &mut self.change_logger {
1096 logger.begin_compound(format!(
1097 "InsertRows sheet={sheet_id} before={before} count={count}"
1098 ));
1099 }
1100 let op = ShiftOperation::InsertRows {
1103 sheet_id,
1104 before,
1105 count,
1106 };
1107 self.shift_and_adjust(
1108 sheet_id,
1109 &op,
1110 vertices_to_shift,
1111 |old_coord| GridAddr::new(old_coord.row() + count, old_coord.col()),
1112 &mut summary,
1113 )?;
1114
1115 let old_names = if self.has_logger() {
1117 Some(self.snapshot_named_definitions())
1118 } else {
1119 None
1120 };
1121 self.graph.adjust_named_ranges(&op)?;
1122 if let Some(old_names) = old_names {
1123 let new_names = self.snapshot_named_definitions();
1124 for ((scope, name), old_definition) in old_names {
1125 if let Some(new_definition) = new_names.get(&(scope, name.clone()))
1126 && *new_definition != old_definition
1127 {
1128 self.log_change(ChangeEvent::NamedRangeAdjusted {
1129 name,
1130 scope,
1131 old_definition,
1132 new_definition: new_definition.clone(),
1133 });
1134 }
1135 }
1136 }
1137
1138 if let Some(logger) = &mut self.change_logger {
1140 logger.end_compound();
1141 }
1142
1143 self.commit_batch();
1144
1145 Ok(summary)
1146 }
1147
1148 pub fn delete_rows(
1150 &mut self,
1151 sheet_id: SheetId,
1152 start: u32,
1153 count: u32,
1154 ) -> Result<ShiftSummary, EditorError> {
1155 let result = self.delete_rows_impl(sheet_id, start, count);
1156 self.graph.authority_end_structural();
1157 result
1158 }
1159
1160 fn delete_rows_impl(
1161 &mut self,
1162 sheet_id: SheetId,
1163 start: u32,
1164 count: u32,
1165 ) -> Result<ShiftSummary, EditorError> {
1166 if count == 0 {
1167 self.graph.authority_note_structural_shift();
1168 return Ok(ShiftSummary::default());
1169 }
1170 let end = start.saturating_add(count - 1);
1175 let rects: Vec<(SheetId, u32, u32, u32, u32)> = match self
1176 .structural_occupancy
1177 .as_ref()
1178 .and_then(|o| o.occupied_column_runs())
1179 {
1180 Some(runs) => runs
1181 .into_iter()
1182 .map(|(c0, c1)| (sheet_id, start, end, c0, c1))
1183 .collect(),
1184 None => vec![(sheet_id, start, end, 0, 16_383)],
1185 };
1186 let _ = self.graph.mark_dirty_rects(&rects);
1187 self.graph.authority_note_structural_shift();
1188
1189 let mut summary = ShiftSummary::default();
1190
1191 self.begin_batch();
1192
1193 if let Some(logger) = &mut self.change_logger {
1194 logger.begin_compound(format!(
1195 "DeleteRows sheet={sheet_id} start={start} count={count}"
1196 ));
1197 }
1198
1199 let vertices_to_delete: Vec<VertexId> = self
1201 .graph
1202 .grid_vertices_in_sheet(sheet_id)
1203 .filter(|(_, coord)| coord.row() >= start && coord.row() < start + count)
1204 .map(|(id, _)| id)
1205 .collect();
1206 let conservative = crate::engine::graph::StructuralOccupancy::conservative();
1207 let occupancy = self.structural_occupancy.as_ref().unwrap_or(&conservative);
1208 let range_dependents = self.graph.authority_structural_band_readers(
1209 sheet_id,
1210 crate::engine::graph::StructuralEdit::DeleteRows {
1211 start,
1212 end: start.saturating_add(count).saturating_sub(1).max(start),
1213 },
1214 occupancy,
1215 );
1216 #[cfg(test)]
1217 {
1218 summary.structural_dependents_dirtied = range_dependents.clone();
1219 }
1220 self.graph.mark_dirty_many(&range_dependents);
1221
1222 for id in vertices_to_delete {
1223 self.remove_vertex(id)?;
1224 summary.vertices_deleted.push(id);
1225 }
1226 let vertices_to_shift: Vec<(VertexId, GridAddr)> = self
1229 .graph
1230 .grid_vertices_in_sheet(sheet_id)
1231 .filter(|(_, coord)| coord.row() >= start + count)
1232 .collect();
1233 let op = ShiftOperation::DeleteRows {
1234 sheet_id,
1235 start,
1236 count,
1237 };
1238 self.shift_and_adjust(
1239 sheet_id,
1240 &op,
1241 vertices_to_shift,
1242 |old_coord| GridAddr::new(old_coord.row() - count, old_coord.col()),
1243 &mut summary,
1244 )?;
1245
1246 let old_names = if self.has_logger() {
1248 Some(self.snapshot_named_definitions())
1249 } else {
1250 None
1251 };
1252 self.graph.adjust_named_ranges(&op)?;
1253 if let Some(old_names) = old_names {
1254 let new_names = self.snapshot_named_definitions();
1255 for ((scope, name), old_definition) in old_names {
1256 if let Some(new_definition) = new_names.get(&(scope, name.clone()))
1257 && *new_definition != old_definition
1258 {
1259 self.log_change(ChangeEvent::NamedRangeAdjusted {
1260 name,
1261 scope,
1262 old_definition,
1263 new_definition: new_definition.clone(),
1264 });
1265 }
1266 }
1267 }
1268
1269 if let Some(logger) = &mut self.change_logger {
1271 logger.end_compound();
1272 }
1273
1274 self.commit_batch();
1275
1276 Ok(summary)
1277 }
1278
1279 pub fn insert_columns(
1281 &mut self,
1282 sheet_id: SheetId,
1283 before: u32,
1284 count: u32,
1285 ) -> Result<ShiftSummary, EditorError> {
1286 let result = self.insert_columns_impl(sheet_id, before, count);
1287 self.graph.authority_end_structural();
1288 result
1289 }
1290
1291 fn insert_columns_impl(
1292 &mut self,
1293 sheet_id: SheetId,
1294 before: u32,
1295 count: u32,
1296 ) -> Result<ShiftSummary, EditorError> {
1297 self.graph.authority_note_structural_shift();
1298 if count == 0 {
1299 return Ok(ShiftSummary::default());
1300 }
1301
1302 let mut summary = ShiftSummary::default();
1303
1304 self.begin_batch();
1306 let conservative = crate::engine::graph::StructuralOccupancy::conservative();
1307 let occupancy = self.structural_occupancy.as_ref().unwrap_or(&conservative);
1308 let range_dependents = self.graph.authority_structural_band_readers(
1309 sheet_id,
1310 crate::engine::graph::StructuralEdit::InsertColumns { before },
1311 occupancy,
1312 );
1313 #[cfg(test)]
1314 {
1315 summary.structural_dependents_dirtied = range_dependents.clone();
1316 }
1317 self.graph.mark_dirty_many(&range_dependents);
1318
1319 let vertices_to_shift: Vec<(VertexId, GridAddr)> = self
1321 .graph
1322 .grid_vertices_in_sheet(sheet_id)
1323 .filter(|(_, coord)| coord.col() >= before)
1324 .collect();
1325
1326 if let Some(logger) = &mut self.change_logger {
1327 logger.begin_compound(format!(
1328 "InsertColumns sheet={sheet_id} before={before} count={count}"
1329 ));
1330 }
1331 let op = ShiftOperation::InsertColumns {
1334 sheet_id,
1335 before,
1336 count,
1337 };
1338 self.shift_and_adjust(
1339 sheet_id,
1340 &op,
1341 vertices_to_shift,
1342 |old_coord| GridAddr::new(old_coord.row(), old_coord.col() + count),
1343 &mut summary,
1344 )?;
1345
1346 let old_names = if self.has_logger() {
1348 Some(self.snapshot_named_definitions())
1349 } else {
1350 None
1351 };
1352 self.graph.adjust_named_ranges(&op)?;
1353 if let Some(old_names) = old_names {
1354 let new_names = self.snapshot_named_definitions();
1355 for ((scope, name), old_definition) in old_names {
1356 if let Some(new_definition) = new_names.get(&(scope, name.clone()))
1357 && *new_definition != old_definition
1358 {
1359 self.log_change(ChangeEvent::NamedRangeAdjusted {
1360 name,
1361 scope,
1362 old_definition,
1363 new_definition: new_definition.clone(),
1364 });
1365 }
1366 }
1367 }
1368
1369 if let Some(logger) = &mut self.change_logger {
1371 logger.end_compound();
1372 }
1373
1374 self.commit_batch();
1375
1376 Ok(summary)
1377 }
1378
1379 fn shift_and_adjust(
1392 &mut self,
1393 sheet_id: SheetId,
1394 op: &ShiftOperation,
1395 vertices_to_shift: Vec<(VertexId, GridAddr)>,
1396 shift: impl Fn(GridAddr) -> GridAddr,
1397 summary: &mut ShiftSummary,
1398 ) -> Result<(), EditorError> {
1399 let journal = self.has_logger();
1400 self.graph.shift_retired_ids(op, journal);
1401 let runs = self.graph.shift_virtual_runs(op);
1402
1403 for (id, old_coord) in vertices_to_shift {
1404 let new_coord = shift(old_coord);
1405 if self.has_logger() {
1406 self.log_change(ChangeEvent::VertexMoved {
1407 id,
1408 sheet_id,
1409 old_coord,
1410 new_coord,
1411 });
1412 }
1413 if self.graph.is_virtual_member(id) {
1414 debug_assert_eq!(self.graph.get_grid_addr(id), Some(new_coord));
1416 } else {
1417 let cell = CellRef::new(
1423 sheet_id,
1424 Coord::new(new_coord.row(), new_coord.col(), true, true),
1425 );
1426 let member_moves_later = self
1427 .graph
1428 .virtual_member_at(&cell)
1429 .is_some_and(|m| m.0 > id.0 && moved_run_holds(&runs, m));
1430 self.move_vertex_inner(id, new_coord, !member_moves_later)?;
1431 }
1432 summary.vertices_moved.push(id);
1433 }
1434 self.graph.after_shift_moves(&runs);
1435
1436 let adjuster = ReferenceAdjuster::new();
1437 let mut adjusted_runs: Vec<&crate::engine::graph::ShiftedRun> =
1438 runs.iter().filter(|r| r.adjusted.is_some()).collect();
1439 adjusted_runs.sort_unstable_by_key(|r| r.first.0);
1440 let mut next_run = 0;
1441 let formula_vertices = self.graph.materialized_formula_vertices_sorted();
1442 for id in formula_vertices {
1443 while next_run < adjusted_runs.len() && adjusted_runs[next_run].first.0 < id.0 {
1444 self.log_adjusted_run(adjusted_runs[next_run], summary);
1445 next_run += 1;
1446 }
1447 if let Some(ast) = self.get_formula_ast(id)
1448 && let Some(adjusted) = adjuster.adjust_ast_if_changed_in_context(
1449 &ast,
1450 op,
1451 &ReferenceContext::new(self.graph.get_sheet_id(id), self.graph.sheet_reg()),
1452 )
1453 {
1454 if self.has_logger() {
1455 self.log_change(ChangeEvent::FormulaAdjusted {
1456 id,
1457 addr: self.graph.get_cell_ref_for_vertex(id),
1458 old_ast: ast.clone(),
1459 new_ast: adjusted.clone(),
1460 });
1461 }
1462 self.graph.update_vertex_formula(id, adjusted)?;
1463 self.graph.mark_vertex_dirty(id);
1464 summary.formulas_updated += 1;
1465 }
1466 }
1467 for run in &adjusted_runs[next_run..] {
1468 self.log_adjusted_run(run, summary);
1469 }
1470 Ok(())
1471 }
1472
1473 fn log_adjusted_run(
1477 &mut self,
1478 run: &crate::engine::graph::ShiftedRun,
1479 summary: &mut ShiftSummary,
1480 ) {
1481 summary.formulas_updated += run.len as usize;
1482 let Some((old_first, new_first)) = &run.adjusted else {
1483 return;
1484 };
1485 if let Some(logger) = &mut self.change_logger {
1486 logger.record_formula_run(FormulaRunAdjusted {
1487 first: run.first,
1488 len: run.len,
1489 sheet_id: run.sheet,
1490 col: run.col,
1491 row0: run.row0,
1492 old_first: old_first.clone(),
1493 new_first: new_first.clone(),
1494 });
1495 }
1496 }
1497
1498 pub fn delete_columns(
1500 &mut self,
1501 sheet_id: SheetId,
1502 start: u32,
1503 count: u32,
1504 ) -> Result<ShiftSummary, EditorError> {
1505 let result = self.delete_columns_impl(sheet_id, start, count);
1506 self.graph.authority_end_structural();
1507 result
1508 }
1509
1510 fn delete_columns_impl(
1511 &mut self,
1512 sheet_id: SheetId,
1513 start: u32,
1514 count: u32,
1515 ) -> Result<ShiftSummary, EditorError> {
1516 if count == 0 {
1517 self.graph.authority_note_structural_shift();
1518 return Ok(ShiftSummary::default());
1519 }
1520 let _ = self.graph.mark_dirty_rects(&[(
1524 sheet_id,
1525 0,
1526 1_048_575,
1527 start,
1528 start.saturating_add(count - 1),
1529 )]);
1530 self.graph.authority_note_structural_shift();
1531
1532 let mut summary = ShiftSummary::default();
1533
1534 self.begin_batch();
1535
1536 if let Some(logger) = &mut self.change_logger {
1537 logger.begin_compound(format!(
1538 "DeleteColumns sheet={sheet_id} start={start} count={count}"
1539 ));
1540 }
1541
1542 let vertices_to_delete: Vec<VertexId> = self
1544 .graph
1545 .grid_vertices_in_sheet(sheet_id)
1546 .filter(|(_, coord)| coord.col() >= start && coord.col() < start + count)
1547 .map(|(id, _)| id)
1548 .collect();
1549 let conservative = crate::engine::graph::StructuralOccupancy::conservative();
1550 let occupancy = self.structural_occupancy.as_ref().unwrap_or(&conservative);
1551 let range_dependents = self.graph.authority_structural_band_readers(
1552 sheet_id,
1553 crate::engine::graph::StructuralEdit::DeleteColumns {
1554 start,
1555 end: start.saturating_add(count).saturating_sub(1).max(start),
1556 },
1557 occupancy,
1558 );
1559 #[cfg(test)]
1560 {
1561 summary.structural_dependents_dirtied = range_dependents.clone();
1562 }
1563 self.graph.mark_dirty_many(&range_dependents);
1564
1565 for id in vertices_to_delete {
1566 self.remove_vertex(id)?;
1567 summary.vertices_deleted.push(id);
1568 }
1569 let vertices_to_shift: Vec<(VertexId, GridAddr)> = self
1572 .graph
1573 .grid_vertices_in_sheet(sheet_id)
1574 .filter(|(_, coord)| coord.col() >= start + count)
1575 .collect();
1576 let op = ShiftOperation::DeleteColumns {
1577 sheet_id,
1578 start,
1579 count,
1580 };
1581 self.shift_and_adjust(
1582 sheet_id,
1583 &op,
1584 vertices_to_shift,
1585 |old_coord| GridAddr::new(old_coord.row(), old_coord.col() - count),
1586 &mut summary,
1587 )?;
1588
1589 let old_names = if self.has_logger() {
1591 Some(self.snapshot_named_definitions())
1592 } else {
1593 None
1594 };
1595 self.graph.adjust_named_ranges(&op)?;
1596 if let Some(old_names) = old_names {
1597 let new_names = self.snapshot_named_definitions();
1598 for ((scope, name), old_definition) in old_names {
1599 if let Some(new_definition) = new_names.get(&(scope, name.clone()))
1600 && *new_definition != old_definition
1601 {
1602 self.log_change(ChangeEvent::NamedRangeAdjusted {
1603 name,
1604 scope,
1605 old_definition,
1606 new_definition: new_definition.clone(),
1607 });
1608 }
1609 }
1610 }
1611
1612 if let Some(logger) = &mut self.change_logger {
1614 logger.end_compound();
1615 }
1616
1617 self.commit_batch();
1618
1619 Ok(summary)
1620 }
1621
1622 pub fn shift_rows(&mut self, sheet_id: SheetId, start_row: u32, delta: i32) {
1624 if delta == 0 {
1625 return;
1626 }
1627
1628 let change_event = ChangeEvent::SetValue {
1630 addr: CellRef {
1631 sheet_id,
1632 coord: Coord::new(start_row, 0, true, true),
1633 },
1634 old_value: None,
1635 old_formula: None,
1636 new: LiteralValue::Text(format!("Row shift: start={start_row}, delta={delta}")),
1637 };
1638 self.log_change(change_event);
1639
1640 }
1643
1644 pub fn shift_columns(&mut self, sheet_id: SheetId, start_col: u32, delta: i32) {
1646 if delta == 0 {
1647 return;
1648 }
1649
1650 let change_event = ChangeEvent::SetValue {
1652 addr: CellRef {
1653 sheet_id,
1654 coord: Coord::new(0, start_col, true, true),
1655 },
1656 old_value: None,
1657 old_formula: None,
1658 new: LiteralValue::Text(format!("Column shift: start={start_col}, delta={delta}")),
1659 };
1660 self.log_change(change_event);
1661
1662 }
1665
1666 pub fn set_cell_value(&mut self, cell_ref: CellRef, value: LiteralValue) -> VertexId {
1669 self.set_cell_value_with_old_state(cell_ref, value, None, None)
1670 }
1671
1672 pub fn set_cell_value_with_old_state(
1682 &mut self,
1683 cell_ref: CellRef,
1684 value: LiteralValue,
1685 fallback_old_value: Option<LiteralValue>,
1686 fallback_old_formula: Option<ASTNode>,
1687 ) -> VertexId {
1688 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id).to_string();
1689
1690 let old_id = self.graph.get_vertex_id_for_address(&cell_ref);
1693 let old_value = old_id
1694 .and_then(|id| self.graph.get_value(id))
1695 .or(fallback_old_value);
1696 let old_formula = old_id
1697 .and_then(|id| self.get_formula_ast(id))
1698 .or(fallback_old_formula);
1699
1700 let spill_snapshot =
1703 old_id.and_then(|id| self.snapshot_spill_for_anchor(id).map(|s| (id, s)));
1704 let did_spill_clear = spill_snapshot.is_some();
1705 if let Some((anchor, old_spill)) = spill_snapshot {
1706 if let Some(logger) = &mut self.change_logger {
1707 logger.begin_compound(format!(
1708 "SetValueWithSpillClear sheet={} row={} col={}",
1709 cell_ref.sheet_id,
1710 cell_ref.coord.row(),
1711 cell_ref.coord.col()
1712 ));
1713 }
1714 self.graph.clear_spill_region(anchor);
1715 self.log_change(ChangeEvent::SpillCleared {
1716 anchor,
1717 old: old_spill,
1718 });
1719 }
1720
1721 match self.graph.set_cell_value(
1724 &sheet_name,
1725 cell_ref.coord.row() + 1,
1726 cell_ref.coord.col() + 1,
1727 value.clone(),
1728 ) {
1729 Ok(_) => {
1730 let change_event = ChangeEvent::SetValue {
1732 addr: cell_ref,
1733 old_value,
1734 old_formula,
1735 new: value,
1736 };
1737 self.log_change(change_event);
1738
1739 if did_spill_clear && let Some(logger) = &mut self.change_logger {
1740 logger.end_compound();
1741 }
1742
1743 VertexId::new(0)
1745 }
1746 Err(_) => VertexId::new(0),
1747 }
1748 }
1749
1750 pub fn set_cell_formula(&mut self, cell_ref: CellRef, formula: ASTNode) -> VertexId {
1755 self.set_cell_formula_with_old_state(cell_ref, formula, None, None)
1756 }
1757
1758 pub fn try_set_cell_formula(
1760 &mut self,
1761 cell_ref: CellRef,
1762 formula: ASTNode,
1763 ) -> Result<VertexId, ExcelError> {
1764 self.try_set_cell_formula_with_old_state(cell_ref, formula, None, None)
1765 }
1766
1767 pub fn set_cell_formula_with_old_state(
1773 &mut self,
1774 cell_ref: CellRef,
1775 formula: ASTNode,
1776 fallback_old_value: Option<LiteralValue>,
1777 fallback_old_formula: Option<ASTNode>,
1778 ) -> VertexId {
1779 self.try_set_cell_formula_with_old_state(
1780 cell_ref,
1781 formula,
1782 fallback_old_value,
1783 fallback_old_formula,
1784 )
1785 .unwrap_or_else(|_| VertexId::new(0))
1786 }
1787
1788 pub fn try_set_cell_formula_with_old_state(
1791 &mut self,
1792 cell_ref: CellRef,
1793 formula: ASTNode,
1794 fallback_old_value: Option<LiteralValue>,
1795 fallback_old_formula: Option<ASTNode>,
1796 ) -> Result<VertexId, ExcelError> {
1797 self.set_cell_formula_with_old_state_and_plan(
1798 cell_ref,
1799 formula,
1800 fallback_old_value,
1801 fallback_old_formula,
1802 None,
1803 )
1804 }
1805
1806 pub(crate) fn set_cell_formula_with_prepared_plan(
1807 &mut self,
1808 cell_ref: CellRef,
1809 formula: ASTNode,
1810 fallback_old_value: Option<LiteralValue>,
1811 fallback_old_formula: Option<ASTNode>,
1812 ast_id: crate::engine::arena::AstNodeId,
1813 plan: crate::engine::ingest_pipeline::DependencyPlanRow,
1814 ) -> VertexId {
1815 self.set_cell_formula_with_old_state_and_plan(
1816 cell_ref,
1817 formula,
1818 fallback_old_value,
1819 fallback_old_formula,
1820 Some((ast_id, plan)),
1821 )
1822 .unwrap_or_else(|_| VertexId::new(0))
1823 }
1824
1825 fn set_cell_formula_with_old_state_and_plan(
1826 &mut self,
1827 cell_ref: CellRef,
1828 formula: ASTNode,
1829 fallback_old_value: Option<LiteralValue>,
1830 fallback_old_formula: Option<ASTNode>,
1831 prepared: Option<(
1832 crate::engine::arena::AstNodeId,
1833 crate::engine::ingest_pipeline::DependencyPlanRow,
1834 )>,
1835 ) -> Result<VertexId, ExcelError> {
1836 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id).to_string();
1837
1838 let old_id = self.graph.get_vertex_id_for_address(&cell_ref);
1841 let old_value = old_id
1842 .and_then(|id| self.graph.get_value(id))
1843 .or(fallback_old_value);
1844 let old_formula = old_id
1845 .and_then(|id| self.get_formula_ast(id))
1846 .or(fallback_old_formula);
1847
1848 let spill_snapshot =
1851 old_id.and_then(|id| self.snapshot_spill_for_anchor(id).map(|s| (id, s)));
1852 let did_spill_clear = spill_snapshot.is_some();
1853
1854 let result = if let Some((ast_id, plan)) = prepared {
1856 self.graph.set_cell_formula_with_plan(
1857 &sheet_name,
1858 cell_ref.coord.row() + 1,
1859 cell_ref.coord.col() + 1,
1860 ast_id,
1861 &plan,
1862 plan.volatile,
1863 plan.dynamic,
1864 )
1865 } else {
1866 self.graph.set_cell_formula(
1867 &sheet_name,
1868 cell_ref.coord.row() + 1,
1869 cell_ref.coord.col() + 1,
1870 formula.clone(),
1871 )
1872 };
1873 match result {
1874 Ok(summary) => {
1875 if let Some((anchor, old_spill)) = spill_snapshot {
1876 if let Some(logger) = &mut self.change_logger {
1877 logger.begin_compound(format!(
1878 "SetFormulaWithSpillClear sheet={} row={} col={}",
1879 cell_ref.sheet_id,
1880 cell_ref.coord.row(),
1881 cell_ref.coord.col()
1882 ));
1883 }
1884 self.graph.clear_spill_region(anchor);
1885 self.log_change(ChangeEvent::SpillCleared {
1886 anchor,
1887 old: old_spill,
1888 });
1889 }
1890 let change_event = ChangeEvent::SetFormula {
1892 addr: cell_ref,
1893 old_value,
1894 old_formula,
1895 new: formula,
1896 };
1897 self.log_change(change_event);
1898
1899 if did_spill_clear && let Some(logger) = &mut self.change_logger {
1900 logger.end_compound();
1901 }
1902
1903 Ok(summary
1904 .affected_vertices
1905 .into_iter()
1906 .next()
1907 .unwrap_or(VertexId::new(0)))
1908 }
1909 Err(error) => Err(error),
1910 }
1911 }
1912
1913 pub fn set_range_values(
1917 &mut self,
1918 sheet_id: SheetId,
1919 start_row: u32,
1920 start_col: u32,
1921 values: &[Vec<LiteralValue>],
1922 ) -> Result<RangeSummary, EditorError> {
1923 let mut summary = RangeSummary::default();
1924
1925 self.begin_batch();
1926 self.graph.begin_deferred_dirty();
1930
1931 for (row_offset, row_values) in values.iter().enumerate() {
1932 for (col_offset, value) in row_values.iter().enumerate() {
1933 let row = start_row + row_offset as u32;
1934 let col = start_col + col_offset as u32;
1935 let cell_ref = self.graph.make_cell_ref_internal(sheet_id, row, col);
1936 let existing_id = self.graph.get_vertex_id_for_address(&cell_ref);
1937
1938 let id = self.set_cell_value(cell_ref, value.clone());
1939 match existing_id {
1940 Some(existing_id) => summary.vertices_updated.push(existing_id),
1941 None if id.0 != 0 => summary.vertices_created.push(id),
1942 None => {}
1943 }
1944 summary.cells_affected += 1;
1945 }
1946 }
1947
1948 let _ = self.graph.end_deferred_dirty();
1949 self.commit_batch();
1950
1951 Ok(summary)
1952 }
1953
1954 pub fn clear_range(
1956 &mut self,
1957 sheet_id: SheetId,
1958 start_row: u32,
1959 start_col: u32,
1960 end_row: u32,
1961 end_col: u32,
1962 ) -> Result<RangeSummary, EditorError> {
1963 let mut summary = RangeSummary::default();
1964
1965 self.begin_batch();
1966
1967 let vertices_in_range: Vec<_> = self
1969 .graph
1970 .grid_vertices_in_sheet(sheet_id)
1971 .filter(|(_, coord)| {
1972 let row = coord.row();
1973 let col = coord.col();
1974 row >= start_row && row <= end_row && col >= start_col && col <= end_col
1975 })
1976 .collect();
1977
1978 let mut vertex_cells = rustc_hash::FxHashSet::default();
1979 for (id, coord) in vertices_in_range {
1980 self.remove_vertex(id)?;
1981 vertex_cells.insert((coord.row(), coord.col()));
1982 summary.cells_affected += 1;
1983 }
1984 for (col, r0, r1) in
1987 self.graph
1988 .forget_extent_cells(sheet_id, (start_row, end_row), (start_col, end_col))
1989 {
1990 summary.cells_affected += (r0..=r1)
1991 .filter(|&row| !vertex_cells.contains(&(row, col)))
1992 .count();
1993 }
1994
1995 self.commit_batch();
1996
1997 Ok(summary)
1998 }
1999
2000 pub fn copy_range(
2002 &mut self,
2003 sheet_id: SheetId,
2004 from_start_row: u32,
2005 from_start_col: u32,
2006 from_end_row: u32,
2007 from_end_col: u32,
2008 to_sheet_id: SheetId,
2009 to_row: u32,
2010 to_col: u32,
2011 ) -> Result<RangeSummary, EditorError> {
2012 let row_offset = to_row as i32 - from_start_row as i32;
2013 let col_offset = to_col as i32 - from_start_col as i32;
2014
2015 let mut summary = RangeSummary::default();
2016 let mut cell_data = Vec::new();
2017
2018 let vertices_in_range: Vec<_> = self
2020 .graph
2021 .grid_vertices_in_sheet(sheet_id)
2022 .filter(|(_, coord)| {
2023 let row = coord.row();
2024 let col = coord.col();
2025 row >= from_start_row
2026 && row <= from_end_row
2027 && col >= from_start_col
2028 && col <= from_end_col
2029 })
2030 .collect();
2031
2032 for (id, coord) in vertices_in_range {
2033 let row = coord.row();
2034 let col = coord.col();
2035
2036 if let Some(formula) = self.get_formula_ast(id) {
2038 cell_data.push((
2039 row - from_start_row,
2040 col - from_start_col,
2041 CellData::Formula(formula),
2042 ));
2043 } else if let Some(value) = self.graph.get_value(id) {
2044 cell_data.push((
2045 row - from_start_row,
2046 col - from_start_col,
2047 CellData::Value(value),
2048 ));
2049 }
2050 }
2051
2052 self.begin_batch();
2053
2054 for (row_idx, col_idx, data) in cell_data {
2056 let dest_row = (to_row as i32 + row_idx as i32) as u32;
2057 let dest_col = (to_col as i32 + col_idx as i32) as u32;
2058
2059 match data {
2060 CellData::Value(value) => {
2061 let cell_ref =
2062 self.graph
2063 .make_cell_ref_internal(to_sheet_id, dest_row, dest_col);
2064
2065 if let Some(existing_id) = self.graph.get_vertex_id_for_address(&cell_ref) {
2066 self.graph.update_vertex_value(existing_id, value);
2067 self.graph.mark_vertex_dirty(existing_id);
2068 summary.vertices_updated.push(existing_id);
2069 } else {
2070 let meta =
2071 VertexMeta::new(dest_row, dest_col, to_sheet_id, VertexKind::Cell);
2072 let id = self.try_add_vertex(meta)?;
2073 self.graph.update_vertex_value(id, value);
2074 summary.vertices_created.push(id);
2075 }
2076 }
2077 CellData::Formula(formula) => {
2078 let adjuster = RelativeReferenceAdjuster::new(row_offset, col_offset);
2080 let adjusted = adjuster.adjust_formula(&formula);
2081
2082 let cell_ref =
2083 self.graph
2084 .make_cell_ref_internal(to_sheet_id, dest_row, dest_col);
2085
2086 if let Some(existing_id) = self.graph.get_vertex_id_for_address(&cell_ref) {
2087 self.graph.update_vertex_formula(existing_id, adjusted)?;
2088 summary.vertices_updated.push(existing_id);
2089 } else {
2090 let meta = VertexMeta::new(
2091 dest_row,
2092 dest_col,
2093 to_sheet_id,
2094 VertexKind::FormulaScalar,
2095 );
2096 let id = self.try_add_vertex(meta)?;
2097 self.graph.update_vertex_formula(id, adjusted)?;
2098 summary.vertices_created.push(id);
2099 }
2100 }
2101 }
2102
2103 summary.cells_affected += 1;
2104 }
2105
2106 self.commit_batch();
2107
2108 Ok(summary)
2109 }
2110
2111 pub fn move_range(
2113 &mut self,
2114 sheet_id: SheetId,
2115 from_start_row: u32,
2116 from_start_col: u32,
2117 from_end_row: u32,
2118 from_end_col: u32,
2119 to_sheet_id: SheetId,
2120 to_row: u32,
2121 to_col: u32,
2122 ) -> Result<RangeSummary, EditorError> {
2123 let result = self.move_range_impl(
2124 sheet_id,
2125 from_start_row,
2126 from_start_col,
2127 from_end_row,
2128 from_end_col,
2129 to_sheet_id,
2130 to_row,
2131 to_col,
2132 );
2133 self.graph.authority_end_structural();
2134 result
2135 }
2136
2137 fn move_range_impl(
2138 &mut self,
2139 sheet_id: SheetId,
2140 from_start_row: u32,
2141 from_start_col: u32,
2142 from_end_row: u32,
2143 from_end_col: u32,
2144 to_sheet_id: SheetId,
2145 to_row: u32,
2146 to_col: u32,
2147 ) -> Result<RangeSummary, EditorError> {
2148 self.graph.authority_note_structural(true);
2149 let mut summary = self.copy_range(
2151 sheet_id,
2152 from_start_row,
2153 from_start_col,
2154 from_end_row,
2155 from_end_col,
2156 to_sheet_id,
2157 to_row,
2158 to_col,
2159 )?;
2160
2161 let clear_summary = self.clear_range(
2163 sheet_id,
2164 from_start_row,
2165 from_start_col,
2166 from_end_row,
2167 from_end_col,
2168 )?;
2169
2170 summary.cells_moved = clear_summary.cells_affected;
2171
2172 let row_offset = to_row as i32 - from_start_row as i32;
2174 let col_offset = to_col as i32 - from_start_col as i32;
2175
2176 let all_formula_vertices: Vec<_> = self.graph.vertices_with_formulas().collect();
2178
2179 let from_sheet_name = self.graph.sheet_name(sheet_id).to_string();
2180 let to_sheet_name = self.graph.sheet_name(to_sheet_id).to_string();
2181 let adjuster = MoveReferenceAdjuster::new(
2182 sheet_id,
2183 from_sheet_name,
2184 from_start_row,
2185 from_start_col,
2186 from_end_row,
2187 from_end_col,
2188 to_sheet_id,
2189 to_sheet_name,
2190 row_offset,
2191 col_offset,
2192 );
2193
2194 for formula_id in all_formula_vertices {
2195 if let Some(formula) = self.get_formula_ast(formula_id) {
2196 let formula_sheet_id = self.graph.get_vertex_sheet_id(formula_id);
2197 if let Some(adjusted) = adjuster.adjust_if_references(&formula, formula_sheet_id) {
2198 self.graph.update_vertex_formula(formula_id, adjusted)?;
2199 }
2200 }
2201 }
2202
2203 Ok(summary)
2204 }
2205
2206 pub fn define_name(
2208 &mut self,
2209 name: &str,
2210 definition: NamedDefinition,
2211 scope: NameScope,
2212 ) -> Result<(), EditorError> {
2213 self.graph.define_name(name, definition.clone(), scope)?;
2214
2215 self.log_change(ChangeEvent::DefineName {
2216 name: name.to_string(),
2217 scope,
2218 definition,
2219 });
2220
2221 Ok(())
2222 }
2223
2224 pub fn define_name_for_cell(
2226 &mut self,
2227 name: &str,
2228 sheet_name: &str,
2229 row: u32,
2230 col: u32,
2231 scope: NameScope,
2232 ) -> Result<(), EditorError> {
2233 let sheet_id = self
2234 .graph
2235 .sheet_id(sheet_name)
2236 .ok_or_else(|| EditorError::InvalidName {
2237 name: sheet_name.to_string(),
2238 reason: "Sheet not found".to_string(),
2239 })?;
2240 let cell_ref = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
2241 self.define_name(name, NamedDefinition::Cell(cell_ref), scope)
2242 }
2243
2244 pub fn define_name_for_range(
2246 &mut self,
2247 name: &str,
2248 sheet_name: &str,
2249 start_row: u32,
2250 start_col: u32,
2251 end_row: u32,
2252 end_col: u32,
2253 scope: NameScope,
2254 ) -> Result<(), EditorError> {
2255 let sheet_id = self
2256 .graph
2257 .sheet_id(sheet_name)
2258 .ok_or_else(|| EditorError::InvalidName {
2259 name: sheet_name.to_string(),
2260 reason: "Sheet not found".to_string(),
2261 })?;
2262 let start = CellRef::new(
2263 sheet_id,
2264 Coord::from_excel(start_row, start_col, true, true),
2265 );
2266 let end = CellRef::new(sheet_id, Coord::from_excel(end_row, end_col, true, true));
2267 let range_ref = crate::reference::RangeRef::new(start, end);
2268 self.define_name(name, NamedDefinition::Range(range_ref), scope)
2269 }
2270
2271 pub fn update_name(
2273 &mut self,
2274 name: &str,
2275 new_definition: NamedDefinition,
2276 scope: NameScope,
2277 ) -> Result<(), EditorError> {
2278 let old_definition = self
2280 .graph
2281 .resolve_name(
2282 name,
2283 match scope {
2284 NameScope::Sheet(id) => id,
2285 NameScope::Workbook => 0,
2286 },
2287 )
2288 .cloned();
2289
2290 self.graph
2291 .update_name(name, new_definition.clone(), scope)?;
2292
2293 if let Some(old_def) = old_definition {
2294 self.log_change(ChangeEvent::UpdateName {
2295 name: name.to_string(),
2296 scope,
2297 old_definition: old_def,
2298 new_definition,
2299 });
2300 }
2301
2302 Ok(())
2303 }
2304
2305 pub fn delete_name(&mut self, name: &str, scope: NameScope) -> Result<(), EditorError> {
2307 let old_def = if self.has_logger() {
2309 self.graph
2310 .resolve_name(
2311 name,
2312 match scope {
2313 NameScope::Sheet(id) => id,
2314 NameScope::Workbook => 0,
2315 },
2316 )
2317 .cloned()
2318 } else {
2319 None
2320 };
2321
2322 self.graph.delete_name(name, scope)?;
2323 self.log_change(ChangeEvent::DeleteName {
2324 name: name.to_string(),
2325 scope,
2326 old_definition: old_def,
2327 });
2328
2329 Ok(())
2330 }
2331}
2332
2333enum CellData {
2335 Value(LiteralValue),
2336 Formula(ASTNode),
2337}
2338
2339impl<'g> Drop for VertexEditor<'g> {
2340 fn drop(&mut self) {
2341 if self.batch_mode {
2343 self.commit_batch();
2344 }
2345 }
2346}
2347
2348#[cfg(test)]
2349mod tests {
2350 use super::*;
2351 use crate::engine::graph::editor::change_log::{ChangeEvent, ChangeLog};
2352 use crate::reference::Coord;
2353
2354 fn create_test_graph() -> DependencyGraph {
2355 DependencyGraph::new()
2356 }
2357
2358 #[test]
2359 fn test_vertex_editor_creation() {
2360 let mut graph = create_test_graph();
2361 let editor = VertexEditor::new(&mut graph);
2362 assert!(!editor.has_logger());
2363 assert!(!editor.batch_mode);
2364 }
2365
2366 #[test]
2367 fn test_vertex_editor_with_logger() {
2368 let mut graph = create_test_graph();
2369 let mut log = ChangeLog::new();
2370 let editor = VertexEditor::with_logger(&mut graph, &mut log);
2371 assert!(editor.has_logger());
2372 assert!(!editor.batch_mode);
2373 }
2374
2375 #[test]
2376 fn test_add_vertex() {
2377 let mut graph = create_test_graph();
2378 let mut editor = VertexEditor::new(&mut graph);
2379
2380 let meta = VertexMeta::new(5, 10, 0, VertexKind::Cell).dirty();
2381 let vertex_id = editor.add_vertex(meta);
2382
2383 assert!(vertex_id.0 > 0);
2385 }
2386
2387 #[test]
2388 fn test_batch_operations() {
2389 let mut graph = create_test_graph();
2390 let mut editor = VertexEditor::new(&mut graph);
2391
2392 assert!(!editor.batch_mode);
2393 editor.begin_batch();
2394 assert!(editor.batch_mode);
2395
2396 let meta1 = VertexMeta::new(1, 1, 0, VertexKind::Cell);
2398 let meta2 = VertexMeta::new(2, 2, 0, VertexKind::Cell);
2399
2400 let id1 = editor.add_vertex(meta1);
2401 let id2 = editor.add_vertex(meta2);
2402
2403 assert!(editor.add_edge(id1, id2));
2405
2406 editor.commit_batch();
2407 assert!(!editor.batch_mode);
2408 }
2409
2410 #[test]
2411 fn test_remove_vertex() {
2412 let mut graph = create_test_graph();
2413 let mut editor = VertexEditor::new(&mut graph);
2414
2415 let meta = VertexMeta::new(3, 4, 0, VertexKind::Cell).dirty();
2416 let vertex_id = editor.add_vertex(meta);
2417
2418 assert!(editor.remove_vertex(vertex_id).is_ok());
2420 }
2421
2422 #[test]
2423 fn test_remove_vertex_clears_spill_registry_for_anchor() {
2424 let mut graph = create_test_graph();
2425 let sheet_id = graph.sheet_id_mut("Sheet1");
2426
2427 let anchor_cell = CellRef::new(sheet_id, Coord::new(0, 0, true, true));
2429 let anchor_vid = {
2430 let mut editor = VertexEditor::new(&mut graph);
2431 editor.set_cell_formula(anchor_cell, formualizer_parse::parser::parse("=0").unwrap())
2434 };
2435
2436 let target_cells = vec![
2437 CellRef::new(sheet_id, Coord::new(0, 0, true, true)),
2438 CellRef::new(sheet_id, Coord::new(0, 1, true, true)),
2439 CellRef::new(sheet_id, Coord::new(1, 0, true, true)),
2440 CellRef::new(sheet_id, Coord::new(1, 1, true, true)),
2441 ];
2442 let values = vec![
2443 vec![LiteralValue::Number(1.0), LiteralValue::Number(2.0)],
2444 vec![LiteralValue::Number(3.0), LiteralValue::Number(4.0)],
2445 ];
2446
2447 graph
2448 .commit_spill_region_atomic_with_fault(anchor_vid, target_cells.clone(), values, None)
2449 .unwrap();
2450
2451 assert!(graph.spill_registry_has_anchor(anchor_vid));
2452 for cell in &target_cells {
2453 assert_eq!(
2454 graph.spill_registry_anchor_for_cell(*cell),
2455 Some(anchor_vid)
2456 );
2457 }
2458
2459 {
2460 let mut editor = VertexEditor::new(&mut graph);
2461 editor.remove_vertex(anchor_vid).unwrap();
2462 }
2463
2464 assert!(!graph.spill_registry_has_anchor(anchor_vid));
2465 for cell in &target_cells {
2466 assert_eq!(graph.spill_registry_anchor_for_cell(*cell), None);
2467 }
2468 assert_eq!(graph.spill_registry_counts(), (0, 0));
2469 }
2470
2471 #[test]
2472 fn test_edge_operations() {
2473 let mut graph = create_test_graph();
2474 let mut editor = VertexEditor::new(&mut graph);
2475
2476 let meta1 = VertexMeta::new(1, 1, 0, VertexKind::Cell);
2477 let meta2 = VertexMeta::new(2, 2, 0, VertexKind::FormulaScalar);
2478
2479 let id1 = editor.add_vertex(meta1);
2480 let id2 = editor.add_vertex(meta2);
2481
2482 assert!(editor.add_edge(id1, id2));
2484
2485 assert!(!editor.add_edge(id1, id1));
2487
2488 assert!(editor.remove_edge(id1, id2));
2490 }
2491
2492 #[test]
2493 fn test_set_cell_value() {
2494 let mut graph = create_test_graph();
2495 let mut log = ChangeLog::new();
2496
2497 let cell_ref = CellRef {
2498 sheet_id: 0,
2499 coord: Coord::new(2, 3, true, true),
2500 };
2501 let value = LiteralValue::Number(42.0);
2502
2503 let vertex_id = {
2504 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2505 editor.set_cell_value(cell_ref, value.clone())
2506 };
2507
2508 assert_eq!(vertex_id.0, 0);
2510
2511 assert_eq!(log.len(), 1);
2513 match &log.events()[0] {
2514 ChangeEvent::SetValue { addr, new, .. } => {
2515 assert_eq!(addr.sheet_id, cell_ref.sheet_id);
2516 assert_eq!(addr.coord.row(), cell_ref.coord.row());
2517 assert_eq!(addr.coord.col(), cell_ref.coord.col());
2518 assert_eq!(new, &value);
2519 }
2520 _ => panic!("Expected SetValue event"),
2521 }
2522 }
2523
2524 #[test]
2525 fn test_set_cell_formula() {
2526 let mut graph = create_test_graph();
2527 let mut log = ChangeLog::new();
2528
2529 let cell_ref = CellRef {
2530 sheet_id: 0,
2531 coord: Coord::new(1, 1, true, true),
2532 };
2533
2534 use formualizer_parse::parser::ASTNodeType;
2535 let formula = formualizer_parse::parser::ASTNode {
2536 node_type: ASTNodeType::Literal(LiteralValue::Number(100.0)),
2537 source_token: None,
2538 contains_volatile: false,
2539 };
2540
2541 let vertex_id = {
2542 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2543 editor.set_cell_formula(cell_ref, formula.clone())
2544 };
2545
2546 assert!(vertex_id.0 > 0);
2548
2549 assert_eq!(log.len(), 1);
2551 match &log.events()[0] {
2552 ChangeEvent::SetFormula { addr, .. } => {
2553 assert_eq!(addr.sheet_id, cell_ref.sheet_id);
2554 assert_eq!(addr.coord.row(), cell_ref.coord.row());
2555 assert_eq!(addr.coord.col(), cell_ref.coord.col());
2556 }
2557 _ => panic!("Expected SetFormula event"),
2558 }
2559 }
2560
2561 #[test]
2562 fn test_shift_rows() {
2563 let mut graph = create_test_graph();
2564 let mut log = ChangeLog::new();
2565
2566 {
2567 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2568
2569 let cell1 = CellRef {
2571 sheet_id: 0,
2572 coord: Coord::new(5, 1, true, true),
2573 };
2574 let cell2 = CellRef {
2575 sheet_id: 0,
2576 coord: Coord::new(10, 1, true, true),
2577 };
2578 let cell3 = CellRef {
2579 sheet_id: 0,
2580 coord: Coord::new(15, 1, true, true),
2581 };
2582
2583 editor.set_cell_value(cell1, LiteralValue::Number(1.0));
2584 editor.set_cell_value(cell2, LiteralValue::Number(2.0));
2585 editor.set_cell_value(cell3, LiteralValue::Number(3.0));
2586 }
2587
2588 log.clear();
2590
2591 {
2592 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2593 editor.shift_rows(0, 10, 2);
2595 }
2596
2597 assert_eq!(log.len(), 1);
2599 match &log.events()[0] {
2600 ChangeEvent::SetValue { addr, new, .. } => {
2601 assert_eq!(addr.sheet_id, 0);
2602 assert_eq!(addr.coord.row(), 10);
2603 if let LiteralValue::Text(msg) = new {
2604 assert!(msg.contains("Row shift"));
2605 assert!(msg.contains("start=10"));
2606 assert!(msg.contains("delta=2"));
2607 }
2608 }
2609 _ => panic!("Expected SetValue event for row shift"),
2610 }
2611 }
2612
2613 #[test]
2614 fn test_shift_columns() {
2615 let mut graph = create_test_graph();
2616 let mut log = ChangeLog::new();
2617
2618 {
2619 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2620
2621 let cell1 = CellRef {
2623 sheet_id: 0,
2624 coord: Coord::new(1, 5, true, true),
2625 };
2626 let cell2 = CellRef {
2627 sheet_id: 0,
2628 coord: Coord::new(1, 10, true, true),
2629 };
2630
2631 editor.set_cell_value(cell1, LiteralValue::Number(1.0));
2632 editor.set_cell_value(cell2, LiteralValue::Number(2.0));
2633 }
2634
2635 log.clear();
2637
2638 {
2639 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2640 editor.shift_columns(0, 8, 3);
2642 }
2643
2644 assert_eq!(log.len(), 1);
2646 match &log.events()[0] {
2647 ChangeEvent::SetValue { addr, new, .. } => {
2648 assert_eq!(addr.sheet_id, 0);
2649 assert_eq!(addr.coord.col(), 8);
2650 if let LiteralValue::Text(msg) = new {
2651 assert!(msg.contains("Column shift"));
2652 assert!(msg.contains("start=8"));
2653 assert!(msg.contains("delta=3"));
2654 }
2655 }
2656 _ => panic!("Expected SetValue event for column shift"),
2657 }
2658 }
2659
2660 #[test]
2661 fn test_move_vertex() {
2662 let mut graph = create_test_graph();
2663 let mut editor = VertexEditor::new(&mut graph);
2664
2665 let meta = VertexMeta::new(5, 10, 0, VertexKind::Cell);
2666 let vertex_id = editor.add_vertex(meta);
2667
2668 assert!(editor.move_vertex(vertex_id, GridAddr::new(8, 12)).is_ok());
2670
2671 assert!(editor.move_vertex(vertex_id, GridAddr::new(8, 12)).is_ok());
2673 }
2674
2675 #[test]
2676 fn test_vertex_meta_builder() {
2677 let meta = VertexMeta::new(1, 2, 3, VertexKind::FormulaScalar)
2678 .dirty()
2679 .volatile()
2680 .with_flags(0x08);
2681
2682 assert_eq!(meta.coord.row(), 1);
2683 assert_eq!(meta.coord.col(), 2);
2684 assert_eq!(meta.sheet_id, 3);
2685 assert_eq!(meta.kind, VertexKind::FormulaScalar);
2686 assert_eq!(meta.flags, 0x08); }
2688
2689 #[test]
2690 fn test_change_log_management() {
2691 let mut graph = create_test_graph();
2692 let mut log = ChangeLog::new();
2693
2694 {
2695 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2696 let cell_ref = CellRef {
2697 sheet_id: 0,
2698 coord: Coord::new(0, 0, true, true),
2699 };
2700 editor.set_cell_value(cell_ref, LiteralValue::Number(1.0));
2701 editor.set_cell_value(cell_ref, LiteralValue::Number(2.0));
2702 }
2703
2704 assert_eq!(log.len(), 2);
2705
2706 log.clear();
2707 assert_eq!(log.len(), 0);
2708 }
2709
2710 #[test]
2711 fn test_editor_drop_commits_batch() {
2712 let mut graph = create_test_graph();
2713 {
2714 let mut editor = VertexEditor::new(&mut graph);
2715 editor.begin_batch();
2716
2717 let meta = VertexMeta::new(1, 1, 0, VertexKind::Cell);
2718 editor.add_vertex(meta);
2719
2720 }
2722
2723 }
2725}