1use std::collections::{BTreeSet, HashMap};
75
76use crate::calc::{Extent, Values};
77use crate::messages::{raise, Refusal};
78use crate::projection::{Projection, Window};
79use crate::rows::{line as relation, quoted};
80use crate::rows::{Row, Value};
81use crate::sheet::{
82 cell_ref, column_letters, formula_text, number_text, parse_a1_range, shared_masters, A1Range,
83 Cell, Column, Content, NumFmt, Sheet, Style, Workbook,
84};
85
86#[derive(Clone, Copy, Debug, Default)]
91pub struct Grid<'v> {
92 pub values: Option<&'v dyn Values>,
93}
94
95impl<'v> Grid<'v> {
96 pub fn valued(values: &'v dyn Values) -> Grid<'v> {
98 Grid {
99 values: Some(values),
100 }
101 }
102}
103
104impl Projection for Grid<'_> {
105 fn name(&self) -> &str {
106 "grid"
107 }
108
109 fn project(&self, model: &Workbook, window: &Window, out: &mut dyn FnMut(&str)) {
110 self.rows(model, window, true, out);
111 }
112}
113
114#[derive(Clone, Copy, Debug, Default)]
120pub struct ValueGrid<'v> {
121 pub values: Option<&'v dyn Values>,
122}
123
124impl<'v> ValueGrid<'v> {
125 pub fn valued(values: &'v dyn Values) -> ValueGrid<'v> {
127 ValueGrid {
128 values: Some(values),
129 }
130 }
131}
132
133impl Projection for ValueGrid<'_> {
134 fn name(&self) -> &str {
135 "values"
136 }
137
138 fn project(&self, model: &Workbook, window: &Window, out: &mut dyn FnMut(&str)) {
139 Grid {
140 values: self.values,
141 }
142 .rows(model, window, false, out);
143 }
144}
145
146impl Grid<'_> {
147 fn rows(&self, model: &Workbook, window: &Window, formulas: bool, out: &mut dyn FnMut(&str)) {
151 for s in &model.sheets {
152 out(&relation(&Row::Sheet {
153 name: &s.name,
154 visibility: s.visibility,
155 }));
156 }
157 let Some(i) = model.find_sheet(&window.sheet) else {
158 out(&format!(
161 "(window {} {})",
162 quoted(&window.sheet),
163 quoted(&window.range.text())
164 ));
165 return;
166 };
167 let sheet = &model.sheets[i];
168 let name = sheet.name.as_str();
169 let range = window.range;
170 out(&format!(
171 "(window {} {})",
172 quoted(name),
173 quoted(&range.text())
174 ));
175 match extent_in(sheet, i, self.values) {
176 Some(e) => out(&format!("(extent {} {})", quoted(name), quoted(&e.text()))),
177 None => out(&format!("(extent {} none)", quoted(name))),
178 }
179 out(&format!(
180 "(gridlines {} {})",
181 quoted(name),
182 if sheet.gridlines { "on" } else { "off" }
183 ));
184 let mut columns: Vec<&Column> = sheet
185 .columns
186 .iter()
187 .filter(|c| c.index >= range.left && c.index <= range.right)
188 .collect();
189 columns.sort_by_key(|c| c.index);
190 let mut used: BTreeSet<u32> = BTreeSet::new();
191 used.insert(0);
192 for c in &columns {
193 let width = c
194 .width
195 .and_then(number_text)
196 .unwrap_or_else(|| "none".to_string());
197 let style = match c.style {
198 Some(s) => {
199 used.insert(s);
200 s.to_string()
201 }
202 None => "none".to_string(),
203 };
204 out(&format!(
205 "(column {} {} {} {} {})",
206 quoted(name),
207 quoted(&column_letters(c.index)),
208 width,
209 if c.hidden { "hidden" } else { "shown" },
210 style
211 ));
212 }
213 for (n, r) in sheet.rows.range(range.top..=range.bottom) {
216 let height = r
217 .height
218 .and_then(number_text)
219 .unwrap_or_else(|| "none".to_string());
220 out(&format!(
221 "(row {} {} {} {})",
222 quoted(name),
223 n,
224 height,
225 if r.hidden { "hidden" } else { "shown" }
226 ));
227 }
228 let cells: Vec<(&(u32, u32), &Cell)> = sheet
229 .cells
230 .range((range.top, 0)..=(range.bottom, u32::MAX))
231 .filter(|((_, col), _)| *col >= range.left && *col <= range.right)
232 .collect();
233 for (_, cell) in &cells {
234 used.insert(cell.style);
235 }
236 for (_, index) in &sheet.looks {
237 used.insert(*index);
238 }
239 for index in used {
240 out(&format_row(index, model.styles.xfs().get(index as usize)));
241 }
242 for (value, index) in &sheet.looks {
245 out(&format!("(look {} {} {})", quoted(name), value, index));
246 }
247 let mut masters = None;
252 let lags = self.values.filter(|v| v.lags(i));
255 #[cfg(test)]
256 crate::sheet::work::viewed(cells.len());
257 for (&(row, col), cell) in cells {
258 self.cell_rows(
259 sheet,
260 i,
261 (row, col),
262 cell,
263 lags,
264 formulas,
265 &mut masters,
266 out,
267 );
268 }
269 }
270
271 #[allow(clippy::too_many_arguments)]
278 fn cell_rows<'s>(
279 &self,
280 sheet: &'s Sheet,
281 i: usize,
282 (row, col): (u32, u32),
283 cell: &Cell,
284 lags: Option<&dyn Values>,
285 formulas: bool,
286 masters: &mut Option<HashMap<u32, (&'s str, u32, u32)>>,
287 out: &mut dyn FnMut(&str),
288 ) -> bool {
289 let name = sheet.name.as_str();
290 let addr = cell_ref(row, col);
291 let newer = lags.and_then(|v| v.content((i, row, col)));
292 let printed = match newer.as_ref().unwrap_or(&cell.content) {
293 Content::Text(t) => {
294 let value = Value::Text(t.clone());
295 out(&relation(&Row::Cell {
296 sheet: name,
297 addr: &addr,
298 value: &value,
299 }));
300 true
301 }
302 Content::Number(n) => {
303 let value = match number_text(*n) {
306 Some(text) => Value::Number(text),
307 None => Value::Error("#NUM!".to_string()),
308 };
309 out(&relation(&Row::Cell {
310 sheet: name,
311 addr: &addr,
312 value: &value,
313 }));
314 true
315 }
316 Content::Bool(b) => {
317 let value = Value::Bool(*b);
318 out(&relation(&Row::Cell {
319 sheet: name,
320 addr: &addr,
321 value: &value,
322 }));
323 true
324 }
325 Content::Error(e) => {
326 let value = Value::Error(e.clone());
327 out(&relation(&Row::Cell {
328 sheet: name,
329 addr: &addr,
330 value: &value,
331 }));
332 true
333 }
334 other => {
335 let computed = self.values.and_then(|calc| calc.computed((i, row, col)));
336 let held = if formulas {
340 let walked = masters.get_or_insert_with(|| shared_masters(sheet));
341 match formula_text(other, row, col, walked) {
342 Some(text) => {
343 let text = format!("={text}");
344 out(&relation(&Row::Formula {
345 sheet: name,
346 addr: &addr,
347 text: &text,
348 }));
349 true
350 }
351 None => false,
352 }
353 } else {
354 match other {
355 Content::SharedChild { si } if computed.is_none() => masters
359 .get_or_insert_with(|| shared_masters(sheet))
360 .contains_key(si),
361 _ => true,
362 }
363 };
364 if let Some(computed) = computed.filter(|_| held) {
365 out(&format!(
366 "(value {} {} {})",
367 quoted(name),
368 quoted(&addr),
369 computed.spell()
370 ));
371 }
372 held
373 }
374 };
375 if !printed {
376 return false;
377 }
378 if cell.style != 0 {
379 out(&format!(
380 "(style {} {} {})",
381 quoted(name),
382 quoted(&addr),
383 cell.style
384 ));
385 }
386 if let Some(r) = sheet.sentences.get(&(row, col)) {
387 out(&format!(
388 "(sentence {} {} {})",
389 quoted(name),
390 quoted(&addr),
391 r
392 ));
393 }
394 true
395 }
396}
397
398fn format_row(index: u32, style: Option<&Style>) -> String {
402 let default = Style::default();
403 let s = style.unwrap_or(&default);
404 let fill = match s.fill {
405 Some(rgb) => quoted(&format!("{:02X}{:02X}{:02X}", rgb.0, rgb.1, rgb.2)),
406 None => "none".to_string(),
407 };
408 let num = match s.num_fmt {
409 NumFmt::General => "general",
410 NumFmt::Text => "text",
411 };
412 format!(
413 "(format {index} {fill} {num} {})",
414 if s.wrap { "wrap" } else { "nowrap" }
415 )
416}
417
418fn extent_of(sheet: &Sheet) -> Option<A1Range> {
420 rectangle(sheet.extent())
421}
422
423fn extent_in(sheet: &Sheet, i: usize, values: Option<&dyn Values>) -> Option<A1Range> {
426 match values.and_then(|v| v.kept_extent(i)) {
427 Some(kept) => rectangle(kept),
428 None => extent_of(sheet),
429 }
430}
431
432fn rectangle(extent: Extent) -> Option<A1Range> {
434 extent.map(|((top, left), (bottom, right))| A1Range {
435 top,
436 left,
437 bottom,
438 right,
439 })
440}
441
442pub fn whole(sheet: &Sheet) -> A1Range {
445 or_a1(extent_of(sheet))
446}
447
448fn or_a1(range: Option<A1Range>) -> A1Range {
450 range.unwrap_or(A1Range {
451 top: 1,
452 left: 1,
453 bottom: 1,
454 right: 1,
455 })
456}
457
458pub fn window_of(model: &Workbook, sheet: &str, range: Option<&str>) -> Result<Window, Refusal> {
466 window_over(model, sheet, range, None)
467}
468
469pub(crate) fn window_over(
472 model: &Workbook,
473 sheet: &str,
474 range: Option<&str>,
475 values: Option<&dyn Values>,
476) -> Result<Window, Refusal> {
477 let Some(i) = model.find_sheet(sheet) else {
478 let sheets = model
479 .sheets
480 .iter()
481 .map(|s| s.name.as_str())
482 .collect::<Vec<_>>()
483 .join(", ");
484 return Err(raise(
485 "view-sheet-unknown",
486 &[("name", sheet), ("sheets", &sheets)],
487 ));
488 };
489 let s = &model.sheets[i];
490 let range = match range {
491 Some(text) => parse_a1_range(text)
492 .ok_or_else(|| raise("view-window-not-a-range", &[("text", text)]))?,
493 None => or_a1(extent_in(s, i, values)),
494 };
495 Ok(Window {
496 sheet: s.name.clone(),
497 range,
498 })
499}
500
501pub fn view_text(model: &Workbook, window: &Window) -> String {
504 let mut out = String::new();
505 Grid::default().project(model, window, &mut |line| {
506 out.push_str(line);
507 out.push('\n');
508 });
509 out
510}
511
512pub fn view_text_valued(model: &Workbook, window: &Window, values: &dyn Values) -> String {
517 let mut out = String::new();
518 Grid::valued(values).project(model, window, &mut |line| {
519 out.push_str(line);
520 out.push('\n');
521 });
522 out
523}
524
525pub fn view_text_values(model: &Workbook, window: &Window, values: &dyn Values) -> String {
529 let mut out = String::new();
530 ValueGrid::valued(values).project(model, window, &mut |line| {
531 out.push_str(line);
532 out.push('\n');
533 });
534 out
535}
536
537pub(crate) fn changes_text(
546 model: &Workbook,
547 window: &Window,
548 values: &dyn Values,
549 (since, at): (u64, u64),
550 cells: Option<&[(u32, u32)]>,
551) -> String {
552 let Some(i) = model.find_sheet(&window.sheet) else {
553 return String::new();
554 };
555 let sheet = &model.sheets[i];
556 let head = |n: &str| {
557 format!(
558 "(changes {} {} {since} {at} {n})\n",
559 quoted(&sheet.name),
560 quoted(&window.range.text())
561 )
562 };
563 let Some(cells) = cells else {
564 return head("whole");
565 };
566 let mut out = head(&cells.len().to_string());
567 #[cfg(test)]
568 crate::sheet::work::viewed(cells.len());
569 let grid = Grid::valued(values);
570 let lags = Some(values).filter(|v| v.lags(i));
571 let mut masters = None;
572 let mut push = |line: &str| {
573 out.push_str(line);
574 out.push('\n');
575 };
576 for &(row, col) in cells {
577 let printed = sheet.cells.get(&(row, col)).is_some_and(|cell| {
578 grid.cell_rows(
579 sheet,
580 i,
581 (row, col),
582 cell,
583 lags,
584 false,
585 &mut masters,
586 &mut push,
587 )
588 });
589 if !printed {
590 push(&format!(
591 "(empty {} {})",
592 quoted(&sheet.name),
593 quoted(&cell_ref(row, col))
594 ));
595 }
596 }
597 out
598}
599
600#[cfg(test)]
601mod tests {
602 use super::*;
603 use crate::sheet::{Rgb, Styles};
604
605 fn range(text: &str) -> A1Range {
606 parse_a1_range(text).unwrap()
607 }
608
609 fn lines(text: &str) -> Vec<&str> {
610 text.lines().collect()
611 }
612
613 fn a_model() -> Workbook {
617 let mut wb = Workbook::new();
618 let mut styles = Styles::new();
619 let lavender = styles.id(Style {
620 fill: Some(Rgb(247, 244, 252)),
621 num_fmt: NumFmt::Text,
622 wrap: false,
623 });
624 let gray = styles.id(Style {
625 fill: Some(Rgb(242, 242, 242)),
626 num_fmt: NumFmt::General,
627 wrap: true,
628 });
629 wb.styles = styles;
630 let mut model = Sheet::new("Model");
631 model.gridlines = false;
632 model.columns = vec![
633 Column {
634 index: 3,
635 width: Some(60.0),
636 hidden: false,
637 style: Some(gray),
638 },
639 Column {
640 index: 1,
641 width: None,
642 hidden: true,
643 style: None,
644 },
645 Column {
646 index: 2,
647 width: Some(72.0),
648 hidden: false,
649 style: Some(lavender),
650 },
651 ];
652 model.set(
653 1,
654 2,
655 Cell {
656 content: Content::Text("Revenue".to_string()),
657 style: lavender,
658 },
659 );
660 model.set(
661 2,
662 2,
663 Cell {
664 content: Content::Number(1200.5),
665 style: 0,
666 },
667 );
668 model.set(
669 3,
670 2,
671 Cell {
672 content: Content::Bool(true),
673 style: 0,
674 },
675 );
676 model.set(
677 4,
678 2,
679 Cell {
680 content: Content::Number(f64::INFINITY),
681 style: 0,
682 },
683 );
684 model.set_formula(range("C2:C4"), "B2*2", 0);
685 model.set_formula_dynamic(range("D2"), "_xlfn.IFS(B2>3,\"big\",TRUE,\"small\")", 0);
686 model.wrote(range("B1"), 1);
687 model.wrote(range("B2:B4"), 2);
688 model.wrote(range("C2:C4"), 3);
689 model.wrote(range("D2"), 4);
690 wb.sheets.push(model);
691 wb.sheets.push(Sheet::new("Empty"));
692 wb
693 }
694
695 #[test]
696 fn the_record_s_rows_have_the_fixed_spelling_in_the_fixed_order() {
697 let wb = a_model();
698 let text = view_text(
699 &wb,
700 &Window {
701 sheet: "Model".to_string(),
702 range: range("A1:F20"),
703 },
704 );
705 assert_eq!(
706 lines(&text),
707 vec![
708 "(sheet \"Model\" visible)",
709 "(sheet \"Empty\" visible)",
710 "(window \"Model\" \"A1:F20\")",
711 "(extent \"Model\" \"B1:D4\")",
712 "(gridlines \"Model\" off)",
713 "(column \"Model\" \"A\" none hidden none)",
714 "(column \"Model\" \"B\" 72 shown 1)",
715 "(column \"Model\" \"C\" 60 shown 2)",
716 "(format 0 none general nowrap)",
717 "(format 1 \"F7F4FC\" text nowrap)",
718 "(format 2 \"F2F2F2\" general wrap)",
719 "(cell \"Model\" \"B1\" \"Revenue\")",
720 "(style \"Model\" \"B1\" 1)",
721 "(sentence \"Model\" \"B1\" 1)",
722 "(cell \"Model\" \"B2\" 1200.5)",
723 "(sentence \"Model\" \"B2\" 2)",
724 "(formula \"Model\" \"C2\" \"=B2*2\")",
725 "(sentence \"Model\" \"C2\" 3)",
726 "(formula \"Model\" \"D2\" \"=IFS(B2>3,\\\"big\\\",TRUE,\\\"small\\\")\")",
727 "(sentence \"Model\" \"D2\" 4)",
728 "(cell \"Model\" \"B3\" true)",
729 "(sentence \"Model\" \"B3\" 2)",
730 "(formula \"Model\" \"C3\" \"=B3*2\")",
731 "(sentence \"Model\" \"C3\" 3)",
732 "(cell \"Model\" \"B4\" (error \"#NUM!\"))",
733 "(sentence \"Model\" \"B4\" 2)",
734 "(formula \"Model\" \"C4\" \"=B4*2\")",
735 "(sentence \"Model\" \"C4\" 3)",
736 ]
737 );
738 }
739
740 #[test]
741 fn the_changes_print_each_cell_s_rows_as_the_values_print_them() {
742 let mut wb = a_model();
749 let library = crate::calc::library::language();
750 let calc = crate::calc::Calc::run(&wb, &library);
751 let w = Window {
752 sheet: "Model".to_string(),
753 range: range("A1:F20"),
754 };
755 let values = view_text_values(&wb, &w, &calc);
756 let rows = |addr: &str| -> String {
757 let at = format!(" \"{addr}\" ");
758 values
759 .lines()
760 .filter(|l| l.contains(&at))
761 .map(|l| format!("{l}\n"))
762 .collect()
763 };
764 let text = changes_text(&wb, &w, &calc, (6, 7), Some(&[(1, 2), (2, 3), (9, 9)]));
765 assert_eq!(
766 text,
767 format!(
768 "(changes \"Model\" \"A1:F20\" 6 7 3)\n{}{}(empty \"Model\" \"I9\")\n",
769 rows("B1"),
770 rows("C2")
771 )
772 );
773 assert_eq!(rows("B1").lines().count(), 3, "{values}");
774 assert_eq!(
775 changes_text(&wb, &w, &calc, (6, 7), None),
776 "(changes \"Model\" \"A1:F20\" 6 7 whole)\n"
777 );
778 wb.sheets[0].cells.remove(&(2, 3));
779 let lost = crate::calc::Calc::run(&wb, &library);
780 assert_eq!(
781 changes_text(&wb, &w, &lost, (0, 1), Some(&[(3, 3)])),
782 "(changes \"Model\" \"A1:F20\" 0 1 1)\n(empty \"Model\" \"C3\")\n",
783 "a fill's cell whose master is lost"
784 );
785 }
786
787 #[test]
788 fn a_window_clips_to_its_rectangle_and_an_empty_one_prints_its_header_alone() {
789 let wb = a_model();
790 let text = view_text(
793 &wb,
794 &Window {
795 sheet: "Model".to_string(),
796 range: range("C3:D4"),
797 },
798 );
799 assert_eq!(
800 lines(&text),
801 vec![
802 "(sheet \"Model\" visible)",
803 "(sheet \"Empty\" visible)",
804 "(window \"Model\" \"C3:D4\")",
805 "(extent \"Model\" \"B1:D4\")",
806 "(gridlines \"Model\" off)",
807 "(column \"Model\" \"C\" 60 shown 2)",
808 "(format 0 none general nowrap)",
809 "(format 2 \"F2F2F2\" general wrap)",
810 "(formula \"Model\" \"C3\" \"=B3*2\")",
811 "(sentence \"Model\" \"C3\" 3)",
812 "(formula \"Model\" \"C4\" \"=B4*2\")",
813 "(sentence \"Model\" \"C4\" 3)",
814 ]
815 );
816 let text = view_text(
818 &wb,
819 &Window {
820 sheet: "Model".to_string(),
821 range: range("H10:J12"),
822 },
823 );
824 assert_eq!(
825 lines(&text)[2..],
826 [
827 "(window \"Model\" \"H10:J12\")",
828 "(extent \"Model\" \"B1:D4\")",
829 "(gridlines \"Model\" off)",
830 "(format 0 none general nowrap)",
831 ]
832 );
833 let text = view_text(
835 &wb,
836 &Window {
837 sheet: "Empty".to_string(),
838 range: whole(&wb.sheets[1]),
839 },
840 );
841 assert_eq!(
842 lines(&text)[2..],
843 [
844 "(window \"Empty\" \"A1\")",
845 "(extent \"Empty\" none)",
846 "(gridlines \"Empty\" on)",
847 "(format 0 none general nowrap)",
848 ]
849 );
850 let text = view_text(
853 &wb,
854 &Window {
855 sheet: "Nowhere".to_string(),
856 range: range("A1"),
857 },
858 );
859 assert_eq!(lines(&text).len(), 3);
860 assert_eq!(lines(&text)[2], "(window \"Nowhere\" \"A1\")");
861 }
862
863 #[test]
864 fn row_settings_looks_and_hidden_sheets_print_in_their_places() {
865 let mut wb = a_model();
869 wb.sheets[1].visibility = crate::rows::Visibility::Hidden;
870 let model = &mut wb.sheets[0];
871 model.rows.insert(
872 3,
873 crate::sheet::RowSettings {
874 height: Some(30.0),
875 hidden: false,
876 },
877 );
878 model.rows.insert(
879 9,
880 crate::sheet::RowSettings {
881 height: None,
882 hidden: true,
883 },
884 );
885 model.looks.push(("1".to_string(), 2));
886 let text = view_text(
887 &wb,
888 &Window {
889 sheet: "Model".to_string(),
890 range: range("B2:B4"),
891 },
892 );
893 assert_eq!(
894 lines(&text)[..10],
895 [
896 "(sheet \"Model\" visible)",
897 "(sheet \"Empty\" hidden)",
898 "(window \"Model\" \"B2:B4\")",
899 "(extent \"Model\" \"B1:D4\")",
900 "(gridlines \"Model\" off)",
901 "(column \"Model\" \"B\" 72 shown 1)",
902 "(row \"Model\" 3 30 shown)",
903 "(format 0 none general nowrap)",
904 "(format 1 \"F7F4FC\" text nowrap)",
905 "(format 2 \"F2F2F2\" general wrap)",
906 ]
907 );
908 assert_eq!(lines(&text)[10], "(look \"Model\" 1 2)");
909 assert!(
910 !text.contains("(row \"Model\" 9"),
911 "row 9 is outside the window"
912 );
913 }
914
915 #[test]
916 fn window_of_finds_the_sheet_without_case_and_refuses_the_rest_by_name() {
917 let wb = a_model();
918 let w = window_of(&wb, "MODEL", None).unwrap();
919 assert_eq!(w.sheet, "Model");
920 assert_eq!(w.range, range("B1:D4"));
921 let w = window_of(&wb, "empty", None).unwrap();
922 assert_eq!((w.sheet.as_str(), w.range.text().as_str()), ("Empty", "A1"));
923 let w = window_of(&wb, "Model", Some("f20:a1")).unwrap();
924 assert_eq!(w.range.text(), "A1:F20");
925 let r = window_of(&wb, "Summary", None).unwrap_err();
926 assert_eq!(
927 (r.id.as_str(), r.source.as_str()),
928 ("view-sheet-unknown", "VLA-View")
929 );
930 assert!(r.text.contains("Summary"), "{}", r.text);
931 assert!(r.text.contains("Model, Empty"), "{}", r.text);
932 for text in ["A:A", "3:3", "nonsense", "", "A1:B"] {
933 let r = window_of(&wb, "Model", Some(text)).unwrap_err();
934 assert_eq!(r.id, "view-window-not-a-range", "{text}");
935 assert!(r.text.contains(text), "{}", r.text);
936 }
937 assert_eq!(Grid::default().name(), "grid");
938 }
939
940 fn less_formulas(record: &str) -> String {
942 record
943 .lines()
944 .filter(|l| !l.starts_with("(formula "))
945 .map(|l| format!("{l}\n"))
946 .collect()
947 }
948
949 fn values_text(wb: &Workbook, window: &Window, values: Option<&dyn Values>) -> String {
951 let mut out = String::new();
952 ValueGrid { values }.project(wb, window, &mut |line| {
953 out.push_str(line);
954 out.push('\n');
955 });
956 out
957 }
958
959 fn walks(f: impl FnOnce()) -> crate::sheet::work::Work {
961 crate::sheet::work::begin();
962 f();
963 crate::sheet::work::end()
964 }
965
966 #[test]
967 fn the_values_are_the_grid_less_its_formula_rows() {
968 let mut wb = a_model();
972 wb.sheets[0].cells.get_mut(&(3, 3)).unwrap().style = 2;
973 let library = crate::calc::library::language();
974 let calc = crate::calc::Calc::run(&wb, &library);
975 for (sheet, window) in [
976 ("Model", "A1:F20"),
977 ("Model", "C3:D4"),
978 ("Model", "B1:B4"),
979 ("Model", "H10:J12"),
980 ("Empty", "A1"),
981 ] {
982 let w = Window {
983 sheet: sheet.to_string(),
984 range: range(window),
985 };
986 let bare = values_text(&wb, &w, None);
987 assert_eq!(bare, less_formulas(&view_text(&wb, &w)), "{window}");
988 let valued = view_text_values(&wb, &w, &calc);
989 assert_eq!(
990 valued,
991 less_formulas(&view_text_valued(&wb, &w, &calc)),
992 "{window}"
993 );
994 assert!(!valued.contains("(formula "), "{valued}");
995 }
996 let w = Window {
997 sheet: "Model".to_string(),
998 range: range("A1:F20"),
999 };
1000 let bare = values_text(&wb, &w, None);
1001 assert!(bare.contains("(style \"Model\" \"C3\" 2)\n(sentence \"Model\" \"C3\" 3)\n"));
1002 assert!(!bare.contains("(value "), "{bare}");
1003 let valued = view_text_values(&wb, &w, &calc);
1004 assert!(
1005 valued.contains("(value \"Model\" \"C2\" 2401)\n(sentence \"Model\" \"C2\" 3)\n"),
1006 "{valued}"
1007 );
1008 let w = Window {
1010 sheet: "Model".to_string(),
1011 range: range("B1:B4"),
1012 };
1013 assert_eq!(values_text(&wb, &w, None), view_text(&wb, &w));
1014 assert_eq!(ValueGrid::default().name(), "values");
1015 }
1016
1017 #[test]
1018 fn a_fill_s_cell_whose_master_is_lost_shows_in_neither_record() {
1019 let mut wb = Workbook::new();
1025 let mut sheet = Sheet::new("S");
1026 sheet.set(
1027 2,
1028 2,
1029 Cell {
1030 content: Content::Number(5.0),
1031 style: 0,
1032 },
1033 );
1034 sheet.set_formula(range("C2:C4"), "B2+1", 1);
1035 sheet.wrote(range("C2:C4"), 2);
1036 sheet.set(
1037 2,
1038 3,
1039 Cell {
1040 content: Content::Number(9.0),
1041 style: 0,
1042 },
1043 );
1044 sheet.wrote(range("C2"), 3);
1045 wb.sheets.push(sheet);
1046 let w = Window {
1047 sheet: "S".to_string(),
1048 range: range("A1:D5"),
1049 };
1050 let library = crate::calc::library::language();
1051 let calc = crate::calc::Calc::run(&wb, &library);
1052 let grid = view_text(&wb, &w);
1053 let valued = view_text_valued(&wb, &w, &calc);
1054 let mut bare = String::new();
1055 let work = walks(|| bare = values_text(&wb, &w, None));
1056 assert_eq!(bare, less_formulas(&grid));
1057 assert_eq!(work.walked, 4, "the sheet's four cells walked once");
1058 let mut values = String::new();
1059 let work = walks(|| values = view_text_values(&wb, &w, &calc));
1060 assert_eq!(values, less_formulas(&valued));
1061 assert_eq!(work.walked, 4, "no value vouches for C3, so one walk");
1062 assert_eq!(work.extents, 0, "the extent the calculation keeps");
1063 for record in [&grid, &valued, &bare, &values] {
1064 assert!(
1065 !record.contains("\"C3\"") && !record.contains("\"C4\""),
1066 "{record}"
1067 );
1068 assert!(record.contains("(cell \"S\" \"C2\" 9)\n(sentence \"S\" \"C2\" 3)\n"));
1069 }
1070 }
1071
1072 #[test]
1073 fn the_values_of_a_computed_fill_walk_nothing() {
1074 let mut wb = Workbook::new();
1079 let mut sheet = Sheet::new("S");
1080 for row in 1..=4 {
1081 sheet.set(
1082 row,
1083 1,
1084 Cell {
1085 content: Content::Number(f64::from(row)),
1086 style: 0,
1087 },
1088 );
1089 }
1090 sheet.set_formula(range("B1:C4"), "A1*2", 0);
1091 wb.sheets.push(sheet);
1092 let w = Window {
1093 sheet: "S".to_string(),
1094 range: range("B2:C3"),
1095 };
1096 let library = crate::calc::library::language();
1097 let calc = crate::calc::Calc::run(&wb, &library);
1098 let work = walks(|| {
1099 view_text_values(&wb, &w, &calc);
1100 });
1101 assert_eq!((work.walked, work.extents), (0, 0));
1102 let work = walks(|| {
1103 view_text_valued(&wb, &w, &calc);
1104 });
1105 assert_eq!((work.walked, work.extents), (12, 0));
1106 let work = walks(|| {
1107 values_text(&wb, &w, None);
1108 });
1109 assert_eq!((work.walked, work.extents), (12, 12));
1110 let work = walks(|| {
1111 view_text(&wb, &w);
1112 });
1113 assert_eq!((work.walked, work.extents), (12, 12));
1114 }
1115}