1use crate::SheetId;
2use crate::engine::addr::GridAddr;
3use crate::engine::graph::DependencyGraph;
4use crate::engine::graph::editor::reference_adjuster::{
5 MoveReferenceAdjuster, ReferenceAdjuster, ReferenceContext, RelativeReferenceAdjuster,
6 ShiftOperation,
7};
8use crate::engine::named_range::{NameScope, NamedDefinition};
9use crate::engine::{ChangeEvent, ChangeLogger, VertexId, VertexKind};
10use crate::reference::{CellRef, Coord};
11use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
12use formualizer_parse::parser::ASTNode;
13use rustc_hash::FxHashMap;
14use std::sync::atomic::{AtomicU64, Ordering};
15
16#[derive(Debug, Clone)]
18pub struct VertexMeta {
19 pub coord: GridAddr,
20 pub sheet_id: SheetId,
21 pub kind: VertexKind,
22 pub flags: u8,
23}
24
25impl VertexMeta {
26 pub fn new(row: u32, col: u32, sheet_id: SheetId, kind: VertexKind) -> Self {
27 Self {
28 coord: GridAddr::new(row, col),
29 sheet_id,
30 kind,
31 flags: 0,
32 }
33 }
34
35 pub fn with_flags(mut self, flags: u8) -> Self {
36 self.flags = flags;
37 self
38 }
39
40 pub fn dirty(mut self) -> Self {
41 self.flags |= 0x01;
42 self
43 }
44
45 pub fn volatile(mut self) -> Self {
46 self.flags |= 0x02;
47 self
48 }
49}
50
51#[derive(Debug, Clone)]
53pub struct VertexMetaPatch {
54 pub kind: Option<VertexKind>,
55 pub coord: Option<GridAddr>,
56 pub dirty: Option<bool>,
57 pub volatile: Option<bool>,
58}
59
60#[derive(Debug, Clone)]
62pub struct VertexDataPatch {
63 pub value: Option<LiteralValue>,
64 pub formula: Option<ASTNode>,
65}
66
67#[derive(Debug, Clone, Default)]
69pub struct MetaUpdateSummary {
70 pub coord_changed: bool,
71 pub kind_changed: bool,
72 pub flags_changed: bool,
73}
74
75#[derive(Debug, Clone, Default)]
77pub struct DataUpdateSummary {
78 pub value_changed: bool,
79 pub formula_changed: bool,
80 pub dependents_marked_dirty: Vec<VertexId>,
81}
82
83#[derive(Debug, Clone, Default)]
85pub struct ShiftSummary {
86 pub vertices_moved: Vec<VertexId>,
87 pub vertices_deleted: Vec<VertexId>,
88 pub references_adjusted: usize,
89 pub formulas_updated: usize,
90 #[cfg(test)]
91 pub(crate) structural_dependents_dirtied: Vec<VertexId>,
92}
93
94#[derive(Debug, Clone, Default)]
96pub struct RangeSummary {
97 pub cells_affected: usize,
98 pub vertices_created: Vec<VertexId>,
99 pub vertices_updated: Vec<VertexId>,
100 pub cells_moved: usize,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub struct TransactionId(u64);
106
107impl TransactionId {
108 fn new() -> Self {
109 static COUNTER: AtomicU64 = AtomicU64::new(0);
110 TransactionId(COUNTER.fetch_add(1, Ordering::Relaxed))
111 }
112}
113
114#[derive(Debug)]
116struct Transaction {
117 id: TransactionId,
118 start_index: usize, }
120
121#[derive(Debug, Clone)]
123pub enum EditorError {
124 TargetOccupied { cell: CellRef },
125 OutOfBounds { row: u32, col: u32 },
126 InvalidName { name: String, reason: String },
127 TransactionFailed { reason: String },
128 TransactionUnsupported { reason: String },
129 NoActiveTransaction,
130 VertexNotFound { id: VertexId },
131 Excel(ExcelError),
132}
133
134impl From<ExcelError> for EditorError {
135 fn from(e: ExcelError) -> Self {
136 EditorError::Excel(e)
137 }
138}
139
140impl std::fmt::Display for EditorError {
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 match self {
143 EditorError::TargetOccupied { cell } => {
144 write!(
145 f,
146 "Target cell occupied at row {}, col {}",
147 cell.coord.row(),
148 cell.coord.col()
149 )
150 }
151 EditorError::OutOfBounds { row, col } => {
152 write!(f, "Cell position out of bounds: row {row}, col {col}")
153 }
154 EditorError::InvalidName { name, reason } => {
155 write!(f, "Invalid name '{name}': {reason}")
156 }
157 EditorError::TransactionFailed { reason } => {
158 write!(f, "Transaction failed: {reason}")
159 }
160 EditorError::TransactionUnsupported { reason } => {
161 write!(f, "Transaction unsupported: {reason}")
162 }
163 EditorError::NoActiveTransaction => {
164 write!(f, "No active transaction")
165 }
166 EditorError::VertexNotFound { id } => {
167 write!(f, "Vertex not found: {id:?}")
168 }
169 EditorError::Excel(e) => write!(f, "Excel error: {e:?}"),
170 }
171 }
172}
173
174impl std::error::Error for EditorError {}
175
176pub trait SpillValueReader {
211 fn read_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue>;
212}
213
214pub struct VertexEditor<'g> {
215 graph: &'g mut DependencyGraph,
216 change_logger: Option<&'g mut dyn ChangeLogger>,
217 spill_value_reader: Option<&'g dyn SpillValueReader>,
218 structural_occupancy: Option<crate::engine::graph::StructuralOccupancy>,
219 batch_mode: bool,
220}
221
222impl<'g> VertexEditor<'g> {
223 pub fn new(graph: &'g mut DependencyGraph) -> Self {
225 Self {
226 graph,
227 change_logger: None,
228 spill_value_reader: None,
229 structural_occupancy: None,
230 batch_mode: false,
231 }
232 }
233
234 pub(crate) fn with_structural_occupancy(
236 mut self,
237 occupancy: crate::engine::graph::StructuralOccupancy,
238 ) -> Self {
239 self.structural_occupancy = Some(occupancy);
240 self
241 }
242
243 pub(crate) fn set_structural_occupancy(
244 &mut self,
245 occupancy: crate::engine::graph::StructuralOccupancy,
246 ) {
247 self.structural_occupancy = Some(occupancy);
248 }
249
250 pub fn with_logger<L: ChangeLogger + 'g>(
252 graph: &'g mut DependencyGraph,
253 logger: &'g mut L,
254 ) -> Self {
255 Self {
256 graph,
257 change_logger: Some(logger as &'g mut dyn ChangeLogger),
258 spill_value_reader: None,
259 structural_occupancy: None,
260 batch_mode: false,
261 }
262 }
263
264 pub fn with_logger_and_spill_reader<L: ChangeLogger + 'g>(
266 graph: &'g mut DependencyGraph,
267 logger: &'g mut L,
268 spill_value_reader: &'g dyn SpillValueReader,
269 ) -> Self {
270 Self {
271 graph,
272 change_logger: Some(logger as &'g mut dyn ChangeLogger),
273 spill_value_reader: Some(spill_value_reader),
274 structural_occupancy: None,
275 batch_mode: false,
276 }
277 }
278
279 pub fn begin_batch(&mut self) {
281 if !self.batch_mode {
282 self.graph.begin_batch();
283 self.batch_mode = true;
284 }
285 }
286
287 pub fn commit_batch(&mut self) {
289 if self.batch_mode {
290 self.graph.end_batch();
291 self.batch_mode = false;
292 }
293 }
294
295 fn log_change(&mut self, event: ChangeEvent) {
297 if let Some(logger) = &mut self.change_logger {
298 logger.record(event);
299 }
300 }
301
302 fn snapshot_spill_for_anchor(
303 &self,
304 anchor: VertexId,
305 ) -> Option<crate::engine::graph::editor::change_log::SpillSnapshot> {
306 let cells = self.graph.spill_cells_for_anchor(anchor)?.to_vec();
307 if cells.is_empty() {
308 return None;
309 }
310
311 let max = self.graph.get_config().spill.max_spill_cells as usize;
313 let mut cells = cells;
314 if cells.len() > max {
315 cells.truncate(max);
316 }
317
318 let first = *cells.first().expect("non-empty spill cells");
319 let sheet_name = self.graph.sheet_name(first.sheet_id).to_string();
320 let row0 = first.coord.row();
321 let col0 = first.coord.col();
322
323 let mut max_row = row0;
324 let mut max_col = col0;
325 let mut by_coord: FxHashMap<(u32, u32), LiteralValue> = FxHashMap::default();
326 for cell in &cells {
327 max_row = max_row.max(cell.coord.row());
328 max_col = max_col.max(cell.coord.col());
329 let v = if let Some(reader) = self.spill_value_reader {
330 reader
331 .read_cell_value(&sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
332 .unwrap_or(LiteralValue::Empty)
333 } else {
334 self.graph
335 .get_cell_value(&sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
336 .unwrap_or(LiteralValue::Empty)
337 };
338 by_coord.insert((cell.coord.row(), cell.coord.col()), v);
339 }
340
341 let rows = (max_row - row0 + 1) as usize;
342 let cols = (max_col - col0 + 1) as usize;
343 let mut values: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
344 for r in 0..rows {
345 let mut row: Vec<LiteralValue> = Vec::with_capacity(cols);
346 for c in 0..cols {
347 row.push(
348 by_coord
349 .get(&(row0 + r as u32, col0 + c as u32))
350 .cloned()
351 .unwrap_or(LiteralValue::Empty),
352 );
353 }
354 values.push(row);
355 }
356
357 Some(crate::engine::graph::editor::change_log::SpillSnapshot {
358 target_cells: cells,
359 values,
360 })
361 }
362
363 pub fn commit_spill_region(
365 &mut self,
366 anchor: VertexId,
367 target_cells: Vec<CellRef>,
368 values: Vec<Vec<LiteralValue>>,
369 ) -> Result<(), EditorError> {
370 let old = self.snapshot_spill_for_anchor(anchor);
371 self.graph
372 .commit_spill_region_atomic_with_fault(
373 anchor,
374 target_cells.clone(),
375 values.clone(),
376 None,
377 )
378 .map_err(EditorError::Excel)?;
379 self.log_change(ChangeEvent::SpillCommitted {
380 anchor,
381 old,
382 new: crate::engine::graph::editor::change_log::SpillSnapshot {
383 target_cells,
384 values,
385 },
386 });
387 Ok(())
388 }
389
390 pub fn clear_spill_region(&mut self, anchor: VertexId) {
392 let Some(old) = self.snapshot_spill_for_anchor(anchor) else {
393 return;
394 };
395 self.graph.clear_spill_region(anchor);
396 self.log_change(ChangeEvent::SpillCleared { anchor, old });
397 }
398
399 pub fn has_logger(&self) -> bool {
401 self.change_logger.is_some()
402 }
403
404 fn get_formula_ast(&self, id: VertexId) -> Option<ASTNode> {
405 self.graph.get_formula_id(id).and_then(|ast_id| {
406 self.graph
407 .data_store()
408 .retrieve_ast(ast_id, self.graph.sheet_reg())
409 })
410 }
411
412 fn snapshot_named_definitions(&self) -> FxHashMap<(NameScope, String), NamedDefinition> {
413 let mut out: FxHashMap<(NameScope, String), NamedDefinition> = FxHashMap::default();
414 for (name, nr) in self.graph.named_ranges_iter() {
415 out.insert((NameScope::Workbook, name.clone()), nr.definition.clone());
416 }
417 for ((sheet_id, name), nr) in self.graph.sheet_named_ranges_iter() {
418 out.insert(
419 (NameScope::Sheet(*sheet_id), name.clone()),
420 nr.definition.clone(),
421 );
422 }
423 out
424 }
425
426 pub fn apply_inverse(&mut self, change: ChangeEvent) -> Result<(), EditorError> {
433 match change {
434 ChangeEvent::SetValue {
435 addr,
436 old_value,
437 old_formula,
438 new: _,
439 } => {
440 if let Some(old_formula) = old_formula {
442 self.set_cell_formula(addr, old_formula);
443 } else if let Some(old_value) = old_value {
444 self.set_cell_value(addr, old_value);
445 } else if let Some(&id) = self.graph.get_vertex_id_for_address(&addr) {
446 self.remove_vertex(id)?;
447 }
448 }
449 ChangeEvent::SetFormula {
450 addr,
451 old_value,
452 old_formula,
453 new: _,
454 } => {
455 if let Some(old_formula) = old_formula {
457 self.set_cell_formula(addr, old_formula);
458 } else if let Some(old_value) = old_value {
459 self.set_cell_value(addr, old_value);
460 } else if let Some(&id) = self.graph.get_vertex_id_for_address(&addr) {
461 self.remove_vertex(id)?;
462 }
463 }
464 ChangeEvent::SetRowVisibility { .. } => {
465 }
467 ChangeEvent::AddVertex { id, .. } => {
468 let _ = self.remove_vertex(id); }
471 ChangeEvent::RemoveVertex {
472 id: _,
473 old_value,
474 old_formula,
475 old_dependencies,
476 old_dependents,
477 coord,
478 sheet_id,
479 kind,
480 ..
481 } => {
482 if let (Some(c), Some(sid)) = (coord, sheet_id) {
483 let meta =
484 VertexMeta::new(c.row(), c.col(), sid, kind.unwrap_or(VertexKind::Cell));
485 let new_id = self.try_add_vertex(meta)?;
486 if let Some(v) = old_value {
487 let cell_ref = self.graph.make_cell_ref_internal(sid, c.row(), c.col());
488 self.set_cell_value(cell_ref, v);
489 }
490 if let Some(f) = old_formula {
491 let cell_ref = self.graph.make_cell_ref_internal(sid, c.row(), c.col());
492 self.set_cell_formula(cell_ref, f);
493 }
494 for dep in old_dependencies {
495 self.graph.add_dependency_edge(new_id, dep)?;
496 }
497 for parent in old_dependents {
498 self.graph.add_dependency_edge(parent, new_id)?;
499 }
500 }
501 }
502 ChangeEvent::DefineName { name, scope, .. } => {
503 self.graph.delete_name(&name, scope)?;
505 }
506 ChangeEvent::UpdateName {
507 name,
508 scope,
509 old_definition,
510 ..
511 } => {
512 self.graph.update_name(&name, old_definition, scope)?;
514 }
515 ChangeEvent::DeleteName {
516 name,
517 scope,
518 old_definition,
519 } => {
520 if let Some(def) = old_definition {
521 self.graph.define_name(&name, def, scope)?;
522 } else {
523 return Err(EditorError::TransactionFailed {
524 reason: "Missing old definition for name deletion rollback".to_string(),
525 });
526 }
527 }
528 ChangeEvent::SpillCommitted { anchor, old, .. } => {
529 if let Some(old) = old {
531 self.graph
532 .commit_spill_region_atomic_with_fault(
533 anchor,
534 old.target_cells,
535 old.values,
536 None,
537 )
538 .map_err(EditorError::Excel)?;
539 } else {
540 self.graph.clear_spill_region(anchor);
541 }
542 }
543 ChangeEvent::SpillCleared { anchor, old } => {
544 self.graph
546 .commit_spill_region_atomic_with_fault(
547 anchor,
548 old.target_cells,
549 old.values,
550 None,
551 )
552 .map_err(EditorError::Excel)?;
553 }
554 ChangeEvent::StagedFormulaCellChanged { .. } => {
555 }
557 ChangeEvent::CompoundStart { .. } | ChangeEvent::CompoundEnd { .. } => {
559 }
561 ChangeEvent::VertexMoved {
562 id,
563 sheet_id: _,
564 old_coord,
565 ..
566 } => {
567 self.move_vertex(id, old_coord)?;
569 }
570 ChangeEvent::FormulaAdjusted { id, old_ast, .. } => {
571 self.graph
573 .update_vertex_formula(id, old_ast)
574 .map_err(EditorError::Excel)?;
575 self.graph.mark_vertex_dirty(id);
576 }
577 ChangeEvent::NamedRangeAdjusted {
578 name,
579 scope,
580 old_definition,
581 ..
582 } => {
583 self.graph.update_name(&name, old_definition, scope)?;
585 }
586 ChangeEvent::EdgeAdded { from, to } => {
587 return Err(EditorError::TransactionFailed {
590 reason: "Cannot rollback edge addition yet".to_string(),
591 });
592 }
593 ChangeEvent::EdgeRemoved { from, to } => {
594 return Err(EditorError::TransactionFailed {
597 reason: "Cannot rollback edge removal yet".to_string(),
598 });
599 }
600 }
601 Ok(())
602 }
603
604 pub fn add_vertex(&mut self, meta: VertexMeta) -> VertexId {
609 self.try_add_vertex(meta)
610 .unwrap_or_else(|_| VertexId::new(0))
611 }
612
613 pub fn try_add_vertex(&mut self, meta: VertexMeta) -> Result<VertexId, EditorError> {
614 let sheet_name = self.graph.sheet_name(meta.sheet_id).to_string();
617
618 let id = self
621 .graph
622 .set_cell_value(
623 &sheet_name,
624 meta.coord.row() + 1,
625 meta.coord.col() + 1,
626 LiteralValue::Empty,
627 )
628 .map_err(EditorError::Excel)?
629 .affected_vertices
630 .into_iter()
631 .next()
632 .ok_or_else(|| EditorError::TransactionFailed {
633 reason: "vertex addition produced no affected vertex".to_string(),
634 })?;
635
636 if self.has_logger() && id.0 != 0 {
637 self.log_change(ChangeEvent::AddVertex {
638 id,
639 coord: meta.coord,
640 sheet_id: meta.sheet_id,
641 value: Some(LiteralValue::Empty),
642 formula: None,
643 kind: Some(meta.kind),
644 flags: Some(meta.flags),
645 });
646 }
647 Ok(id)
648 }
649
650 pub fn remove_vertex(&mut self, id: VertexId) -> Result<(), EditorError> {
652 if !self.graph.vertex_exists(id) {
654 return Err(EditorError::Excel(
655 ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
656 ));
657 }
658
659 let spill_snapshot = self.snapshot_spill_for_anchor(id);
662 let did_spill_clear = spill_snapshot.is_some();
663 if let Some(old_spill) = spill_snapshot {
664 if let Some(logger) = &mut self.change_logger {
665 logger.begin_compound(format!("RemoveVertexWithSpillClear id={}", id.0));
666 }
667 self.graph.clear_spill_region(id);
668 self.log_change(ChangeEvent::SpillCleared {
669 anchor: id,
670 old: old_spill,
671 });
672 }
673
674 let dependents = self.graph.get_dependents(id);
676
677 let (
679 old_value,
680 old_formula,
681 old_dependencies,
682 old_dependents,
683 coord,
684 sheet_id_opt,
685 kind,
686 flags,
687 ) = if self.has_logger() {
688 let coord = self.graph.get_grid_addr(id);
689 let sheet_id = self.graph.get_sheet_id(id);
690 let kind = self.graph.get_vertex_kind(id);
691 let flags = 0u8;
693 (
694 self.graph.get_value(id),
695 self.get_formula_ast(id),
696 self.graph.get_dependencies(id), dependents.clone(), coord,
699 Some(sheet_id),
700 Some(kind),
701 Some(flags),
702 )
703 } else {
704 (None, None, vec![], vec![], None, None, None, None)
705 };
706
707 if let Some(cell_ref) = self.graph.get_cell_ref_for_vertex(id) {
709 self.graph.remove_cell_mapping(&cell_ref);
710 }
711
712 self.graph.vertex_formulas.remove(&id);
716 self.graph.vertex_values.remove(&id);
717 self.graph.clear_formula_vertex_dirty(id);
718 self.graph.mark_volatile(id, false);
719 self.graph.store.set_kind(id, VertexKind::Empty);
720 self.graph.store.set_dynamic(id, false);
721
722 self.graph.remove_all_edges(id);
724
725 for dep_id in &dependents {
727 self.graph.mark_as_ref_error(*dep_id);
728 }
729
730 self.graph.mark_deleted(id, true);
732
733 self.log_change(ChangeEvent::RemoveVertex {
735 id,
736 old_value,
737 old_formula,
738 old_dependencies,
739 old_dependents,
740 coord,
741 sheet_id: sheet_id_opt,
742 kind,
743 flags,
744 });
745
746 if did_spill_clear && let Some(logger) = &mut self.change_logger {
747 logger.end_compound();
748 }
749
750 Ok(())
751 }
752
753 pub fn remove_vertex_at(&mut self, cell: CellRef) -> Result<(), EditorError> {
755 if let Some(id) = self.graph.get_vertex_for_cell(&cell) {
756 self.remove_vertex(id)
757 } else {
758 Ok(())
759 }
760 }
761
762 pub fn move_vertex(&mut self, id: VertexId, new_coord: GridAddr) -> Result<(), EditorError> {
770 if !self.graph.vertex_exists(id) {
772 return Err(EditorError::Excel(
773 ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
774 ));
775 }
776 if self.graph.get_grid_addr(id).is_none() {
777 return Err(EditorError::Excel(
778 ExcelError::new(ExcelErrorKind::Ref).with_message(
779 "Symbol vertices have no position and cannot be moved onto the grid",
780 ),
781 ));
782 }
783
784 let old_cell_ref = self.graph.get_cell_ref_for_vertex(id);
786
787 let sheet_id = self.graph.get_sheet_id(id);
789 let new_cell_ref = CellRef::new(
790 sheet_id,
791 Coord::new(new_coord.row(), new_coord.col(), true, true),
792 );
793
794 self.graph.set_grid_addr(id, new_coord);
796
797 self.graph.update_edge_grid_addr(id, new_coord);
799
800 self.graph
802 .update_cell_mapping(id, old_cell_ref, new_cell_ref);
803
804 self.graph.mark_dependents_dirty(id);
806
807 Ok(())
808 }
809
810 pub fn patch_vertex_meta(
812 &mut self,
813 id: VertexId,
814 patch: VertexMetaPatch,
815 ) -> Result<MetaUpdateSummary, EditorError> {
816 if !self.graph.vertex_exists(id) {
817 return Err(EditorError::Excel(
818 ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
819 ));
820 }
821
822 let mut summary = MetaUpdateSummary::default();
823
824 if let Some(coord) = patch.coord {
825 if self.graph.get_grid_addr(id).is_none() {
827 return Err(EditorError::Excel(
828 ExcelError::new(ExcelErrorKind::Ref).with_message(
829 "Symbol vertices have no position and cannot be moved onto the grid",
830 ),
831 ));
832 }
833 self.graph.set_grid_addr(id, coord);
834 self.graph.update_edge_grid_addr(id, coord);
835 summary.coord_changed = true;
836 }
837
838 if let Some(kind) = patch.kind {
839 self.graph.set_kind(id, kind);
840 summary.kind_changed = true;
841 }
842
843 if let Some(dirty) = patch.dirty {
844 self.graph.set_dirty(id, dirty);
845 summary.flags_changed = true;
846 }
847
848 if let Some(volatile) = patch.volatile {
849 self.graph.mark_volatile(id, volatile);
850 summary.flags_changed = true;
851 }
852
853 Ok(summary)
854 }
855
856 pub fn patch_vertex_data(
858 &mut self,
859 id: VertexId,
860 patch: VertexDataPatch,
861 ) -> Result<DataUpdateSummary, EditorError> {
862 if !self.graph.vertex_exists(id) {
863 return Err(EditorError::Excel(
864 ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
865 ));
866 }
867
868 let mut summary = DataUpdateSummary::default();
869
870 if let Some(value) = patch.value {
871 self.graph.update_vertex_value(id, value);
872 summary.value_changed = true;
873
874 let dependents = self.graph.get_dependents(id);
877 for dep in &dependents {
878 self.graph.set_dirty(*dep, true);
879 }
880 summary.dependents_marked_dirty = dependents;
881 }
882
883 if let Some(_formula) = patch.formula {
884 summary.formula_changed = true;
887 }
888
889 Ok(summary)
890 }
891
892 pub fn add_edge(&mut self, from: VertexId, to: VertexId) -> bool {
894 if from == to {
895 return false; }
897
898 true
901 }
902
903 pub fn remove_edge(&mut self, _from: VertexId, _to: VertexId) -> bool {
905 true
907 }
908
909 pub fn insert_rows(
911 &mut self,
912 sheet_id: SheetId,
913 before: u32,
914 count: u32,
915 ) -> Result<ShiftSummary, EditorError> {
916 if count == 0 {
917 return Ok(ShiftSummary::default());
918 }
919
920 let mut summary = ShiftSummary::default();
921
922 self.begin_batch();
924
925 let conservative = crate::engine::graph::StructuralOccupancy::conservative();
926 let occupancy = self.structural_occupancy.as_ref().unwrap_or(&conservative);
927 let range_dependents = self.graph.compressed_range_dependents_for_structural_edit(
928 sheet_id,
929 crate::engine::graph::StructuralEdit::InsertRows { before },
930 occupancy,
931 );
932 #[cfg(test)]
933 {
934 summary.structural_dependents_dirtied = range_dependents.clone();
935 }
936 self.graph.mark_dirty_many(&range_dependents);
937
938 let vertices_to_shift: Vec<(VertexId, GridAddr)> = self
940 .graph
941 .grid_vertices_in_sheet(sheet_id)
942 .filter(|(_, coord)| coord.row() >= before)
943 .collect();
944
945 if let Some(logger) = &mut self.change_logger {
946 logger.begin_compound(format!(
947 "InsertRows sheet={sheet_id} before={before} count={count}"
948 ));
949 }
950 for (id, old_coord) in vertices_to_shift {
952 let new_coord = GridAddr::new(old_coord.row() + count, old_coord.col());
953 if self.has_logger() {
954 self.log_change(ChangeEvent::VertexMoved {
955 id,
956 sheet_id,
957 old_coord,
958 new_coord,
959 });
960 }
961 self.move_vertex(id, new_coord)?;
962 summary.vertices_moved.push(id);
963 }
964
965 let op = ShiftOperation::InsertRows {
967 sheet_id,
968 before,
969 count,
970 };
971 let adjuster = ReferenceAdjuster::new();
972
973 let formula_vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
975
976 for id in formula_vertices {
977 if let Some(ast) = self.get_formula_ast(id)
978 && let Some(adjusted) = adjuster.adjust_ast_if_changed_in_context(
979 &ast,
980 &op,
981 &ReferenceContext::new(self.graph.get_sheet_id(id), self.graph.sheet_reg()),
982 )
983 {
984 if self.has_logger() {
985 self.log_change(ChangeEvent::FormulaAdjusted {
986 id,
987 addr: self.graph.get_cell_ref_for_vertex(id),
988 old_ast: ast.clone(),
989 new_ast: adjusted.clone(),
990 });
991 }
992 self.graph.update_vertex_formula(id, adjusted)?;
993 self.graph.mark_vertex_dirty(id);
994 summary.formulas_updated += 1;
995 }
996 }
997
998 let old_names = if self.has_logger() {
1000 Some(self.snapshot_named_definitions())
1001 } else {
1002 None
1003 };
1004 self.graph.adjust_named_ranges(&op)?;
1005 if let Some(old_names) = old_names {
1006 let new_names = self.snapshot_named_definitions();
1007 for ((scope, name), old_definition) in old_names {
1008 if let Some(new_definition) = new_names.get(&(scope, name.clone()))
1009 && *new_definition != old_definition
1010 {
1011 self.log_change(ChangeEvent::NamedRangeAdjusted {
1012 name,
1013 scope,
1014 old_definition,
1015 new_definition: new_definition.clone(),
1016 });
1017 }
1018 }
1019 }
1020
1021 if let Some(logger) = &mut self.change_logger {
1023 logger.end_compound();
1024 }
1025
1026 self.commit_batch();
1027
1028 Ok(summary)
1029 }
1030
1031 pub fn delete_rows(
1033 &mut self,
1034 sheet_id: SheetId,
1035 start: u32,
1036 count: u32,
1037 ) -> Result<ShiftSummary, EditorError> {
1038 if count == 0 {
1039 return Ok(ShiftSummary::default());
1040 }
1041
1042 let mut summary = ShiftSummary::default();
1043
1044 self.begin_batch();
1045
1046 if let Some(logger) = &mut self.change_logger {
1047 logger.begin_compound(format!(
1048 "DeleteRows sheet={sheet_id} start={start} count={count}"
1049 ));
1050 }
1051
1052 let vertices_to_delete: Vec<VertexId> = self
1054 .graph
1055 .grid_vertices_in_sheet(sheet_id)
1056 .filter(|(_, coord)| coord.row() >= start && coord.row() < start + count)
1057 .map(|(id, _)| id)
1058 .collect();
1059 let conservative = crate::engine::graph::StructuralOccupancy::conservative();
1060 let occupancy = self.structural_occupancy.as_ref().unwrap_or(&conservative);
1061 let range_dependents = self.graph.compressed_range_dependents_for_structural_edit(
1062 sheet_id,
1063 crate::engine::graph::StructuralEdit::DeleteRows {
1064 start,
1065 end: start.saturating_add(count).saturating_sub(1).max(start),
1066 },
1067 occupancy,
1068 );
1069 #[cfg(test)]
1070 {
1071 summary.structural_dependents_dirtied = range_dependents.clone();
1072 }
1073 self.graph.mark_dirty_many(&range_dependents);
1074
1075 for id in vertices_to_delete {
1076 self.remove_vertex(id)?;
1077 summary.vertices_deleted.push(id);
1078 }
1079 let vertices_to_shift: Vec<(VertexId, GridAddr)> = self
1081 .graph
1082 .grid_vertices_in_sheet(sheet_id)
1083 .filter(|(_, coord)| coord.row() >= start + count)
1084 .collect();
1085
1086 for (id, old_coord) in vertices_to_shift {
1087 let new_coord = GridAddr::new(old_coord.row() - count, old_coord.col());
1088 if self.has_logger() {
1089 self.log_change(ChangeEvent::VertexMoved {
1090 id,
1091 sheet_id,
1092 old_coord,
1093 new_coord,
1094 });
1095 }
1096 self.move_vertex(id, new_coord)?;
1097 summary.vertices_moved.push(id);
1098 }
1099
1100 let op = ShiftOperation::DeleteRows {
1102 sheet_id,
1103 start,
1104 count,
1105 };
1106 let adjuster = ReferenceAdjuster::new();
1107
1108 let formula_vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
1109
1110 for id in formula_vertices {
1111 if let Some(ast) = self.get_formula_ast(id)
1112 && let Some(adjusted) = adjuster.adjust_ast_if_changed_in_context(
1113 &ast,
1114 &op,
1115 &ReferenceContext::new(self.graph.get_sheet_id(id), self.graph.sheet_reg()),
1116 )
1117 {
1118 if self.has_logger() {
1119 self.log_change(ChangeEvent::FormulaAdjusted {
1120 id,
1121 addr: self.graph.get_cell_ref_for_vertex(id),
1122 old_ast: ast.clone(),
1123 new_ast: adjusted.clone(),
1124 });
1125 }
1126 self.graph.update_vertex_formula(id, adjusted)?;
1127 self.graph.mark_vertex_dirty(id);
1128 summary.formulas_updated += 1;
1129 }
1130 }
1131
1132 let old_names = if self.has_logger() {
1134 Some(self.snapshot_named_definitions())
1135 } else {
1136 None
1137 };
1138 self.graph.adjust_named_ranges(&op)?;
1139 if let Some(old_names) = old_names {
1140 let new_names = self.snapshot_named_definitions();
1141 for ((scope, name), old_definition) in old_names {
1142 if let Some(new_definition) = new_names.get(&(scope, name.clone()))
1143 && *new_definition != old_definition
1144 {
1145 self.log_change(ChangeEvent::NamedRangeAdjusted {
1146 name,
1147 scope,
1148 old_definition,
1149 new_definition: new_definition.clone(),
1150 });
1151 }
1152 }
1153 }
1154
1155 if let Some(logger) = &mut self.change_logger {
1157 logger.end_compound();
1158 }
1159
1160 self.commit_batch();
1161
1162 Ok(summary)
1163 }
1164
1165 pub fn insert_columns(
1167 &mut self,
1168 sheet_id: SheetId,
1169 before: u32,
1170 count: u32,
1171 ) -> Result<ShiftSummary, EditorError> {
1172 if count == 0 {
1173 return Ok(ShiftSummary::default());
1174 }
1175
1176 let mut summary = ShiftSummary::default();
1177
1178 self.begin_batch();
1180
1181 let conservative = crate::engine::graph::StructuralOccupancy::conservative();
1182 let occupancy = self.structural_occupancy.as_ref().unwrap_or(&conservative);
1183 let range_dependents = self.graph.compressed_range_dependents_for_structural_edit(
1184 sheet_id,
1185 crate::engine::graph::StructuralEdit::InsertColumns { before },
1186 occupancy,
1187 );
1188 #[cfg(test)]
1189 {
1190 summary.structural_dependents_dirtied = range_dependents.clone();
1191 }
1192 self.graph.mark_dirty_many(&range_dependents);
1193
1194 let vertices_to_shift: Vec<(VertexId, GridAddr)> = self
1196 .graph
1197 .grid_vertices_in_sheet(sheet_id)
1198 .filter(|(_, coord)| coord.col() >= before)
1199 .collect();
1200
1201 if let Some(logger) = &mut self.change_logger {
1202 logger.begin_compound(format!(
1203 "InsertColumns sheet={sheet_id} before={before} count={count}"
1204 ));
1205 }
1206 for (id, old_coord) in vertices_to_shift {
1208 let new_coord = GridAddr::new(old_coord.row(), old_coord.col() + count);
1209 if self.has_logger() {
1210 self.log_change(ChangeEvent::VertexMoved {
1211 id,
1212 sheet_id,
1213 old_coord,
1214 new_coord,
1215 });
1216 }
1217 self.move_vertex(id, new_coord)?;
1218 summary.vertices_moved.push(id);
1219 }
1220
1221 let op = ShiftOperation::InsertColumns {
1223 sheet_id,
1224 before,
1225 count,
1226 };
1227 let adjuster = ReferenceAdjuster::new();
1228
1229 let formula_vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
1231
1232 for id in formula_vertices {
1233 if let Some(ast) = self.get_formula_ast(id)
1234 && let Some(adjusted) = adjuster.adjust_ast_if_changed_in_context(
1235 &ast,
1236 &op,
1237 &ReferenceContext::new(self.graph.get_sheet_id(id), self.graph.sheet_reg()),
1238 )
1239 {
1240 if self.has_logger() {
1241 self.log_change(ChangeEvent::FormulaAdjusted {
1242 id,
1243 addr: self.graph.get_cell_ref_for_vertex(id),
1244 old_ast: ast.clone(),
1245 new_ast: adjusted.clone(),
1246 });
1247 }
1248 self.graph.update_vertex_formula(id, adjusted)?;
1249 self.graph.mark_vertex_dirty(id);
1250 summary.formulas_updated += 1;
1251 }
1252 }
1253
1254 let old_names = if self.has_logger() {
1256 Some(self.snapshot_named_definitions())
1257 } else {
1258 None
1259 };
1260 self.graph.adjust_named_ranges(&op)?;
1261 if let Some(old_names) = old_names {
1262 let new_names = self.snapshot_named_definitions();
1263 for ((scope, name), old_definition) in old_names {
1264 if let Some(new_definition) = new_names.get(&(scope, name.clone()))
1265 && *new_definition != old_definition
1266 {
1267 self.log_change(ChangeEvent::NamedRangeAdjusted {
1268 name,
1269 scope,
1270 old_definition,
1271 new_definition: new_definition.clone(),
1272 });
1273 }
1274 }
1275 }
1276
1277 if let Some(logger) = &mut self.change_logger {
1279 logger.end_compound();
1280 }
1281
1282 self.commit_batch();
1283
1284 Ok(summary)
1285 }
1286
1287 pub fn delete_columns(
1289 &mut self,
1290 sheet_id: SheetId,
1291 start: u32,
1292 count: u32,
1293 ) -> Result<ShiftSummary, EditorError> {
1294 if count == 0 {
1295 return Ok(ShiftSummary::default());
1296 }
1297
1298 let mut summary = ShiftSummary::default();
1299
1300 self.begin_batch();
1301
1302 if let Some(logger) = &mut self.change_logger {
1303 logger.begin_compound(format!(
1304 "DeleteColumns sheet={sheet_id} start={start} count={count}"
1305 ));
1306 }
1307
1308 let vertices_to_delete: Vec<VertexId> = self
1310 .graph
1311 .grid_vertices_in_sheet(sheet_id)
1312 .filter(|(_, coord)| coord.col() >= start && coord.col() < start + count)
1313 .map(|(id, _)| id)
1314 .collect();
1315 let conservative = crate::engine::graph::StructuralOccupancy::conservative();
1316 let occupancy = self.structural_occupancy.as_ref().unwrap_or(&conservative);
1317 let range_dependents = self.graph.compressed_range_dependents_for_structural_edit(
1318 sheet_id,
1319 crate::engine::graph::StructuralEdit::DeleteColumns {
1320 start,
1321 end: start.saturating_add(count).saturating_sub(1).max(start),
1322 },
1323 occupancy,
1324 );
1325 #[cfg(test)]
1326 {
1327 summary.structural_dependents_dirtied = range_dependents.clone();
1328 }
1329 self.graph.mark_dirty_many(&range_dependents);
1330
1331 for id in vertices_to_delete {
1332 self.remove_vertex(id)?;
1333 summary.vertices_deleted.push(id);
1334 }
1335 let vertices_to_shift: Vec<(VertexId, GridAddr)> = self
1337 .graph
1338 .grid_vertices_in_sheet(sheet_id)
1339 .filter(|(_, coord)| coord.col() >= start + count)
1340 .collect();
1341
1342 for (id, old_coord) in vertices_to_shift {
1343 let new_coord = GridAddr::new(old_coord.row(), old_coord.col() - count);
1344 if self.has_logger() {
1345 self.log_change(ChangeEvent::VertexMoved {
1346 id,
1347 sheet_id,
1348 old_coord,
1349 new_coord,
1350 });
1351 }
1352 self.move_vertex(id, new_coord)?;
1353 summary.vertices_moved.push(id);
1354 }
1355
1356 let op = ShiftOperation::DeleteColumns {
1358 sheet_id,
1359 start,
1360 count,
1361 };
1362 let adjuster = ReferenceAdjuster::new();
1363
1364 let formula_vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
1365
1366 for id in formula_vertices {
1367 if let Some(ast) = self.get_formula_ast(id)
1368 && let Some(adjusted) = adjuster.adjust_ast_if_changed_in_context(
1369 &ast,
1370 &op,
1371 &ReferenceContext::new(self.graph.get_sheet_id(id), self.graph.sheet_reg()),
1372 )
1373 {
1374 if self.has_logger() {
1375 self.log_change(ChangeEvent::FormulaAdjusted {
1376 id,
1377 addr: self.graph.get_cell_ref_for_vertex(id),
1378 old_ast: ast.clone(),
1379 new_ast: adjusted.clone(),
1380 });
1381 }
1382 self.graph.update_vertex_formula(id, adjusted)?;
1383 self.graph.mark_vertex_dirty(id);
1384 summary.formulas_updated += 1;
1385 }
1386 }
1387
1388 let old_names = if self.has_logger() {
1390 Some(self.snapshot_named_definitions())
1391 } else {
1392 None
1393 };
1394 self.graph.adjust_named_ranges(&op)?;
1395 if let Some(old_names) = old_names {
1396 let new_names = self.snapshot_named_definitions();
1397 for ((scope, name), old_definition) in old_names {
1398 if let Some(new_definition) = new_names.get(&(scope, name.clone()))
1399 && *new_definition != old_definition
1400 {
1401 self.log_change(ChangeEvent::NamedRangeAdjusted {
1402 name,
1403 scope,
1404 old_definition,
1405 new_definition: new_definition.clone(),
1406 });
1407 }
1408 }
1409 }
1410
1411 if let Some(logger) = &mut self.change_logger {
1413 logger.end_compound();
1414 }
1415
1416 self.commit_batch();
1417
1418 Ok(summary)
1419 }
1420
1421 pub fn shift_rows(&mut self, sheet_id: SheetId, start_row: u32, delta: i32) {
1423 if delta == 0 {
1424 return;
1425 }
1426
1427 let change_event = ChangeEvent::SetValue {
1429 addr: CellRef {
1430 sheet_id,
1431 coord: Coord::new(start_row, 0, true, true),
1432 },
1433 old_value: None,
1434 old_formula: None,
1435 new: LiteralValue::Text(format!("Row shift: start={start_row}, delta={delta}")),
1436 };
1437 self.log_change(change_event);
1438
1439 }
1442
1443 pub fn shift_columns(&mut self, sheet_id: SheetId, start_col: u32, delta: i32) {
1445 if delta == 0 {
1446 return;
1447 }
1448
1449 let change_event = ChangeEvent::SetValue {
1451 addr: CellRef {
1452 sheet_id,
1453 coord: Coord::new(0, start_col, true, true),
1454 },
1455 old_value: None,
1456 old_formula: None,
1457 new: LiteralValue::Text(format!("Column shift: start={start_col}, delta={delta}")),
1458 };
1459 self.log_change(change_event);
1460
1461 }
1464
1465 pub fn set_cell_value(&mut self, cell_ref: CellRef, value: LiteralValue) -> VertexId {
1467 self.set_cell_value_with_old_state(cell_ref, value, None, None)
1468 }
1469
1470 pub fn set_cell_value_with_old_state(
1480 &mut self,
1481 cell_ref: CellRef,
1482 value: LiteralValue,
1483 fallback_old_value: Option<LiteralValue>,
1484 fallback_old_formula: Option<ASTNode>,
1485 ) -> VertexId {
1486 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id).to_string();
1487
1488 let old_id = self.graph.get_vertex_id_for_address(&cell_ref).copied();
1491 let old_value = old_id
1492 .and_then(|id| self.graph.get_value(id))
1493 .or(fallback_old_value);
1494 let old_formula = old_id
1495 .and_then(|id| self.get_formula_ast(id))
1496 .or(fallback_old_formula);
1497
1498 let spill_snapshot =
1501 old_id.and_then(|id| self.snapshot_spill_for_anchor(id).map(|s| (id, s)));
1502 let did_spill_clear = spill_snapshot.is_some();
1503 if let Some((anchor, old_spill)) = spill_snapshot {
1504 if let Some(logger) = &mut self.change_logger {
1505 logger.begin_compound(format!(
1506 "SetValueWithSpillClear sheet={} row={} col={}",
1507 cell_ref.sheet_id,
1508 cell_ref.coord.row(),
1509 cell_ref.coord.col()
1510 ));
1511 }
1512 self.graph.clear_spill_region(anchor);
1513 self.log_change(ChangeEvent::SpillCleared {
1514 anchor,
1515 old: old_spill,
1516 });
1517 }
1518
1519 match self.graph.set_cell_value(
1522 &sheet_name,
1523 cell_ref.coord.row() + 1,
1524 cell_ref.coord.col() + 1,
1525 value.clone(),
1526 ) {
1527 Ok(summary) => {
1528 let change_event = ChangeEvent::SetValue {
1530 addr: cell_ref,
1531 old_value,
1532 old_formula,
1533 new: value,
1534 };
1535 self.log_change(change_event);
1536
1537 if did_spill_clear && let Some(logger) = &mut self.change_logger {
1538 logger.end_compound();
1539 }
1540
1541 summary
1542 .affected_vertices
1543 .into_iter()
1544 .next()
1545 .unwrap_or(VertexId::new(0))
1546 }
1547 Err(_) => VertexId::new(0),
1548 }
1549 }
1550
1551 pub fn set_cell_formula(&mut self, cell_ref: CellRef, formula: ASTNode) -> VertexId {
1556 self.set_cell_formula_with_old_state(cell_ref, formula, None, None)
1557 }
1558
1559 pub fn try_set_cell_formula(
1561 &mut self,
1562 cell_ref: CellRef,
1563 formula: ASTNode,
1564 ) -> Result<VertexId, ExcelError> {
1565 self.try_set_cell_formula_with_old_state(cell_ref, formula, None, None)
1566 }
1567
1568 pub fn set_cell_formula_with_old_state(
1574 &mut self,
1575 cell_ref: CellRef,
1576 formula: ASTNode,
1577 fallback_old_value: Option<LiteralValue>,
1578 fallback_old_formula: Option<ASTNode>,
1579 ) -> VertexId {
1580 self.try_set_cell_formula_with_old_state(
1581 cell_ref,
1582 formula,
1583 fallback_old_value,
1584 fallback_old_formula,
1585 )
1586 .unwrap_or_else(|_| VertexId::new(0))
1587 }
1588
1589 pub fn try_set_cell_formula_with_old_state(
1592 &mut self,
1593 cell_ref: CellRef,
1594 formula: ASTNode,
1595 fallback_old_value: Option<LiteralValue>,
1596 fallback_old_formula: Option<ASTNode>,
1597 ) -> Result<VertexId, ExcelError> {
1598 self.set_cell_formula_with_old_state_and_plan(
1599 cell_ref,
1600 formula,
1601 fallback_old_value,
1602 fallback_old_formula,
1603 None,
1604 )
1605 }
1606
1607 pub(crate) fn set_cell_formula_with_prepared_plan(
1608 &mut self,
1609 cell_ref: CellRef,
1610 formula: ASTNode,
1611 fallback_old_value: Option<LiteralValue>,
1612 fallback_old_formula: Option<ASTNode>,
1613 ast_id: crate::engine::arena::AstNodeId,
1614 plan: crate::engine::ingest_pipeline::DependencyPlanRow,
1615 ) -> VertexId {
1616 self.set_cell_formula_with_old_state_and_plan(
1617 cell_ref,
1618 formula,
1619 fallback_old_value,
1620 fallback_old_formula,
1621 Some((ast_id, plan)),
1622 )
1623 .unwrap_or_else(|_| VertexId::new(0))
1624 }
1625
1626 fn set_cell_formula_with_old_state_and_plan(
1627 &mut self,
1628 cell_ref: CellRef,
1629 formula: ASTNode,
1630 fallback_old_value: Option<LiteralValue>,
1631 fallback_old_formula: Option<ASTNode>,
1632 prepared: Option<(
1633 crate::engine::arena::AstNodeId,
1634 crate::engine::ingest_pipeline::DependencyPlanRow,
1635 )>,
1636 ) -> Result<VertexId, ExcelError> {
1637 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id).to_string();
1638
1639 let old_id = self.graph.get_vertex_id_for_address(&cell_ref).copied();
1642 let old_value = old_id
1643 .and_then(|id| self.graph.get_value(id))
1644 .or(fallback_old_value);
1645 let old_formula = old_id
1646 .and_then(|id| self.get_formula_ast(id))
1647 .or(fallback_old_formula);
1648
1649 let spill_snapshot =
1652 old_id.and_then(|id| self.snapshot_spill_for_anchor(id).map(|s| (id, s)));
1653 let did_spill_clear = spill_snapshot.is_some();
1654
1655 let result = if let Some((ast_id, plan)) = prepared {
1657 self.graph.set_cell_formula_with_plan(
1658 &sheet_name,
1659 cell_ref.coord.row() + 1,
1660 cell_ref.coord.col() + 1,
1661 ast_id,
1662 &plan,
1663 plan.volatile,
1664 plan.dynamic,
1665 )
1666 } else {
1667 self.graph.set_cell_formula(
1668 &sheet_name,
1669 cell_ref.coord.row() + 1,
1670 cell_ref.coord.col() + 1,
1671 formula.clone(),
1672 )
1673 };
1674 match result {
1675 Ok(summary) => {
1676 if let Some((anchor, old_spill)) = spill_snapshot {
1677 if let Some(logger) = &mut self.change_logger {
1678 logger.begin_compound(format!(
1679 "SetFormulaWithSpillClear sheet={} row={} col={}",
1680 cell_ref.sheet_id,
1681 cell_ref.coord.row(),
1682 cell_ref.coord.col()
1683 ));
1684 }
1685 self.graph.clear_spill_region(anchor);
1686 self.log_change(ChangeEvent::SpillCleared {
1687 anchor,
1688 old: old_spill,
1689 });
1690 }
1691 let change_event = ChangeEvent::SetFormula {
1693 addr: cell_ref,
1694 old_value,
1695 old_formula,
1696 new: formula,
1697 };
1698 self.log_change(change_event);
1699
1700 if did_spill_clear && let Some(logger) = &mut self.change_logger {
1701 logger.end_compound();
1702 }
1703
1704 Ok(summary
1705 .affected_vertices
1706 .into_iter()
1707 .next()
1708 .unwrap_or(VertexId::new(0)))
1709 }
1710 Err(error) => Err(error),
1711 }
1712 }
1713
1714 pub fn set_range_values(
1718 &mut self,
1719 sheet_id: SheetId,
1720 start_row: u32,
1721 start_col: u32,
1722 values: &[Vec<LiteralValue>],
1723 ) -> Result<RangeSummary, EditorError> {
1724 let mut summary = RangeSummary::default();
1725
1726 self.begin_batch();
1727 self.graph.begin_deferred_dirty();
1731
1732 for (row_offset, row_values) in values.iter().enumerate() {
1733 for (col_offset, value) in row_values.iter().enumerate() {
1734 let row = start_row + row_offset as u32;
1735 let col = start_col + col_offset as u32;
1736 let cell_ref = self.graph.make_cell_ref_internal(sheet_id, row, col);
1737 let existing_id = self.graph.get_vertex_id_for_address(&cell_ref).copied();
1738
1739 let id = self.set_cell_value(cell_ref, value.clone());
1740 match existing_id {
1741 Some(existing_id) => summary.vertices_updated.push(existing_id),
1742 None if id.0 != 0 => summary.vertices_created.push(id),
1743 None => {}
1744 }
1745 summary.cells_affected += 1;
1746 }
1747 }
1748
1749 let _ = self.graph.end_deferred_dirty();
1750 self.commit_batch();
1751
1752 Ok(summary)
1753 }
1754
1755 pub fn clear_range(
1757 &mut self,
1758 sheet_id: SheetId,
1759 start_row: u32,
1760 start_col: u32,
1761 end_row: u32,
1762 end_col: u32,
1763 ) -> Result<RangeSummary, EditorError> {
1764 let mut summary = RangeSummary::default();
1765
1766 self.begin_batch();
1767
1768 let vertices_in_range: Vec<_> = self
1770 .graph
1771 .grid_vertices_in_sheet(sheet_id)
1772 .filter(|(_, coord)| {
1773 let row = coord.row();
1774 let col = coord.col();
1775 row >= start_row && row <= end_row && col >= start_col && col <= end_col
1776 })
1777 .map(|(id, _)| id)
1778 .collect();
1779
1780 for id in vertices_in_range {
1781 self.remove_vertex(id)?;
1782 summary.cells_affected += 1;
1783 }
1784
1785 self.commit_batch();
1786
1787 Ok(summary)
1788 }
1789
1790 pub fn copy_range(
1792 &mut self,
1793 sheet_id: SheetId,
1794 from_start_row: u32,
1795 from_start_col: u32,
1796 from_end_row: u32,
1797 from_end_col: u32,
1798 to_sheet_id: SheetId,
1799 to_row: u32,
1800 to_col: u32,
1801 ) -> Result<RangeSummary, EditorError> {
1802 let row_offset = to_row as i32 - from_start_row as i32;
1803 let col_offset = to_col as i32 - from_start_col as i32;
1804
1805 let mut summary = RangeSummary::default();
1806 let mut cell_data = Vec::new();
1807
1808 let vertices_in_range: Vec<_> = self
1810 .graph
1811 .grid_vertices_in_sheet(sheet_id)
1812 .filter(|(_, coord)| {
1813 let row = coord.row();
1814 let col = coord.col();
1815 row >= from_start_row
1816 && row <= from_end_row
1817 && col >= from_start_col
1818 && col <= from_end_col
1819 })
1820 .collect();
1821
1822 for (id, coord) in vertices_in_range {
1823 let row = coord.row();
1824 let col = coord.col();
1825
1826 if let Some(formula) = self.get_formula_ast(id) {
1828 cell_data.push((
1829 row - from_start_row,
1830 col - from_start_col,
1831 CellData::Formula(formula),
1832 ));
1833 } else if let Some(value) = self.graph.get_value(id) {
1834 cell_data.push((
1835 row - from_start_row,
1836 col - from_start_col,
1837 CellData::Value(value),
1838 ));
1839 }
1840 }
1841
1842 self.begin_batch();
1843
1844 for (row_idx, col_idx, data) in cell_data {
1846 let dest_row = (to_row as i32 + row_idx as i32) as u32;
1847 let dest_col = (to_col as i32 + col_idx as i32) as u32;
1848
1849 match data {
1850 CellData::Value(value) => {
1851 let cell_ref =
1852 self.graph
1853 .make_cell_ref_internal(to_sheet_id, dest_row, dest_col);
1854
1855 if let Some(&existing_id) = self.graph.get_vertex_id_for_address(&cell_ref) {
1856 self.graph.update_vertex_value(existing_id, value);
1857 self.graph.mark_vertex_dirty(existing_id);
1858 summary.vertices_updated.push(existing_id);
1859 } else {
1860 let meta =
1861 VertexMeta::new(dest_row, dest_col, to_sheet_id, VertexKind::Cell);
1862 let id = self.try_add_vertex(meta)?;
1863 self.graph.update_vertex_value(id, value);
1864 summary.vertices_created.push(id);
1865 }
1866 }
1867 CellData::Formula(formula) => {
1868 let adjuster = RelativeReferenceAdjuster::new(row_offset, col_offset);
1870 let adjusted = adjuster.adjust_formula(&formula);
1871
1872 let cell_ref =
1873 self.graph
1874 .make_cell_ref_internal(to_sheet_id, dest_row, dest_col);
1875
1876 if let Some(&existing_id) = self.graph.get_vertex_id_for_address(&cell_ref) {
1877 self.graph.update_vertex_formula(existing_id, adjusted)?;
1878 summary.vertices_updated.push(existing_id);
1879 } else {
1880 let meta = VertexMeta::new(
1881 dest_row,
1882 dest_col,
1883 to_sheet_id,
1884 VertexKind::FormulaScalar,
1885 );
1886 let id = self.try_add_vertex(meta)?;
1887 self.graph.update_vertex_formula(id, adjusted)?;
1888 summary.vertices_created.push(id);
1889 }
1890 }
1891 }
1892
1893 summary.cells_affected += 1;
1894 }
1895
1896 self.commit_batch();
1897
1898 Ok(summary)
1899 }
1900
1901 pub fn move_range(
1903 &mut self,
1904 sheet_id: SheetId,
1905 from_start_row: u32,
1906 from_start_col: u32,
1907 from_end_row: u32,
1908 from_end_col: u32,
1909 to_sheet_id: SheetId,
1910 to_row: u32,
1911 to_col: u32,
1912 ) -> Result<RangeSummary, EditorError> {
1913 let mut summary = self.copy_range(
1915 sheet_id,
1916 from_start_row,
1917 from_start_col,
1918 from_end_row,
1919 from_end_col,
1920 to_sheet_id,
1921 to_row,
1922 to_col,
1923 )?;
1924
1925 let clear_summary = self.clear_range(
1927 sheet_id,
1928 from_start_row,
1929 from_start_col,
1930 from_end_row,
1931 from_end_col,
1932 )?;
1933
1934 summary.cells_moved = clear_summary.cells_affected;
1935
1936 let row_offset = to_row as i32 - from_start_row as i32;
1938 let col_offset = to_col as i32 - from_start_col as i32;
1939
1940 let all_formula_vertices: Vec<_> = self.graph.vertices_with_formulas().collect();
1942
1943 let from_sheet_name = self.graph.sheet_name(sheet_id).to_string();
1944 let to_sheet_name = self.graph.sheet_name(to_sheet_id).to_string();
1945 let adjuster = MoveReferenceAdjuster::new(
1946 sheet_id,
1947 from_sheet_name,
1948 from_start_row,
1949 from_start_col,
1950 from_end_row,
1951 from_end_col,
1952 to_sheet_id,
1953 to_sheet_name,
1954 row_offset,
1955 col_offset,
1956 );
1957
1958 for formula_id in all_formula_vertices {
1959 if let Some(formula) = self.get_formula_ast(formula_id) {
1960 let formula_sheet_id = self.graph.get_vertex_sheet_id(formula_id);
1961 if let Some(adjusted) = adjuster.adjust_if_references(&formula, formula_sheet_id) {
1962 self.graph.update_vertex_formula(formula_id, adjusted)?;
1963 }
1964 }
1965 }
1966
1967 Ok(summary)
1968 }
1969
1970 pub fn define_name(
1972 &mut self,
1973 name: &str,
1974 definition: NamedDefinition,
1975 scope: NameScope,
1976 ) -> Result<(), EditorError> {
1977 self.graph.define_name(name, definition.clone(), scope)?;
1978
1979 self.log_change(ChangeEvent::DefineName {
1980 name: name.to_string(),
1981 scope,
1982 definition,
1983 });
1984
1985 Ok(())
1986 }
1987
1988 pub fn define_name_for_cell(
1990 &mut self,
1991 name: &str,
1992 sheet_name: &str,
1993 row: u32,
1994 col: u32,
1995 scope: NameScope,
1996 ) -> Result<(), EditorError> {
1997 let sheet_id = self
1998 .graph
1999 .sheet_id(sheet_name)
2000 .ok_or_else(|| EditorError::InvalidName {
2001 name: sheet_name.to_string(),
2002 reason: "Sheet not found".to_string(),
2003 })?;
2004 let cell_ref = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
2005 self.define_name(name, NamedDefinition::Cell(cell_ref), scope)
2006 }
2007
2008 pub fn define_name_for_range(
2010 &mut self,
2011 name: &str,
2012 sheet_name: &str,
2013 start_row: u32,
2014 start_col: u32,
2015 end_row: u32,
2016 end_col: u32,
2017 scope: NameScope,
2018 ) -> Result<(), EditorError> {
2019 let sheet_id = self
2020 .graph
2021 .sheet_id(sheet_name)
2022 .ok_or_else(|| EditorError::InvalidName {
2023 name: sheet_name.to_string(),
2024 reason: "Sheet not found".to_string(),
2025 })?;
2026 let start = CellRef::new(
2027 sheet_id,
2028 Coord::from_excel(start_row, start_col, true, true),
2029 );
2030 let end = CellRef::new(sheet_id, Coord::from_excel(end_row, end_col, true, true));
2031 let range_ref = crate::reference::RangeRef::new(start, end);
2032 self.define_name(name, NamedDefinition::Range(range_ref), scope)
2033 }
2034
2035 pub fn update_name(
2037 &mut self,
2038 name: &str,
2039 new_definition: NamedDefinition,
2040 scope: NameScope,
2041 ) -> Result<(), EditorError> {
2042 let old_definition = self
2044 .graph
2045 .resolve_name(
2046 name,
2047 match scope {
2048 NameScope::Sheet(id) => id,
2049 NameScope::Workbook => 0,
2050 },
2051 )
2052 .cloned();
2053
2054 self.graph
2055 .update_name(name, new_definition.clone(), scope)?;
2056
2057 if let Some(old_def) = old_definition {
2058 self.log_change(ChangeEvent::UpdateName {
2059 name: name.to_string(),
2060 scope,
2061 old_definition: old_def,
2062 new_definition,
2063 });
2064 }
2065
2066 Ok(())
2067 }
2068
2069 pub fn delete_name(&mut self, name: &str, scope: NameScope) -> Result<(), EditorError> {
2071 let old_def = if self.has_logger() {
2073 self.graph
2074 .resolve_name(
2075 name,
2076 match scope {
2077 NameScope::Sheet(id) => id,
2078 NameScope::Workbook => 0,
2079 },
2080 )
2081 .cloned()
2082 } else {
2083 None
2084 };
2085
2086 self.graph.delete_name(name, scope)?;
2087 self.log_change(ChangeEvent::DeleteName {
2088 name: name.to_string(),
2089 scope,
2090 old_definition: old_def,
2091 });
2092
2093 Ok(())
2094 }
2095}
2096
2097enum CellData {
2099 Value(LiteralValue),
2100 Formula(ASTNode),
2101}
2102
2103impl<'g> Drop for VertexEditor<'g> {
2104 fn drop(&mut self) {
2105 if self.batch_mode {
2107 self.commit_batch();
2108 }
2109 }
2110}
2111
2112#[cfg(test)]
2113mod tests {
2114 use super::*;
2115 use crate::engine::graph::editor::change_log::{ChangeEvent, ChangeLog};
2116 use crate::reference::Coord;
2117
2118 fn create_test_graph() -> DependencyGraph {
2119 DependencyGraph::new()
2120 }
2121
2122 #[test]
2123 fn test_vertex_editor_creation() {
2124 let mut graph = create_test_graph();
2125 let editor = VertexEditor::new(&mut graph);
2126 assert!(!editor.has_logger());
2127 assert!(!editor.batch_mode);
2128 }
2129
2130 #[test]
2131 fn test_vertex_editor_with_logger() {
2132 let mut graph = create_test_graph();
2133 let mut log = ChangeLog::new();
2134 let editor = VertexEditor::with_logger(&mut graph, &mut log);
2135 assert!(editor.has_logger());
2136 assert!(!editor.batch_mode);
2137 }
2138
2139 #[test]
2140 fn test_add_vertex() {
2141 let mut graph = create_test_graph();
2142 let mut editor = VertexEditor::new(&mut graph);
2143
2144 let meta = VertexMeta::new(5, 10, 0, VertexKind::Cell).dirty();
2145 let vertex_id = editor.add_vertex(meta);
2146
2147 assert!(vertex_id.0 > 0);
2149 }
2150
2151 #[test]
2152 fn test_batch_operations() {
2153 let mut graph = create_test_graph();
2154 let mut editor = VertexEditor::new(&mut graph);
2155
2156 assert!(!editor.batch_mode);
2157 editor.begin_batch();
2158 assert!(editor.batch_mode);
2159
2160 let meta1 = VertexMeta::new(1, 1, 0, VertexKind::Cell);
2162 let meta2 = VertexMeta::new(2, 2, 0, VertexKind::Cell);
2163
2164 let id1 = editor.add_vertex(meta1);
2165 let id2 = editor.add_vertex(meta2);
2166
2167 assert!(editor.add_edge(id1, id2));
2169
2170 editor.commit_batch();
2171 assert!(!editor.batch_mode);
2172 }
2173
2174 #[test]
2175 fn test_remove_vertex() {
2176 let mut graph = create_test_graph();
2177 let mut editor = VertexEditor::new(&mut graph);
2178
2179 let meta = VertexMeta::new(3, 4, 0, VertexKind::Cell).dirty();
2180 let vertex_id = editor.add_vertex(meta);
2181
2182 assert!(editor.remove_vertex(vertex_id).is_ok());
2184 }
2185
2186 #[test]
2187 fn test_remove_vertex_clears_spill_registry_for_anchor() {
2188 let mut graph = create_test_graph();
2189 let sheet_id = graph.sheet_id_mut("Sheet1");
2190
2191 let anchor_cell = CellRef::new(sheet_id, Coord::new(0, 0, true, true));
2193 let anchor_vid = {
2194 let mut editor = VertexEditor::new(&mut graph);
2195 editor.set_cell_value(anchor_cell, LiteralValue::Number(0.0))
2196 };
2197
2198 let target_cells = vec![
2199 CellRef::new(sheet_id, Coord::new(0, 0, true, true)),
2200 CellRef::new(sheet_id, Coord::new(0, 1, true, true)),
2201 CellRef::new(sheet_id, Coord::new(1, 0, true, true)),
2202 CellRef::new(sheet_id, Coord::new(1, 1, true, true)),
2203 ];
2204 let values = vec![
2205 vec![LiteralValue::Number(1.0), LiteralValue::Number(2.0)],
2206 vec![LiteralValue::Number(3.0), LiteralValue::Number(4.0)],
2207 ];
2208
2209 graph
2210 .commit_spill_region_atomic_with_fault(anchor_vid, target_cells.clone(), values, None)
2211 .unwrap();
2212
2213 assert!(graph.spill_registry_has_anchor(anchor_vid));
2214 for cell in &target_cells {
2215 assert_eq!(
2216 graph.spill_registry_anchor_for_cell(*cell),
2217 Some(anchor_vid)
2218 );
2219 }
2220
2221 {
2222 let mut editor = VertexEditor::new(&mut graph);
2223 editor.remove_vertex(anchor_vid).unwrap();
2224 }
2225
2226 assert!(!graph.spill_registry_has_anchor(anchor_vid));
2227 for cell in &target_cells {
2228 assert_eq!(graph.spill_registry_anchor_for_cell(*cell), None);
2229 }
2230 assert_eq!(graph.spill_registry_counts(), (0, 0));
2231 }
2232
2233 #[test]
2234 fn test_edge_operations() {
2235 let mut graph = create_test_graph();
2236 let mut editor = VertexEditor::new(&mut graph);
2237
2238 let meta1 = VertexMeta::new(1, 1, 0, VertexKind::Cell);
2239 let meta2 = VertexMeta::new(2, 2, 0, VertexKind::FormulaScalar);
2240
2241 let id1 = editor.add_vertex(meta1);
2242 let id2 = editor.add_vertex(meta2);
2243
2244 assert!(editor.add_edge(id1, id2));
2246
2247 assert!(!editor.add_edge(id1, id1));
2249
2250 assert!(editor.remove_edge(id1, id2));
2252 }
2253
2254 #[test]
2255 fn test_set_cell_value() {
2256 let mut graph = create_test_graph();
2257 let mut log = ChangeLog::new();
2258
2259 let cell_ref = CellRef {
2260 sheet_id: 0,
2261 coord: Coord::new(2, 3, true, true),
2262 };
2263 let value = LiteralValue::Number(42.0);
2264
2265 let vertex_id = {
2266 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2267 editor.set_cell_value(cell_ref, value.clone())
2268 };
2269
2270 assert!(vertex_id.0 > 0);
2272
2273 assert_eq!(log.len(), 1);
2275 match &log.events()[0] {
2276 ChangeEvent::SetValue { addr, new, .. } => {
2277 assert_eq!(addr.sheet_id, cell_ref.sheet_id);
2278 assert_eq!(addr.coord.row(), cell_ref.coord.row());
2279 assert_eq!(addr.coord.col(), cell_ref.coord.col());
2280 assert_eq!(new, &value);
2281 }
2282 _ => panic!("Expected SetValue event"),
2283 }
2284 }
2285
2286 #[test]
2287 fn test_set_cell_formula() {
2288 let mut graph = create_test_graph();
2289 let mut log = ChangeLog::new();
2290
2291 let cell_ref = CellRef {
2292 sheet_id: 0,
2293 coord: Coord::new(1, 1, true, true),
2294 };
2295
2296 use formualizer_parse::parser::ASTNodeType;
2297 let formula = formualizer_parse::parser::ASTNode {
2298 node_type: ASTNodeType::Literal(LiteralValue::Number(100.0)),
2299 source_token: None,
2300 contains_volatile: false,
2301 };
2302
2303 let vertex_id = {
2304 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2305 editor.set_cell_formula(cell_ref, formula.clone())
2306 };
2307
2308 assert!(vertex_id.0 > 0);
2310
2311 assert_eq!(log.len(), 1);
2313 match &log.events()[0] {
2314 ChangeEvent::SetFormula { addr, .. } => {
2315 assert_eq!(addr.sheet_id, cell_ref.sheet_id);
2316 assert_eq!(addr.coord.row(), cell_ref.coord.row());
2317 assert_eq!(addr.coord.col(), cell_ref.coord.col());
2318 }
2319 _ => panic!("Expected SetFormula event"),
2320 }
2321 }
2322
2323 #[test]
2324 fn test_shift_rows() {
2325 let mut graph = create_test_graph();
2326 let mut log = ChangeLog::new();
2327
2328 {
2329 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2330
2331 let cell1 = CellRef {
2333 sheet_id: 0,
2334 coord: Coord::new(5, 1, true, true),
2335 };
2336 let cell2 = CellRef {
2337 sheet_id: 0,
2338 coord: Coord::new(10, 1, true, true),
2339 };
2340 let cell3 = CellRef {
2341 sheet_id: 0,
2342 coord: Coord::new(15, 1, true, true),
2343 };
2344
2345 editor.set_cell_value(cell1, LiteralValue::Number(1.0));
2346 editor.set_cell_value(cell2, LiteralValue::Number(2.0));
2347 editor.set_cell_value(cell3, LiteralValue::Number(3.0));
2348 }
2349
2350 log.clear();
2352
2353 {
2354 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2355 editor.shift_rows(0, 10, 2);
2357 }
2358
2359 assert_eq!(log.len(), 1);
2361 match &log.events()[0] {
2362 ChangeEvent::SetValue { addr, new, .. } => {
2363 assert_eq!(addr.sheet_id, 0);
2364 assert_eq!(addr.coord.row(), 10);
2365 if let LiteralValue::Text(msg) = new {
2366 assert!(msg.contains("Row shift"));
2367 assert!(msg.contains("start=10"));
2368 assert!(msg.contains("delta=2"));
2369 }
2370 }
2371 _ => panic!("Expected SetValue event for row shift"),
2372 }
2373 }
2374
2375 #[test]
2376 fn test_shift_columns() {
2377 let mut graph = create_test_graph();
2378 let mut log = ChangeLog::new();
2379
2380 {
2381 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2382
2383 let cell1 = CellRef {
2385 sheet_id: 0,
2386 coord: Coord::new(1, 5, true, true),
2387 };
2388 let cell2 = CellRef {
2389 sheet_id: 0,
2390 coord: Coord::new(1, 10, true, true),
2391 };
2392
2393 editor.set_cell_value(cell1, LiteralValue::Number(1.0));
2394 editor.set_cell_value(cell2, LiteralValue::Number(2.0));
2395 }
2396
2397 log.clear();
2399
2400 {
2401 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2402 editor.shift_columns(0, 8, 3);
2404 }
2405
2406 assert_eq!(log.len(), 1);
2408 match &log.events()[0] {
2409 ChangeEvent::SetValue { addr, new, .. } => {
2410 assert_eq!(addr.sheet_id, 0);
2411 assert_eq!(addr.coord.col(), 8);
2412 if let LiteralValue::Text(msg) = new {
2413 assert!(msg.contains("Column shift"));
2414 assert!(msg.contains("start=8"));
2415 assert!(msg.contains("delta=3"));
2416 }
2417 }
2418 _ => panic!("Expected SetValue event for column shift"),
2419 }
2420 }
2421
2422 #[test]
2423 fn test_move_vertex() {
2424 let mut graph = create_test_graph();
2425 let mut editor = VertexEditor::new(&mut graph);
2426
2427 let meta = VertexMeta::new(5, 10, 0, VertexKind::Cell);
2428 let vertex_id = editor.add_vertex(meta);
2429
2430 assert!(editor.move_vertex(vertex_id, GridAddr::new(8, 12)).is_ok());
2432
2433 assert!(editor.move_vertex(vertex_id, GridAddr::new(8, 12)).is_ok());
2435 }
2436
2437 #[test]
2438 fn test_vertex_meta_builder() {
2439 let meta = VertexMeta::new(1, 2, 3, VertexKind::FormulaScalar)
2440 .dirty()
2441 .volatile()
2442 .with_flags(0x08);
2443
2444 assert_eq!(meta.coord.row(), 1);
2445 assert_eq!(meta.coord.col(), 2);
2446 assert_eq!(meta.sheet_id, 3);
2447 assert_eq!(meta.kind, VertexKind::FormulaScalar);
2448 assert_eq!(meta.flags, 0x08); }
2450
2451 #[test]
2452 fn test_change_log_management() {
2453 let mut graph = create_test_graph();
2454 let mut log = ChangeLog::new();
2455
2456 {
2457 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
2458 let cell_ref = CellRef {
2459 sheet_id: 0,
2460 coord: Coord::new(0, 0, true, true),
2461 };
2462 editor.set_cell_value(cell_ref, LiteralValue::Number(1.0));
2463 editor.set_cell_value(cell_ref, LiteralValue::Number(2.0));
2464 }
2465
2466 assert_eq!(log.len(), 2);
2467
2468 log.clear();
2469 assert_eq!(log.len(), 0);
2470 }
2471
2472 #[test]
2473 fn test_editor_drop_commits_batch() {
2474 let mut graph = create_test_graph();
2475 {
2476 let mut editor = VertexEditor::new(&mut graph);
2477 editor.begin_batch();
2478
2479 let meta = VertexMeta::new(1, 1, 0, VertexKind::Cell);
2480 editor.add_vertex(meta);
2481
2482 }
2484
2485 }
2487}