1use std::collections::BTreeSet;
64
65use crate::calc::{Extent, Values};
66use crate::messages::{raise, Refusal};
67use crate::projection::{Projection, Window};
68use crate::rows::{line as relation, quoted};
69use crate::rows::{Row, Value};
70use crate::sheet::{
71 cell_ref, column_letters, formula_text, number_text, parse_a1_range, shared_masters, A1Range,
72 Cell, Column, Content, NumFmt, Sheet, Style, Workbook,
73};
74
75#[derive(Clone, Copy, Debug, Default)]
80pub struct Grid<'v> {
81 pub values: Option<&'v dyn Values>,
82}
83
84impl<'v> Grid<'v> {
85 pub fn valued(values: &'v dyn Values) -> Grid<'v> {
87 Grid {
88 values: Some(values),
89 }
90 }
91}
92
93impl Projection for Grid<'_> {
94 fn name(&self) -> &str {
95 "grid"
96 }
97
98 fn project(&self, model: &Workbook, window: &Window, out: &mut dyn FnMut(&str)) {
99 self.rows(model, window, true, out);
100 }
101}
102
103#[derive(Clone, Copy, Debug, Default)]
109pub struct ValueGrid<'v> {
110 pub values: Option<&'v dyn Values>,
111}
112
113impl<'v> ValueGrid<'v> {
114 pub fn valued(values: &'v dyn Values) -> ValueGrid<'v> {
116 ValueGrid {
117 values: Some(values),
118 }
119 }
120}
121
122impl Projection for ValueGrid<'_> {
123 fn name(&self) -> &str {
124 "values"
125 }
126
127 fn project(&self, model: &Workbook, window: &Window, out: &mut dyn FnMut(&str)) {
128 Grid {
129 values: self.values,
130 }
131 .rows(model, window, false, out);
132 }
133}
134
135impl Grid<'_> {
136 fn rows(&self, model: &Workbook, window: &Window, formulas: bool, out: &mut dyn FnMut(&str)) {
140 for s in &model.sheets {
141 out(&relation(&Row::Sheet {
142 name: &s.name,
143 visibility: s.visibility,
144 }));
145 }
146 let Some(i) = model.find_sheet(&window.sheet) else {
147 out(&format!(
150 "(window {} {})",
151 quoted(&window.sheet),
152 quoted(&window.range.text())
153 ));
154 return;
155 };
156 let sheet = &model.sheets[i];
157 let name = sheet.name.as_str();
158 let range = window.range;
159 out(&format!(
160 "(window {} {})",
161 quoted(name),
162 quoted(&range.text())
163 ));
164 match extent_in(sheet, i, self.values) {
165 Some(e) => out(&format!("(extent {} {})", quoted(name), quoted(&e.text()))),
166 None => out(&format!("(extent {} none)", quoted(name))),
167 }
168 out(&format!(
169 "(gridlines {} {})",
170 quoted(name),
171 if sheet.gridlines { "on" } else { "off" }
172 ));
173 let mut columns: Vec<&Column> = sheet
174 .columns
175 .iter()
176 .filter(|c| c.index >= range.left && c.index <= range.right)
177 .collect();
178 columns.sort_by_key(|c| c.index);
179 let mut used: BTreeSet<u32> = BTreeSet::new();
180 used.insert(0);
181 for c in &columns {
182 let width = c
183 .width
184 .and_then(number_text)
185 .unwrap_or_else(|| "none".to_string());
186 let style = match c.style {
187 Some(s) => {
188 used.insert(s);
189 s.to_string()
190 }
191 None => "none".to_string(),
192 };
193 out(&format!(
194 "(column {} {} {} {} {})",
195 quoted(name),
196 quoted(&column_letters(c.index)),
197 width,
198 if c.hidden { "hidden" } else { "shown" },
199 style
200 ));
201 }
202 for (n, r) in sheet.rows.range(range.top..=range.bottom) {
205 let height = r
206 .height
207 .and_then(number_text)
208 .unwrap_or_else(|| "none".to_string());
209 out(&format!(
210 "(row {} {} {} {})",
211 quoted(name),
212 n,
213 height,
214 if r.hidden { "hidden" } else { "shown" }
215 ));
216 }
217 let cells: Vec<(&(u32, u32), &Cell)> = sheet
218 .cells
219 .range((range.top, 0)..=(range.bottom, u32::MAX))
220 .filter(|((_, col), _)| *col >= range.left && *col <= range.right)
221 .collect();
222 for (_, cell) in &cells {
223 used.insert(cell.style);
224 }
225 for (_, index) in &sheet.looks {
226 used.insert(*index);
227 }
228 for index in used {
229 out(&format_row(index, model.styles.xfs().get(index as usize)));
230 }
231 for (value, index) in &sheet.looks {
234 out(&format!("(look {} {} {})", quoted(name), value, index));
235 }
236 let mut masters = None;
241 let lags = self.values.filter(|v| v.lags(i));
244 for ((row, col), cell) in cells {
245 let addr = cell_ref(*row, *col);
246 let newer = lags.and_then(|v| v.content((i, *row, *col)));
247 let printed = match newer.as_ref().unwrap_or(&cell.content) {
248 Content::Text(t) => {
249 let value = Value::Text(t.clone());
250 out(&relation(&Row::Cell {
251 sheet: name,
252 addr: &addr,
253 value: &value,
254 }));
255 true
256 }
257 Content::Number(n) => {
258 let value = match number_text(*n) {
261 Some(text) => Value::Number(text),
262 None => Value::Error("#NUM!".to_string()),
263 };
264 out(&relation(&Row::Cell {
265 sheet: name,
266 addr: &addr,
267 value: &value,
268 }));
269 true
270 }
271 Content::Bool(b) => {
272 let value = Value::Bool(*b);
273 out(&relation(&Row::Cell {
274 sheet: name,
275 addr: &addr,
276 value: &value,
277 }));
278 true
279 }
280 Content::Error(e) => {
281 let value = Value::Error(e.clone());
282 out(&relation(&Row::Cell {
283 sheet: name,
284 addr: &addr,
285 value: &value,
286 }));
287 true
288 }
289 other => {
290 let computed = self.values.and_then(|calc| calc.computed((i, *row, *col)));
291 let held = if formulas {
295 let walked = masters.get_or_insert_with(|| shared_masters(sheet));
296 match formula_text(other, *row, *col, walked) {
297 Some(text) => {
298 let text = format!("={text}");
299 out(&relation(&Row::Formula {
300 sheet: name,
301 addr: &addr,
302 text: &text,
303 }));
304 true
305 }
306 None => false,
307 }
308 } else {
309 match other {
310 Content::SharedChild { si } if computed.is_none() => masters
314 .get_or_insert_with(|| shared_masters(sheet))
315 .contains_key(si),
316 _ => true,
317 }
318 };
319 if let Some(computed) = computed.filter(|_| held) {
320 out(&format!(
321 "(value {} {} {})",
322 quoted(name),
323 quoted(&addr),
324 computed.spell()
325 ));
326 }
327 held
328 }
329 };
330 if !printed {
331 continue;
332 }
333 if cell.style != 0 {
334 out(&format!(
335 "(style {} {} {})",
336 quoted(name),
337 quoted(&addr),
338 cell.style
339 ));
340 }
341 if let Some(r) = sheet.sentences.get(&(*row, *col)) {
342 out(&format!(
343 "(sentence {} {} {})",
344 quoted(name),
345 quoted(&addr),
346 r
347 ));
348 }
349 }
350 }
351}
352
353fn format_row(index: u32, style: Option<&Style>) -> String {
357 let default = Style::default();
358 let s = style.unwrap_or(&default);
359 let fill = match s.fill {
360 Some(rgb) => quoted(&format!("{:02X}{:02X}{:02X}", rgb.0, rgb.1, rgb.2)),
361 None => "none".to_string(),
362 };
363 let num = match s.num_fmt {
364 NumFmt::General => "general",
365 NumFmt::Text => "text",
366 };
367 format!(
368 "(format {index} {fill} {num} {})",
369 if s.wrap { "wrap" } else { "nowrap" }
370 )
371}
372
373fn extent_of(sheet: &Sheet) -> Option<A1Range> {
375 rectangle(sheet.extent())
376}
377
378fn extent_in(sheet: &Sheet, i: usize, values: Option<&dyn Values>) -> Option<A1Range> {
381 match values.and_then(|v| v.kept_extent(i)) {
382 Some(kept) => rectangle(kept),
383 None => extent_of(sheet),
384 }
385}
386
387fn rectangle(extent: Extent) -> Option<A1Range> {
389 extent.map(|((top, left), (bottom, right))| A1Range {
390 top,
391 left,
392 bottom,
393 right,
394 })
395}
396
397pub fn whole(sheet: &Sheet) -> A1Range {
400 or_a1(extent_of(sheet))
401}
402
403fn or_a1(range: Option<A1Range>) -> A1Range {
405 range.unwrap_or(A1Range {
406 top: 1,
407 left: 1,
408 bottom: 1,
409 right: 1,
410 })
411}
412
413pub fn window_of(model: &Workbook, sheet: &str, range: Option<&str>) -> Result<Window, Refusal> {
421 window_over(model, sheet, range, None)
422}
423
424pub(crate) fn window_over(
427 model: &Workbook,
428 sheet: &str,
429 range: Option<&str>,
430 values: Option<&dyn Values>,
431) -> Result<Window, Refusal> {
432 let Some(i) = model.find_sheet(sheet) else {
433 let sheets = model
434 .sheets
435 .iter()
436 .map(|s| s.name.as_str())
437 .collect::<Vec<_>>()
438 .join(", ");
439 return Err(raise(
440 "view-sheet-unknown",
441 &[("name", sheet), ("sheets", &sheets)],
442 ));
443 };
444 let s = &model.sheets[i];
445 let range = match range {
446 Some(text) => parse_a1_range(text)
447 .ok_or_else(|| raise("view-window-not-a-range", &[("text", text)]))?,
448 None => or_a1(extent_in(s, i, values)),
449 };
450 Ok(Window {
451 sheet: s.name.clone(),
452 range,
453 })
454}
455
456pub fn view_text(model: &Workbook, window: &Window) -> String {
459 let mut out = String::new();
460 Grid::default().project(model, window, &mut |line| {
461 out.push_str(line);
462 out.push('\n');
463 });
464 out
465}
466
467pub fn view_text_valued(model: &Workbook, window: &Window, values: &dyn Values) -> String {
472 let mut out = String::new();
473 Grid::valued(values).project(model, window, &mut |line| {
474 out.push_str(line);
475 out.push('\n');
476 });
477 out
478}
479
480pub fn view_text_values(model: &Workbook, window: &Window, values: &dyn Values) -> String {
484 let mut out = String::new();
485 ValueGrid::valued(values).project(model, window, &mut |line| {
486 out.push_str(line);
487 out.push('\n');
488 });
489 out
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495 use crate::sheet::{Rgb, Styles};
496
497 fn range(text: &str) -> A1Range {
498 parse_a1_range(text).unwrap()
499 }
500
501 fn lines(text: &str) -> Vec<&str> {
502 text.lines().collect()
503 }
504
505 fn a_model() -> Workbook {
509 let mut wb = Workbook::new();
510 let mut styles = Styles::new();
511 let lavender = styles.id(Style {
512 fill: Some(Rgb(247, 244, 252)),
513 num_fmt: NumFmt::Text,
514 wrap: false,
515 });
516 let gray = styles.id(Style {
517 fill: Some(Rgb(242, 242, 242)),
518 num_fmt: NumFmt::General,
519 wrap: true,
520 });
521 wb.styles = styles;
522 let mut model = Sheet::new("Model");
523 model.gridlines = false;
524 model.columns = vec![
525 Column {
526 index: 3,
527 width: Some(60.0),
528 hidden: false,
529 style: Some(gray),
530 },
531 Column {
532 index: 1,
533 width: None,
534 hidden: true,
535 style: None,
536 },
537 Column {
538 index: 2,
539 width: Some(72.0),
540 hidden: false,
541 style: Some(lavender),
542 },
543 ];
544 model.set(
545 1,
546 2,
547 Cell {
548 content: Content::Text("Revenue".to_string()),
549 style: lavender,
550 },
551 );
552 model.set(
553 2,
554 2,
555 Cell {
556 content: Content::Number(1200.5),
557 style: 0,
558 },
559 );
560 model.set(
561 3,
562 2,
563 Cell {
564 content: Content::Bool(true),
565 style: 0,
566 },
567 );
568 model.set(
569 4,
570 2,
571 Cell {
572 content: Content::Number(f64::INFINITY),
573 style: 0,
574 },
575 );
576 model.set_formula(range("C2:C4"), "B2*2", 0);
577 model.set_formula_dynamic(range("D2"), "_xlfn.IFS(B2>3,\"big\",TRUE,\"small\")", 0);
578 model.wrote(range("B1"), 1);
579 model.wrote(range("B2:B4"), 2);
580 model.wrote(range("C2:C4"), 3);
581 model.wrote(range("D2"), 4);
582 wb.sheets.push(model);
583 wb.sheets.push(Sheet::new("Empty"));
584 wb
585 }
586
587 #[test]
588 fn the_record_s_rows_have_the_fixed_spelling_in_the_fixed_order() {
589 let wb = a_model();
590 let text = view_text(
591 &wb,
592 &Window {
593 sheet: "Model".to_string(),
594 range: range("A1:F20"),
595 },
596 );
597 assert_eq!(
598 lines(&text),
599 vec![
600 "(sheet \"Model\" visible)",
601 "(sheet \"Empty\" visible)",
602 "(window \"Model\" \"A1:F20\")",
603 "(extent \"Model\" \"B1:D4\")",
604 "(gridlines \"Model\" off)",
605 "(column \"Model\" \"A\" none hidden none)",
606 "(column \"Model\" \"B\" 72 shown 1)",
607 "(column \"Model\" \"C\" 60 shown 2)",
608 "(format 0 none general nowrap)",
609 "(format 1 \"F7F4FC\" text nowrap)",
610 "(format 2 \"F2F2F2\" general wrap)",
611 "(cell \"Model\" \"B1\" \"Revenue\")",
612 "(style \"Model\" \"B1\" 1)",
613 "(sentence \"Model\" \"B1\" 1)",
614 "(cell \"Model\" \"B2\" 1200.5)",
615 "(sentence \"Model\" \"B2\" 2)",
616 "(formula \"Model\" \"C2\" \"=B2*2\")",
617 "(sentence \"Model\" \"C2\" 3)",
618 "(formula \"Model\" \"D2\" \"=IFS(B2>3,\\\"big\\\",TRUE,\\\"small\\\")\")",
619 "(sentence \"Model\" \"D2\" 4)",
620 "(cell \"Model\" \"B3\" true)",
621 "(sentence \"Model\" \"B3\" 2)",
622 "(formula \"Model\" \"C3\" \"=B3*2\")",
623 "(sentence \"Model\" \"C3\" 3)",
624 "(cell \"Model\" \"B4\" (error \"#NUM!\"))",
625 "(sentence \"Model\" \"B4\" 2)",
626 "(formula \"Model\" \"C4\" \"=B4*2\")",
627 "(sentence \"Model\" \"C4\" 3)",
628 ]
629 );
630 }
631
632 #[test]
633 fn a_window_clips_to_its_rectangle_and_an_empty_one_prints_its_header_alone() {
634 let wb = a_model();
635 let text = view_text(
638 &wb,
639 &Window {
640 sheet: "Model".to_string(),
641 range: range("C3:D4"),
642 },
643 );
644 assert_eq!(
645 lines(&text),
646 vec![
647 "(sheet \"Model\" visible)",
648 "(sheet \"Empty\" visible)",
649 "(window \"Model\" \"C3:D4\")",
650 "(extent \"Model\" \"B1:D4\")",
651 "(gridlines \"Model\" off)",
652 "(column \"Model\" \"C\" 60 shown 2)",
653 "(format 0 none general nowrap)",
654 "(format 2 \"F2F2F2\" general wrap)",
655 "(formula \"Model\" \"C3\" \"=B3*2\")",
656 "(sentence \"Model\" \"C3\" 3)",
657 "(formula \"Model\" \"C4\" \"=B4*2\")",
658 "(sentence \"Model\" \"C4\" 3)",
659 ]
660 );
661 let text = view_text(
663 &wb,
664 &Window {
665 sheet: "Model".to_string(),
666 range: range("H10:J12"),
667 },
668 );
669 assert_eq!(
670 lines(&text)[2..],
671 [
672 "(window \"Model\" \"H10:J12\")",
673 "(extent \"Model\" \"B1:D4\")",
674 "(gridlines \"Model\" off)",
675 "(format 0 none general nowrap)",
676 ]
677 );
678 let text = view_text(
680 &wb,
681 &Window {
682 sheet: "Empty".to_string(),
683 range: whole(&wb.sheets[1]),
684 },
685 );
686 assert_eq!(
687 lines(&text)[2..],
688 [
689 "(window \"Empty\" \"A1\")",
690 "(extent \"Empty\" none)",
691 "(gridlines \"Empty\" on)",
692 "(format 0 none general nowrap)",
693 ]
694 );
695 let text = view_text(
698 &wb,
699 &Window {
700 sheet: "Nowhere".to_string(),
701 range: range("A1"),
702 },
703 );
704 assert_eq!(lines(&text).len(), 3);
705 assert_eq!(lines(&text)[2], "(window \"Nowhere\" \"A1\")");
706 }
707
708 #[test]
709 fn row_settings_looks_and_hidden_sheets_print_in_their_places() {
710 let mut wb = a_model();
714 wb.sheets[1].visibility = crate::rows::Visibility::Hidden;
715 let model = &mut wb.sheets[0];
716 model.rows.insert(
717 3,
718 crate::sheet::RowSettings {
719 height: Some(30.0),
720 hidden: false,
721 },
722 );
723 model.rows.insert(
724 9,
725 crate::sheet::RowSettings {
726 height: None,
727 hidden: true,
728 },
729 );
730 model.looks.push(("1".to_string(), 2));
731 let text = view_text(
732 &wb,
733 &Window {
734 sheet: "Model".to_string(),
735 range: range("B2:B4"),
736 },
737 );
738 assert_eq!(
739 lines(&text)[..10],
740 [
741 "(sheet \"Model\" visible)",
742 "(sheet \"Empty\" hidden)",
743 "(window \"Model\" \"B2:B4\")",
744 "(extent \"Model\" \"B1:D4\")",
745 "(gridlines \"Model\" off)",
746 "(column \"Model\" \"B\" 72 shown 1)",
747 "(row \"Model\" 3 30 shown)",
748 "(format 0 none general nowrap)",
749 "(format 1 \"F7F4FC\" text nowrap)",
750 "(format 2 \"F2F2F2\" general wrap)",
751 ]
752 );
753 assert_eq!(lines(&text)[10], "(look \"Model\" 1 2)");
754 assert!(
755 !text.contains("(row \"Model\" 9"),
756 "row 9 is outside the window"
757 );
758 }
759
760 #[test]
761 fn window_of_finds_the_sheet_without_case_and_refuses_the_rest_by_name() {
762 let wb = a_model();
763 let w = window_of(&wb, "MODEL", None).unwrap();
764 assert_eq!(w.sheet, "Model");
765 assert_eq!(w.range, range("B1:D4"));
766 let w = window_of(&wb, "empty", None).unwrap();
767 assert_eq!((w.sheet.as_str(), w.range.text().as_str()), ("Empty", "A1"));
768 let w = window_of(&wb, "Model", Some("f20:a1")).unwrap();
769 assert_eq!(w.range.text(), "A1:F20");
770 let r = window_of(&wb, "Summary", None).unwrap_err();
771 assert_eq!(
772 (r.id.as_str(), r.source.as_str()),
773 ("view-sheet-unknown", "VLA-View")
774 );
775 assert!(r.text.contains("Summary"), "{}", r.text);
776 assert!(r.text.contains("Model, Empty"), "{}", r.text);
777 for text in ["A:A", "3:3", "nonsense", "", "A1:B"] {
778 let r = window_of(&wb, "Model", Some(text)).unwrap_err();
779 assert_eq!(r.id, "view-window-not-a-range", "{text}");
780 assert!(r.text.contains(text), "{}", r.text);
781 }
782 assert_eq!(Grid::default().name(), "grid");
783 }
784
785 fn less_formulas(record: &str) -> String {
787 record
788 .lines()
789 .filter(|l| !l.starts_with("(formula "))
790 .map(|l| format!("{l}\n"))
791 .collect()
792 }
793
794 fn values_text(wb: &Workbook, window: &Window, values: Option<&dyn Values>) -> String {
796 let mut out = String::new();
797 ValueGrid { values }.project(wb, window, &mut |line| {
798 out.push_str(line);
799 out.push('\n');
800 });
801 out
802 }
803
804 fn walks(f: impl FnOnce()) -> crate::sheet::work::Work {
806 crate::sheet::work::begin();
807 f();
808 crate::sheet::work::end()
809 }
810
811 #[test]
812 fn the_values_are_the_grid_less_its_formula_rows() {
813 let mut wb = a_model();
817 wb.sheets[0].cells.get_mut(&(3, 3)).unwrap().style = 2;
818 let library = crate::calc::library::language();
819 let calc = crate::calc::Calc::run(&wb, &library);
820 for (sheet, window) in [
821 ("Model", "A1:F20"),
822 ("Model", "C3:D4"),
823 ("Model", "B1:B4"),
824 ("Model", "H10:J12"),
825 ("Empty", "A1"),
826 ] {
827 let w = Window {
828 sheet: sheet.to_string(),
829 range: range(window),
830 };
831 let bare = values_text(&wb, &w, None);
832 assert_eq!(bare, less_formulas(&view_text(&wb, &w)), "{window}");
833 let valued = view_text_values(&wb, &w, &calc);
834 assert_eq!(
835 valued,
836 less_formulas(&view_text_valued(&wb, &w, &calc)),
837 "{window}"
838 );
839 assert!(!valued.contains("(formula "), "{valued}");
840 }
841 let w = Window {
842 sheet: "Model".to_string(),
843 range: range("A1:F20"),
844 };
845 let bare = values_text(&wb, &w, None);
846 assert!(bare.contains("(style \"Model\" \"C3\" 2)\n(sentence \"Model\" \"C3\" 3)\n"));
847 assert!(!bare.contains("(value "), "{bare}");
848 let valued = view_text_values(&wb, &w, &calc);
849 assert!(
850 valued.contains("(value \"Model\" \"C2\" 2401)\n(sentence \"Model\" \"C2\" 3)\n"),
851 "{valued}"
852 );
853 let w = Window {
855 sheet: "Model".to_string(),
856 range: range("B1:B4"),
857 };
858 assert_eq!(values_text(&wb, &w, None), view_text(&wb, &w));
859 assert_eq!(ValueGrid::default().name(), "values");
860 }
861
862 #[test]
863 fn a_fill_s_cell_whose_master_is_lost_shows_in_neither_record() {
864 let mut wb = Workbook::new();
870 let mut sheet = Sheet::new("S");
871 sheet.set(
872 2,
873 2,
874 Cell {
875 content: Content::Number(5.0),
876 style: 0,
877 },
878 );
879 sheet.set_formula(range("C2:C4"), "B2+1", 1);
880 sheet.wrote(range("C2:C4"), 2);
881 sheet.set(
882 2,
883 3,
884 Cell {
885 content: Content::Number(9.0),
886 style: 0,
887 },
888 );
889 sheet.wrote(range("C2"), 3);
890 wb.sheets.push(sheet);
891 let w = Window {
892 sheet: "S".to_string(),
893 range: range("A1:D5"),
894 };
895 let library = crate::calc::library::language();
896 let calc = crate::calc::Calc::run(&wb, &library);
897 let grid = view_text(&wb, &w);
898 let valued = view_text_valued(&wb, &w, &calc);
899 let mut bare = String::new();
900 let work = walks(|| bare = values_text(&wb, &w, None));
901 assert_eq!(bare, less_formulas(&grid));
902 assert_eq!(work.walked, 4, "the sheet's four cells walked once");
903 let mut values = String::new();
904 let work = walks(|| values = view_text_values(&wb, &w, &calc));
905 assert_eq!(values, less_formulas(&valued));
906 assert_eq!(work.walked, 4, "no value vouches for C3, so one walk");
907 assert_eq!(work.extents, 0, "the extent the calculation keeps");
908 for record in [&grid, &valued, &bare, &values] {
909 assert!(
910 !record.contains("\"C3\"") && !record.contains("\"C4\""),
911 "{record}"
912 );
913 assert!(record.contains("(cell \"S\" \"C2\" 9)\n(sentence \"S\" \"C2\" 3)\n"));
914 }
915 }
916
917 #[test]
918 fn the_values_of_a_computed_fill_walk_nothing() {
919 let mut wb = Workbook::new();
924 let mut sheet = Sheet::new("S");
925 for row in 1..=4 {
926 sheet.set(
927 row,
928 1,
929 Cell {
930 content: Content::Number(f64::from(row)),
931 style: 0,
932 },
933 );
934 }
935 sheet.set_formula(range("B1:C4"), "A1*2", 0);
936 wb.sheets.push(sheet);
937 let w = Window {
938 sheet: "S".to_string(),
939 range: range("B2:C3"),
940 };
941 let library = crate::calc::library::language();
942 let calc = crate::calc::Calc::run(&wb, &library);
943 let work = walks(|| {
944 view_text_values(&wb, &w, &calc);
945 });
946 assert_eq!((work.walked, work.extents), (0, 0));
947 let work = walks(|| {
948 view_text_valued(&wb, &w, &calc);
949 });
950 assert_eq!((work.walked, work.extents), (12, 0));
951 let work = walks(|| {
952 values_text(&wb, &w, None);
953 });
954 assert_eq!((work.walked, work.extents), (12, 12));
955 let work = walks(|| {
956 view_text(&wb, &w);
957 });
958 assert_eq!((work.walked, work.extents), (12, 12));
959 }
960}