Skip to main content

ironcalc_base/user_model/
common.rs

1#![deny(missing_docs)]
2
3use std::{collections::HashMap, fmt::Debug};
4
5use serde::{Deserialize, Serialize};
6
7use crate::{
8    cf_types::ExtendedStyle,
9    constants::{LAST_COLUMN, LAST_ROW},
10    expressions::{
11        parser::CompletionContext,
12        types::Area,
13        utils::{is_valid_column_number, is_valid_row},
14    },
15    model::{FmtSettings, Model},
16    types::{
17        Alignment, ArrayKind, BorderItem, Cell, CellType, Col, Color, HorizontalAlignment,
18        SheetProperties, SheetState, Style, Theme, VerticalAlignment,
19    },
20};
21
22use crate::user_model::history::{
23    ColumnData, Diff, DiffList, DiffType, History, QueueDiffs, RowData,
24};
25
26use super::border_utils::is_max_border;
27
28#[derive(Serialize, Deserialize)]
29pub enum CellArrayStructure {
30    // It's just a single cell
31    SingleCell,
32    // It is part of a dynamic array
33    // (anchor_row, anchor_column, width, height)
34    DynamicChild(i32, i32, i32, i32),
35    // Anchor of a dynamic array (width, height)
36    DynamicAnchor(i32, i32),
37    // It is part of an array formula
38    ArrayChild(i32, i32, i32, i32),
39    // Anchor of an array formula
40    ArrayAnchor(i32, i32),
41}
42
43#[derive(Serialize, Deserialize, PartialEq)]
44pub enum BorderType {
45    All,
46    Inner,
47    Outer,
48    Top,
49    Right,
50    Bottom,
51    Left,
52    CenterH,
53    CenterV,
54    None,
55}
56
57/// This is the struct for a border area
58#[derive(Serialize, Deserialize)]
59pub struct BorderArea {
60    pub(crate) item: BorderItem,
61    pub(crate) r#type: BorderType,
62}
63
64fn boolean(value: &str) -> Result<bool, String> {
65    match value {
66        "true" => Ok(true),
67        "false" => Ok(false),
68        _ => Err(format!("Invalid value for boolean: '{value}'.")),
69    }
70}
71
72fn horizontal(value: &str) -> Result<HorizontalAlignment, String> {
73    match value {
74        "center" => Ok(HorizontalAlignment::Center),
75        "centerContinuous" => Ok(HorizontalAlignment::CenterContinuous),
76        "distributed" => Ok(HorizontalAlignment::Distributed),
77        "fill" => Ok(HorizontalAlignment::Fill),
78        "general" => Ok(HorizontalAlignment::General),
79        "justify" => Ok(HorizontalAlignment::Justify),
80        "left" => Ok(HorizontalAlignment::Left),
81        "right" => Ok(HorizontalAlignment::Right),
82        _ => Err(format!(
83            "Invalid value for horizontal alignment: '{value}'."
84        )),
85    }
86}
87
88fn vertical(value: &str) -> Result<VerticalAlignment, String> {
89    match value {
90        "bottom" => Ok(VerticalAlignment::Bottom),
91        "center" => Ok(VerticalAlignment::Center),
92        "distributed" => Ok(VerticalAlignment::Distributed),
93        "justify" => Ok(VerticalAlignment::Justify),
94        "top" => Ok(VerticalAlignment::Top),
95        _ => Err(format!("Invalid value for vertical alignment: '{value}'.")),
96    }
97}
98
99fn update_style(old_value: &Style, style_path: &str, value: &str) -> Result<Style, String> {
100    let mut style = old_value.clone();
101    match style_path {
102        "font.b" => {
103            style.font.b = boolean(value)?;
104        }
105        "font.i" => {
106            style.font.i = boolean(value)?;
107        }
108        "font.u" => {
109            style.font.u = boolean(value)?;
110        }
111        "font.strike" => {
112            style.font.strike = boolean(value)?;
113        }
114        "font.color" => {
115            style.font.color = Color::from_param(value)?;
116        }
117        "font.size" => {
118            let new_size: i32 = value
119                .parse()
120                .map_err(|_| format!("Invalid value for font size: '{value}'."))?;
121            if new_size < 1 {
122                return Err(format!("Invalid value for font size: '{new_size}'."));
123            }
124            style.font.sz = new_size;
125        }
126        "font.size_delta" => {
127            // This is a special case, we need to add the value to the current size
128            let size_delta: i32 = value
129                .parse()
130                .map_err(|_| format!("Invalid value for font size: '{value}'."))?;
131            let new_size = style.font.sz + size_delta;
132            if new_size < 1 {
133                return Err(format!("Invalid value for font size: '{new_size}'."));
134            }
135            style.font.sz = new_size;
136        }
137        "fill.color" | "fill.bg_color" | "fill.fg_color" => {
138            style.fill.color = Color::from_param(value)?;
139        }
140        "num_fmt" => {
141            value.clone_into(&mut style.num_fmt);
142        }
143        "alignment" => {
144            if !value.is_empty() {
145                return Err(format!("Alignment must be empty, but found: '{value}'."));
146            }
147            style.alignment = None;
148        }
149        "alignment.horizontal" => match style.alignment {
150            Some(ref mut s) => s.horizontal = horizontal(value)?,
151            None => {
152                let alignment = Alignment {
153                    horizontal: horizontal(value)?,
154                    ..Default::default()
155                };
156                style.alignment = Some(alignment)
157            }
158        },
159        "alignment.vertical" => match style.alignment {
160            Some(ref mut s) => s.vertical = vertical(value)?,
161            None => {
162                let alignment = Alignment {
163                    vertical: vertical(value)?,
164                    ..Default::default()
165                };
166                style.alignment = Some(alignment)
167            }
168        },
169        "alignment.wrap_text" => match style.alignment {
170            Some(ref mut s) => s.wrap_text = boolean(value)?,
171            None => {
172                let alignment = Alignment {
173                    wrap_text: boolean(value)?,
174                    ..Default::default()
175                };
176                style.alignment = Some(alignment)
177            }
178        },
179        _ => {
180            return Err(format!("Invalid style path: '{style_path}'."));
181        }
182    }
183    Ok(style)
184}
185
186/// # A wrapper around [`Model`] for a spreadsheet end user.
187/// UserModel is a wrapper around Model with undo/redo history, _diffs_, automatic evaluation and view management.
188///
189/// A diff in this context (or more correctly a _user diff_) is a change created by a user.
190///
191/// Automatic evaluation means that actions like setting a value on a cell or deleting a column
192/// will evaluate the model if needed.
193///
194/// It is meant to be used by UI applications like Web IronCalc or TironCalc.
195///
196///
197/// # Examples
198///
199/// ```rust
200/// # use ironcalc_base::UserModel;
201/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
202/// let mut model = UserModel::new_empty("model", "en", "UTC", "en")?;
203/// model.set_user_input(0, 1, 1, "=1+1")?;
204/// assert_eq!(model.get_formatted_cell_value(0, 1, 1)?, "2");
205/// model.undo()?;
206/// assert_eq!(model.get_formatted_cell_value(0, 1, 1)?, "");
207/// model.redo()?;
208/// assert_eq!(model.get_formatted_cell_value(0, 1, 1)?, "2");
209/// # Ok(())
210/// # }
211/// ```
212pub struct UserModel<'a> {
213    pub(crate) model: Model<'a>,
214    history: History,
215    send_queue: Vec<QueueDiffs>,
216    pause_evaluation: bool,
217}
218
219/// Given the index of the currently selected sheet, returns the index that same
220/// sheet occupies after the worksheet at `from` is moved to `to`. This lets the
221/// selection follow a sheet by identity across a reorder instead of pointing at
222/// whichever sheet lands in the old slot.
223pub(crate) fn selected_sheet_after_move(selected: u32, from: u32, to: u32) -> u32 {
224    if selected == from {
225        return to;
226    }
227    // Mirror `Model::move_sheet`: remove at `from`, then insert at `to`.
228    let after_remove = if selected > from {
229        selected - 1
230    } else {
231        selected
232    };
233    if after_remove >= to {
234        after_remove + 1
235    } else {
236        after_remove
237    }
238}
239
240impl<'a> Debug for UserModel<'a> {
241    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242        f.debug_struct("UserModel").finish()
243    }
244}
245
246impl<'a> UserModel<'a> {
247    /// Creates a user model from an existing model
248    pub fn from_model(model: Model) -> UserModel {
249        UserModel {
250            model,
251            history: History::default(),
252            send_queue: vec![],
253            pause_evaluation: false,
254        }
255    }
256
257    /// Creates a new UserModel.
258    ///
259    /// See also:
260    /// * [Model::new_empty]
261    pub fn new_empty(
262        name: &'a str,
263        locale_id: &'a str,
264        timezone: &'a str,
265        language_id: &'a str,
266    ) -> Result<UserModel<'a>, String> {
267        let model = Model::new_empty(name, locale_id, timezone, language_id)?;
268        Ok(UserModel {
269            model,
270            history: History::default(),
271            send_queue: vec![],
272            pause_evaluation: false,
273        })
274    }
275
276    /// Creates a model from it's internal representation
277    ///
278    /// See also:
279    /// * [Model::from_bytes]
280    pub fn from_bytes(s: &[u8], language_id: &'a str) -> Result<UserModel<'a>, String> {
281        let model = Model::from_bytes(s, language_id)?;
282        Ok(UserModel {
283            model,
284            history: History::default(),
285            send_queue: vec![],
286            pause_evaluation: false,
287        })
288    }
289
290    /// Returns the internal representation of a model
291    ///
292    /// See also:
293    ///  * [Model::to_bytes]
294    pub fn to_bytes(&self) -> Vec<u8> {
295        self.model.to_bytes()
296    }
297
298    /// Returns the internal model
299    pub fn get_model(&self) -> &Model<'_> {
300        &self.model
301    }
302
303    /// Returns the workbook name
304    pub fn get_name(&self) -> String {
305        self.model.workbook.name.clone()
306    }
307
308    /// Sets the name of a workbook
309    pub fn set_name(&mut self, name: &str) {
310        let old_value = self.model.workbook.name.clone();
311        if old_value == name {
312            return;
313        }
314        self.push_diff_list(vec![Diff::SetWorkbookName {
315            old_value,
316            new_value: name.to_string(),
317        }]);
318        self.model.workbook.name = name.to_string();
319    }
320
321    /// Undoes last change if any, places the change in the redo list and evaluates the model if needed
322    ///
323    /// See also:
324    /// * [UserModel::redo]
325    pub fn undo(&mut self) -> Result<(), String> {
326        if let Some(diff_list) = self.history.undo() {
327            self.apply_undo_diff_list(&diff_list)?;
328            self.send_queue.push(QueueDiffs {
329                r#type: DiffType::Undo,
330                list: diff_list.clone(),
331            });
332        };
333        Ok(())
334    }
335
336    /// Redoes the last undone change, places the change in the undo list and evaluates the model if needed
337    ///
338    /// See also:
339    /// * [UserModel::redo]
340    pub fn redo(&mut self) -> Result<(), String> {
341        if let Some(diff_list) = self.history.redo() {
342            self.apply_diff_list(&diff_list)?;
343            self.send_queue.push(QueueDiffs {
344                r#type: DiffType::Redo,
345                list: diff_list.clone(),
346            });
347        };
348        Ok(())
349    }
350
351    /// Returns true if there are items to be undone
352    pub fn can_undo(&self) -> bool {
353        !self.history.undo_stack.is_empty()
354    }
355
356    /// Returns true if there are items to be redone
357    pub fn can_redo(&self) -> bool {
358        !self.history.redo_stack.is_empty()
359    }
360
361    /// Pauses automatic evaluation.
362    ///
363    /// See also:
364    /// * [UserModel::evaluate]
365    /// * [UserModel::resume_evaluation]
366    pub fn pause_evaluation(&mut self) {
367        self.pause_evaluation = true;
368    }
369
370    /// Resumes automatic evaluation.
371    ///
372    /// See also:
373    /// * [UserModel::evaluate]
374    /// * [UserModel::pause_evaluation]
375    pub fn resume_evaluation(&mut self) {
376        self.pause_evaluation = false;
377    }
378
379    /// Forces an evaluation of the model
380    ///
381    /// See also:
382    /// * [Model::evaluate]
383    /// * [UserModel::pause_evaluation]
384    pub fn evaluate(&mut self) {
385        self.model.evaluate()
386    }
387
388    /// Returns the list of pending diffs and removes them from the queue
389    ///
390    /// This is used together with [apply_external_diffs](UserModel::apply_external_diffs) to keep two remote models
391    /// in sync.
392    ///
393    /// See also:
394    /// * [UserModel::apply_external_diffs]
395    pub fn flush_send_queue(&mut self) -> Vec<u8> {
396        // This can never fail :O:
397        let q = bitcode::encode(&self.send_queue);
398        self.send_queue = vec![];
399        q
400    }
401
402    /// This are external diffs that need to be applied to the model
403    ///
404    /// This is used together with [flush_send_queue](UserModel::flush_send_queue) to keep two remote models in sync
405    ///
406    /// See also:
407    /// * [UserModel::flush_send_queue]
408    pub fn apply_external_diffs(&mut self, diff_list_str: &[u8]) -> Result<(), String> {
409        if let Ok(queue_diffs_list) = bitcode::decode::<Vec<QueueDiffs>>(diff_list_str) {
410            for queue_diff in queue_diffs_list {
411                if matches!(queue_diff.r#type, DiffType::Redo) {
412                    self.apply_diff_list(&queue_diff.list)?;
413                } else {
414                    self.apply_undo_diff_list(&queue_diff.list)?;
415                }
416            }
417        } else {
418            return Err("Error parsing diff list".to_string());
419        }
420        Ok(())
421    }
422
423    /// Set the input in a cell
424    ///
425    /// See also:
426    /// * [Model::set_user_input]
427    pub fn set_user_input(
428        &mut self,
429        sheet: u32,
430        row: i32,
431        column: i32,
432        value: &str,
433    ) -> Result<(), String> {
434        if !is_valid_column_number(column) {
435            return Err("Invalid column".to_string());
436        }
437        if !is_valid_row(row) {
438            return Err("Invalid row".to_string());
439        }
440        let old_value = self
441            .model
442            .workbook
443            .worksheet(sheet)?
444            .cell(row, column)
445            .cloned();
446        // If it is a spill cell we want to save the old value as None, because the value of a spill cell is determined by the anchor cell
447        let old_value = if matches!(old_value, Some(Cell::SpillCell { .. })) {
448            None
449        } else {
450            old_value
451        };
452        self.model
453            .set_user_input(sheet, row, column, value.to_string())?;
454
455        self.evaluate_if_not_paused();
456
457        let mut diff_list = vec![Diff::SetCellValue {
458            sheet,
459            row,
460            column,
461            new_value: value.to_string(),
462            old_value: Box::new(old_value),
463        }];
464        let style = self.model.get_style_for_cell(sheet, row, column)?;
465
466        let line_count = value.split('\n').count() as f64;
467        let row_height = self.model.get_row_height(sheet, row)?;
468        // This is in sync with the front-end auto fit row
469        let font_size = style.font.sz as f64;
470        let line_height = font_size * 1.5;
471        let cell_height = (line_count - 1.0) * line_height + 8.0 + font_size;
472        if cell_height > row_height {
473            diff_list.push(Diff::SetRowHeight {
474                sheet,
475                row,
476                new_value: cell_height,
477                old_value: row_height,
478            });
479            self.model.set_row_height(sheet, row, cell_height)?;
480        }
481
482        self.push_diff_list(diff_list);
483        Ok(())
484    }
485
486    /// Returns the content of a cell
487    ///
488    /// See also:
489    /// * [Model::get_localized_cell_content]
490    #[inline]
491    pub fn get_cell_content(&self, sheet: u32, row: i32, column: i32) -> Result<String, String> {
492        self.model.get_localized_cell_content(sheet, row, column)
493    }
494
495    /// Returns completion information for a formula being edited in a cell.
496    ///
497    /// `formula` is the raw cell input (it may start with `=`) and `cursor` is a
498    /// char offset into it.
499    ///
500    /// See also:
501    /// * [Model::formula_completion]
502    #[inline]
503    pub fn formula_completion(
504        &mut self,
505        sheet: u32,
506        row: i32,
507        column: i32,
508        formula: &str,
509        cursor: usize,
510    ) -> Result<CompletionContext, String> {
511        self.model
512            .formula_completion(sheet, row, column, formula, cursor)
513    }
514
515    /// Cycles the references touched by the cursor through the four
516    /// absolute/relative states, Excel F4 style: A1 -> $A$1 -> A$1 -> $A1 -> A1.
517    /// Returns the new text together with the new cursor start and end.
518    ///
519    /// See also:
520    /// * [Model::cycle_reference]
521    #[inline]
522    pub fn cycle_reference(
523        &self,
524        value: &str,
525        start: usize,
526        end: usize,
527    ) -> Result<(String, i32, i32), String> {
528        self.model.cycle_reference(value, start, end)
529    }
530
531    /// Returns the formatted value of a cell
532    ///
533    /// See also:
534    /// * [Model::get_formatted_cell_value]
535    #[inline]
536    pub fn get_formatted_cell_value(
537        &self,
538        sheet: u32,
539        row: i32,
540        column: i32,
541    ) -> Result<String, String> {
542        self.model.get_formatted_cell_value(sheet, row, column)
543    }
544
545    /// Returns the type of the cell
546    ///
547    /// See also
548    /// * [Model::get_cell_type]
549    pub fn get_cell_type(&self, sheet: u32, row: i32, column: i32) -> Result<CellType, String> {
550        self.model.get_cell_type(sheet, row, column)
551    }
552
553    /// Adds new sheet
554    ///
555    /// See also:
556    /// * [Model::new_sheet]
557    pub fn new_sheet(&mut self) -> Result<(), String> {
558        let (name, index) = self.model.new_sheet();
559        self.set_selected_sheet(index)?;
560        self.push_diff_list(vec![Diff::NewSheet { index, name }]);
561        Ok(())
562    }
563
564    /// Duplicates a sheet by index, placing the copy right after it and
565    /// selecting it.
566    ///
567    /// See also:
568    /// * [Model::duplicate_sheet]
569    pub fn duplicate_sheet(&mut self, sheet: u32) -> Result<(), String> {
570        let (_name, new_index) = self.model.duplicate_sheet(sheet)?;
571        self.set_selected_sheet(new_index)?;
572        self.push_diff_list(vec![Diff::DuplicateSheet {
573            source_index: sheet,
574            new_index,
575        }]);
576        Ok(())
577    }
578
579    /// Deletes sheet by index
580    ///
581    /// See also:
582    /// * [Model::delete_sheet]
583    pub fn delete_sheet(&mut self, sheet: u32) -> Result<(), String> {
584        let worksheet = self.model.workbook.worksheet(sheet)?;
585
586        self.push_diff_list(vec![Diff::DeleteSheet {
587            sheet,
588            old_data: Box::new(worksheet.clone()),
589        }]);
590
591        let sheet_count = self.model.workbook.worksheets.len() as u32;
592        // If we are deleting the last sheet we need to change the selected sheet
593        if sheet == sheet_count - 1 && sheet_count > 1 {
594            if let Some(view) = self.model.workbook.views.get_mut(&self.model.view_id) {
595                view.sheet = sheet_count - 2;
596            };
597        }
598
599        self.model.delete_sheet(sheet)?;
600        Ok(())
601    }
602
603    /// Renames a sheet by index
604    ///
605    /// See also:
606    /// * [Model::rename_sheet_by_index]
607    pub fn rename_sheet(&mut self, sheet: u32, new_name: &str) -> Result<(), String> {
608        let old_value = self.model.workbook.worksheet(sheet)?.name.clone();
609        if old_value == new_name {
610            return Ok(());
611        }
612        self.model.rename_sheet_by_index(sheet, new_name)?;
613        self.push_diff_list(vec![Diff::RenameSheet {
614            index: sheet,
615            old_value,
616            new_value: new_name.to_string(),
617        }]);
618        Ok(())
619    }
620
621    /// Moves the worksheet at `sheet_index` to `new_index` within the workbook,
622    /// shifting the other sheets to accommodate. The moved worksheet ends up at
623    /// exactly `new_index`.
624    ///
625    /// The reorder is undoable/redoable, and the new order is preserved when the
626    /// workbook is saved. Cross-sheet formula references stay valid across the
627    /// move (sheet order is a position, not an identity — references key off the
628    /// sheet name/id). The selection follows the same sheet across the move.
629    ///
630    /// Moving a sheet to its current position is a no-op (no history entry). Fails
631    /// if either index is out of range.
632    ///
633    /// See also:
634    /// * [Model::move_sheet]
635    pub fn move_sheet(&mut self, sheet_index: u32, new_index: u32) -> Result<(), String> {
636        let sheet_count = self.model.workbook.worksheets.len() as u32;
637        if sheet_index >= sheet_count {
638            return Err(format!("Invalid sheet index {sheet_index}"));
639        }
640        if new_index >= sheet_count {
641            return Err(format!("Invalid target index {new_index}"));
642        }
643        if sheet_index == new_index {
644            return Ok(());
645        }
646        let selected = self.get_selected_sheet();
647        self.model.move_sheet(sheet_index, new_index)?;
648        self.set_selected_sheet(selected_sheet_after_move(selected, sheet_index, new_index))?;
649        self.push_diff_list(vec![Diff::MoveSheet {
650            sheet_index,
651            new_index,
652        }]);
653        Ok(())
654    }
655
656    /// Hides sheet by index
657    ///
658    /// See also:
659    /// * [Model::set_sheet_state]
660    /// * [UserModel::unhide_sheet]
661    pub fn hide_sheet(&mut self, sheet: u32) -> Result<(), String> {
662        let sheet_count = self.model.workbook.worksheets.len() as u32;
663        for index in 1..sheet_count {
664            let sheet_index = (sheet + index) % sheet_count;
665            if self.model.workbook.worksheet(sheet_index)?.state == SheetState::Visible {
666                if let Some(view) = self.model.workbook.views.get_mut(&self.model.view_id) {
667                    view.sheet = sheet_index;
668                };
669                break;
670            }
671        }
672        let old_value = self.model.workbook.worksheet(sheet)?.state.clone();
673        self.push_diff_list(vec![Diff::SetSheetState {
674            index: sheet,
675            new_value: SheetState::Hidden,
676            old_value,
677        }]);
678        self.model.set_sheet_state(sheet, SheetState::Hidden)?;
679        Ok(())
680    }
681
682    /// Un hides sheet by index
683    ///
684    /// See also:
685    /// * [Model::set_sheet_state]
686    /// * [UserModel::hide_sheet]
687    pub fn unhide_sheet(&mut self, sheet: u32) -> Result<(), String> {
688        let old_value = self.model.workbook.worksheet(sheet)?.state.clone();
689        self.push_diff_list(vec![Diff::SetSheetState {
690            index: sheet,
691            new_value: SheetState::Visible,
692            old_value,
693        }]);
694        self.model.set_sheet_state(sheet, SheetState::Visible)?;
695        Ok(())
696    }
697
698    /// Sets sheet color
699    ///
700    /// Note: an empty string will remove the color
701    ///
702    /// See also
703    /// * [Model::set_sheet_color]
704    /// * [UserModel::get_worksheets_properties]
705    pub fn set_sheet_color(&mut self, sheet: u32, color: &Color) -> Result<(), String> {
706        let old_value = self.model.workbook.worksheet(sheet)?.color.clone();
707        self.model.set_sheet_color(sheet, color)?;
708        self.push_diff_list(vec![Diff::SetSheetColor {
709            index: sheet,
710            old_value,
711            new_value: color.clone(),
712        }]);
713        Ok(())
714    }
715
716    /// Removes cells contents and style
717    ///
718    /// See also:
719    /// * [Model::range_clear_all]
720    pub fn range_clear_all(&mut self, range: &Area) -> Result<(), String> {
721        let sheet = range.sheet;
722        // TODO: full rows/columns
723        let mut old_value = Vec::new();
724        let mut old_style = Vec::new();
725
726        for row in range.row..range.row + range.height {
727            let mut data_row = Vec::new();
728            let mut style_row = Vec::new();
729            for column in range.column..range.column + range.width {
730                let old_value = self
731                    .model
732                    .workbook
733                    .worksheet(sheet)?
734                    .cell(row, column)
735                    .cloned();
736                data_row.push(old_value);
737                let old_style = self.model.get_style_for_cell(sheet, row, column)?;
738                style_row.push(old_style);
739            }
740            old_value.push(data_row);
741            old_style.push(style_row);
742        }
743        self.model.range_clear_all(range)?;
744        let diff_list = vec![Diff::RangeClearAll {
745            sheet,
746            row: range.row,
747            column: range.column,
748            width: range.width,
749            height: range.height,
750            old_value,
751            old_style,
752        }];
753
754        self.push_diff_list(diff_list);
755        self.evaluate_if_not_paused();
756        Ok(())
757    }
758
759    /// Deletes the content in cells, but keeps the style
760    ///
761    /// See also:
762    /// * [Model::range_clear_contents]
763    pub fn range_clear_contents(&mut self, range: &Area) -> Result<(), String> {
764        let sheet = range.sheet;
765        // TODO: full rows/columns
766        let mut old_value = Vec::new();
767        for row in range.row..range.row + range.height {
768            let mut data_row = Vec::new();
769            for column in range.column..range.column + range.width {
770                let old_value = self
771                    .model
772                    .workbook
773                    .worksheet(sheet)?
774                    .cell(row, column)
775                    .cloned();
776                data_row.push(old_value);
777            }
778            old_value.push(data_row);
779        }
780        self.model.range_clear_contents(range)?;
781        let diff_list = vec![Diff::RangeClearContents {
782            sheet,
783            row: range.row,
784            column: range.column,
785            width: range.width,
786            height: range.height,
787            old_value,
788        }];
789        self.push_diff_list(diff_list);
790        self.evaluate_if_not_paused();
791        Ok(())
792    }
793
794    fn clear_column_formatting(
795        &mut self,
796        sheet: u32,
797        column: i32,
798        diff_list: &mut Vec<Diff>,
799    ) -> Result<(), String> {
800        let old_value = self.model.get_column_style(sheet, column)?;
801        self.model.delete_column_style(sheet, column)?;
802        diff_list.push(Diff::DeleteColumnStyle {
803            sheet,
804            column,
805            old_value: Box::new(old_value),
806        });
807
808        let data_rows: Vec<i32> = self
809            .model
810            .workbook
811            .worksheet(sheet)?
812            .sheet_data
813            .keys()
814            .copied()
815            .collect();
816        let styled_rows = &self.model.workbook.worksheet(sheet)?.rows.clone();
817
818        // Delete the formatting in all non empty cells
819        for row in data_rows {
820            if let Some(old_style) = self.model.get_cell_style_or_none(sheet, row, column)? {
821                // We can always assume that style with style_index 0 exists and it is the default
822                self.model
823                    .workbook
824                    .worksheet_mut(sheet)?
825                    .set_cell_style(row, column, 0)?;
826                diff_list.push(Diff::CellClearFormatting {
827                    sheet,
828                    row,
829                    column,
830                    old_style: Box::new(Some(old_style)),
831                });
832            } else {
833                let old_style = self.model.get_style_for_cell(sheet, row, column)?;
834                if old_style != Style::default() {
835                    self.model
836                        .workbook
837                        .worksheet_mut(sheet)?
838                        .set_cell_style(row, column, 0)?;
839                    diff_list.push(Diff::CellClearFormatting {
840                        sheet,
841                        row,
842                        column,
843                        old_style: Box::new(None),
844                    });
845                }
846            }
847        }
848        // Delete the formatting in all cells with a row style
849        for row in styled_rows {
850            if let Some(old_style) = self.model.get_cell_style_or_none(sheet, row.r, column)? {
851                // We can always assume that style with style_index 0 exists and it is the default
852                self.model
853                    .workbook
854                    .worksheet_mut(sheet)?
855                    .set_cell_style(row.r, column, 0)?;
856                diff_list.push(Diff::CellClearFormatting {
857                    sheet,
858                    row: row.r,
859                    column,
860                    old_style: Box::new(Some(old_style)),
861                });
862            } else {
863                let old_style = self.model.get_style_for_cell(sheet, row.r, column)?;
864                if old_style != Style::default() {
865                    self.model
866                        .workbook
867                        .worksheet_mut(sheet)?
868                        .set_cell_style(row.r, column, 0)?;
869                    diff_list.push(Diff::CellClearFormatting {
870                        sheet,
871                        row: row.r,
872                        column,
873                        old_style: Box::new(None),
874                    });
875                }
876            }
877        }
878        Ok(())
879    }
880
881    fn clear_row_formatting(
882        &mut self,
883        sheet: u32,
884        row: i32,
885        diff_list: &mut Vec<Diff>,
886    ) -> Result<(), String> {
887        let old_value = self.model.get_row_style(sheet, row)?;
888        self.model.delete_row_style(sheet, row)?;
889        diff_list.push(Diff::DeleteRowStyle {
890            sheet,
891            row,
892            old_value: Box::new(old_value),
893        });
894
895        // Delete the formatting in all non empty cells
896        let columns: Vec<i32> = self
897            .model
898            .workbook
899            .worksheet(sheet)?
900            .sheet_data
901            .get(&row)
902            .map(|row_data| row_data.keys().copied().collect())
903            .unwrap_or_default();
904        for column in columns {
905            if let Some(old_style) = self.model.get_cell_style_or_none(sheet, row, column)? {
906                // We can always assume that style with style_index 0 exists and it is the default
907                self.model
908                    .workbook
909                    .worksheet_mut(sheet)?
910                    .set_cell_style(row, column, 0)?;
911                diff_list.push(Diff::CellClearFormatting {
912                    sheet,
913                    row,
914                    column,
915                    old_style: Box::new(Some(old_style)),
916                });
917            } else {
918                let old_style = self.model.get_style_for_cell(sheet, row, column)?;
919                if old_style != Style::default() {
920                    self.model
921                        .workbook
922                        .worksheet_mut(sheet)?
923                        .set_cell_style(row, column, 0)?;
924                    diff_list.push(Diff::CellClearFormatting {
925                        sheet,
926                        row,
927                        column,
928                        old_style: Box::new(None),
929                    });
930                }
931            }
932        }
933        Ok(())
934    }
935
936    /// Removes cells styles and formatting, but keeps the content
937    ///
938    /// See also:
939    /// * [UserModel::range_clear_all]
940    /// * [UserModel::range_clear_contents]
941    pub fn range_clear_formatting(&mut self, range: &Area) -> Result<(), String> {
942        let sheet = range.sheet;
943        let mut diff_list = Vec::new();
944        if range.row == 1 && range.height == LAST_ROW {
945            for column in range.column..range.column + range.width {
946                self.clear_column_formatting(sheet, column, &mut diff_list)?;
947            }
948            self.push_diff_list(diff_list);
949            return Ok(());
950        }
951        if range.column == 1 && range.width == LAST_COLUMN {
952            for row in range.row..range.row + range.height {
953                self.clear_row_formatting(sheet, row, &mut diff_list)?;
954            }
955            self.push_diff_list(diff_list);
956            return Ok(());
957        }
958        for row in range.row..range.row + range.height {
959            for column in range.column..range.column + range.width {
960                if let Some(old_style) = self.model.get_cell_style_or_none(sheet, row, column)? {
961                    // We can always assume that style with style_index 0 exists and it is the default
962                    self.model
963                        .workbook
964                        .worksheet_mut(sheet)?
965                        .set_cell_style(row, column, 0)?;
966                    diff_list.push(Diff::CellClearFormatting {
967                        sheet,
968                        row,
969                        column,
970                        old_style: Box::new(Some(old_style)),
971                    });
972                } else {
973                    let old_style = self.model.get_style_for_cell(sheet, row, column)?;
974                    if old_style != Style::default() {
975                        self.model
976                            .workbook
977                            .worksheet_mut(sheet)?
978                            .set_cell_style(row, column, 0)?;
979                        diff_list.push(Diff::CellClearFormatting {
980                            sheet,
981                            row,
982                            column,
983                            old_style: Box::new(None),
984                        });
985                    }
986                }
987            }
988        }
989        self.push_diff_list(diff_list);
990        Ok(())
991    }
992
993    /// Inserts `row_count` blank rows starting at `row` (both 0-based).
994    ///
995    /// Parameters
996    /// * `sheet` – worksheet index.
997    /// * `row` – first row to insert.
998    /// * `row_count` – number of rows (> 0).
999    ///
1000    /// History: the method pushes `row_count` `Diff::InsertRow`
1001    /// items **all using the same `row` index**.  Replaying those diffs (undo / redo)
1002    /// is therefore immune to the row-shifts that happen after each individual
1003    /// insertion.
1004    ///
1005    /// See also [`Model::insert_rows`].
1006    pub fn insert_rows(&mut self, sheet: u32, row: i32, row_count: i32) -> Result<(), String> {
1007        self.model.insert_rows(sheet, row, row_count)?;
1008
1009        let diff_list = vec![Diff::InsertRows {
1010            sheet,
1011            row,
1012            count: row_count,
1013        }];
1014        self.push_diff_list(diff_list);
1015        self.evaluate_if_not_paused();
1016        Ok(())
1017    }
1018
1019    /// Inserts `column_count` blank columns starting at `column` (0-based).
1020    ///
1021    /// Parameters
1022    /// * `sheet` – worksheet index.
1023    /// * `column` – first column to insert.
1024    /// * `column_count` – number of columns (> 0).
1025    ///
1026    /// History: pushes one `Diff::InsertColumn`
1027    /// per inserted column, all with the same `column` value, preventing index
1028    /// drift when the diffs are reapplied.
1029    ///
1030    /// See also [`Model::insert_columns`].
1031    pub fn insert_columns(
1032        &mut self,
1033        sheet: u32,
1034        column: i32,
1035        column_count: i32,
1036    ) -> Result<(), String> {
1037        self.model.insert_columns(sheet, column, column_count)?;
1038
1039        let diff_list = vec![Diff::InsertColumns {
1040            sheet,
1041            column,
1042            count: column_count,
1043        }];
1044        self.push_diff_list(diff_list);
1045        self.evaluate_if_not_paused();
1046        Ok(())
1047    }
1048
1049    /// Deletes `row_count` rows starting at `row`.
1050    ///
1051    /// History: a `Diff::DeleteRow` is created for
1052    /// each row, ordered **bottom → top**.  Undo therefore recreates rows from
1053    /// top → bottom and redo removes them bottom → top, avoiding index drift.
1054    ///
1055    /// See also [`Model::delete_rows`].
1056    pub fn delete_rows(&mut self, sheet: u32, row: i32, row_count: i32) -> Result<(), String> {
1057        let worksheet = self.model.workbook.worksheet(sheet)?;
1058        let mut old_data = Vec::new();
1059        // Collect data for all rows to be deleted
1060        for r in row..row + row_count {
1061            let mut row_data = None;
1062            for rd in &worksheet.rows {
1063                if rd.r == r {
1064                    row_data = Some(rd.clone());
1065                    break;
1066                }
1067            }
1068            // SpillCells are transient; save their style as EmptyCell so undo can
1069            // restore the style index, letting evaluate() recreate the SpillCell correctly.
1070            let data = match worksheet.sheet_data.get(&r) {
1071                Some(s) => s
1072                    .iter()
1073                    .map(|(k, v)| {
1074                        let cell = if let Cell::SpillCell { s, .. } = v {
1075                            Cell::EmptyCell { s: *s }
1076                        } else {
1077                            v.clone()
1078                        };
1079                        (*k, cell)
1080                    })
1081                    .collect(),
1082                None => HashMap::new(),
1083            };
1084            old_data.push(RowData {
1085                row: row_data,
1086                data,
1087            });
1088        }
1089
1090        self.model.delete_rows(sheet, row, row_count)?;
1091
1092        let diff_list = vec![Diff::DeleteRows {
1093            sheet,
1094            row,
1095            count: row_count,
1096            old_data,
1097        }];
1098        self.push_diff_list(diff_list);
1099        self.evaluate_if_not_paused();
1100        Ok(())
1101    }
1102
1103    /// Deletes `column_count` columns starting at `column`.
1104    ///
1105    /// History: pushes one `Diff::DeleteColumn`
1106    /// per column, **right → left**, so replaying the list is always safe with
1107    /// respect to index shifts.
1108    ///
1109    /// See also [`Model::delete_columns`].
1110    pub fn delete_columns(
1111        &mut self,
1112        sheet: u32,
1113        column: i32,
1114        column_count: i32,
1115    ) -> Result<(), String> {
1116        let worksheet = self.model.workbook.worksheet(sheet)?;
1117        let mut old_data = Vec::new();
1118        // Collect data for all columns to be deleted
1119        for c in column..column + column_count {
1120            let mut column_data = None;
1121            for col in &worksheet.cols {
1122                if c >= col.min && c <= col.max {
1123                    column_data = Some(Col {
1124                        min: c,
1125                        max: c,
1126                        width: col.width,
1127                        custom_width: col.custom_width,
1128                        style: col.style,
1129                        hidden: col.hidden,
1130                    });
1131                    break;
1132                }
1133            }
1134
1135            // SpillCells are transient; save their style as EmptyCell so undo can
1136            // restore the style index, letting evaluate() recreate the SpillCell correctly.
1137            let mut data = HashMap::new();
1138            for (row_idx, row_data) in &worksheet.sheet_data {
1139                if let Some(cell) = row_data.get(&c) {
1140                    let saved = if let Cell::SpillCell { s, .. } = cell {
1141                        Cell::EmptyCell { s: *s }
1142                    } else {
1143                        cell.clone()
1144                    };
1145                    data.insert(*row_idx, saved);
1146                }
1147            }
1148
1149            old_data.push(ColumnData {
1150                column: column_data,
1151                data,
1152            });
1153        }
1154
1155        self.model.delete_columns(sheet, column, column_count)?;
1156
1157        let diff_list = vec![Diff::DeleteColumns {
1158            sheet,
1159            column,
1160            count: column_count,
1161            old_data,
1162        }];
1163        self.push_diff_list(diff_list);
1164        self.evaluate_if_not_paused();
1165        Ok(())
1166    }
1167
1168    /// Moves a column horizontally and adjusts formulas
1169    pub fn move_columns_action(
1170        &mut self,
1171        sheet: u32,
1172        column: i32,
1173        column_count: i32,
1174        delta: i32,
1175    ) -> Result<(), String> {
1176        if delta == 0 || column_count <= 0 {
1177            return Ok(());
1178        }
1179        // Adjust delta to skip hidden columns in the landing zone
1180        let mut new_delta = delta;
1181        let worksheet = self.model.workbook.worksheet(sheet)?;
1182        if delta > 0 {
1183            for col in column + column_count..=column + column_count + delta {
1184                if worksheet.is_column_hidden(col)? {
1185                    new_delta += 1;
1186                }
1187            }
1188        } else {
1189            for col in column + delta..column {
1190                if worksheet.is_column_hidden(col)? {
1191                    new_delta -= 1;
1192                }
1193            }
1194        }
1195
1196        self.model
1197            .move_columns_action(sheet, column, column_count, new_delta)?;
1198
1199        self.push_diff_list(vec![Diff::MoveColumns {
1200            sheet,
1201            column,
1202            column_count,
1203            delta: new_delta,
1204        }]);
1205        self.evaluate_if_not_paused();
1206        Ok(())
1207    }
1208
1209    /// Moves a group of rows vertically and adjusts formulas
1210    pub fn move_rows_action(
1211        &mut self,
1212        sheet: u32,
1213        row: i32,
1214        row_count: i32,
1215        delta: i32,
1216    ) -> Result<(), String> {
1217        if delta == 0 || row_count <= 0 {
1218            return Ok(());
1219        }
1220        let mut new_delta = delta;
1221        let worksheet = self.model.workbook.worksheet(sheet)?;
1222        if delta > 0 {
1223            for r in row + row_count..=row + row_count + delta {
1224                if worksheet.is_row_hidden(r)? {
1225                    new_delta += 1;
1226                }
1227            }
1228        } else {
1229            for r in row + delta..row {
1230                if worksheet.is_row_hidden(r)? {
1231                    new_delta -= 1;
1232                }
1233            }
1234        }
1235
1236        self.model
1237            .move_rows_action(sheet, row, row_count, new_delta)?;
1238
1239        self.push_diff_list(vec![Diff::MoveRows {
1240            sheet,
1241            row,
1242            row_count,
1243            delta: new_delta,
1244        }]);
1245        self.evaluate_if_not_paused();
1246        Ok(())
1247    }
1248
1249    /// Sets the width of a group of columns in a single diff list
1250    ///
1251    /// See also:
1252    /// * [Model::set_column_width]
1253    pub fn set_columns_width(
1254        &mut self,
1255        sheet: u32,
1256        column_start: i32,
1257        column_end: i32,
1258        width: f64,
1259    ) -> Result<(), String> {
1260        let mut diff_list = Vec::new();
1261        for column in column_start..=column_end {
1262            let old_value = self.model.get_column_width(sheet, column)?;
1263            diff_list.push(Diff::SetColumnWidth {
1264                sheet,
1265                column,
1266                new_value: width,
1267                old_value,
1268            });
1269            self.model.set_column_width(sheet, column, width)?;
1270        }
1271        self.push_diff_list(diff_list);
1272        Ok(())
1273    }
1274
1275    /// Sets the hidden state of a range of columns in a single diff list
1276    ////
1277    /// See also:
1278    /// * [Model::set_column_hidden]
1279    pub fn set_columns_hidden(
1280        &mut self,
1281        sheet: u32,
1282        column_start: i32,
1283        column_end: i32,
1284        hidden: bool,
1285    ) -> Result<(), String> {
1286        let mut diff_list = Vec::new();
1287        for column in column_start..=column_end {
1288            let old_value = self
1289                .model
1290                .workbook
1291                .worksheet(sheet)?
1292                .is_column_hidden(column)?;
1293            diff_list.push(Diff::SetColumnHidden {
1294                sheet,
1295                column,
1296                new_value: hidden,
1297                old_value,
1298            });
1299            self.model.set_column_hidden(sheet, column, hidden)?;
1300        }
1301        // If we are hiding columns we might need to adjust the selected column
1302        if hidden {
1303            if let Some(view) = self.model.workbook.views.get_mut(&self.model.view_id) {
1304                if view.sheet == sheet {
1305                    // We select the next visible column
1306                    let mut column = column_end + 1;
1307                    while self
1308                        .model
1309                        .workbook
1310                        .worksheet(sheet)?
1311                        .is_column_hidden(column)?
1312                    {
1313                        column += 1;
1314                        if column > LAST_COLUMN {
1315                            break;
1316                        }
1317                    }
1318                    if column > LAST_COLUMN {
1319                        // We select the previous visible column
1320                        column = column_start - 1;
1321                        while self
1322                            .model
1323                            .workbook
1324                            .worksheet(sheet)?
1325                            .is_column_hidden(column)?
1326                        {
1327                            column -= 1;
1328                            if column <= 0 {
1329                                // We can't find a visible column
1330                                column = 1;
1331                                break;
1332                            }
1333                        }
1334                    }
1335                    self.set_selected_cell(1, column)?;
1336                    self.set_selected_range(1, column, LAST_ROW, column)?;
1337                }
1338            };
1339        }
1340        self.push_diff_list(diff_list);
1341        Ok(())
1342    }
1343
1344    /// Sets the hidden state of a range of rows in a single diff list
1345    ///// See also:
1346    /// * [Model::set_row_hidden]
1347    pub fn set_rows_hidden(
1348        &mut self,
1349        sheet: u32,
1350        row_start: i32,
1351        row_end: i32,
1352        hidden: bool,
1353    ) -> Result<(), String> {
1354        let mut diff_list = Vec::new();
1355        for row in row_start..=row_end {
1356            let old_value = self.model.workbook.worksheet(sheet)?.is_row_hidden(row)?;
1357            diff_list.push(Diff::SetRowHidden {
1358                sheet,
1359                row,
1360                new_value: hidden,
1361                old_value,
1362            });
1363            self.model.set_row_hidden(sheet, row, hidden)?;
1364        }
1365        // Select the next visible row if needed
1366        if hidden {
1367            if let Some(view) = self.model.workbook.views.get_mut(&self.model.view_id) {
1368                if view.sheet == sheet {
1369                    // We select the next visible row
1370                    let mut row = row_end + 1;
1371                    while self.model.workbook.worksheet(sheet)?.is_row_hidden(row)? {
1372                        row += 1;
1373                        if row > LAST_ROW {
1374                            break;
1375                        }
1376                    }
1377                    if row > LAST_ROW {
1378                        // We select the previous visible row
1379                        row = row_start - 1;
1380                        while self.model.workbook.worksheet(sheet)?.is_row_hidden(row)? {
1381                            row -= 1;
1382                            if row <= 0 {
1383                                // We can't find a visible row
1384                                row = 1;
1385                                break;
1386                            }
1387                        }
1388                    }
1389                    self.set_selected_cell(row, 1)?;
1390                    self.set_selected_range(row, 1, row, LAST_COLUMN)?;
1391                }
1392            };
1393        }
1394        self.push_diff_list(diff_list);
1395        Ok(())
1396    }
1397
1398    /// Sets the height of a range of rows in a single diff list
1399    ///
1400    /// See also:
1401    /// * [Model::set_row_height]
1402    pub fn set_rows_height(
1403        &mut self,
1404        sheet: u32,
1405        row_start: i32,
1406        row_end: i32,
1407        height: f64,
1408    ) -> Result<(), String> {
1409        let mut diff_list = Vec::new();
1410        for row in row_start..=row_end {
1411            let old_value = self.model.get_row_height(sheet, row)?;
1412            diff_list.push(Diff::SetRowHeight {
1413                sheet,
1414                row,
1415                new_value: height,
1416                old_value,
1417            });
1418            self.model.set_row_height(sheet, row, height)?;
1419        }
1420        self.push_diff_list(diff_list);
1421        Ok(())
1422    }
1423
1424    /// Gets the height of a row
1425    ///
1426    /// See also:
1427    /// * [Model::get_row_height]
1428    #[inline]
1429    pub fn get_row_height(&self, sheet: u32, row: i32) -> Result<f64, String> {
1430        self.model.get_row_height(sheet, row)
1431    }
1432
1433    /// Gets the width of a column
1434    ///
1435    /// See also:
1436    /// * [Model::get_column_width]
1437    #[inline]
1438    pub fn get_column_width(&self, sheet: u32, column: i32) -> Result<f64, String> {
1439        self.model.get_column_width(sheet, column)
1440    }
1441
1442    /// Returns the number of frozen rows in the sheet
1443    ///
1444    /// See also:
1445    /// * [Model::get_frozen_rows_count()]
1446    #[inline]
1447    pub fn get_frozen_rows_count(&self, sheet: u32) -> Result<i32, String> {
1448        self.model.get_frozen_rows_count(sheet)
1449    }
1450
1451    /// Returns the number of frozen columns in the sheet
1452    ///
1453    /// See also:
1454    /// * [Model::get_frozen_columns_count()]
1455    #[inline]
1456    pub fn get_frozen_columns_count(&self, sheet: u32) -> Result<i32, String> {
1457        self.model.get_frozen_columns_count(sheet)
1458    }
1459
1460    /// Sets the number of frozen rows in sheet
1461    ///
1462    /// See also:
1463    /// * [Model::set_frozen_rows()]
1464    pub fn set_frozen_rows_count(&mut self, sheet: u32, frozen_rows: i32) -> Result<(), String> {
1465        let old_value = self.model.get_frozen_rows_count(sheet)?;
1466        self.push_diff_list(vec![Diff::SetFrozenRowsCount {
1467            sheet,
1468            new_value: frozen_rows,
1469            old_value,
1470        }]);
1471        self.model.set_frozen_rows(sheet, frozen_rows)
1472    }
1473
1474    /// Sets the number of frozen columns in sheet
1475    ///
1476    /// See also:
1477    /// * [Model::set_frozen_columns()]
1478    pub fn set_frozen_columns_count(
1479        &mut self,
1480        sheet: u32,
1481        frozen_columns: i32,
1482    ) -> Result<(), String> {
1483        let old_value = self.model.get_frozen_columns_count(sheet)?;
1484        self.push_diff_list(vec![Diff::SetFrozenColumnsCount {
1485            sheet,
1486            new_value: frozen_columns,
1487            old_value,
1488        }]);
1489        self.model.set_frozen_columns(sheet, frozen_columns)
1490    }
1491
1492    /// Paste `styles` in the selected area
1493    pub fn on_paste_styles(&mut self, styles: &[Vec<Style>]) -> Result<(), String> {
1494        let styles_height = styles.len() as i32;
1495        let styles_width = styles[0].len() as i32;
1496        let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
1497            view.sheet
1498        } else {
1499            return Ok(());
1500        };
1501        let range = if let Ok(worksheet) = self.model.workbook.worksheet(sheet) {
1502            if let Some(view) = worksheet.views.get(&self.model.view_id) {
1503                view.range
1504            } else {
1505                return Ok(());
1506            }
1507        } else {
1508            return Ok(());
1509        };
1510
1511        // If the pasted area is smaller than the selected area we increase it
1512        let [row_start, column_start, row_end, column_end] = range;
1513        let last_row = row_end.max(row_start + styles_height - 1);
1514        let last_column = column_end.max(column_start + styles_width - 1);
1515
1516        let mut diff_list = Vec::new();
1517        for row in row_start..=last_row {
1518            for column in column_start..=last_column {
1519                let row_index = ((row - row_start) % styles_height) as usize;
1520                let column_index = ((column - column_start) % styles_width) as usize;
1521                let style = &styles[row_index][column_index];
1522                let old_value = self.model.get_cell_style_or_none(sheet, row, column)?;
1523                self.model.set_cell_style(sheet, row, column, style)?;
1524                diff_list.push(Diff::SetCellStyle {
1525                    sheet,
1526                    row,
1527                    column,
1528                    old_value: Box::new(old_value),
1529                    new_value: Box::new(style.clone()),
1530                });
1531            }
1532        }
1533        self.push_diff_list(diff_list);
1534
1535        // select the pasted range
1536        if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
1537            if let Some(view) = worksheet.views.get_mut(&self.model.view_id) {
1538                view.range = [row_start, column_start, last_row, last_column];
1539            }
1540        }
1541        Ok(())
1542    }
1543
1544    // Updates the style of a cell, adding the new style to the diff list
1545    fn update_single_cell_style(
1546        &mut self,
1547        sheet: u32,
1548        row: i32,
1549        column: i32,
1550        style_path: &str,
1551        value: &str,
1552        diff_list: &mut Vec<Diff>,
1553    ) -> Result<(), String> {
1554        // This is the value in the cell itself
1555        let old_value = self.model.get_cell_style_or_none(sheet, row, column)?;
1556
1557        // This takes into account row or column styles. We use the base style (no CF overlay)
1558        // because we are writing back a persistent style, not a transient CF result.
1559        let old_style = self.model.get_style_for_cell(sheet, row, column)?;
1560        let new_style = update_style(&old_style, style_path, value)?;
1561        self.model.set_cell_style(sheet, row, column, &new_style)?;
1562        diff_list.push(Diff::SetCellStyle {
1563            sheet,
1564            row,
1565            column,
1566            old_value: Box::new(old_value),
1567            new_value: Box::new(new_style),
1568        });
1569        Ok(())
1570    }
1571
1572    /// Updates the range with a cell style.
1573    /// See also:
1574    /// * [Model::set_cell_style]
1575    pub fn update_range_style(
1576        &mut self,
1577        range: &Area,
1578        style_path: &str,
1579        value: &str,
1580    ) -> Result<(), String> {
1581        let sheet = range.sheet;
1582        let mut diff_list = Vec::new();
1583        if range.row == 1 && range.height == LAST_ROW {
1584            // Full columns
1585            let styled_rows = &self.model.workbook.worksheet(sheet)?.rows.clone();
1586            // We need all the rows in the column to update the style
1587            // NB: This is too much, this is all the rows that have values
1588            let data_rows: Vec<i32> = self
1589                .model
1590                .workbook
1591                .worksheet(sheet)?
1592                .sheet_data
1593                .keys()
1594                .copied()
1595                .collect();
1596            for column in range.column..range.column + range.width {
1597                // we set the style of the full column
1598                let old_style = self.model.get_column_style(sheet, column)?;
1599                let style = match old_style.as_ref() {
1600                    Some(s) => s,
1601                    None => &Style::default(),
1602                };
1603                let style = update_style(style, style_path, value)?;
1604                self.model.set_column_style(sheet, column, &style)?;
1605                diff_list.push(Diff::SetColumnStyle {
1606                    sheet,
1607                    column,
1608                    old_value: Box::new(old_style),
1609                    new_value: Box::new(style),
1610                });
1611
1612                // We need to update the styles in all cells that have a row style
1613                for row_s in styled_rows.iter() {
1614                    let row = row_s.r;
1615                    self.update_single_cell_style(
1616                        sheet,
1617                        row,
1618                        column,
1619                        style_path,
1620                        value,
1621                        &mut diff_list,
1622                    )?;
1623                }
1624
1625                // Update style in all cells that have different styles
1626                // FIXME: We need a better way to transverse of cells in a column
1627                for &row in &data_rows {
1628                    if let Some(data_row) =
1629                        self.model.workbook.worksheet(sheet)?.sheet_data.get(&row)
1630                    {
1631                        if data_row.get(&column).is_some() {
1632                            // If the cell has non empty content it will always have some style
1633                            self.update_single_cell_style(
1634                                sheet,
1635                                row,
1636                                column,
1637                                style_path,
1638                                value,
1639                                &mut diff_list,
1640                            )?;
1641                        }
1642                    }
1643                }
1644            }
1645        } else if range.column == 1 && range.width == LAST_COLUMN {
1646            // Full rows
1647            let styled_columns = &self.model.workbook.worksheet(sheet)?.cols.clone();
1648            for row in range.row..range.row + range.height {
1649                // Now update style in all cells that are not empty
1650                let columns: Vec<i32> = self
1651                    .model
1652                    .workbook
1653                    .worksheet(sheet)?
1654                    .sheet_data
1655                    .get(&row)
1656                    .map(|row_data| row_data.keys().copied().collect())
1657                    .unwrap_or_default();
1658                for column in columns {
1659                    self.update_single_cell_style(
1660                        sheet,
1661                        row,
1662                        column,
1663                        style_path,
1664                        value,
1665                        &mut diff_list,
1666                    )?;
1667                }
1668
1669                // We need to go through all the cells that have a column style and merge the styles
1670                for col in styled_columns.iter() {
1671                    for column in col.min..col.max + 1 {
1672                        self.update_single_cell_style(
1673                            sheet,
1674                            row,
1675                            column,
1676                            style_path,
1677                            value,
1678                            &mut diff_list,
1679                        )?;
1680                    }
1681                }
1682
1683                // Finally update the style of the row
1684                let old_style = self.model.get_row_style(sheet, row)?;
1685                let style = match old_style.as_ref() {
1686                    Some(s) => s,
1687                    None => &Style::default(),
1688                };
1689                let style = update_style(style, style_path, value)?;
1690                self.model.set_row_style(sheet, row, &style)?;
1691                diff_list.push(Diff::SetRowStyle {
1692                    sheet,
1693                    row,
1694                    old_value: Box::new(old_style),
1695                    new_value: Box::new(style),
1696                });
1697            }
1698        } else {
1699            for row in range.row..range.row + range.height {
1700                for column in range.column..range.column + range.width {
1701                    self.update_single_cell_style(
1702                        sheet,
1703                        row,
1704                        column,
1705                        style_path,
1706                        value,
1707                        &mut diff_list,
1708                    )?;
1709                }
1710            }
1711        }
1712        self.push_diff_list(diff_list);
1713        Ok(())
1714    }
1715
1716    /// Returns the style for a cell
1717    ///
1718    /// Cells share a border, so the left border of B1 is the right border of A1
1719    /// In the object structure the borders of the cells might be difference,
1720    /// We always pick the "heaviest" border.
1721    ///
1722    /// See also:
1723    /// * [Model::get_style_for_cell]
1724    pub fn get_cell_style(&self, sheet: u32, row: i32, column: i32) -> Result<Style, String> {
1725        let mut style = self.model.get_style_for_cell(sheet, row, column)?;
1726
1727        // We need to check if the adjacent cells have a "heavier" border
1728        let border_top = if row > 1 {
1729            self.model
1730                .get_style_for_cell(sheet, row - 1, column)?
1731                .border
1732                .bottom
1733        } else {
1734            None
1735        };
1736
1737        let border_right = if column < LAST_COLUMN {
1738            self.model
1739                .get_style_for_cell(sheet, row, column + 1)?
1740                .border
1741                .left
1742        } else {
1743            None
1744        };
1745
1746        let border_bottom = if row < LAST_ROW {
1747            self.model
1748                .get_style_for_cell(sheet, row + 1, column)?
1749                .border
1750                .top
1751        } else {
1752            None
1753        };
1754
1755        let border_left = if column > 1 {
1756            self.model
1757                .get_style_for_cell(sheet, row, column - 1)?
1758                .border
1759                .right
1760        } else {
1761            None
1762        };
1763
1764        if is_max_border(style.border.top.as_ref(), border_top.as_ref()) {
1765            style.border.top = border_top;
1766        }
1767
1768        if is_max_border(style.border.right.as_ref(), border_right.as_ref()) {
1769            style.border.right = border_right;
1770        }
1771
1772        if is_max_border(style.border.bottom.as_ref(), border_bottom.as_ref()) {
1773            style.border.bottom = border_bottom;
1774        }
1775
1776        if is_max_border(style.border.left.as_ref(), border_left.as_ref()) {
1777            style.border.left = border_left;
1778        }
1779
1780        Ok(style)
1781    }
1782
1783    /// Returns the full extended style for a cell, including any conditional formatting overlay.
1784    ///
1785    /// Identical border-adjacency logic as [`Self::get_cell_style`] but applied to the CF-overlaid style.
1786    /// Use this when you need icon-set or data-bar decorations in addition to the base style.
1787    pub fn get_extended_cell_style(
1788        &self,
1789        sheet: u32,
1790        row: i32,
1791        column: i32,
1792    ) -> Result<ExtendedStyle, String> {
1793        let mut extended = self.model.get_extended_style_for_cell(sheet, row, column)?;
1794
1795        let border_top = if row > 1 {
1796            self.model
1797                .get_style_for_cell(sheet, row - 1, column)?
1798                .border
1799                .bottom
1800        } else {
1801            None
1802        };
1803
1804        let border_right = if column < LAST_COLUMN {
1805            self.model
1806                .get_style_for_cell(sheet, row, column + 1)?
1807                .border
1808                .left
1809        } else {
1810            None
1811        };
1812
1813        let border_bottom = if row < LAST_ROW {
1814            self.model
1815                .get_style_for_cell(sheet, row + 1, column)?
1816                .border
1817                .top
1818        } else {
1819            None
1820        };
1821
1822        let border_left = if column > 1 {
1823            self.model
1824                .get_style_for_cell(sheet, row, column - 1)?
1825                .border
1826                .right
1827        } else {
1828            None
1829        };
1830
1831        if is_max_border(extended.style.border.top.as_ref(), border_top.as_ref()) {
1832            extended.style.border.top = border_top;
1833        }
1834
1835        if is_max_border(extended.style.border.right.as_ref(), border_right.as_ref()) {
1836            extended.style.border.right = border_right;
1837        }
1838
1839        if is_max_border(
1840            extended.style.border.bottom.as_ref(),
1841            border_bottom.as_ref(),
1842        ) {
1843            extended.style.border.bottom = border_bottom;
1844        }
1845
1846        if is_max_border(extended.style.border.left.as_ref(), border_left.as_ref()) {
1847            extended.style.border.left = border_left;
1848        }
1849
1850        Ok(extended)
1851    }
1852
1853    /// Returns information about the sheets
1854    ///
1855    /// See also:
1856    /// * [Model::get_worksheets_properties]
1857    #[inline]
1858    pub fn get_worksheets_properties(&self) -> Vec<SheetProperties> {
1859        self.model.get_worksheets_properties()
1860    }
1861
1862    /// Sets the workbook theme.
1863    pub fn set_theme(&mut self, theme: Theme) {
1864        let old_value = self.model.workbook.theme.clone();
1865        let new_value = theme.clone();
1866        self.model.set_theme(theme);
1867        self.push_diff_list(vec![Diff::SetTheme {
1868            old_value: Box::new(old_value),
1869            new_value: Box::new(new_value),
1870        }]);
1871    }
1872
1873    /// Returns the current workbook theme.
1874    pub fn get_theme(&self) -> Theme {
1875        self.model.get_theme()
1876    }
1877
1878    /// Resolves a `Color` value to a CSS hex string using the current workbook theme.
1879    /// Returns an empty string for `Color::None`.
1880    pub fn resolve_color(&self, color: &Color) -> String {
1881        color.to_rgb(&self.model.workbook.theme)
1882    }
1883
1884    /// Set the gid lines in the worksheet to visible (`true`) or hidden (`false`)
1885    pub fn set_show_grid_lines(&mut self, sheet: u32, show_grid_lines: bool) -> Result<(), String> {
1886        let old_value = self.model.workbook.worksheet(sheet)?.show_grid_lines;
1887        self.model.set_show_grid_lines(sheet, show_grid_lines)?;
1888
1889        self.push_diff_list(vec![Diff::SetShowGridLines {
1890            sheet,
1891            new_value: show_grid_lines,
1892            old_value,
1893        }]);
1894        Ok(())
1895    }
1896
1897    /// Returns true in the grid lines for
1898    pub fn get_show_grid_lines(&self, sheet: u32) -> Result<bool, String> {
1899        Ok(self.model.workbook.worksheet(sheet)?.show_grid_lines)
1900    }
1901
1902    /// Returns the largest column in the row less than a column whose cell has a non empty value.
1903    /// If there are none it returns `None`.
1904    /// This is useful when rendering a part of a worksheet to know which cells spill over
1905    pub fn get_last_non_empty_in_row_before_column(
1906        &self,
1907        sheet: u32,
1908        row: i32,
1909        column: i32,
1910    ) -> Result<Option<i32>, String> {
1911        let worksheet = self.model.workbook.worksheet(sheet)?;
1912        let data = worksheet.sheet_data.get(&row);
1913        if let Some(row_data) = data {
1914            let mut last_column = None;
1915            let mut columns: Vec<i32> = row_data.keys().copied().collect();
1916            columns.sort_unstable();
1917            for col in columns {
1918                if col < column {
1919                    if let Some(cell) = worksheet.cell(row, col) {
1920                        if matches!(cell, Cell::EmptyCell { .. }) {
1921                            continue;
1922                        }
1923                    }
1924                    last_column = Some(col);
1925                }
1926            }
1927            Ok(last_column)
1928        } else {
1929            Ok(None)
1930        }
1931    }
1932
1933    /// Returns the smallest column in the row larger than "column" whose cell has a non empty value.
1934    /// If there are none it returns `None`.
1935    /// This is useful when rendering a part of a worksheet to know which cells spill over
1936    pub fn get_first_non_empty_in_row_after_column(
1937        &self,
1938        sheet: u32,
1939        row: i32,
1940        column: i32,
1941    ) -> Result<Option<i32>, String> {
1942        let worksheet = self.model.workbook.worksheet(sheet)?;
1943        let data = worksheet.sheet_data.get(&row);
1944        if let Some(row_data) = data {
1945            let mut columns: Vec<i32> = row_data.keys().copied().collect();
1946            // We sort the keys to ensure we are going from left to right
1947            columns.sort_unstable();
1948            for col in columns {
1949                if col > column {
1950                    if let Some(cell) = worksheet.cell(row, col) {
1951                        if matches!(cell, Cell::EmptyCell { .. }) {
1952                            continue;
1953                        }
1954                    }
1955                    return Ok(Some(col));
1956                }
1957            }
1958        }
1959        Ok(None)
1960    }
1961
1962    /// Returns the geometric structure of a cell
1963    pub fn get_cell_array_structure(
1964        &self,
1965        sheet: u32,
1966        row: i32,
1967        column: i32,
1968    ) -> Result<CellArrayStructure, String> {
1969        let cell = self
1970            .model
1971            .workbook
1972            .worksheet(sheet)?
1973            .cell(row, column)
1974            .cloned()
1975            .unwrap_or_default();
1976        match cell {
1977            Cell::EmptyCell { .. }
1978            | Cell::BooleanCell { .. }
1979            | Cell::NumberCell { .. }
1980            | Cell::ErrorCell { .. }
1981            | Cell::SharedString { .. }
1982            | Cell::CellFormula { .. } => Ok(CellArrayStructure::SingleCell),
1983            Cell::SpillCell { a, .. } => {
1984                let (m_row, m_column) = a;
1985                let m_cell = self
1986                    .model
1987                    .workbook
1988                    .worksheet(sheet)?
1989                    .cell(m_row, m_column)
1990                    .cloned()
1991                    .unwrap_or_default();
1992                let (width, height, is_dynamic) = match m_cell {
1993                    Cell::ArrayFormula {
1994                        r,
1995                        kind: ArrayKind::Dynamic,
1996                        ..
1997                    } => (r.0, r.1, true),
1998                    Cell::ArrayFormula {
1999                        r,
2000                        kind: ArrayKind::Cse,
2001                        ..
2002                    } => (r.0, r.1, false),
2003                    _ => return Err("Invalid structure".to_string()),
2004                };
2005                if is_dynamic {
2006                    Ok(CellArrayStructure::DynamicChild(
2007                        m_row, m_column, width, height,
2008                    ))
2009                } else {
2010                    Ok(CellArrayStructure::ArrayChild(
2011                        m_row, m_column, width, height,
2012                    ))
2013                }
2014            }
2015            Cell::ArrayFormula {
2016                r,
2017                kind: ArrayKind::Dynamic,
2018                ..
2019            } => Ok(CellArrayStructure::DynamicAnchor(r.0, r.1)),
2020            Cell::ArrayFormula {
2021                r,
2022                kind: ArrayKind::Cse,
2023                ..
2024            } => Ok(CellArrayStructure::ArrayAnchor(r.0, r.1)),
2025        }
2026    }
2027
2028    /// Sets an array formula in the given range.
2029    pub fn set_user_array_formula(
2030        &mut self,
2031        sheet: u32,
2032        row: i32,
2033        column: i32,
2034        width: i32,
2035        height: i32,
2036        formula: &str,
2037    ) -> Result<(), String> {
2038        let ws = self.model.workbook.worksheet(sheet)?;
2039        let mut old_values = Vec::new();
2040        for r in row..row + height {
2041            let mut row_vals = Vec::new();
2042            for c in column..column + width {
2043                let cell = ws.cell(r, c).cloned();
2044                // SpillCells are transient — restored by re-evaluation, so store as None.
2045                let cell = if matches!(cell, Some(Cell::SpillCell { .. })) {
2046                    None
2047                } else {
2048                    cell
2049                };
2050                row_vals.push(cell);
2051            }
2052            old_values.push(row_vals);
2053        }
2054        self.model
2055            .set_user_array_formula(sheet, row, column, width, height, formula)?;
2056        self.push_diff_list(vec![Diff::SetArrayValue {
2057            sheet,
2058            row,
2059            column,
2060            width,
2061            height,
2062            new_value: formula.to_string(),
2063            old_values,
2064        }]);
2065        self.evaluate_if_not_paused();
2066        Ok(())
2067    }
2068
2069    /// Returns the list of defined names
2070    pub fn get_defined_name_list(&self) -> Vec<(String, Option<u32>, String)> {
2071        self.model.get_defined_name_list()
2072    }
2073
2074    /// Delete an existing defined name
2075    pub fn delete_defined_name(&mut self, name: &str, scope: Option<u32>) -> Result<(), String> {
2076        let old_value = self.model.get_defined_name_formula(name, scope)?;
2077        let diff_list = vec![Diff::DeleteDefinedName {
2078            name: name.to_string(),
2079            scope,
2080            old_value,
2081        }];
2082        self.push_diff_list(diff_list);
2083        self.model.delete_defined_name(name, scope)?;
2084        self.evaluate_if_not_paused();
2085        Ok(())
2086    }
2087
2088    /// Create a new defined name
2089    pub fn new_defined_name(
2090        &mut self,
2091        name: &str,
2092        scope: Option<u32>,
2093        formula: &str,
2094    ) -> Result<(), String> {
2095        self.model.new_defined_name(name, scope, formula)?;
2096        // Diffs store the internal (English) formula so undo/redo replays
2097        // correctly regardless of the active language at replay time. A
2098        // just-created name is guaranteed to be retrievable, so propagate any
2099        // (unexpected) error rather than masking it with a localized formula.
2100        let value = self.model.get_defined_name_formula(name, scope)?;
2101        let diff_list = vec![Diff::CreateDefinedName {
2102            name: name.to_string(),
2103            scope,
2104            value,
2105        }];
2106        self.push_diff_list(diff_list);
2107        self.evaluate_if_not_paused();
2108        Ok(())
2109    }
2110
2111    /// Updates a defined name
2112    pub fn update_defined_name(
2113        &mut self,
2114        name: &str,
2115        scope: Option<u32>,
2116        new_name: &str,
2117        new_scope: Option<u32>,
2118        new_formula: &str,
2119    ) -> Result<(), String> {
2120        // Both formulas in the diff are stored internally (in English) so
2121        // undo/redo replays correctly regardless of the active language.
2122        let old_formula = self
2123            .model
2124            .get_defined_name_formula(name, scope)
2125            .map_err(|_| "General: Failed to get old name")?;
2126        self.model
2127            .update_defined_name(name, scope, new_name, new_scope, new_formula)?;
2128        // Read back the canonical (English) formula that was just stored so the
2129        // diff stays canonical; a successful update guarantees it is retrievable.
2130        let new_formula_internal = self.model.get_defined_name_formula(new_name, new_scope)?;
2131        let diff_list = vec![Diff::UpdateDefinedName {
2132            name: name.to_string(),
2133            scope,
2134            old_formula,
2135            new_name: new_name.to_string(),
2136            new_scope,
2137            new_formula: new_formula_internal,
2138        }];
2139        self.push_diff_list(diff_list);
2140        self.evaluate_if_not_paused();
2141        Ok(())
2142    }
2143
2144    /// validates a new defined name
2145    pub fn is_valid_defined_name(
2146        &mut self,
2147        name: &str,
2148        scope: Option<u32>,
2149        formula: &str,
2150    ) -> Result<Option<u32>, String> {
2151        self.model.is_valid_defined_name(name, scope, formula)
2152    }
2153
2154    /// Sets the timezone for the model
2155    pub fn set_timezone(&mut self, timezone: &str) -> Result<(), String> {
2156        let diff_list = vec![Diff::SetTimezone {
2157            old_value: self.get_timezone(),
2158            new_value: timezone.to_string(),
2159        }];
2160        self.push_diff_list(diff_list);
2161        self.model.set_timezone(timezone)
2162    }
2163
2164    /// Sets the locale for the model
2165    pub fn set_locale(&mut self, locale: &str) -> Result<(), String> {
2166        let diff_list = vec![Diff::SetLocale {
2167            old_value: self.get_locale(),
2168            new_value: locale.to_string(),
2169        }];
2170        self.push_diff_list(diff_list);
2171        self.model.set_locale(locale)
2172    }
2173
2174    /// Gets the timezone of the model
2175    pub fn get_timezone(&self) -> String {
2176        self.model.get_timezone()
2177    }
2178
2179    /// Gets the locale of the model
2180    pub fn get_locale(&self) -> String {
2181        self.model.get_locale()
2182    }
2183
2184    /// Get the language for the model
2185    pub fn get_language(&self) -> String {
2186        self.model.get_language()
2187    }
2188
2189    /// Sets the language for the model
2190    pub fn set_language(&mut self, language: &str) -> Result<(), String> {
2191        self.model.set_language(language)
2192    }
2193
2194    /// Gets the formatting settings for the model
2195    pub fn get_fmt_settings(&self) -> FmtSettings {
2196        self.model.get_fmt_settings()
2197    }
2198
2199    // **** Private methods ****** //
2200
2201    pub(crate) fn push_diff_list(&mut self, diff_list: DiffList) {
2202        self.send_queue.push(QueueDiffs {
2203            r#type: DiffType::Redo,
2204            list: diff_list.clone(),
2205        });
2206        self.history.push(diff_list);
2207    }
2208
2209    pub(super) fn evaluate_if_not_paused(&mut self) {
2210        if !self.pause_evaluation {
2211            self.model.evaluate();
2212        }
2213    }
2214}
2215
2216#[cfg(test)]
2217mod tests {
2218    use crate::{
2219        types::{HorizontalAlignment, VerticalAlignment},
2220        user_model::common::{horizontal, selected_sheet_after_move, vertical},
2221    };
2222
2223    #[test]
2224    fn test_selected_sheet_after_move() {
2225        // The moved sheet is followed to its destination.
2226        assert_eq!(selected_sheet_after_move(0, 0, 2), 2);
2227        assert_eq!(selected_sheet_after_move(3, 3, 0), 0);
2228
2229        // A sheet between the source and destination shifts by one.
2230        // [A,B,C,D], select C (2), move A (0) -> 2 => [B,C,A,D], C is at 1.
2231        assert_eq!(selected_sheet_after_move(2, 0, 2), 1);
2232        // [A,B,C,D], select A (0), move C (2) -> 0 => [C,A,B,D], A is at 1.
2233        assert_eq!(selected_sheet_after_move(0, 2, 0), 1);
2234
2235        // A sheet outside the moved span keeps its index.
2236        // [A,B,C,D], select D (3), move B (1) -> 2 => [A,C,B,D], D still at 3.
2237        assert_eq!(selected_sheet_after_move(3, 1, 2), 3);
2238    }
2239
2240    #[test]
2241    fn test_vertical() {
2242        let all = vec![
2243            VerticalAlignment::Bottom,
2244            VerticalAlignment::Center,
2245            VerticalAlignment::Distributed,
2246            VerticalAlignment::Justify,
2247            VerticalAlignment::Top,
2248        ];
2249        for a in all {
2250            assert_eq!(vertical(&format!("{a}")), Ok(a));
2251        }
2252    }
2253
2254    #[test]
2255    fn test_horizontal() {
2256        let all = vec![
2257            HorizontalAlignment::Center,
2258            HorizontalAlignment::CenterContinuous,
2259            HorizontalAlignment::Distributed,
2260            HorizontalAlignment::Fill,
2261            HorizontalAlignment::General,
2262            HorizontalAlignment::Justify,
2263            HorizontalAlignment::Left,
2264            HorizontalAlignment::Right,
2265        ];
2266        for a in all {
2267            assert_eq!(horizontal(&format!("{a}")), Ok(a));
2268        }
2269    }
2270}