Skip to main content

gridline_engine/engine/
cell.rs

1//! Cell data structures for the spreadsheet grid.
2//!
3//! This module provides the core data types for representing cells:
4//! - [`CellType`] - The type of content in a cell (empty, text, number, or formula)
5//! - [`Cell`] - A cell with content, dependencies, and cached evaluation state
6//! - [`Grid`] - Thread-safe sparse storage for cells (backed by `DashMap`)
7//! - [`SpillMap`] - Thread-safe storage for array formula spill values
8
9use dashmap::DashMap;
10use serde::{Deserialize, Serialize};
11
12use super::cell_ref::CellRef;
13use super::deps::extract_dependencies;
14
15/// The type of content stored in a cell.
16#[derive(Clone, Debug, Serialize, Deserialize)]
17pub enum CellType {
18    Empty,
19    Text(String),
20    Number(f64),
21    Script(String),
22}
23
24/// A cell in the spreadsheet grid.
25#[derive(Clone, Debug, Serialize, Deserialize)]
26pub struct Cell {
27    pub contents: CellType,
28    pub depends_on: Vec<CellRef>,
29    pub dirty: bool,
30    /// Cached display string for script cells (not serialized).
31    #[serde(skip)]
32    pub cached_value: Option<String>,
33}
34
35impl Cell {
36    pub fn new_empty() -> Cell {
37        Cell {
38            contents: CellType::Empty,
39            depends_on: vec![],
40            dirty: false,
41            cached_value: None,
42        }
43    }
44
45    pub fn new_text(text: &str) -> Cell {
46        Cell {
47            contents: CellType::Text(text.to_string()),
48            depends_on: vec![],
49            dirty: false,
50            cached_value: None,
51        }
52    }
53
54    pub fn new_number(n: f64) -> Cell {
55        Cell {
56            contents: CellType::Number(n),
57            depends_on: vec![],
58            dirty: false,
59            cached_value: None,
60        }
61    }
62
63    /// Create a new cell containing a script/formula.
64    /// Dependencies are automatically extracted from the script.
65    pub fn new_script(script: &str) -> Cell {
66        Cell {
67            depends_on: extract_dependencies(script),
68            contents: CellType::Script(script.to_string()),
69            dirty: true,
70            cached_value: None,
71        }
72    }
73
74    /// Parse user input and create appropriate cell type.
75    /// - Empty string or whitespace -> Empty
76    /// - Starts with '=' -> Script (without the '=')
77    /// - Quoted string -> Text (without quotes)
78    /// - Valid number -> Number
79    /// - Otherwise -> Text
80    pub fn from_input(input: &str) -> Cell {
81        let trimmed = input.trim();
82        if trimmed.is_empty() {
83            return Cell::new_empty();
84        }
85
86        if let Some(formula) = trimmed.strip_prefix('=') {
87            return Cell::new_script(formula);
88        }
89
90        if trimmed.starts_with('"') && trimmed.ends_with('"') && trimmed.len() >= 2 {
91            let text = &trimmed[1..trimmed.len() - 1];
92            return Cell::new_text(text);
93        }
94
95        if let Ok(n) = trimmed.parse::<f64>() {
96            return Cell::new_number(n);
97        }
98
99        Cell::new_text(trimmed)
100    }
101
102    /// Get a display string for the cell content (for editing).
103    pub fn to_input_string(&self) -> String {
104        match &self.contents {
105            CellType::Empty => String::new(),
106            CellType::Text(s) => s.clone(),
107            CellType::Number(n) => n.to_string(),
108            CellType::Script(s) => format!("={}", s),
109        }
110    }
111}
112
113/// Thread-safe sparse grid storage.
114pub type Grid = DashMap<CellRef, Cell>;
115
116/// Thread-safe storage for spill cell values.
117/// Maps cell positions to their computed Dynamic values.
118pub type SpillMap = DashMap<CellRef, rhai::Dynamic>;
119
120/// Thread-safe storage for computed formula cell values.
121/// Maps cell positions to their evaluated Dynamic values.
122/// This allows cell references to use pre-computed values instead of re-evaluating.
123pub type ComputedMap = DashMap<CellRef, rhai::Dynamic>;