Skip to main content

gridline_engine/engine/
cell.rs

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