use std::collections::{BTreeSet, HashMap};
use crate::calc::{self, Extent, Values};
use crate::machine::load::Datum;
use crate::messages::{raise, Refusal};
use crate::projection::{Projection, Window};
use crate::rows::{line as relation, quoted};
use crate::rows::{Row, Value};
use crate::sheet::{
cell_ref, column_letters, formula_text, number_text, parse_a1_range, shared_masters, A1Range,
Cell, Column, Content, NumFmt, Sheet, Style, Styles, Workbook,
};
#[derive(Clone, Copy, Debug, Default)]
pub struct Grid<'v> {
pub values: Option<&'v dyn Values>,
}
impl<'v> Grid<'v> {
pub fn valued(values: &'v dyn Values) -> Grid<'v> {
Grid {
values: Some(values),
}
}
}
impl Projection for Grid<'_> {
fn name(&self) -> &str {
"grid"
}
fn project(&self, model: &Workbook, window: &Window, out: &mut dyn FnMut(&str)) {
self.rows(model, window, true, out);
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct ValueGrid<'v> {
pub values: Option<&'v dyn Values>,
}
impl<'v> ValueGrid<'v> {
pub fn valued(values: &'v dyn Values) -> ValueGrid<'v> {
ValueGrid {
values: Some(values),
}
}
}
impl Projection for ValueGrid<'_> {
fn name(&self) -> &str {
"values"
}
fn project(&self, model: &Workbook, window: &Window, out: &mut dyn FnMut(&str)) {
Grid {
values: self.values,
}
.rows(model, window, false, out);
}
}
impl Grid<'_> {
fn rows(&self, model: &Workbook, window: &Window, formulas: bool, out: &mut dyn FnMut(&str)) {
for s in &model.sheets {
out(&relation(&Row::Sheet {
name: &s.name,
visibility: s.visibility,
}));
}
let Some(i) = model.find_sheet(&window.sheet) else {
out(&format!(
"(window {} {})",
quoted(&window.sheet),
quoted(&window.range.text())
));
return;
};
let sheet = &model.sheets[i];
let name = sheet.name.as_str();
let range = window.range;
out(&format!(
"(window {} {})",
quoted(name),
quoted(&range.text())
));
match extent_in(sheet, i, self.values) {
Some(e) => out(&format!("(extent {} {})", quoted(name), quoted(&e.text()))),
None => out(&format!("(extent {} none)", quoted(name))),
}
out(&format!(
"(gridlines {} {})",
quoted(name),
if sheet.gridlines { "on" } else { "off" }
));
let mut columns: Vec<&Column> = sheet
.columns
.iter()
.filter(|c| c.index >= range.left && c.index <= range.right)
.collect();
columns.sort_by_key(|c| c.index);
let mut used: BTreeSet<u32> = BTreeSet::new();
used.insert(0);
for c in &columns {
let width = c
.width
.and_then(number_text)
.unwrap_or_else(|| "none".to_string());
let style = match c.style {
Some(s) => {
used.insert(s);
s.to_string()
}
None => "none".to_string(),
};
out(&format!(
"(column {} {} {} {} {})",
quoted(name),
quoted(&column_letters(c.index)),
width,
if c.hidden { "hidden" } else { "shown" },
style
));
}
for (n, r) in sheet.rows.range(range.top..=range.bottom) {
let height = r
.height
.and_then(number_text)
.unwrap_or_else(|| "none".to_string());
out(&format!(
"(row {} {} {} {})",
quoted(name),
n,
height,
if r.hidden { "hidden" } else { "shown" }
));
}
let cells: Vec<(&(u32, u32), &Cell)> = sheet
.cells
.range((range.top, 0)..=(range.bottom, u32::MAX))
.filter(|((_, col), _)| *col >= range.left && *col <= range.right)
.collect();
for (_, cell) in &cells {
used.insert(cell.style);
}
for (_, index) in &sheet.looks {
used.insert(*index);
}
for index in used {
out(&format_row(index, model.styles.xfs().get(index as usize)));
}
for (value, index) in &sheet.looks {
out(&format!("(look {} {} {})", quoted(name), value, index));
}
let mut masters = None;
let lags = self.values.filter(|v| v.lags(i));
let coded = Coded::of(model, sheet);
#[cfg(test)]
crate::sheet::work::viewed(cells.len());
for (&(row, col), cell) in cells {
self.cell_rows(
sheet,
i,
(row, col),
cell,
lags,
formulas,
&mut masters,
coded.as_ref(),
out,
);
}
}
#[allow(clippy::too_many_arguments)]
fn cell_rows<'s>(
&self,
sheet: &'s Sheet,
i: usize,
(row, col): (u32, u32),
cell: &Cell,
lags: Option<&dyn Values>,
formulas: bool,
masters: &mut Option<HashMap<u32, (&'s str, u32, u32)>>,
coded: Option<&Coded<'_>>,
out: &mut dyn FnMut(&str),
) -> bool {
let name = sheet.name.as_str();
let addr = cell_ref(row, col);
let newer = lags.and_then(|v| v.content((i, row, col)));
let (printed, shown) = match newer.as_ref().unwrap_or(&cell.content) {
Content::Text(t) => {
let value = Value::Text(t.clone());
out(&relation(&Row::Cell {
sheet: name,
addr: &addr,
value: &value,
}));
(true, Some(calc::Value::Text(t.clone())))
}
Content::Number(n) => {
let value = match number_text(*n) {
Some(text) => Value::Number(text),
None => Value::Error("#NUM!".to_string()),
};
out(&relation(&Row::Cell {
sheet: name,
addr: &addr,
value: &value,
}));
(true, Some(calc::Value::Number(*n)))
}
Content::Bool(b) => {
let value = Value::Bool(*b);
out(&relation(&Row::Cell {
sheet: name,
addr: &addr,
value: &value,
}));
(true, None)
}
Content::Error(e) => {
let value = Value::Error(e.clone());
out(&relation(&Row::Cell {
sheet: name,
addr: &addr,
value: &value,
}));
(true, None)
}
other => {
let computed = self.values.and_then(|calc| calc.computed((i, row, col)));
let held = if formulas {
let walked = masters.get_or_insert_with(|| shared_masters(sheet));
match formula_text(other, row, col, walked) {
Some(text) => {
let text = format!("={text}");
out(&relation(&Row::Formula {
sheet: name,
addr: &addr,
text: &text,
}));
true
}
None => false,
}
} else {
match other {
Content::SharedChild { si } if computed.is_none() => masters
.get_or_insert_with(|| shared_masters(sheet))
.contains_key(si),
_ => true,
}
};
let mut shown = None;
if let Some(computed) = computed.filter(|_| held) {
out(&format!(
"(value {} {} {})",
quoted(name),
quoted(&addr),
computed.spell()
));
if let calc::Computed::Value(v) = computed {
shown = Some(v.clone().settled());
}
}
(held, shown)
}
};
if !printed {
return false;
}
if let (Some(coded), Some(v)) = (coded, shown) {
if let Some(text) = coded.text(cell.style, &v) {
out(&format!(
"(text {} {} {})",
quoted(name),
quoted(&addr),
quoted(&text)
));
}
}
if cell.style != 0 {
out(&format!(
"(style {} {} {})",
quoted(name),
quoted(&addr),
cell.style
));
}
if let Some(r) = sheet.sentences.get(&(row, col)) {
out(&format!(
"(sentence {} {} {})",
quoted(name),
quoted(&addr),
r
));
}
true
}
}
fn format_row(index: u32, style: Option<&Style>) -> String {
let default = Style::default();
let s = style.unwrap_or(&default);
let fill = match s.fill {
Some(rgb) => quoted(&format!("{:02X}{:02X}{:02X}", rgb.0, rgb.1, rgb.2)),
None => "none".to_string(),
};
let num = match &s.num_fmt {
NumFmt::General => "general".to_string(),
NumFmt::Text => "text".to_string(),
NumFmt::Code(code) => quoted(code.as_str()),
};
format!(
"(format {index} {fill} {num} {})",
if s.wrap { "wrap" } else { "nowrap" }
)
}
struct Coded<'m> {
styles: &'m Styles,
looks: Vec<(Look, u32)>,
}
enum Look {
Number(f64),
Text(String),
}
impl<'m> Coded<'m> {
fn of(model: &'m Workbook, sheet: &Sheet) -> Option<Coded<'m>> {
if !model.styles.has_code() {
return None;
}
let looks = sheet
.looks
.iter()
.filter_map(|(spelled, index)| {
let forms = crate::reader::read_forms(spelled).ok()?;
let look = match crate::machine::load::datum(forms.first()?)? {
Datum::Number(n) => Look::Number(n),
Datum::Text(t) => Look::Text(t),
_ => return None,
};
Some((look, *index))
})
.collect();
Some(Coded {
styles: &model.styles,
looks,
})
}
fn text(&self, style: u32, value: &calc::Value) -> Option<String> {
let met = self.looks.iter().find(|(look, _)| match (look, value) {
(Look::Number(a), calc::Value::Number(b)) => a == b,
(Look::Text(a), calc::Value::Text(b)) => a == b,
_ => false,
});
let index = met.map_or(style, |(_, index)| *index);
match self.styles.get(index).map(|s| &s.num_fmt) {
Some(NumFmt::Code(code)) => code.text(value),
_ => None,
}
}
}
fn extent_of(sheet: &Sheet) -> Option<A1Range> {
rectangle(sheet.extent())
}
fn extent_in(sheet: &Sheet, i: usize, values: Option<&dyn Values>) -> Option<A1Range> {
match values.and_then(|v| v.kept_extent(i)) {
Some(kept) => rectangle(kept),
None => extent_of(sheet),
}
}
fn rectangle(extent: Extent) -> Option<A1Range> {
extent.map(|((top, left), (bottom, right))| A1Range {
top,
left,
bottom,
right,
})
}
pub fn whole(sheet: &Sheet) -> A1Range {
or_a1(extent_of(sheet))
}
fn or_a1(range: Option<A1Range>) -> A1Range {
range.unwrap_or(A1Range {
top: 1,
left: 1,
bottom: 1,
right: 1,
})
}
pub fn window_of(model: &Workbook, sheet: &str, range: Option<&str>) -> Result<Window, Refusal> {
window_over(model, sheet, range, None)
}
pub(crate) fn window_over(
model: &Workbook,
sheet: &str,
range: Option<&str>,
values: Option<&dyn Values>,
) -> Result<Window, Refusal> {
let Some(i) = model.find_sheet(sheet) else {
let sheets = model
.sheets
.iter()
.map(|s| s.name.as_str())
.collect::<Vec<_>>()
.join(", ");
return Err(raise(
"view-sheet-unknown",
&[("name", sheet), ("sheets", &sheets)],
));
};
let s = &model.sheets[i];
let range = match range {
Some(text) => parse_a1_range(text)
.ok_or_else(|| raise("view-window-not-a-range", &[("text", text)]))?,
None => or_a1(extent_in(s, i, values)),
};
Ok(Window {
sheet: s.name.clone(),
range,
})
}
pub fn view_text(model: &Workbook, window: &Window) -> String {
let mut out = String::new();
Grid::default().project(model, window, &mut |line| {
out.push_str(line);
out.push('\n');
});
out
}
pub fn view_text_valued(model: &Workbook, window: &Window, values: &dyn Values) -> String {
let mut out = String::new();
Grid::valued(values).project(model, window, &mut |line| {
out.push_str(line);
out.push('\n');
});
out
}
pub fn view_text_values(model: &Workbook, window: &Window, values: &dyn Values) -> String {
let mut out = String::new();
ValueGrid::valued(values).project(model, window, &mut |line| {
out.push_str(line);
out.push('\n');
});
out
}
pub(crate) fn changes_text(
model: &Workbook,
window: &Window,
values: &dyn Values,
(since, at): (u64, u64),
cells: Option<&[(u32, u32)]>,
) -> String {
let Some(i) = model.find_sheet(&window.sheet) else {
return String::new();
};
let sheet = &model.sheets[i];
let head = |n: &str| {
format!(
"(changes {} {} {since} {at} {n})\n",
quoted(&sheet.name),
quoted(&window.range.text())
)
};
let Some(cells) = cells else {
return head("whole");
};
let mut out = head(&cells.len().to_string());
#[cfg(test)]
crate::sheet::work::viewed(cells.len());
let grid = Grid::valued(values);
let lags = Some(values).filter(|v| v.lags(i));
let coded = Coded::of(model, sheet);
let mut masters = None;
let mut push = |line: &str| {
out.push_str(line);
out.push('\n');
};
for &(row, col) in cells {
let printed = sheet.cells.get(&(row, col)).is_some_and(|cell| {
grid.cell_rows(
sheet,
i,
(row, col),
cell,
lags,
false,
&mut masters,
coded.as_ref(),
&mut push,
)
});
if !printed {
push(&format!(
"(empty {} {})",
quoted(&sheet.name),
quoted(&cell_ref(row, col))
));
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sheet::{Rgb, Styles};
fn range(text: &str) -> A1Range {
parse_a1_range(text).unwrap()
}
fn lines(text: &str) -> Vec<&str> {
text.lines().collect()
}
fn a_model() -> Workbook {
let mut wb = Workbook::new();
let mut styles = Styles::new();
let lavender = styles.id(Style {
fill: Some(Rgb(247, 244, 252)),
num_fmt: NumFmt::Text,
wrap: false,
});
let gray = styles.id(Style {
fill: Some(Rgb(242, 242, 242)),
num_fmt: NumFmt::General,
wrap: true,
});
wb.styles = styles;
let mut model = Sheet::new("Model");
model.gridlines = false;
model.columns = vec![
Column {
index: 3,
width: Some(60.0),
hidden: false,
style: Some(gray),
},
Column {
index: 1,
width: None,
hidden: true,
style: None,
},
Column {
index: 2,
width: Some(72.0),
hidden: false,
style: Some(lavender),
},
];
model.set(
1,
2,
Cell {
content: Content::Text("Revenue".to_string()),
style: lavender,
},
);
model.set(
2,
2,
Cell {
content: Content::Number(1200.5),
style: 0,
},
);
model.set(
3,
2,
Cell {
content: Content::Bool(true),
style: 0,
},
);
model.set(
4,
2,
Cell {
content: Content::Number(f64::INFINITY),
style: 0,
},
);
model.set_formula(range("C2:C4"), "B2*2", 0);
model.set_formula_dynamic(range("D2"), "_xlfn.IFS(B2>3,\"big\",TRUE,\"small\")", 0);
model.wrote(range("B1"), 1);
model.wrote(range("B2:B4"), 2);
model.wrote(range("C2:C4"), 3);
model.wrote(range("D2"), 4);
wb.sheets.push(model);
wb.sheets.push(Sheet::new("Empty"));
wb
}
#[test]
fn the_record_s_rows_have_the_fixed_spelling_in_the_fixed_order() {
let wb = a_model();
let text = view_text(
&wb,
&Window {
sheet: "Model".to_string(),
range: range("A1:F20"),
},
);
assert_eq!(
lines(&text),
vec![
"(sheet \"Model\" visible)",
"(sheet \"Empty\" visible)",
"(window \"Model\" \"A1:F20\")",
"(extent \"Model\" \"B1:D4\")",
"(gridlines \"Model\" off)",
"(column \"Model\" \"A\" none hidden none)",
"(column \"Model\" \"B\" 72 shown 1)",
"(column \"Model\" \"C\" 60 shown 2)",
"(format 0 none general nowrap)",
"(format 1 \"F7F4FC\" text nowrap)",
"(format 2 \"F2F2F2\" general wrap)",
"(cell \"Model\" \"B1\" \"Revenue\")",
"(style \"Model\" \"B1\" 1)",
"(sentence \"Model\" \"B1\" 1)",
"(cell \"Model\" \"B2\" 1200.5)",
"(sentence \"Model\" \"B2\" 2)",
"(formula \"Model\" \"C2\" \"=B2*2\")",
"(sentence \"Model\" \"C2\" 3)",
"(formula \"Model\" \"D2\" \"=IFS(B2>3,\\\"big\\\",TRUE,\\\"small\\\")\")",
"(sentence \"Model\" \"D2\" 4)",
"(cell \"Model\" \"B3\" true)",
"(sentence \"Model\" \"B3\" 2)",
"(formula \"Model\" \"C3\" \"=B3*2\")",
"(sentence \"Model\" \"C3\" 3)",
"(cell \"Model\" \"B4\" (error \"#NUM!\"))",
"(sentence \"Model\" \"B4\" 2)",
"(formula \"Model\" \"C4\" \"=B4*2\")",
"(sentence \"Model\" \"C4\" 3)",
]
);
}
#[test]
fn the_changes_print_each_cell_s_rows_as_the_values_print_them() {
let mut wb = a_model();
let library = crate::calc::library::language();
let calc = crate::calc::Calc::run(&wb, &library);
let w = Window {
sheet: "Model".to_string(),
range: range("A1:F20"),
};
let values = view_text_values(&wb, &w, &calc);
let rows = |addr: &str| -> String {
let at = format!(" \"{addr}\" ");
values
.lines()
.filter(|l| l.contains(&at))
.map(|l| format!("{l}\n"))
.collect()
};
let text = changes_text(&wb, &w, &calc, (6, 7), Some(&[(1, 2), (2, 3), (9, 9)]));
assert_eq!(
text,
format!(
"(changes \"Model\" \"A1:F20\" 6 7 3)\n{}{}(empty \"Model\" \"I9\")\n",
rows("B1"),
rows("C2")
)
);
assert_eq!(rows("B1").lines().count(), 3, "{values}");
assert_eq!(
changes_text(&wb, &w, &calc, (6, 7), None),
"(changes \"Model\" \"A1:F20\" 6 7 whole)\n"
);
wb.sheets[0].cells.remove(&(2, 3));
let lost = crate::calc::Calc::run(&wb, &library);
assert_eq!(
changes_text(&wb, &w, &lost, (0, 1), Some(&[(3, 3)])),
"(changes \"Model\" \"A1:F20\" 0 1 1)\n(empty \"Model\" \"C3\")\n",
"a fill's cell whose master is lost"
);
}
#[test]
fn a_window_clips_to_its_rectangle_and_an_empty_one_prints_its_header_alone() {
let wb = a_model();
let text = view_text(
&wb,
&Window {
sheet: "Model".to_string(),
range: range("C3:D4"),
},
);
assert_eq!(
lines(&text),
vec![
"(sheet \"Model\" visible)",
"(sheet \"Empty\" visible)",
"(window \"Model\" \"C3:D4\")",
"(extent \"Model\" \"B1:D4\")",
"(gridlines \"Model\" off)",
"(column \"Model\" \"C\" 60 shown 2)",
"(format 0 none general nowrap)",
"(format 2 \"F2F2F2\" general wrap)",
"(formula \"Model\" \"C3\" \"=B3*2\")",
"(sentence \"Model\" \"C3\" 3)",
"(formula \"Model\" \"C4\" \"=B4*2\")",
"(sentence \"Model\" \"C4\" 3)",
]
);
let text = view_text(
&wb,
&Window {
sheet: "Model".to_string(),
range: range("H10:J12"),
},
);
assert_eq!(
lines(&text)[2..],
[
"(window \"Model\" \"H10:J12\")",
"(extent \"Model\" \"B1:D4\")",
"(gridlines \"Model\" off)",
"(format 0 none general nowrap)",
]
);
let text = view_text(
&wb,
&Window {
sheet: "Empty".to_string(),
range: whole(&wb.sheets[1]),
},
);
assert_eq!(
lines(&text)[2..],
[
"(window \"Empty\" \"A1\")",
"(extent \"Empty\" none)",
"(gridlines \"Empty\" on)",
"(format 0 none general nowrap)",
]
);
let text = view_text(
&wb,
&Window {
sheet: "Nowhere".to_string(),
range: range("A1"),
},
);
assert_eq!(lines(&text).len(), 3);
assert_eq!(lines(&text)[2], "(window \"Nowhere\" \"A1\")");
}
#[test]
fn row_settings_looks_and_hidden_sheets_print_in_their_places() {
let mut wb = a_model();
wb.sheets[1].visibility = crate::rows::Visibility::Hidden;
let model = &mut wb.sheets[0];
model.rows.insert(
3,
crate::sheet::RowSettings {
height: Some(30.0),
hidden: false,
},
);
model.rows.insert(
9,
crate::sheet::RowSettings {
height: None,
hidden: true,
},
);
model.looks.push(("1".to_string(), 2));
let text = view_text(
&wb,
&Window {
sheet: "Model".to_string(),
range: range("B2:B4"),
},
);
assert_eq!(
lines(&text)[..10],
[
"(sheet \"Model\" visible)",
"(sheet \"Empty\" hidden)",
"(window \"Model\" \"B2:B4\")",
"(extent \"Model\" \"B1:D4\")",
"(gridlines \"Model\" off)",
"(column \"Model\" \"B\" 72 shown 1)",
"(row \"Model\" 3 30 shown)",
"(format 0 none general nowrap)",
"(format 1 \"F7F4FC\" text nowrap)",
"(format 2 \"F2F2F2\" general wrap)",
]
);
assert_eq!(lines(&text)[10], "(look \"Model\" 1 2)");
assert!(
!text.contains("(row \"Model\" 9"),
"row 9 is outside the window"
);
}
#[test]
fn window_of_finds_the_sheet_without_case_and_refuses_the_rest_by_name() {
let wb = a_model();
let w = window_of(&wb, "MODEL", None).unwrap();
assert_eq!(w.sheet, "Model");
assert_eq!(w.range, range("B1:D4"));
let w = window_of(&wb, "empty", None).unwrap();
assert_eq!((w.sheet.as_str(), w.range.text().as_str()), ("Empty", "A1"));
let w = window_of(&wb, "Model", Some("f20:a1")).unwrap();
assert_eq!(w.range.text(), "A1:F20");
let r = window_of(&wb, "Summary", None).unwrap_err();
assert_eq!(
(r.id.as_str(), r.source.as_str()),
("view-sheet-unknown", "VLA-View")
);
assert!(r.text.contains("Summary"), "{}", r.text);
assert!(r.text.contains("Model, Empty"), "{}", r.text);
for text in ["A:A", "3:3", "nonsense", "", "A1:B"] {
let r = window_of(&wb, "Model", Some(text)).unwrap_err();
assert_eq!(r.id, "view-window-not-a-range", "{text}");
assert!(r.text.contains(text), "{}", r.text);
}
assert_eq!(Grid::default().name(), "grid");
}
fn less_formulas(record: &str) -> String {
record
.lines()
.filter(|l| !l.starts_with("(formula "))
.map(|l| format!("{l}\n"))
.collect()
}
fn values_text(wb: &Workbook, window: &Window, values: Option<&dyn Values>) -> String {
let mut out = String::new();
ValueGrid { values }.project(wb, window, &mut |line| {
out.push_str(line);
out.push('\n');
});
out
}
fn walks(f: impl FnOnce()) -> crate::sheet::work::Work {
crate::sheet::work::begin();
f();
crate::sheet::work::end()
}
#[test]
fn the_values_are_the_grid_less_its_formula_rows() {
let mut wb = a_model();
wb.sheets[0].cells.get_mut(&(3, 3)).unwrap().style = 2;
let library = crate::calc::library::language();
let calc = crate::calc::Calc::run(&wb, &library);
for (sheet, window) in [
("Model", "A1:F20"),
("Model", "C3:D4"),
("Model", "B1:B4"),
("Model", "H10:J12"),
("Empty", "A1"),
] {
let w = Window {
sheet: sheet.to_string(),
range: range(window),
};
let bare = values_text(&wb, &w, None);
assert_eq!(bare, less_formulas(&view_text(&wb, &w)), "{window}");
let valued = view_text_values(&wb, &w, &calc);
assert_eq!(
valued,
less_formulas(&view_text_valued(&wb, &w, &calc)),
"{window}"
);
assert!(!valued.contains("(formula "), "{valued}");
}
let w = Window {
sheet: "Model".to_string(),
range: range("A1:F20"),
};
let bare = values_text(&wb, &w, None);
assert!(bare.contains("(style \"Model\" \"C3\" 2)\n(sentence \"Model\" \"C3\" 3)\n"));
assert!(!bare.contains("(value "), "{bare}");
let valued = view_text_values(&wb, &w, &calc);
assert!(
valued.contains("(value \"Model\" \"C2\" 2401)\n(sentence \"Model\" \"C2\" 3)\n"),
"{valued}"
);
let w = Window {
sheet: "Model".to_string(),
range: range("B1:B4"),
};
assert_eq!(values_text(&wb, &w, None), view_text(&wb, &w));
assert_eq!(ValueGrid::default().name(), "values");
}
#[test]
fn a_fill_s_cell_whose_master_is_lost_shows_in_neither_record() {
let mut wb = Workbook::new();
let mut sheet = Sheet::new("S");
sheet.set(
2,
2,
Cell {
content: Content::Number(5.0),
style: 0,
},
);
sheet.set_formula(range("C2:C4"), "B2+1", 1);
sheet.wrote(range("C2:C4"), 2);
sheet.set(
2,
3,
Cell {
content: Content::Number(9.0),
style: 0,
},
);
sheet.wrote(range("C2"), 3);
wb.sheets.push(sheet);
let w = Window {
sheet: "S".to_string(),
range: range("A1:D5"),
};
let library = crate::calc::library::language();
let calc = crate::calc::Calc::run(&wb, &library);
let grid = view_text(&wb, &w);
let valued = view_text_valued(&wb, &w, &calc);
let mut bare = String::new();
let work = walks(|| bare = values_text(&wb, &w, None));
assert_eq!(bare, less_formulas(&grid));
assert_eq!(work.walked, 4, "the sheet's four cells walked once");
let mut values = String::new();
let work = walks(|| values = view_text_values(&wb, &w, &calc));
assert_eq!(values, less_formulas(&valued));
assert_eq!(work.walked, 4, "no value vouches for C3, so one walk");
assert_eq!(work.extents, 0, "the extent the calculation keeps");
for record in [&grid, &valued, &bare, &values] {
assert!(
!record.contains("\"C3\"") && !record.contains("\"C4\""),
"{record}"
);
assert!(record.contains("(cell \"S\" \"C2\" 9)\n(sentence \"S\" \"C2\" 3)\n"));
}
}
#[test]
fn the_values_of_a_computed_fill_walk_nothing() {
let mut wb = Workbook::new();
let mut sheet = Sheet::new("S");
for row in 1..=4 {
sheet.set(
row,
1,
Cell {
content: Content::Number(f64::from(row)),
style: 0,
},
);
}
sheet.set_formula(range("B1:C4"), "A1*2", 0);
wb.sheets.push(sheet);
let w = Window {
sheet: "S".to_string(),
range: range("B2:C3"),
};
let library = crate::calc::library::language();
let calc = crate::calc::Calc::run(&wb, &library);
let work = walks(|| {
view_text_values(&wb, &w, &calc);
});
assert_eq!((work.walked, work.extents), (0, 0));
let work = walks(|| {
view_text_valued(&wb, &w, &calc);
});
assert_eq!((work.walked, work.extents), (12, 0));
let work = walks(|| {
values_text(&wb, &w, None);
});
assert_eq!((work.walked, work.extents), (12, 12));
let work = walks(|| {
view_text(&wb, &w);
});
assert_eq!((work.walked, work.extents), (12, 12));
}
}