Skip to main content

ironcalc_base/
worksheet.rs

1use std::borrow::Cow;
2
3use crate::constants::{self, LAST_COLUMN, LAST_ROW};
4use crate::expressions::types::CellReferenceIndex;
5use crate::expressions::utils::{is_valid_column_number, is_valid_row};
6use crate::model::CellStructure;
7use crate::{expressions::token::Error, types::*};
8
9#[derive(Debug, PartialEq, Eq)]
10pub struct WorksheetDimension {
11    pub min_row: i32,
12    pub max_row: i32,
13    pub min_column: i32,
14    pub max_column: i32,
15}
16
17#[derive(Clone, Copy, PartialEq, Eq)]
18pub enum NavigationDirection {
19    Left,
20    Right,
21    Up,
22    Down,
23}
24
25impl Worksheet {
26    pub fn get_name(&self) -> String {
27        self.name.clone()
28    }
29
30    pub fn get_sheet_id(&self) -> u32 {
31        self.sheet_id
32    }
33
34    pub fn set_name(&mut self, name: &str) {
35        self.name = name.to_string();
36    }
37
38    /// The cell at a position, if there is one. It is borrowed from the sheet
39    /// if the sheet holds it as a cell, and made for the occasion if it holds
40    /// it as a number (see `SheetData`): either way it reads as a `&Cell`.
41    pub fn cell(&self, row: i32, column: i32) -> Option<Cow<'_, Cell>> {
42        self.sheet_data.cell(row, column)
43    }
44
45    pub(crate) fn cell_mut(&mut self, row: i32, column: i32) -> Option<&mut Cell> {
46        self.sheet_data.cell_mut(row, column)
47    }
48
49    pub(crate) fn update_cell(
50        &mut self,
51        row: i32,
52        column: i32,
53        new_cell: Cell,
54    ) -> Result<(), String> {
55        // validate row and column arg before updating cell of worksheet
56        if !is_valid_row(row) || !is_valid_column_number(column) {
57            return Err("Incorrect row or column".to_string());
58        }
59
60        self.sheet_data.set_cell(row, column, new_cell);
61        Ok(())
62    }
63
64    // See: get_style_for_cell
65    fn get_row_column_style(&self, row_index: i32, column_index: i32) -> i32 {
66        let rows = &self.rows;
67        for row in rows {
68            if row.r == row_index {
69                if row.custom_format {
70                    return row.s;
71                }
72                break;
73            }
74        }
75        let cols = &self.cols;
76        for column in cols.iter() {
77            let min = column.min;
78            let max = column.max;
79            if column_index >= min && column_index <= max {
80                return column.style.unwrap_or(0);
81            }
82        }
83        0
84    }
85
86    pub fn get_style(&self, row: i32, column: i32) -> i32 {
87        match self.sheet_data.cell(row, column) {
88            Some(cell) => cell.get_style(),
89            None => self.get_row_column_style(row, column),
90        }
91    }
92
93    pub fn set_style(&mut self, style_index: i32) -> Result<(), String> {
94        self.cols = vec![Col {
95            min: 1,
96            max: constants::LAST_COLUMN,
97            width: constants::DEFAULT_COLUMN_WIDTH,
98            custom_width: false,
99            style: Some(style_index),
100            hidden: false,
101        }];
102        Ok(())
103    }
104
105    pub fn set_column_style(&mut self, column: i32, style_index: i32) -> Result<(), String> {
106        let width = self
107            .get_column_width(column)
108            .unwrap_or(constants::DEFAULT_COLUMN_WIDTH);
109        let hidden = self.is_column_hidden(column)?;
110        self.set_column_width_and_style(column, width, hidden, Some(style_index))
111    }
112
113    pub fn set_row_style(&mut self, row: i32, style_index: i32) -> Result<(), String> {
114        // FIXME: This is a HACK
115        let custom_format = style_index != 0;
116        for r in self.rows.iter_mut() {
117            if r.r == row {
118                r.s = style_index;
119                r.custom_format = custom_format;
120                return Ok(());
121            }
122        }
123        self.rows.push(Row {
124            height: constants::DEFAULT_ROW_HEIGHT / constants::ROW_HEIGHT_FACTOR,
125            r: row,
126            custom_format,
127            custom_height: false,
128            s: style_index,
129            hidden: false,
130        });
131        Ok(())
132    }
133
134    pub fn delete_row_style(&mut self, row: i32) -> Result<(), String> {
135        let mut index = None;
136        for (i, r) in self.rows.iter().enumerate() {
137            if r.r == row {
138                index = Some(i);
139                break;
140            }
141        }
142        if let Some(i) = index {
143            if let Some(r) = self.rows.get_mut(i) {
144                r.s = 0;
145                r.custom_format = false;
146            }
147        }
148        Ok(())
149    }
150
151    pub fn delete_column_style(&mut self, column: i32) -> Result<(), String> {
152        if !is_valid_column_number(column) {
153            return Err(format!("Column number '{column}' is not valid."));
154        }
155        let cols = &mut self.cols;
156
157        let mut index = 0;
158        let mut split = false;
159        for c in cols.iter_mut() {
160            let min = c.min;
161            let max = c.max;
162            if min <= column && column <= max {
163                //
164                split = true;
165                break;
166            }
167            if column < min {
168                // We passed, there is nothing to delete
169                break;
170            }
171            index += 1;
172        }
173        if split {
174            let min = cols[index].min;
175            let max = cols[index].max;
176            let custom_width = cols[index].custom_width;
177            let width = cols[index].width;
178            let pre = Col {
179                min,
180                max: column - 1,
181                width,
182                custom_width,
183                style: cols[index].style,
184                hidden: cols[index].hidden,
185            };
186            let col = Col {
187                min: column,
188                max: column,
189                width,
190                custom_width,
191                style: None,
192                hidden: false,
193            };
194            let post = Col {
195                min: column + 1,
196                max,
197                width,
198                custom_width,
199                style: cols[index].style,
200                hidden: cols[index].hidden,
201            };
202            cols.remove(index);
203            if column != max {
204                cols.insert(index, post);
205            }
206            if custom_width {
207                cols.insert(index, col);
208            }
209            if column != min {
210                cols.insert(index, pre);
211            }
212        }
213        Ok(())
214    }
215
216    pub fn set_cell_style(
217        &mut self,
218        row: i32,
219        column: i32,
220        style_index: i32,
221    ) -> Result<(), String> {
222        match self.cell_mut(row, column) {
223            Some(cell) => {
224                cell.set_style(style_index);
225            }
226            None => {
227                self.cell_clear_contents_with_style(row, column, style_index)?;
228            }
229        }
230        Ok(())
231
232        // TODO: cleanup check if the old cell style is still in use
233    }
234
235    pub fn set_cell_with_formula(
236        &mut self,
237        row: i32,
238        column: i32,
239        index: i32,
240        style: i32,
241    ) -> Result<(), String> {
242        let cell = Cell::new_formula(index, style);
243        self.update_cell(row, column, cell)
244    }
245
246    pub fn set_cell_with_dynamic_formula(
247        &mut self,
248        row: i32,
249        column: i32,
250        index: i32,
251        style: i32,
252        width: i32,
253        height: i32,
254    ) -> Result<(), String> {
255        let cell = Cell::ArrayFormula {
256            f: index,
257            s: style,
258            r: (width, height),
259            kind: ArrayKind::Dynamic,
260            v: FormulaValue::Unevaluated,
261        };
262        self.update_cell(row, column, cell)
263    }
264
265    pub fn set_cell_with_array_formula(
266        &mut self,
267        row: i32,
268        column: i32,
269        index: i32,
270        style: i32,
271        width: i32,
272        height: i32,
273    ) -> Result<(), String> {
274        let cell = Cell::ArrayFormula {
275            f: index,
276            s: style,
277            r: (width, height),
278            kind: ArrayKind::Cse,
279            v: FormulaValue::Unevaluated,
280        };
281        self.update_cell(row, column, cell)
282    }
283
284    /// Sets a number in a cell. The number has to be finite: no cell holds a
285    /// NaN or an infinity, and the cell is left as it was if given one.
286    pub fn set_cell_with_number(
287        &mut self,
288        row: i32,
289        column: i32,
290        value: f64,
291        style: i32,
292    ) -> Result<(), String> {
293        if !value.is_finite() {
294            return Err(format!("A cell cannot hold the number {value}"));
295        }
296        let cell = Cell::new_number(value, style);
297        self.update_cell(row, column, cell)
298    }
299
300    pub fn set_cell_with_string(
301        &mut self,
302        row: i32,
303        column: i32,
304        index: i32,
305        style: i32,
306    ) -> Result<(), String> {
307        let cell = Cell::new_string(index, style);
308        self.update_cell(row, column, cell)
309    }
310
311    pub fn set_cell_with_boolean(
312        &mut self,
313        row: i32,
314        column: i32,
315        value: bool,
316        style: i32,
317    ) -> Result<(), String> {
318        let cell = Cell::new_boolean(value, style);
319        self.update_cell(row, column, cell)
320    }
321
322    pub fn set_cell_with_error(
323        &mut self,
324        row: i32,
325        column: i32,
326        error: Error,
327        style: i32,
328    ) -> Result<(), String> {
329        let cell = Cell::new_error(error, style);
330        self.update_cell(row, column, cell)
331    }
332
333    pub fn cell_clear_contents(&mut self, row: i32, column: i32) -> Result<(), String> {
334        let s = self.get_style(row, column);
335        let cell = Cell::EmptyCell { s };
336        self.update_cell(row, column, cell)
337    }
338
339    pub fn cell_clear_contents_with_style(
340        &mut self,
341        row: i32,
342        column: i32,
343        style: i32,
344    ) -> Result<(), String> {
345        let cell = Cell::EmptyCell { s: style };
346        self.update_cell(row, column, cell)
347    }
348
349    /// Returns the merged cell containing (row, column), if any
350    pub fn merged_cell_containing(&self, row: i32, column: i32) -> Option<&MergedCell> {
351        self.merged_cells.iter().find(|m| m.contains(row, column))
352    }
353
354    /// Returns true if (row, column) is inside a merged range but is not its anchor
355    pub fn is_covered_cell(&self, row: i32, column: i32) -> bool {
356        match self.merged_cell_containing(row, column) {
357            Some(m) => m.row != row || m.column != column,
358            None => false,
359        }
360    }
361
362    /// Returns the anchor of the merged cell containing (row, column),
363    /// or (row, column) itself if the cell is not merged
364    pub fn merge_anchor(&self, row: i32, column: i32) -> (i32, i32) {
365        match self.merged_cell_containing(row, column) {
366            Some(m) => (m.row, m.column),
367            None => (row, column),
368        }
369    }
370
371    pub fn set_frozen_rows(&mut self, frozen_rows: i32) -> Result<(), String> {
372        if frozen_rows < 0 {
373            return Err("Frozen rows cannot be negative".to_string());
374        }
375        if frozen_rows >= constants::LAST_ROW {
376            return Err("Too many rows".to_string());
377        }
378        self.frozen_rows = frozen_rows;
379        Ok(())
380    }
381
382    pub fn set_frozen_columns(&mut self, frozen_columns: i32) -> Result<(), String> {
383        if frozen_columns < 0 {
384            return Err("Frozen columns cannot be negative".to_string());
385        }
386        if frozen_columns >= constants::LAST_COLUMN {
387            return Err("Too many columns".to_string());
388        }
389        self.frozen_columns = frozen_columns;
390        Ok(())
391    }
392
393    /// Changes the hidden status of a row.
394    pub fn set_row_hidden(&mut self, row: i32, hidden: bool) -> Result<(), String> {
395        if !is_valid_row(row) {
396            return Err(format!("Row number '{row}' is not valid."));
397        }
398
399        let rows = &mut self.rows;
400        for r in rows.iter_mut() {
401            if r.r == row {
402                r.hidden = hidden;
403                return Ok(());
404            }
405        }
406        rows.push(Row {
407            height: constants::DEFAULT_ROW_HEIGHT / constants::ROW_HEIGHT_FACTOR,
408            r: row,
409            custom_format: false,
410            custom_height: false,
411            s: 0,
412            hidden,
413        });
414        Ok(())
415    }
416
417    /// Changes the height of a row.
418    ///   * If the row does not a have a style we add it.
419    ///   * If it has we modify the height and make sure it is applied.
420    ///
421    /// Fails if row index is outside allowed range or height is negative.
422    pub fn set_row_height(&mut self, row: i32, height: f64) -> Result<(), String> {
423        if !is_valid_row(row) {
424            return Err(format!("Row number '{row}' is not valid."));
425        }
426        if height < 0.0 {
427            return Err(format!("Can not set a negative height: {height}"));
428        }
429        let hidden = self.is_row_hidden(row)?;
430        let rows = &mut self.rows;
431        for r in rows.iter_mut() {
432            if r.r == row {
433                r.height = height / constants::ROW_HEIGHT_FACTOR;
434                r.custom_height = true;
435                return Ok(());
436            }
437        }
438        rows.push(Row {
439            height: height / constants::ROW_HEIGHT_FACTOR,
440            r: row,
441            custom_format: false,
442            custom_height: true,
443            s: 0,
444            hidden,
445        });
446        Ok(())
447    }
448
449    /// Changes the width of a column.
450    ///   * If the column does not a have a width we simply add it
451    ///   * If it has, it might be part of a range and we need to split the range.
452    ///
453    /// Fails if column index is outside allowed range or width is negative.
454    pub fn set_column_width(&mut self, column: i32, width: f64) -> Result<(), String> {
455        let style = self.get_column_style(column)?;
456        let hidden = self.is_column_hidden(column)?;
457        self.set_column_width_and_style(column, width, hidden, style)
458    }
459
460    pub fn set_column_hidden(&mut self, column: i32, hidden: bool) -> Result<(), String> {
461        let width = self
462            .get_actual_column_width(column)
463            .unwrap_or(constants::DEFAULT_COLUMN_WIDTH);
464        let style = self.get_column_style(column)?;
465        self.set_column_width_and_style(column, width, hidden, style)
466    }
467
468    pub(crate) fn set_column_width_and_style(
469        &mut self,
470        column: i32,
471        width: f64,
472        hidden: bool,
473        style: Option<i32>,
474    ) -> Result<(), String> {
475        if !is_valid_column_number(column) {
476            return Err(format!("Column number '{column}' is not valid."));
477        }
478        if width < 0.0 {
479            return Err(format!("Can not set a negative width: {width}"));
480        }
481        let cols = &mut self.cols;
482        let mut col = Col {
483            min: column,
484            max: column,
485            width: width / constants::COLUMN_WIDTH_FACTOR,
486            custom_width: width != constants::DEFAULT_COLUMN_WIDTH,
487            style,
488            hidden,
489        };
490        let mut index = 0;
491        let mut split = false;
492        for c in cols.iter_mut() {
493            let min = c.min;
494            let max = c.max;
495            if min <= column && column <= max {
496                if min == column && max == column {
497                    c.style = style;
498                    c.width = width / constants::COLUMN_WIDTH_FACTOR;
499                    c.custom_width = width != constants::DEFAULT_COLUMN_WIDTH;
500                    c.hidden = hidden;
501                    return Ok(());
502                }
503                split = true;
504                break;
505            }
506            if column < min {
507                // We passed, we should insert at index
508                break;
509            }
510            index += 1;
511        }
512        if split {
513            let min = cols[index].min;
514            let max = cols[index].max;
515            let pre = Col {
516                min,
517                max: column - 1,
518                width: cols[index].width,
519                custom_width: cols[index].custom_width,
520                style: cols[index].style,
521                hidden: cols[index].hidden,
522            };
523            let post = Col {
524                min: column + 1,
525                max,
526                width: cols[index].width,
527                custom_width: cols[index].custom_width,
528                style: cols[index].style,
529                hidden: cols[index].hidden,
530            };
531            col.style = cols[index].style;
532            cols.remove(index);
533            if column != max {
534                cols.insert(index, post);
535            }
536            cols.insert(index, col);
537            if column != min {
538                cols.insert(index, pre);
539            }
540        } else {
541            cols.insert(index, col);
542        }
543        Ok(())
544    }
545
546    /// Return the width of a column in pixels
547    pub fn get_column_width(&self, column: i32) -> Result<f64, String> {
548        if !is_valid_column_number(column) {
549            return Err(format!("Column number '{column}' is not valid."));
550        }
551
552        let cols = &self.cols;
553        for col in cols {
554            let min = col.min;
555            let max = col.max;
556            if column >= min && column <= max {
557                if col.hidden {
558                    return Ok(0.0);
559                }
560                if col.custom_width {
561                    return Ok(col.width * constants::COLUMN_WIDTH_FACTOR);
562                }
563                break;
564            }
565        }
566        Ok(constants::DEFAULT_COLUMN_WIDTH)
567    }
568
569    /// Return the actual width of a column in pixels, ignoring hidden status
570    pub fn get_actual_column_width(&self, column: i32) -> Result<f64, String> {
571        if !is_valid_column_number(column) {
572            return Err(format!("Column number '{column}' is not valid."));
573        }
574
575        let cols = &self.cols;
576        for col in cols {
577            let min = col.min;
578            let max = col.max;
579            if column >= min && column <= max {
580                if col.custom_width {
581                    return Ok(col.width * constants::COLUMN_WIDTH_FACTOR);
582                }
583                break;
584            }
585        }
586        Ok(constants::DEFAULT_COLUMN_WIDTH)
587    }
588
589    /// Returns true if the column is hidden
590    pub fn is_column_hidden(&self, column: i32) -> Result<bool, String> {
591        if !is_valid_column_number(column) {
592            return Err(format!("Column number '{column}' is not valid."));
593        }
594
595        let cols = &self.cols;
596        for col in cols {
597            let min = col.min;
598            let max = col.max;
599            if column >= min && column <= max {
600                return Ok(col.hidden);
601            }
602        }
603        Ok(false)
604    }
605
606    /// Returns if a row is hidden
607    pub fn is_row_hidden(&self, row: i32) -> Result<bool, String> {
608        if !is_valid_row(row) {
609            return Err(format!("Row number '{row}' is not valid."));
610        }
611        let rows = &self.rows;
612        for r in rows {
613            if r.r == row {
614                return Ok(r.hidden);
615            }
616        }
617        Ok(false)
618    }
619
620    /// Returns the column style index if present
621    pub fn get_column_style(&self, column: i32) -> Result<Option<i32>, String> {
622        if !is_valid_column_number(column) {
623            return Err(format!("Column number '{column}' is not valid."));
624        }
625
626        let cols = &self.cols;
627        for col in cols {
628            let min = col.min;
629            let max = col.max;
630            if column >= min && column <= max {
631                return Ok(col.style);
632            }
633        }
634        Ok(None)
635    }
636
637    // Returns non empty cells in a column
638    pub(crate) fn column_cell_references(
639        &self,
640        column: i32,
641    ) -> Result<Vec<CellReferenceIndex>, String> {
642        let mut column_cell_references: Vec<CellReferenceIndex> = Vec::new();
643        if !is_valid_column_number(column) {
644            return Err(format!("Column number '{column}' is not valid."));
645        }
646
647        for row in self.sheet_data.rows() {
648            if self.cell(row, column).is_some() {
649                column_cell_references.push(CellReferenceIndex {
650                    sheet: self.sheet_id,
651                    row,
652                    column,
653                });
654            }
655        }
656        Ok(column_cell_references)
657    }
658
659    pub(crate) fn remove_cell(&mut self, row: i32, column: i32) -> Result<(), String> {
660        self.sheet_data.remove_cell(row, column);
661        Ok(())
662    }
663
664    /// Returns the height of a row in pixels
665    pub fn row_height(&self, row: i32) -> Result<f64, String> {
666        if !is_valid_row(row) {
667            return Err(format!("Row number '{row}' is not valid."));
668        }
669
670        let rows = &self.rows;
671        for r in rows {
672            if r.r == row {
673                if r.hidden {
674                    return Ok(0.0);
675                }
676                return Ok(r.height * constants::ROW_HEIGHT_FACTOR);
677            }
678        }
679        Ok(constants::DEFAULT_ROW_HEIGHT)
680    }
681
682    /// Calculates dimension of the sheet. This function isn't cheap to calculate.
683    pub fn dimension(&self) -> WorksheetDimension {
684        // FIXME: It's probably better to just track the size as operations happen.
685        if self.sheet_data.is_empty() {
686            return WorksheetDimension {
687                min_row: 1,
688                max_row: 1,
689                min_column: 1,
690                max_column: 1,
691            };
692        }
693
694        let mut row_range: Option<(i32, i32)> = None;
695        let mut column_range: Option<(i32, i32)> = None;
696
697        for (row_index, column_index, _) in self.sheet_data.cells() {
698            row_range = if let Some((current_min, current_max)) = row_range {
699                Some((current_min.min(row_index), current_max.max(row_index)))
700            } else {
701                Some((row_index, row_index))
702            };
703            column_range = if let Some((current_min, current_max)) = column_range {
704                Some((current_min.min(column_index), current_max.max(column_index)))
705            } else {
706                Some((column_index, column_index))
707            };
708        }
709
710        let dimension = if let Some((min_row, max_row)) = row_range {
711            if let Some((min_column, max_column)) = column_range {
712                Some(WorksheetDimension {
713                    min_row,
714                    min_column,
715                    max_row,
716                    max_column,
717                })
718            } else {
719                None
720            }
721        } else {
722            None
723        };
724
725        dimension.unwrap_or(WorksheetDimension {
726            min_row: 1,
727            max_row: 1,
728            min_column: 1,
729            max_column: 1,
730        })
731    }
732
733    /// Returns true if cell is completely empty.
734    /// Cell with formula that evaluates to empty string is not considered empty.
735    pub fn is_empty_cell(&self, row: i32, column: i32) -> Result<bool, String> {
736        if !is_valid_column_number(column) || !is_valid_row(row) {
737            return Err("Row or column is outside valid range.".to_string());
738        }
739
740        let is_empty = match self.sheet_data.cell(row, column) {
741            Some(cell) => matches!(*cell, Cell::EmptyCell { .. }),
742            None => true,
743        };
744
745        Ok(is_empty)
746    }
747
748    // Returns:
749    // - If it is an anchor for a dynamic array => full range
750    // - If it is an anchor for an array formula => full range
751    // - If it is a single cell with a formula => SingleCell
752    // - If it is a single cell without formula => Error
753    // - If it is a spill cell => Error
754    // - Any other case => Error
755    pub(crate) fn get_cell_spill(&self, row: i32, column: i32) -> Result<(i32, i32), String> {
756        if !is_valid_column_number(column) || !is_valid_row(row) {
757            return Err("Row or column is outside valid range.".to_string());
758        }
759
760        let cell = match self.cell(row, column) {
761            Some(c) => c,
762            None => return Err("Cell does not exist.".to_string()),
763        };
764        match &*cell {
765            Cell::ArrayFormula { r, .. } => Ok((r.0, r.1)),
766            Cell::CellFormula { .. } => Ok((1, 1)),
767            _ => Err("Cell does not contain a formula.".to_string()),
768        }
769    }
770
771    /// Returns true if cell is part of an array formula. This includes both anchor and spill cells.
772    pub(crate) fn get_cell_structure(
773        &self,
774        row: i32,
775        column: i32,
776    ) -> Result<CellStructure, String> {
777        if !is_valid_column_number(column) || !is_valid_row(row) {
778            return Err("Row or column is outside valid range.".to_string());
779        }
780
781        let cell = match self.cell(row, column) {
782            Some(c) => c,
783            None => return Ok(CellStructure::SingleCell),
784        };
785        match &*cell {
786            Cell::ArrayFormula {
787                r,
788                kind: ArrayKind::Cse,
789                ..
790            } => Ok(CellStructure::ArrayFormula { range: *r }),
791            Cell::ArrayFormula {
792                r,
793                kind: ArrayKind::Dynamic,
794                ..
795            } => Ok(CellStructure::DynamicFormula { range: *r }),
796            Cell::SpillCell { a, .. } => {
797                let anchor_cell = match self.cell(a.0, a.1) {
798                    Some(c) => c,
799                    None => return Err("Invalid spill reference".to_string()),
800                };
801                match &*anchor_cell {
802                    Cell::ArrayFormula {
803                        r,
804                        kind: ArrayKind::Cse,
805                        ..
806                    } => Ok(CellStructure::SpillArray {
807                        anchor: (a.0, a.1),
808                        range: *r,
809                    }),
810                    Cell::ArrayFormula {
811                        r,
812                        kind: ArrayKind::Dynamic,
813                        ..
814                    } => Ok(CellStructure::SpillDynamic {
815                        anchor: (a.0, a.1),
816                        range: *r,
817                    }),
818                    _ => Err("Spill cell does not reference an array formula".to_string()),
819                }
820            }
821            _ => Ok(CellStructure::SingleCell),
822        }
823    }
824
825    /// It provides convenient method for user navigation in the spreadsheet by jumping to edges.
826    /// Spreadsheet engines usually allow this method of navigation by using CTRL+arrows.
827    /// Behaviour summary:
828    /// - if starting cell is empty then find first non empty cell in given direction
829    /// - if starting cell is not empty, and neighbour in given direction is empty, then find
830    ///   first non empty cell in given direction
831    /// - if starting cell is not empty, and neighbour in given direction is also not empty, then
832    ///   find last non empty cell in given direction
833    pub fn navigate_to_edge_in_direction(
834        &self,
835        row: i32,
836        column: i32,
837        direction: NavigationDirection,
838    ) -> Result<(i32, i32), String> {
839        if !is_valid_column_number(column) || !is_valid_row(row) {
840            return Err("Row or column is outside valid range.".to_string());
841        }
842
843        // A merged cell behaves as a single cell: stepping out of it starts at
844        // its far edge in the direction of travel.
845        let start_cell = match self.merged_cell_containing(row, column) {
846            Some(m) => match direction {
847                NavigationDirection::Left => (row, m.column),
848                NavigationDirection::Right => (row, m.last_column()),
849                NavigationDirection::Up => (m.row, column),
850                NavigationDirection::Down => (m.last_row(), column),
851            },
852            None => (row, column),
853        };
854        let neighbour_cell = if let Some(cell) = step_in_direction(start_cell, direction) {
855            cell
856        } else {
857            return Ok((start_cell.0, start_cell.1));
858        };
859
860        // The content of a merged cell lives at the anchor: every cell of the
861        // range is as empty as the anchor is.
862        let is_empty_merge_aware = |row: i32, column: i32| -> Result<bool, String> {
863            let (anchor_row, anchor_column) = self.merge_anchor(row, column);
864            self.is_empty_cell(anchor_row, anchor_column)
865        };
866
867        if is_empty_merge_aware(start_cell.0, start_cell.1)? {
868            // Find first non-empty cell or move to the end.
869            let found_cells = walk_in_direction(start_cell, direction, |(row, column)| {
870                Ok(!is_empty_merge_aware(row, column)?)
871            })?;
872            Ok(match found_cells.found_cell {
873                Some(cell) => cell,
874                None => found_cells.previous_cell,
875            })
876        } else {
877            // Neighbour cell is empty     => find FIRST that is NOT empty
878            // Neighbour cell is not empty => find LAST  that is NOT empty in sequence
879            if is_empty_merge_aware(neighbour_cell.0, neighbour_cell.1)? {
880                let found_cells = walk_in_direction(start_cell, direction, |(row, column)| {
881                    Ok(!is_empty_merge_aware(row, column)?)
882                })?;
883                Ok(match found_cells.found_cell {
884                    Some(cell) => cell,
885                    None => found_cells.previous_cell,
886                })
887            } else {
888                let found_cells = walk_in_direction(start_cell, direction, |(row, column)| {
889                    is_empty_merge_aware(row, column)
890                })?;
891                Ok(found_cells.previous_cell)
892            }
893        }
894    }
895}
896
897struct WalkFoundCells {
898    /// If cell is found, it contains coordinates of the cell, otherwise None
899    found_cell: Option<(i32, i32)>,
900    /// Previous cell in chain relative to `found_cell`.
901    /// If `found_cell` is None then it's last considered cell.
902    previous_cell: (i32, i32),
903}
904
905/// Walks in direction until condition is met or boundary reached.
906/// Returns tuple `(current_cell, previous_cell)`. `current_cell` is either None or passes predicate
907fn walk_in_direction<F>(
908    start_cell: (i32, i32),
909    direction: NavigationDirection,
910    predicate: F,
911) -> Result<WalkFoundCells, String>
912where
913    F: Fn((i32, i32)) -> Result<bool, String>,
914{
915    let mut previous_cell = start_cell;
916    let mut current_cell = step_in_direction(start_cell, direction);
917    while let Some(cell) = current_cell {
918        if !predicate((cell.0, cell.1))? {
919            previous_cell = cell;
920            current_cell = step_in_direction(cell, direction);
921        } else {
922            break;
923        }
924    }
925    Ok(WalkFoundCells {
926        found_cell: current_cell,
927        previous_cell,
928    })
929}
930
931/// Returns coordinate of cell in given direction from given cell.
932/// Returns `None` if steps over the edge.
933fn step_in_direction(
934    (row, column): (i32, i32),
935    direction: NavigationDirection,
936) -> Option<(i32, i32)> {
937    if (row == 1 && direction == NavigationDirection::Up)
938        || (row == LAST_ROW && direction == NavigationDirection::Down)
939        || (column == 1 && direction == NavigationDirection::Left)
940        || (column == LAST_COLUMN && direction == NavigationDirection::Right)
941    {
942        return None;
943    }
944
945    Some(match direction {
946        NavigationDirection::Left => (row, column - 1),
947        NavigationDirection::Right => (row, column + 1),
948        NavigationDirection::Up => (row - 1, column),
949        NavigationDirection::Down => (row + 1, column),
950    })
951}