use crate::SheetId;
use crate::engine::graph::DependencyGraph;
use crate::engine::graph::editor::reference_adjuster::{
MoveReferenceAdjuster, ReferenceAdjuster, RelativeReferenceAdjuster, ShiftOperation,
};
use crate::engine::named_range::{NameScope, NamedDefinition};
use crate::engine::{ChangeEvent, ChangeLogger, VertexId, VertexKind};
use crate::reference::{CellRef, Coord};
use formualizer_common::Coord as AbsCoord;
use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
use formualizer_parse::parser::ASTNode;
use rustc_hash::FxHashMap;
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Clone)]
pub struct VertexMeta {
pub coord: AbsCoord,
pub sheet_id: SheetId,
pub kind: VertexKind,
pub flags: u8,
}
impl VertexMeta {
pub fn new(row: u32, col: u32, sheet_id: SheetId, kind: VertexKind) -> Self {
Self {
coord: AbsCoord::new(row, col),
sheet_id,
kind,
flags: 0,
}
}
pub fn with_flags(mut self, flags: u8) -> Self {
self.flags = flags;
self
}
pub fn dirty(mut self) -> Self {
self.flags |= 0x01;
self
}
pub fn volatile(mut self) -> Self {
self.flags |= 0x02;
self
}
}
#[derive(Debug, Clone)]
pub struct VertexMetaPatch {
pub kind: Option<VertexKind>,
pub coord: Option<AbsCoord>,
pub dirty: Option<bool>,
pub volatile: Option<bool>,
}
#[derive(Debug, Clone)]
pub struct VertexDataPatch {
pub value: Option<LiteralValue>,
pub formula: Option<ASTNode>,
}
#[derive(Debug, Clone, Default)]
pub struct MetaUpdateSummary {
pub coord_changed: bool,
pub kind_changed: bool,
pub flags_changed: bool,
}
#[derive(Debug, Clone, Default)]
pub struct DataUpdateSummary {
pub value_changed: bool,
pub formula_changed: bool,
pub dependents_marked_dirty: Vec<VertexId>,
}
#[derive(Debug, Clone, Default)]
pub struct ShiftSummary {
pub vertices_moved: Vec<VertexId>,
pub vertices_deleted: Vec<VertexId>,
pub references_adjusted: usize,
pub formulas_updated: usize,
}
#[derive(Debug, Clone, Default)]
pub struct RangeSummary {
pub cells_affected: usize,
pub vertices_created: Vec<VertexId>,
pub vertices_updated: Vec<VertexId>,
pub cells_moved: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TransactionId(u64);
impl TransactionId {
fn new() -> Self {
static COUNTER: AtomicU64 = AtomicU64::new(0);
TransactionId(COUNTER.fetch_add(1, Ordering::Relaxed))
}
}
#[derive(Debug)]
struct Transaction {
id: TransactionId,
start_index: usize, }
#[derive(Debug, Clone)]
pub enum EditorError {
TargetOccupied { cell: CellRef },
OutOfBounds { row: u32, col: u32 },
InvalidName { name: String, reason: String },
TransactionFailed { reason: String },
TransactionUnsupported { reason: String },
NoActiveTransaction,
VertexNotFound { id: VertexId },
Excel(ExcelError),
}
impl From<ExcelError> for EditorError {
fn from(e: ExcelError) -> Self {
EditorError::Excel(e)
}
}
impl std::fmt::Display for EditorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EditorError::TargetOccupied { cell } => {
write!(
f,
"Target cell occupied at row {}, col {}",
cell.coord.row(),
cell.coord.col()
)
}
EditorError::OutOfBounds { row, col } => {
write!(f, "Cell position out of bounds: row {row}, col {col}")
}
EditorError::InvalidName { name, reason } => {
write!(f, "Invalid name '{name}': {reason}")
}
EditorError::TransactionFailed { reason } => {
write!(f, "Transaction failed: {reason}")
}
EditorError::TransactionUnsupported { reason } => {
write!(f, "Transaction unsupported: {reason}")
}
EditorError::NoActiveTransaction => {
write!(f, "No active transaction")
}
EditorError::VertexNotFound { id } => {
write!(f, "Vertex not found: {id:?}")
}
EditorError::Excel(e) => write!(f, "Excel error: {e:?}"),
}
}
}
impl std::error::Error for EditorError {}
pub trait SpillValueReader {
fn read_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue>;
}
pub struct VertexEditor<'g> {
graph: &'g mut DependencyGraph,
change_logger: Option<&'g mut dyn ChangeLogger>,
spill_value_reader: Option<&'g dyn SpillValueReader>,
batch_mode: bool,
}
impl<'g> VertexEditor<'g> {
pub fn new(graph: &'g mut DependencyGraph) -> Self {
Self {
graph,
change_logger: None,
spill_value_reader: None,
batch_mode: false,
}
}
pub fn with_logger<L: ChangeLogger + 'g>(
graph: &'g mut DependencyGraph,
logger: &'g mut L,
) -> Self {
Self {
graph,
change_logger: Some(logger as &'g mut dyn ChangeLogger),
spill_value_reader: None,
batch_mode: false,
}
}
pub fn with_logger_and_spill_reader<L: ChangeLogger + 'g>(
graph: &'g mut DependencyGraph,
logger: &'g mut L,
spill_value_reader: &'g dyn SpillValueReader,
) -> Self {
Self {
graph,
change_logger: Some(logger as &'g mut dyn ChangeLogger),
spill_value_reader: Some(spill_value_reader),
batch_mode: false,
}
}
pub fn begin_batch(&mut self) {
if !self.batch_mode {
self.graph.begin_batch();
self.batch_mode = true;
}
}
pub fn commit_batch(&mut self) {
if self.batch_mode {
self.graph.end_batch();
self.batch_mode = false;
}
}
fn log_change(&mut self, event: ChangeEvent) {
if let Some(logger) = &mut self.change_logger {
logger.record(event);
}
}
fn snapshot_spill_for_anchor(
&self,
anchor: VertexId,
) -> Option<crate::engine::graph::editor::change_log::SpillSnapshot> {
let cells = self.graph.spill_cells_for_anchor(anchor)?.to_vec();
if cells.is_empty() {
return None;
}
let max = self.graph.get_config().spill.max_spill_cells as usize;
let mut cells = cells;
if cells.len() > max {
cells.truncate(max);
}
let first = *cells.first().expect("non-empty spill cells");
let sheet_name = self.graph.sheet_name(first.sheet_id).to_string();
let row0 = first.coord.row();
let col0 = first.coord.col();
let mut max_row = row0;
let mut max_col = col0;
let mut by_coord: FxHashMap<(u32, u32), LiteralValue> = FxHashMap::default();
for cell in &cells {
max_row = max_row.max(cell.coord.row());
max_col = max_col.max(cell.coord.col());
let v = if let Some(reader) = self.spill_value_reader {
reader
.read_cell_value(&sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
.unwrap_or(LiteralValue::Empty)
} else {
self.graph
.get_cell_value(&sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
.unwrap_or(LiteralValue::Empty)
};
by_coord.insert((cell.coord.row(), cell.coord.col()), v);
}
let rows = (max_row - row0 + 1) as usize;
let cols = (max_col - col0 + 1) as usize;
let mut values: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
for r in 0..rows {
let mut row: Vec<LiteralValue> = Vec::with_capacity(cols);
for c in 0..cols {
row.push(
by_coord
.get(&(row0 + r as u32, col0 + c as u32))
.cloned()
.unwrap_or(LiteralValue::Empty),
);
}
values.push(row);
}
Some(crate::engine::graph::editor::change_log::SpillSnapshot {
target_cells: cells,
values,
})
}
pub fn commit_spill_region(
&mut self,
anchor: VertexId,
target_cells: Vec<CellRef>,
values: Vec<Vec<LiteralValue>>,
) -> Result<(), EditorError> {
let old = self.snapshot_spill_for_anchor(anchor);
self.graph
.commit_spill_region_atomic_with_fault(
anchor,
target_cells.clone(),
values.clone(),
None,
)
.map_err(EditorError::Excel)?;
self.log_change(ChangeEvent::SpillCommitted {
anchor,
old,
new: crate::engine::graph::editor::change_log::SpillSnapshot {
target_cells,
values,
},
});
Ok(())
}
pub fn clear_spill_region(&mut self, anchor: VertexId) {
let Some(old) = self.snapshot_spill_for_anchor(anchor) else {
return;
};
self.graph.clear_spill_region(anchor);
self.log_change(ChangeEvent::SpillCleared { anchor, old });
}
pub fn has_logger(&self) -> bool {
self.change_logger.is_some()
}
fn get_formula_ast(&self, id: VertexId) -> Option<ASTNode> {
self.graph.get_formula_id(id).and_then(|ast_id| {
self.graph
.data_store()
.retrieve_ast(ast_id, self.graph.sheet_reg())
})
}
fn snapshot_named_definitions(&self) -> FxHashMap<(NameScope, String), NamedDefinition> {
let mut out: FxHashMap<(NameScope, String), NamedDefinition> = FxHashMap::default();
for (name, nr) in self.graph.named_ranges_iter() {
out.insert((NameScope::Workbook, name.clone()), nr.definition.clone());
}
for ((sheet_id, name), nr) in self.graph.sheet_named_ranges_iter() {
out.insert(
(NameScope::Sheet(*sheet_id), name.clone()),
nr.definition.clone(),
);
}
out
}
pub fn apply_inverse(&mut self, change: ChangeEvent) -> Result<(), EditorError> {
match change {
ChangeEvent::SetValue {
addr,
old_value,
old_formula,
new: _,
} => {
if let Some(old_formula) = old_formula {
self.set_cell_formula(addr, old_formula);
} else if let Some(old_value) = old_value {
self.set_cell_value(addr, old_value);
} else if let Some(&id) = self.graph.get_vertex_id_for_address(&addr) {
self.remove_vertex(id)?;
}
}
ChangeEvent::SetFormula {
addr,
old_value,
old_formula,
new: _,
} => {
if let Some(old_formula) = old_formula {
self.set_cell_formula(addr, old_formula);
} else if let Some(old_value) = old_value {
self.set_cell_value(addr, old_value);
} else if let Some(&id) = self.graph.get_vertex_id_for_address(&addr) {
self.remove_vertex(id)?;
}
}
ChangeEvent::SetRowVisibility { .. } => {
}
ChangeEvent::AddVertex { id, .. } => {
let _ = self.remove_vertex(id); }
ChangeEvent::RemoveVertex {
id: _,
old_value,
old_formula,
old_dependencies,
old_dependents,
coord,
sheet_id,
kind,
..
} => {
if let (Some(c), Some(sid)) = (coord, sheet_id) {
let meta =
VertexMeta::new(c.row(), c.col(), sid, kind.unwrap_or(VertexKind::Cell));
let new_id = self.add_vertex(meta);
if let Some(v) = old_value {
let cell_ref = self.graph.make_cell_ref_internal(sid, c.row(), c.col());
self.set_cell_value(cell_ref, v);
}
if let Some(f) = old_formula {
let cell_ref = self.graph.make_cell_ref_internal(sid, c.row(), c.col());
self.set_cell_formula(cell_ref, f);
}
for dep in old_dependencies {
self.graph.add_dependency_edge(new_id, dep);
}
for parent in old_dependents {
self.graph.add_dependency_edge(parent, new_id);
}
}
}
ChangeEvent::DefineName { name, scope, .. } => {
self.graph.delete_name(&name, scope)?;
}
ChangeEvent::UpdateName {
name,
scope,
old_definition,
..
} => {
self.graph.update_name(&name, old_definition, scope)?;
}
ChangeEvent::DeleteName {
name,
scope,
old_definition,
} => {
if let Some(def) = old_definition {
self.graph.define_name(&name, def, scope)?;
} else {
return Err(EditorError::TransactionFailed {
reason: "Missing old definition for name deletion rollback".to_string(),
});
}
}
ChangeEvent::SpillCommitted { anchor, old, .. } => {
if let Some(old) = old {
self.graph
.commit_spill_region_atomic_with_fault(
anchor,
old.target_cells,
old.values,
None,
)
.map_err(EditorError::Excel)?;
} else {
self.graph.clear_spill_region(anchor);
}
}
ChangeEvent::SpillCleared { anchor, old } => {
self.graph
.commit_spill_region_atomic_with_fault(
anchor,
old.target_cells,
old.values,
None,
)
.map_err(EditorError::Excel)?;
}
ChangeEvent::StagedFormulaStateChanged { .. } => {
}
ChangeEvent::CompoundStart { .. } | ChangeEvent::CompoundEnd { .. } => {
}
ChangeEvent::VertexMoved {
id,
sheet_id: _,
old_coord,
..
} => {
self.move_vertex(id, old_coord)?;
}
ChangeEvent::FormulaAdjusted { id, old_ast, .. } => {
self.graph
.update_vertex_formula(id, old_ast)
.map_err(EditorError::Excel)?;
self.graph.mark_vertex_dirty(id);
}
ChangeEvent::NamedRangeAdjusted {
name,
scope,
old_definition,
..
} => {
self.graph.update_name(&name, old_definition, scope)?;
}
ChangeEvent::EdgeAdded { from, to } => {
return Err(EditorError::TransactionFailed {
reason: "Cannot rollback edge addition yet".to_string(),
});
}
ChangeEvent::EdgeRemoved { from, to } => {
return Err(EditorError::TransactionFailed {
reason: "Cannot rollback edge removal yet".to_string(),
});
}
}
Ok(())
}
pub fn add_vertex(&mut self, meta: VertexMeta) -> VertexId {
let sheet_name = self.graph.sheet_name(meta.sheet_id).to_string();
let id = match meta.kind {
VertexKind::Cell => {
match self.graph.set_cell_value(
&sheet_name,
meta.coord.row() + 1,
meta.coord.col() + 1,
LiteralValue::Empty,
) {
Ok(summary) => summary
.affected_vertices
.into_iter()
.next()
.unwrap_or(VertexId::new(0)),
Err(_) => VertexId::new(0),
}
}
_ => {
match self.graph.set_cell_value(
&sheet_name,
meta.coord.row() + 1,
meta.coord.col() + 1,
LiteralValue::Empty,
) {
Ok(summary) => summary
.affected_vertices
.into_iter()
.next()
.unwrap_or(VertexId::new(0)),
Err(_) => VertexId::new(0),
}
}
};
if self.has_logger() && id.0 != 0 {
self.log_change(ChangeEvent::AddVertex {
id,
coord: meta.coord,
sheet_id: meta.sheet_id,
value: Some(LiteralValue::Empty),
formula: None,
kind: Some(meta.kind),
flags: Some(meta.flags),
});
}
id
}
pub fn remove_vertex(&mut self, id: VertexId) -> Result<(), EditorError> {
if !self.graph.vertex_exists(id) {
return Err(EditorError::Excel(
ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
));
}
let spill_snapshot = self.snapshot_spill_for_anchor(id);
let did_spill_clear = spill_snapshot.is_some();
if let Some(old_spill) = spill_snapshot {
if let Some(logger) = &mut self.change_logger {
logger.begin_compound(format!("RemoveVertexWithSpillClear id={}", id.0));
}
self.graph.clear_spill_region(id);
self.log_change(ChangeEvent::SpillCleared {
anchor: id,
old: old_spill,
});
}
let dependents = self.graph.get_dependents(id);
let (
old_value,
old_formula,
old_dependencies,
old_dependents,
coord,
sheet_id_opt,
kind,
flags,
) = if self.has_logger() {
let coord = self.graph.get_coord(id);
let sheet_id = self.graph.get_sheet_id(id);
let kind = self.graph.get_vertex_kind(id);
let flags = 0u8;
(
self.graph.get_value(id),
self.get_formula_ast(id),
self.graph.get_dependencies(id), dependents.clone(), Some(coord),
Some(sheet_id),
Some(kind),
Some(flags),
)
} else {
(None, None, vec![], vec![], None, None, None, None)
};
if let Some(cell_ref) = self.graph.get_cell_ref_for_vertex(id) {
self.graph.remove_cell_mapping(&cell_ref);
}
self.graph.vertex_formulas.remove(&id);
self.graph.vertex_values.remove(&id);
self.graph.dirty_vertices.remove(&id);
self.graph.mark_volatile(id, false);
self.graph.store.set_kind(id, VertexKind::Empty);
self.graph.store.set_dynamic(id, false);
self.graph.remove_all_edges(id);
for dep_id in &dependents {
self.graph.mark_as_ref_error(*dep_id);
}
self.graph.mark_deleted(id, true);
self.log_change(ChangeEvent::RemoveVertex {
id,
old_value,
old_formula,
old_dependencies,
old_dependents,
coord,
sheet_id: sheet_id_opt,
kind,
flags,
});
if did_spill_clear && let Some(logger) = &mut self.change_logger {
logger.end_compound();
}
Ok(())
}
pub fn remove_vertex_at(&mut self, cell: CellRef) -> Result<(), EditorError> {
if let Some(id) = self.graph.get_vertex_for_cell(&cell) {
self.remove_vertex(id)
} else {
Ok(())
}
}
pub fn move_vertex(&mut self, id: VertexId, new_coord: AbsCoord) -> Result<(), EditorError> {
if !self.graph.vertex_exists(id) {
return Err(EditorError::Excel(
ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
));
}
let old_cell_ref = self.graph.get_cell_ref_for_vertex(id);
let sheet_id = self.graph.get_sheet_id(id);
let new_cell_ref = CellRef::new(
sheet_id,
Coord::new(new_coord.row(), new_coord.col(), true, true),
);
self.graph.set_coord(id, new_coord);
self.graph.update_edge_coord(id, new_coord);
self.graph
.update_cell_mapping(id, old_cell_ref, new_cell_ref);
self.graph.mark_dependents_dirty(id);
Ok(())
}
pub fn patch_vertex_meta(
&mut self,
id: VertexId,
patch: VertexMetaPatch,
) -> Result<MetaUpdateSummary, EditorError> {
if !self.graph.vertex_exists(id) {
return Err(EditorError::Excel(
ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
));
}
let mut summary = MetaUpdateSummary::default();
if let Some(coord) = patch.coord {
self.graph.set_coord(id, coord);
self.graph.update_edge_coord(id, coord);
summary.coord_changed = true;
}
if let Some(kind) = patch.kind {
self.graph.set_kind(id, kind);
summary.kind_changed = true;
}
if let Some(dirty) = patch.dirty {
self.graph.set_dirty(id, dirty);
summary.flags_changed = true;
}
if let Some(volatile) = patch.volatile {
self.graph.mark_volatile(id, volatile);
summary.flags_changed = true;
}
Ok(summary)
}
pub fn patch_vertex_data(
&mut self,
id: VertexId,
patch: VertexDataPatch,
) -> Result<DataUpdateSummary, EditorError> {
if !self.graph.vertex_exists(id) {
return Err(EditorError::Excel(
ExcelError::new(ExcelErrorKind::Ref).with_message("Vertex does not exist"),
));
}
let mut summary = DataUpdateSummary::default();
if let Some(value) = patch.value {
self.graph.update_vertex_value(id, value);
summary.value_changed = true;
if self.graph.edges_delta_size() > 0 {
self.graph.rebuild_edges();
}
let dependents = self.graph.get_dependents(id);
for dep in &dependents {
self.graph.set_dirty(*dep, true);
}
summary.dependents_marked_dirty = dependents;
}
if let Some(_formula) = patch.formula {
summary.formula_changed = true;
}
Ok(summary)
}
pub fn add_edge(&mut self, from: VertexId, to: VertexId) -> bool {
if from == to {
return false; }
true
}
pub fn remove_edge(&mut self, _from: VertexId, _to: VertexId) -> bool {
true
}
pub fn insert_rows(
&mut self,
sheet_id: SheetId,
before: u32,
count: u32,
) -> Result<ShiftSummary, EditorError> {
if count == 0 {
return Ok(ShiftSummary::default());
}
let mut summary = ShiftSummary::default();
self.begin_batch();
let vertices_to_shift: Vec<(VertexId, AbsCoord)> = self
.graph
.vertices_in_sheet(sheet_id)
.filter_map(|id| {
let coord = self.graph.get_coord(id);
if coord.row() >= before {
Some((id, coord))
} else {
None
}
})
.collect();
if let Some(logger) = &mut self.change_logger {
logger.begin_compound(format!(
"InsertRows sheet={sheet_id} before={before} count={count}"
));
}
for (id, old_coord) in vertices_to_shift {
let new_coord = AbsCoord::new(old_coord.row() + count, old_coord.col());
if self.has_logger() {
self.log_change(ChangeEvent::VertexMoved {
id,
sheet_id,
old_coord,
new_coord,
});
}
self.move_vertex(id, new_coord)?;
summary.vertices_moved.push(id);
}
let op = ShiftOperation::InsertRows {
sheet_id,
before,
count,
};
let adjuster = ReferenceAdjuster::new();
let formula_vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
for id in formula_vertices {
if let Some(ast) = self.get_formula_ast(id) {
let adjusted = adjuster.adjust_ast(&ast, &op);
if format!("{ast:?}") != format!("{adjusted:?}") {
if self.has_logger() {
self.log_change(ChangeEvent::FormulaAdjusted {
id,
addr: self.graph.get_cell_ref_for_vertex(id),
old_ast: ast.clone(),
new_ast: adjusted.clone(),
});
}
self.graph.update_vertex_formula(id, adjusted)?;
self.graph.mark_vertex_dirty(id);
summary.formulas_updated += 1;
}
}
}
let old_names = if self.has_logger() {
Some(self.snapshot_named_definitions())
} else {
None
};
self.graph.adjust_named_ranges(&op)?;
if let Some(old_names) = old_names {
let new_names = self.snapshot_named_definitions();
for ((scope, name), old_definition) in old_names {
if let Some(new_definition) = new_names.get(&(scope, name.clone()))
&& *new_definition != old_definition
{
self.log_change(ChangeEvent::NamedRangeAdjusted {
name,
scope,
old_definition,
new_definition: new_definition.clone(),
});
}
}
}
if let Some(logger) = &mut self.change_logger {
logger.end_compound();
}
self.commit_batch();
Ok(summary)
}
pub fn delete_rows(
&mut self,
sheet_id: SheetId,
start: u32,
count: u32,
) -> Result<ShiftSummary, EditorError> {
if count == 0 {
return Ok(ShiftSummary::default());
}
let mut summary = ShiftSummary::default();
self.begin_batch();
if let Some(logger) = &mut self.change_logger {
logger.begin_compound(format!(
"DeleteRows sheet={sheet_id} start={start} count={count}"
));
}
let vertices_to_delete: Vec<VertexId> = self
.graph
.vertices_in_sheet(sheet_id)
.filter(|&id| {
let coord = self.graph.get_coord(id);
coord.row() >= start && coord.row() < start + count
})
.collect();
for id in vertices_to_delete {
self.remove_vertex(id)?;
summary.vertices_deleted.push(id);
}
let vertices_to_shift: Vec<(VertexId, AbsCoord)> = self
.graph
.vertices_in_sheet(sheet_id)
.filter_map(|id| {
let coord = self.graph.get_coord(id);
if coord.row() >= start + count {
Some((id, coord))
} else {
None
}
})
.collect();
for (id, old_coord) in vertices_to_shift {
let new_coord = AbsCoord::new(old_coord.row() - count, old_coord.col());
if self.has_logger() {
self.log_change(ChangeEvent::VertexMoved {
id,
sheet_id,
old_coord,
new_coord,
});
}
self.move_vertex(id, new_coord)?;
summary.vertices_moved.push(id);
}
let op = ShiftOperation::DeleteRows {
sheet_id,
start,
count,
};
let adjuster = ReferenceAdjuster::new();
let formula_vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
for id in formula_vertices {
if let Some(ast) = self.get_formula_ast(id) {
let adjusted = adjuster.adjust_ast(&ast, &op);
if format!("{ast:?}") != format!("{adjusted:?}") {
if self.has_logger() {
self.log_change(ChangeEvent::FormulaAdjusted {
id,
addr: self.graph.get_cell_ref_for_vertex(id),
old_ast: ast.clone(),
new_ast: adjusted.clone(),
});
}
self.graph.update_vertex_formula(id, adjusted)?;
self.graph.mark_vertex_dirty(id);
summary.formulas_updated += 1;
}
}
}
let old_names = if self.has_logger() {
Some(self.snapshot_named_definitions())
} else {
None
};
self.graph.adjust_named_ranges(&op)?;
if let Some(old_names) = old_names {
let new_names = self.snapshot_named_definitions();
for ((scope, name), old_definition) in old_names {
if let Some(new_definition) = new_names.get(&(scope, name.clone()))
&& *new_definition != old_definition
{
self.log_change(ChangeEvent::NamedRangeAdjusted {
name,
scope,
old_definition,
new_definition: new_definition.clone(),
});
}
}
}
if let Some(logger) = &mut self.change_logger {
logger.end_compound();
}
self.commit_batch();
Ok(summary)
}
pub fn insert_columns(
&mut self,
sheet_id: SheetId,
before: u32,
count: u32,
) -> Result<ShiftSummary, EditorError> {
if count == 0 {
return Ok(ShiftSummary::default());
}
let mut summary = ShiftSummary::default();
self.begin_batch();
let vertices_to_shift: Vec<(VertexId, AbsCoord)> = self
.graph
.vertices_in_sheet(sheet_id)
.filter_map(|id| {
let coord = self.graph.get_coord(id);
if coord.col() >= before {
Some((id, coord))
} else {
None
}
})
.collect();
if let Some(logger) = &mut self.change_logger {
logger.begin_compound(format!(
"InsertColumns sheet={sheet_id} before={before} count={count}"
));
}
for (id, old_coord) in vertices_to_shift {
let new_coord = AbsCoord::new(old_coord.row(), old_coord.col() + count);
if self.has_logger() {
self.log_change(ChangeEvent::VertexMoved {
id,
sheet_id,
old_coord,
new_coord,
});
}
self.move_vertex(id, new_coord)?;
summary.vertices_moved.push(id);
}
let op = ShiftOperation::InsertColumns {
sheet_id,
before,
count,
};
let adjuster = ReferenceAdjuster::new();
let formula_vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
for id in formula_vertices {
if let Some(ast) = self.get_formula_ast(id)
&& let Some(adjusted) = adjuster.adjust_ast_if_changed(&ast, &op)
{
if self.has_logger() {
self.log_change(ChangeEvent::FormulaAdjusted {
id,
addr: self.graph.get_cell_ref_for_vertex(id),
old_ast: ast.clone(),
new_ast: adjusted.clone(),
});
}
self.graph.update_vertex_formula(id, adjusted)?;
self.graph.mark_vertex_dirty(id);
summary.formulas_updated += 1;
}
}
let old_names = if self.has_logger() {
Some(self.snapshot_named_definitions())
} else {
None
};
self.graph.adjust_named_ranges(&op)?;
if let Some(old_names) = old_names {
let new_names = self.snapshot_named_definitions();
for ((scope, name), old_definition) in old_names {
if let Some(new_definition) = new_names.get(&(scope, name.clone()))
&& *new_definition != old_definition
{
self.log_change(ChangeEvent::NamedRangeAdjusted {
name,
scope,
old_definition,
new_definition: new_definition.clone(),
});
}
}
}
if let Some(logger) = &mut self.change_logger {
logger.end_compound();
}
self.commit_batch();
Ok(summary)
}
pub fn delete_columns(
&mut self,
sheet_id: SheetId,
start: u32,
count: u32,
) -> Result<ShiftSummary, EditorError> {
if count == 0 {
return Ok(ShiftSummary::default());
}
let mut summary = ShiftSummary::default();
self.begin_batch();
if let Some(logger) = &mut self.change_logger {
logger.begin_compound(format!(
"DeleteColumns sheet={sheet_id} start={start} count={count}"
));
}
let vertices_to_delete: Vec<VertexId> = self
.graph
.vertices_in_sheet(sheet_id)
.filter(|&id| {
let coord = self.graph.get_coord(id);
coord.col() >= start && coord.col() < start + count
})
.collect();
for id in vertices_to_delete {
self.remove_vertex(id)?;
summary.vertices_deleted.push(id);
}
let vertices_to_shift: Vec<(VertexId, AbsCoord)> = self
.graph
.vertices_in_sheet(sheet_id)
.filter_map(|id| {
let coord = self.graph.get_coord(id);
if coord.col() >= start + count {
Some((id, coord))
} else {
None
}
})
.collect();
for (id, old_coord) in vertices_to_shift {
let new_coord = AbsCoord::new(old_coord.row(), old_coord.col() - count);
if self.has_logger() {
self.log_change(ChangeEvent::VertexMoved {
id,
sheet_id,
old_coord,
new_coord,
});
}
self.move_vertex(id, new_coord)?;
summary.vertices_moved.push(id);
}
let op = ShiftOperation::DeleteColumns {
sheet_id,
start,
count,
};
let adjuster = ReferenceAdjuster::new();
let formula_vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
for id in formula_vertices {
if let Some(ast) = self.get_formula_ast(id)
&& let Some(adjusted) = adjuster.adjust_ast_if_changed(&ast, &op)
{
if self.has_logger() {
self.log_change(ChangeEvent::FormulaAdjusted {
id,
addr: self.graph.get_cell_ref_for_vertex(id),
old_ast: ast.clone(),
new_ast: adjusted.clone(),
});
}
self.graph.update_vertex_formula(id, adjusted)?;
self.graph.mark_vertex_dirty(id);
summary.formulas_updated += 1;
}
}
let old_names = if self.has_logger() {
Some(self.snapshot_named_definitions())
} else {
None
};
self.graph.adjust_named_ranges(&op)?;
if let Some(old_names) = old_names {
let new_names = self.snapshot_named_definitions();
for ((scope, name), old_definition) in old_names {
if let Some(new_definition) = new_names.get(&(scope, name.clone()))
&& *new_definition != old_definition
{
self.log_change(ChangeEvent::NamedRangeAdjusted {
name,
scope,
old_definition,
new_definition: new_definition.clone(),
});
}
}
}
if let Some(logger) = &mut self.change_logger {
logger.end_compound();
}
self.commit_batch();
Ok(summary)
}
pub fn shift_rows(&mut self, sheet_id: SheetId, start_row: u32, delta: i32) {
if delta == 0 {
return;
}
let change_event = ChangeEvent::SetValue {
addr: CellRef {
sheet_id,
coord: Coord::new(start_row, 0, true, true),
},
old_value: None,
old_formula: None,
new: LiteralValue::Text(format!("Row shift: start={start_row}, delta={delta}")),
};
self.log_change(change_event);
}
pub fn shift_columns(&mut self, sheet_id: SheetId, start_col: u32, delta: i32) {
if delta == 0 {
return;
}
let change_event = ChangeEvent::SetValue {
addr: CellRef {
sheet_id,
coord: Coord::new(0, start_col, true, true),
},
old_value: None,
old_formula: None,
new: LiteralValue::Text(format!("Column shift: start={start_col}, delta={delta}")),
};
self.log_change(change_event);
}
pub fn set_cell_value(&mut self, cell_ref: CellRef, value: LiteralValue) -> VertexId {
let sheet_name = self.graph.sheet_name(cell_ref.sheet_id).to_string();
let old_id = self.graph.get_vertex_id_for_address(&cell_ref).copied();
let old_value = old_id.and_then(|id| self.graph.get_value(id));
let old_formula = old_id.and_then(|id| self.get_formula_ast(id));
let spill_snapshot =
old_id.and_then(|id| self.snapshot_spill_for_anchor(id).map(|s| (id, s)));
let did_spill_clear = spill_snapshot.is_some();
if let Some((anchor, old_spill)) = spill_snapshot {
if let Some(logger) = &mut self.change_logger {
logger.begin_compound(format!(
"SetValueWithSpillClear sheet={} row={} col={}",
cell_ref.sheet_id,
cell_ref.coord.row(),
cell_ref.coord.col()
));
}
self.graph.clear_spill_region(anchor);
self.log_change(ChangeEvent::SpillCleared {
anchor,
old: old_spill,
});
}
match self.graph.set_cell_value(
&sheet_name,
cell_ref.coord.row() + 1,
cell_ref.coord.col() + 1,
value.clone(),
) {
Ok(summary) => {
let change_event = ChangeEvent::SetValue {
addr: cell_ref,
old_value,
old_formula,
new: value,
};
self.log_change(change_event);
if did_spill_clear && let Some(logger) = &mut self.change_logger {
logger.end_compound();
}
summary
.affected_vertices
.into_iter()
.next()
.unwrap_or(VertexId::new(0))
}
Err(_) => VertexId::new(0),
}
}
pub fn set_cell_formula(&mut self, cell_ref: CellRef, formula: ASTNode) -> VertexId {
let sheet_name = self.graph.sheet_name(cell_ref.sheet_id).to_string();
let old_id = self.graph.get_vertex_id_for_address(&cell_ref).copied();
let old_value = old_id.and_then(|id| self.graph.get_value(id));
let old_formula = old_id.and_then(|id| self.get_formula_ast(id));
let spill_snapshot =
old_id.and_then(|id| self.snapshot_spill_for_anchor(id).map(|s| (id, s)));
let did_spill_clear = spill_snapshot.is_some();
if let Some((anchor, old_spill)) = spill_snapshot {
if let Some(logger) = &mut self.change_logger {
logger.begin_compound(format!(
"SetFormulaWithSpillClear sheet={} row={} col={}",
cell_ref.sheet_id,
cell_ref.coord.row(),
cell_ref.coord.col()
));
}
self.graph.clear_spill_region(anchor);
self.log_change(ChangeEvent::SpillCleared {
anchor,
old: old_spill,
});
}
match self.graph.set_cell_formula(
&sheet_name,
cell_ref.coord.row() + 1,
cell_ref.coord.col() + 1,
formula.clone(),
) {
Ok(summary) => {
let change_event = ChangeEvent::SetFormula {
addr: cell_ref,
old_value,
old_formula,
new: formula,
};
self.log_change(change_event);
if did_spill_clear && let Some(logger) = &mut self.change_logger {
logger.end_compound();
}
summary
.affected_vertices
.into_iter()
.next()
.unwrap_or(VertexId::new(0))
}
Err(_) => VertexId::new(0),
}
}
pub fn set_range_values(
&mut self,
sheet_id: SheetId,
start_row: u32,
start_col: u32,
values: &[Vec<LiteralValue>],
) -> Result<RangeSummary, EditorError> {
let mut summary = RangeSummary::default();
self.begin_batch();
for (row_offset, row_values) in values.iter().enumerate() {
for (col_offset, value) in row_values.iter().enumerate() {
let row = start_row + row_offset as u32;
let col = start_col + col_offset as u32;
let cell_ref = self.graph.make_cell_ref_internal(sheet_id, row, col);
let existing_id = self.graph.get_vertex_id_for_address(&cell_ref).copied();
let id = self.set_cell_value(cell_ref, value.clone());
match existing_id {
Some(existing_id) => summary.vertices_updated.push(existing_id),
None if id.0 != 0 => summary.vertices_created.push(id),
None => {}
}
summary.cells_affected += 1;
}
}
self.commit_batch();
Ok(summary)
}
pub fn clear_range(
&mut self,
sheet_id: SheetId,
start_row: u32,
start_col: u32,
end_row: u32,
end_col: u32,
) -> Result<RangeSummary, EditorError> {
let mut summary = RangeSummary::default();
self.begin_batch();
let vertices_in_range: Vec<_> = self
.graph
.vertices_in_sheet(sheet_id)
.filter(|&id| {
let coord = self.graph.get_coord(id);
let row = coord.row();
let col = coord.col();
row >= start_row && row <= end_row && col >= start_col && col <= end_col
})
.collect();
for id in vertices_in_range {
self.remove_vertex(id)?;
summary.cells_affected += 1;
}
self.commit_batch();
Ok(summary)
}
pub fn copy_range(
&mut self,
sheet_id: SheetId,
from_start_row: u32,
from_start_col: u32,
from_end_row: u32,
from_end_col: u32,
to_sheet_id: SheetId,
to_row: u32,
to_col: u32,
) -> Result<RangeSummary, EditorError> {
let row_offset = to_row as i32 - from_start_row as i32;
let col_offset = to_col as i32 - from_start_col as i32;
let mut summary = RangeSummary::default();
let mut cell_data = Vec::new();
let vertices_in_range: Vec<_> = self
.graph
.vertices_in_sheet(sheet_id)
.filter(|&id| {
let coord = self.graph.get_coord(id);
let row = coord.row();
let col = coord.col();
row >= from_start_row
&& row <= from_end_row
&& col >= from_start_col
&& col <= from_end_col
})
.collect();
for id in vertices_in_range {
let coord = self.graph.get_coord(id);
let row = coord.row();
let col = coord.col();
if let Some(formula) = self.get_formula_ast(id) {
cell_data.push((
row - from_start_row,
col - from_start_col,
CellData::Formula(formula),
));
} else if let Some(value) = self.graph.get_value(id) {
cell_data.push((
row - from_start_row,
col - from_start_col,
CellData::Value(value),
));
}
}
self.begin_batch();
for (row_idx, col_idx, data) in cell_data {
let dest_row = (to_row as i32 + row_idx as i32) as u32;
let dest_col = (to_col as i32 + col_idx as i32) as u32;
match data {
CellData::Value(value) => {
let cell_ref =
self.graph
.make_cell_ref_internal(to_sheet_id, dest_row, dest_col);
if let Some(&existing_id) = self.graph.get_vertex_id_for_address(&cell_ref) {
self.graph.update_vertex_value(existing_id, value);
self.graph.mark_vertex_dirty(existing_id);
summary.vertices_updated.push(existing_id);
} else {
let meta =
VertexMeta::new(dest_row, dest_col, to_sheet_id, VertexKind::Cell);
let id = self.add_vertex(meta);
self.graph.update_vertex_value(id, value);
summary.vertices_created.push(id);
}
}
CellData::Formula(formula) => {
let adjuster = RelativeReferenceAdjuster::new(row_offset, col_offset);
let adjusted = adjuster.adjust_formula(&formula);
let cell_ref =
self.graph
.make_cell_ref_internal(to_sheet_id, dest_row, dest_col);
if let Some(&existing_id) = self.graph.get_vertex_id_for_address(&cell_ref) {
self.graph.update_vertex_formula(existing_id, adjusted)?;
summary.vertices_updated.push(existing_id);
} else {
let meta = VertexMeta::new(
dest_row,
dest_col,
to_sheet_id,
VertexKind::FormulaScalar,
);
let id = self.add_vertex(meta);
self.graph.update_vertex_formula(id, adjusted)?;
summary.vertices_created.push(id);
}
}
}
summary.cells_affected += 1;
}
self.commit_batch();
Ok(summary)
}
pub fn move_range(
&mut self,
sheet_id: SheetId,
from_start_row: u32,
from_start_col: u32,
from_end_row: u32,
from_end_col: u32,
to_sheet_id: SheetId,
to_row: u32,
to_col: u32,
) -> Result<RangeSummary, EditorError> {
let mut summary = self.copy_range(
sheet_id,
from_start_row,
from_start_col,
from_end_row,
from_end_col,
to_sheet_id,
to_row,
to_col,
)?;
let clear_summary = self.clear_range(
sheet_id,
from_start_row,
from_start_col,
from_end_row,
from_end_col,
)?;
summary.cells_moved = clear_summary.cells_affected;
let row_offset = to_row as i32 - from_start_row as i32;
let col_offset = to_col as i32 - from_start_col as i32;
let all_formula_vertices: Vec<_> = self.graph.vertices_with_formulas().collect();
let from_sheet_name = self.graph.sheet_name(sheet_id).to_string();
let to_sheet_name = self.graph.sheet_name(to_sheet_id).to_string();
let adjuster = MoveReferenceAdjuster::new(
sheet_id,
from_sheet_name,
from_start_row,
from_start_col,
from_end_row,
from_end_col,
to_sheet_id,
to_sheet_name,
row_offset,
col_offset,
);
for formula_id in all_formula_vertices {
if let Some(formula) = self.get_formula_ast(formula_id) {
let formula_sheet_id = self.graph.get_vertex_sheet_id(formula_id);
if let Some(adjusted) = adjuster.adjust_if_references(&formula, formula_sheet_id) {
self.graph.update_vertex_formula(formula_id, adjusted)?;
}
}
}
Ok(summary)
}
pub fn define_name(
&mut self,
name: &str,
definition: NamedDefinition,
scope: NameScope,
) -> Result<(), EditorError> {
self.graph.define_name(name, definition.clone(), scope)?;
self.log_change(ChangeEvent::DefineName {
name: name.to_string(),
scope,
definition,
});
Ok(())
}
pub fn define_name_for_cell(
&mut self,
name: &str,
sheet_name: &str,
row: u32,
col: u32,
scope: NameScope,
) -> Result<(), EditorError> {
let sheet_id = self
.graph
.sheet_id(sheet_name)
.ok_or_else(|| EditorError::InvalidName {
name: sheet_name.to_string(),
reason: "Sheet not found".to_string(),
})?;
let cell_ref = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
self.define_name(name, NamedDefinition::Cell(cell_ref), scope)
}
pub fn define_name_for_range(
&mut self,
name: &str,
sheet_name: &str,
start_row: u32,
start_col: u32,
end_row: u32,
end_col: u32,
scope: NameScope,
) -> Result<(), EditorError> {
let sheet_id = self
.graph
.sheet_id(sheet_name)
.ok_or_else(|| EditorError::InvalidName {
name: sheet_name.to_string(),
reason: "Sheet not found".to_string(),
})?;
let start = CellRef::new(
sheet_id,
Coord::from_excel(start_row, start_col, true, true),
);
let end = CellRef::new(sheet_id, Coord::from_excel(end_row, end_col, true, true));
let range_ref = crate::reference::RangeRef::new(start, end);
self.define_name(name, NamedDefinition::Range(range_ref), scope)
}
pub fn update_name(
&mut self,
name: &str,
new_definition: NamedDefinition,
scope: NameScope,
) -> Result<(), EditorError> {
let old_definition = self
.graph
.resolve_name(
name,
match scope {
NameScope::Sheet(id) => id,
NameScope::Workbook => 0,
},
)
.cloned();
self.graph
.update_name(name, new_definition.clone(), scope)?;
if let Some(old_def) = old_definition {
self.log_change(ChangeEvent::UpdateName {
name: name.to_string(),
scope,
old_definition: old_def,
new_definition,
});
}
Ok(())
}
pub fn delete_name(&mut self, name: &str, scope: NameScope) -> Result<(), EditorError> {
let old_def = if self.has_logger() {
self.graph
.resolve_name(
name,
match scope {
NameScope::Sheet(id) => id,
NameScope::Workbook => 0,
},
)
.cloned()
} else {
None
};
self.graph.delete_name(name, scope)?;
self.log_change(ChangeEvent::DeleteName {
name: name.to_string(),
scope,
old_definition: old_def,
});
Ok(())
}
}
enum CellData {
Value(LiteralValue),
Formula(ASTNode),
}
impl<'g> Drop for VertexEditor<'g> {
fn drop(&mut self) {
if self.batch_mode {
self.commit_batch();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::graph::editor::change_log::{ChangeEvent, ChangeLog};
use crate::reference::Coord;
fn create_test_graph() -> DependencyGraph {
DependencyGraph::new()
}
#[test]
fn test_vertex_editor_creation() {
let mut graph = create_test_graph();
let editor = VertexEditor::new(&mut graph);
assert!(!editor.has_logger());
assert!(!editor.batch_mode);
}
#[test]
fn test_vertex_editor_with_logger() {
let mut graph = create_test_graph();
let mut log = ChangeLog::new();
let editor = VertexEditor::with_logger(&mut graph, &mut log);
assert!(editor.has_logger());
assert!(!editor.batch_mode);
}
#[test]
fn test_add_vertex() {
let mut graph = create_test_graph();
let mut editor = VertexEditor::new(&mut graph);
let meta = VertexMeta::new(5, 10, 0, VertexKind::Cell).dirty();
let vertex_id = editor.add_vertex(meta);
assert!(vertex_id.0 > 0);
}
#[test]
fn test_batch_operations() {
let mut graph = create_test_graph();
let mut editor = VertexEditor::new(&mut graph);
assert!(!editor.batch_mode);
editor.begin_batch();
assert!(editor.batch_mode);
let meta1 = VertexMeta::new(1, 1, 0, VertexKind::Cell);
let meta2 = VertexMeta::new(2, 2, 0, VertexKind::Cell);
let id1 = editor.add_vertex(meta1);
let id2 = editor.add_vertex(meta2);
assert!(editor.add_edge(id1, id2));
editor.commit_batch();
assert!(!editor.batch_mode);
}
#[test]
fn test_remove_vertex() {
let mut graph = create_test_graph();
let mut editor = VertexEditor::new(&mut graph);
let meta = VertexMeta::new(3, 4, 0, VertexKind::Cell).dirty();
let vertex_id = editor.add_vertex(meta);
assert!(editor.remove_vertex(vertex_id).is_ok());
}
#[test]
fn test_remove_vertex_clears_spill_registry_for_anchor() {
let mut graph = create_test_graph();
let sheet_id = graph.sheet_id_mut("Sheet1");
let anchor_cell = CellRef::new(sheet_id, Coord::new(0, 0, true, true));
let anchor_vid = {
let mut editor = VertexEditor::new(&mut graph);
editor.set_cell_value(anchor_cell, LiteralValue::Number(0.0))
};
let target_cells = vec![
CellRef::new(sheet_id, Coord::new(0, 0, true, true)),
CellRef::new(sheet_id, Coord::new(0, 1, true, true)),
CellRef::new(sheet_id, Coord::new(1, 0, true, true)),
CellRef::new(sheet_id, Coord::new(1, 1, true, true)),
];
let values = vec![
vec![LiteralValue::Number(1.0), LiteralValue::Number(2.0)],
vec![LiteralValue::Number(3.0), LiteralValue::Number(4.0)],
];
graph
.commit_spill_region_atomic_with_fault(anchor_vid, target_cells.clone(), values, None)
.unwrap();
assert!(graph.spill_registry_has_anchor(anchor_vid));
for cell in &target_cells {
assert_eq!(
graph.spill_registry_anchor_for_cell(*cell),
Some(anchor_vid)
);
}
{
let mut editor = VertexEditor::new(&mut graph);
editor.remove_vertex(anchor_vid).unwrap();
}
assert!(!graph.spill_registry_has_anchor(anchor_vid));
for cell in &target_cells {
assert_eq!(graph.spill_registry_anchor_for_cell(*cell), None);
}
assert_eq!(graph.spill_registry_counts(), (0, 0));
}
#[test]
fn test_edge_operations() {
let mut graph = create_test_graph();
let mut editor = VertexEditor::new(&mut graph);
let meta1 = VertexMeta::new(1, 1, 0, VertexKind::Cell);
let meta2 = VertexMeta::new(2, 2, 0, VertexKind::FormulaScalar);
let id1 = editor.add_vertex(meta1);
let id2 = editor.add_vertex(meta2);
assert!(editor.add_edge(id1, id2));
assert!(!editor.add_edge(id1, id1));
assert!(editor.remove_edge(id1, id2));
}
#[test]
fn test_set_cell_value() {
let mut graph = create_test_graph();
let mut log = ChangeLog::new();
let cell_ref = CellRef {
sheet_id: 0,
coord: Coord::new(2, 3, true, true),
};
let value = LiteralValue::Number(42.0);
let vertex_id = {
let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
editor.set_cell_value(cell_ref, value.clone())
};
assert!(vertex_id.0 > 0);
assert_eq!(log.len(), 1);
match &log.events()[0] {
ChangeEvent::SetValue { addr, new, .. } => {
assert_eq!(addr.sheet_id, cell_ref.sheet_id);
assert_eq!(addr.coord.row(), cell_ref.coord.row());
assert_eq!(addr.coord.col(), cell_ref.coord.col());
assert_eq!(new, &value);
}
_ => panic!("Expected SetValue event"),
}
}
#[test]
fn test_set_cell_formula() {
let mut graph = create_test_graph();
let mut log = ChangeLog::new();
let cell_ref = CellRef {
sheet_id: 0,
coord: Coord::new(1, 1, true, true),
};
use formualizer_parse::parser::ASTNodeType;
let formula = formualizer_parse::parser::ASTNode {
node_type: ASTNodeType::Literal(LiteralValue::Number(100.0)),
source_token: None,
contains_volatile: false,
};
let vertex_id = {
let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
editor.set_cell_formula(cell_ref, formula.clone())
};
assert!(vertex_id.0 > 0);
assert_eq!(log.len(), 1);
match &log.events()[0] {
ChangeEvent::SetFormula { addr, .. } => {
assert_eq!(addr.sheet_id, cell_ref.sheet_id);
assert_eq!(addr.coord.row(), cell_ref.coord.row());
assert_eq!(addr.coord.col(), cell_ref.coord.col());
}
_ => panic!("Expected SetFormula event"),
}
}
#[test]
fn test_shift_rows() {
let mut graph = create_test_graph();
let mut log = ChangeLog::new();
{
let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
let cell1 = CellRef {
sheet_id: 0,
coord: Coord::new(5, 1, true, true),
};
let cell2 = CellRef {
sheet_id: 0,
coord: Coord::new(10, 1, true, true),
};
let cell3 = CellRef {
sheet_id: 0,
coord: Coord::new(15, 1, true, true),
};
editor.set_cell_value(cell1, LiteralValue::Number(1.0));
editor.set_cell_value(cell2, LiteralValue::Number(2.0));
editor.set_cell_value(cell3, LiteralValue::Number(3.0));
}
log.clear();
{
let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
editor.shift_rows(0, 10, 2);
}
assert_eq!(log.len(), 1);
match &log.events()[0] {
ChangeEvent::SetValue { addr, new, .. } => {
assert_eq!(addr.sheet_id, 0);
assert_eq!(addr.coord.row(), 10);
if let LiteralValue::Text(msg) = new {
assert!(msg.contains("Row shift"));
assert!(msg.contains("start=10"));
assert!(msg.contains("delta=2"));
}
}
_ => panic!("Expected SetValue event for row shift"),
}
}
#[test]
fn test_shift_columns() {
let mut graph = create_test_graph();
let mut log = ChangeLog::new();
{
let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
let cell1 = CellRef {
sheet_id: 0,
coord: Coord::new(1, 5, true, true),
};
let cell2 = CellRef {
sheet_id: 0,
coord: Coord::new(1, 10, true, true),
};
editor.set_cell_value(cell1, LiteralValue::Number(1.0));
editor.set_cell_value(cell2, LiteralValue::Number(2.0));
}
log.clear();
{
let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
editor.shift_columns(0, 8, 3);
}
assert_eq!(log.len(), 1);
match &log.events()[0] {
ChangeEvent::SetValue { addr, new, .. } => {
assert_eq!(addr.sheet_id, 0);
assert_eq!(addr.coord.col(), 8);
if let LiteralValue::Text(msg) = new {
assert!(msg.contains("Column shift"));
assert!(msg.contains("start=8"));
assert!(msg.contains("delta=3"));
}
}
_ => panic!("Expected SetValue event for column shift"),
}
}
#[test]
fn test_move_vertex() {
let mut graph = create_test_graph();
let mut editor = VertexEditor::new(&mut graph);
let meta = VertexMeta::new(5, 10, 0, VertexKind::Cell);
let vertex_id = editor.add_vertex(meta);
assert!(editor.move_vertex(vertex_id, AbsCoord::new(8, 12)).is_ok());
assert!(editor.move_vertex(vertex_id, AbsCoord::new(8, 12)).is_ok());
}
#[test]
fn test_vertex_meta_builder() {
let meta = VertexMeta::new(1, 2, 3, VertexKind::FormulaScalar)
.dirty()
.volatile()
.with_flags(0x08);
assert_eq!(meta.coord.row(), 1);
assert_eq!(meta.coord.col(), 2);
assert_eq!(meta.sheet_id, 3);
assert_eq!(meta.kind, VertexKind::FormulaScalar);
assert_eq!(meta.flags, 0x08); }
#[test]
fn test_change_log_management() {
let mut graph = create_test_graph();
let mut log = ChangeLog::new();
{
let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
let cell_ref = CellRef {
sheet_id: 0,
coord: Coord::new(0, 0, true, true),
};
editor.set_cell_value(cell_ref, LiteralValue::Number(1.0));
editor.set_cell_value(cell_ref, LiteralValue::Number(2.0));
}
assert_eq!(log.len(), 2);
log.clear();
assert_eq!(log.len(), 0);
}
#[test]
fn test_editor_drop_commits_batch() {
let mut graph = create_test_graph();
{
let mut editor = VertexEditor::new(&mut graph);
editor.begin_batch();
let meta = VertexMeta::new(1, 1, 0, VertexKind::Cell);
editor.add_vertex(meta);
}
}
}