mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-11 16:34:47 -06:00
A Ctrl+Shift+Enter array formula was evaluated like a dynamic array:
- Read from a file, the cached values in its range were kept as
constants, so the formula showed #SPILL! (any multi-cell array
formula saved by Excel or LibreOffice).
- Entered over C2:C4 with a two-value result, C4 stayed blank where
Excel shows #N/A, and a single-cell array formula spilled.
- On save, the values of its other cells were not cached.
The result is now fitted to the range: a single row or column repeats
across it, cells past the result show #N/A, and a larger result is cut
off. Reading drops the cached values inside the range, entering clears
the range, and saving caches its values. The formula bar shows the
formula in braces, {=B2:B3*2}.
87 lines
3.1 KiB
Rust
87 lines
3.1 KiB
Rust
//! Cells.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use gridcraft_core::{RangeRef, Value};
|
|
use gridcraft_formula::Expr;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::style::StyleId;
|
|
|
|
/// A parsed formula. `text` is the canonical formula text without the leading `=`.
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
pub struct Formula {
|
|
pub text: String,
|
|
#[serde(skip)]
|
|
pub expr: Option<Arc<Expr>>,
|
|
/// Legacy array formula (Ctrl+Shift+Enter) covering this range; the formula lives in the
|
|
/// top-left cell.
|
|
pub array: Option<RangeRef>,
|
|
}
|
|
|
|
impl Formula {
|
|
/// Parses `text` (with or without `=`). Unparseable formulas keep their text and evaluate to
|
|
/// `#NAME?`.
|
|
pub fn new(text: &str) -> Formula {
|
|
let body = text.strip_prefix('=').unwrap_or(text);
|
|
match gridcraft_formula::parse(body) {
|
|
Ok(e) => Formula { text: gridcraft_formula::print(&e), expr: Some(Arc::new(e)), array: None },
|
|
Err(_) => Formula { text: body.to_string(), expr: None, array: None },
|
|
}
|
|
}
|
|
pub fn from_expr(e: Expr) -> Formula {
|
|
Formula { text: gridcraft_formula::print(&e), expr: Some(Arc::new(e)), array: None }
|
|
}
|
|
/// The parsed expression, re-parsing after deserialization.
|
|
pub fn expr(&self) -> Option<Expr> {
|
|
self.expr.as_deref().cloned().or_else(|| gridcraft_formula::parse(&self.text).ok())
|
|
}
|
|
/// The parsed expression without copying it (re-parses after deserialization).
|
|
pub fn expr_arc(&self) -> Option<Arc<Expr>> {
|
|
self.expr.clone().or_else(|| gridcraft_formula::parse(&self.text).ok().map(Arc::new))
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
|
pub struct Cell {
|
|
/// Constant value, or the cached result of the formula.
|
|
pub value: Value,
|
|
pub formula: Option<Arc<Formula>>,
|
|
pub style: StyleId,
|
|
}
|
|
|
|
impl Cell {
|
|
pub fn value(v: Value) -> Cell {
|
|
Cell { value: v, formula: None, style: StyleId::DEFAULT }
|
|
}
|
|
pub fn formula(f: Formula) -> Cell {
|
|
Cell { value: Value::Empty, formula: Some(Arc::new(f)), style: StyleId::DEFAULT }
|
|
}
|
|
/// No value, no formula and the default style: storing it is pointless.
|
|
pub fn is_blank(&self) -> bool {
|
|
self.value.is_empty() && self.formula.is_none() && self.style == StyleId::DEFAULT
|
|
}
|
|
/// What the formula bar shows.
|
|
pub fn input_text(&self) -> String {
|
|
if let Some(f) = &self.formula {
|
|
return format!("={}", f.text);
|
|
}
|
|
match &self.value {
|
|
Value::Text(t) => {
|
|
// Text that would parse as something else is shown with its apostrophe by Excel
|
|
// only when entered that way; we show it plainly.
|
|
t.to_string()
|
|
}
|
|
v => v.display(),
|
|
}
|
|
}
|
|
/// What the formula bar shows when not editing: [`input_text`](Self::input_text), with a
|
|
/// legacy array formula in braces (`{=A1:A2*2}`).
|
|
pub fn bar_text(&self) -> String {
|
|
match &self.formula {
|
|
Some(f) if f.array.is_some() => format!("{{={}}}", f.text),
|
|
_ => self.input_text(),
|
|
}
|
|
}
|
|
}
|