Skip to main content

excel_cli/app/
sheet.rs

1use crate::actions::{
2    ActionCommand, ColumnAction, MultiColumnAction, MultiRowAction, RowAction, SheetAction,
3    SheetOperation,
4};
5use crate::app::AppState;
6use crate::utils::index_to_col_name;
7use anyhow::Result;
8
9impl AppState<'_> {
10    pub fn next_sheet(&mut self) -> Result<()> {
11        let sheet_count = self.workbook.get_sheet_names().len();
12        let current_index = self.workbook.get_current_sheet_index();
13
14        if current_index >= sheet_count - 1 {
15            self.add_notification("Already at the last sheet".to_string());
16            return Ok(());
17        }
18
19        self.switch_sheet_by_index(current_index + 1)
20    }
21
22    pub fn prev_sheet(&mut self) -> Result<()> {
23        let current_index = self.workbook.get_current_sheet_index();
24
25        if current_index == 0 {
26            self.add_notification("Already at the first sheet".to_string());
27            return Ok(());
28        }
29
30        self.switch_sheet_by_index(current_index - 1)
31    }
32
33    pub fn switch_sheet_by_index(&mut self, index: usize) -> Result<()> {
34        let current_sheet_name = self.workbook.get_current_sheet_name();
35
36        // Save current column widths if they've changed
37        if !self.sheet_column_widths.contains_key(&current_sheet_name)
38            || self.sheet_column_widths[&current_sheet_name] != self.column_widths
39        {
40            self.sheet_column_widths
41                .insert(current_sheet_name.clone(), self.column_widths.clone());
42        }
43
44        // Save current cell position and view position
45        let current_position = crate::app::CellPosition {
46            selected: self.selected_cell,
47            view: (self.start_row, self.start_col),
48        };
49        self.sheet_cell_positions
50            .insert(current_sheet_name, current_position);
51
52        self.workbook.switch_sheet(index)?;
53
54        let new_sheet_name = self.workbook.get_current_sheet_name();
55
56        // Restore column widths for the new sheet
57        if let Some(saved_widths) = self.sheet_column_widths.get(&new_sheet_name) {
58            if &self.column_widths != saved_widths {
59                self.column_widths = saved_widths.clone();
60            }
61        } else {
62            let max_cols = self.workbook.get_current_sheet().max_cols;
63            let default_width = 15;
64            self.column_widths = vec![default_width; max_cols + 1];
65
66            self.sheet_column_widths
67                .insert(new_sheet_name.clone(), self.column_widths.clone());
68        }
69
70        // Restore cell position and view position for the new sheet
71        if let Some(saved_position) = self.sheet_cell_positions.get(&new_sheet_name) {
72            // Ensure the saved position is valid for the current sheet
73            let sheet = self.workbook.get_current_sheet();
74            let valid_row = saved_position.selected.0.min(sheet.max_rows.max(1));
75            let valid_col = saved_position.selected.1.min(sheet.max_cols.max(1));
76
77            self.selected_cell = (valid_row, valid_col);
78            self.start_row = saved_position.view.0;
79            self.start_col = saved_position.view.1;
80
81            // Make sure the view position is valid relative to the selected cell
82            self.handle_scrolling();
83        } else {
84            // If no saved position exists, use default position
85            self.selected_cell = (1, 1);
86            self.start_row = 1;
87            self.start_col = 1;
88        }
89
90        // Clear search results as they're specific to the previous sheet
91        if !self.search_results.is_empty() {
92            self.search_results.clear();
93            self.current_search_idx = None;
94        }
95
96        self.update_row_number_width();
97
98        // Check if the new sheet is loaded when using lazy loading
99        let is_lazy_loading = self.workbook.is_lazy_loading();
100        let is_sheet_loaded = self.workbook.is_sheet_loaded(index);
101
102        if is_lazy_loading && !is_sheet_loaded {
103            // If the sheet is not loaded, switch to LazyLoading mode
104            self.input_mode = crate::app::InputMode::LazyLoading;
105            self.add_notification(format!(
106                "Switched to sheet: {new_sheet_name} (press Enter to load)"
107            ));
108        } else {
109            self.add_notification(format!("Switched to sheet: {new_sheet_name}"));
110        }
111
112        Ok(())
113    }
114
115    pub fn switch_to_sheet(&mut self, name_or_index: &str) {
116        // Get all sheet names
117        let sheet_names = self.workbook.get_sheet_names();
118
119        // Try to parse as index first
120        if let Ok(index) = name_or_index.parse::<usize>() {
121            // Convert to 0-based index
122            let zero_based_index = index.saturating_sub(1);
123
124            if zero_based_index < sheet_names.len() {
125                match self.switch_sheet_by_index(zero_based_index) {
126                    Ok(()) => return,
127                    Err(e) => {
128                        self.add_notification(format!("Failed to switch to sheet {index}: {e}"));
129                        return;
130                    }
131                }
132            }
133        }
134
135        // Try to find by name
136        for (i, name) in sheet_names.iter().enumerate() {
137            if name.eq_ignore_ascii_case(name_or_index) {
138                match self.switch_sheet_by_index(i) {
139                    Ok(()) => return,
140                    Err(e) => {
141                        self.add_notification(format!(
142                            "Failed to switch to sheet '{name_or_index}': {e}"
143                        ));
144                        return;
145                    }
146                }
147            }
148        }
149
150        // If we get here, no matching sheet was found
151        self.add_notification(format!("Sheet '{name_or_index}' not found"));
152    }
153
154    pub fn create_sheet(&mut self, name: &str) {
155        let insert_index = self.workbook.get_current_sheet_index() + 1;
156
157        match self.workbook.add_sheet(name, insert_index) {
158            Ok(sheet_name) => {
159                let default_width = 15;
160                let max_cols = self
161                    .workbook
162                    .get_sheet_by_index(insert_index)
163                    .map(|sheet| sheet.max_cols)
164                    .unwrap_or(1);
165
166                self.sheet_column_widths
167                    .insert(sheet_name.clone(), vec![default_width; max_cols + 1]);
168                self.sheet_cell_positions.insert(
169                    sheet_name.clone(),
170                    crate::app::CellPosition {
171                        selected: (1, 1),
172                        view: (1, 1),
173                    },
174                );
175
176                if let Err(e) = self.switch_sheet_by_index(insert_index) {
177                    self.sheet_column_widths.remove(&sheet_name);
178                    self.sheet_cell_positions.remove(&sheet_name);
179                    let _ = self.workbook.delete_sheet_at_index(insert_index);
180                    self.add_notification(format!("Failed to switch to new sheet: {e}"));
181                    return;
182                }
183
184                self.notification_messages.pop();
185
186                let sheet_action = SheetAction {
187                    sheet_index: insert_index,
188                    sheet_name: sheet_name.clone(),
189                    sheet_data: self.workbook.get_current_sheet().clone(),
190                    column_widths: self.column_widths.clone(),
191                    operation: SheetOperation::Create,
192                };
193
194                self.undo_history.push(ActionCommand::Sheet(sheet_action));
195                self.input_mode = crate::app::InputMode::Normal;
196                self.add_notification(format!("Created sheet: {sheet_name}"));
197            }
198            Err(e) => {
199                self.add_notification(format!("Failed to add sheet: {e}"));
200            }
201        }
202    }
203
204    pub fn delete_current_sheet(&mut self) {
205        let current_sheet_name = self.workbook.get_current_sheet_name();
206        let sheet_index = self.workbook.get_current_sheet_index();
207
208        // Save the sheet data for undo
209        let sheet_data = self.workbook.get_current_sheet().clone();
210        let column_widths = self.column_widths.clone();
211
212        match self.workbook.delete_current_sheet() {
213            Ok(()) => {
214                // Create the undo action
215                let sheet_action = SheetAction {
216                    sheet_index,
217                    sheet_name: current_sheet_name.clone(),
218                    sheet_data,
219                    column_widths,
220                    operation: SheetOperation::Delete,
221                };
222
223                self.undo_history.push(ActionCommand::Sheet(sheet_action));
224                self.sheet_column_widths.remove(&current_sheet_name);
225                self.sheet_cell_positions.remove(&current_sheet_name);
226
227                let new_sheet_name = self.workbook.get_current_sheet_name();
228                let new_sheet_index = self.workbook.get_current_sheet_index();
229                let is_new_sheet_loaded = self.workbook.is_sheet_loaded(new_sheet_index);
230
231                // Restore saved cell position for the new current sheet or use default
232                if let Some(saved_position) = self.sheet_cell_positions.get(&new_sheet_name) {
233                    // Ensure the saved position is valid for the current sheet
234                    let sheet = self.workbook.get_current_sheet();
235                    let valid_row = saved_position.selected.0.min(sheet.max_rows.max(1));
236                    let valid_col = saved_position.selected.1.min(sheet.max_cols.max(1));
237
238                    self.selected_cell = (valid_row, valid_col);
239                    self.start_row = saved_position.view.0;
240                    self.start_col = saved_position.view.1;
241
242                    // Make sure the view position is valid relative to the selected cell
243                    self.handle_scrolling();
244                } else {
245                    // If no saved position exists, use default position
246                    self.selected_cell = (1, 1);
247                    self.start_row = 1;
248                    self.start_col = 1;
249                }
250
251                if let Some(saved_widths) = self.sheet_column_widths.get(&new_sheet_name) {
252                    self.column_widths = saved_widths.clone();
253                } else {
254                    let max_cols = self.workbook.get_current_sheet().max_cols;
255                    let default_width = 15;
256                    self.column_widths = vec![default_width; max_cols + 1];
257
258                    self.sheet_column_widths
259                        .insert(new_sheet_name.clone(), self.column_widths.clone());
260                }
261
262                // Clear search results as they're specific to the previous sheet
263                self.search_results.clear();
264                self.current_search_idx = None;
265                self.update_row_number_width();
266
267                // Check if the new current sheet is loaded when using lazy loading
268                if self.workbook.is_lazy_loading() && !is_new_sheet_loaded {
269                    // If the sheet is not loaded, switch to LazyLoading mode
270                    self.input_mode = crate::app::InputMode::LazyLoading;
271                    self.add_notification(format!(
272                        "Deleted sheet: {current_sheet_name}. Switched to sheet: {new_sheet_name} (press Enter to load)"
273                    ));
274                } else {
275                    self.add_notification(format!("Deleted sheet: {current_sheet_name}"));
276                }
277            }
278            Err(e) => {
279                self.add_notification(format!("Failed to delete sheet: {e}"));
280            }
281        }
282    }
283
284    pub fn delete_current_row(&mut self) -> Result<()> {
285        let row = self.selected_cell.0;
286        let sheet = self.workbook.get_current_sheet();
287
288        // If row is outside the valid range, return success
289        if row < 1 || row > sheet.max_rows {
290            return Ok(());
291        }
292
293        // Save row data for undo
294        let sheet_index = self.workbook.get_current_sheet_index();
295        let sheet_name = self.workbook.get_current_sheet_name();
296
297        // Create a copy of the row data before deletion
298        let row_data = if row < sheet.data.len() {
299            sheet.data[row].clone()
300        } else {
301            Vec::new()
302        };
303
304        // Create and add undo action
305        let row_action = RowAction {
306            sheet_index,
307            sheet_name,
308            row,
309            row_data,
310        };
311
312        self.undo_history.push(ActionCommand::Row(row_action));
313        self.workbook.delete_row(row)?;
314
315        self.workbook.recalculate_max_rows();
316        self.workbook.recalculate_max_cols();
317        let sheet = self.workbook.get_current_sheet();
318
319        if self.selected_cell.0 > sheet.max_rows {
320            self.selected_cell.0 = sheet.max_rows.max(1);
321        }
322
323        self.handle_scrolling();
324        self.search_results.clear();
325        self.current_search_idx = None;
326
327        self.add_notification(format!("Deleted row {row}"));
328        Ok(())
329    }
330
331    pub fn delete_row(&mut self, row: usize) -> Result<()> {
332        let sheet = self.workbook.get_current_sheet();
333
334        // If row is outside the valid range, return success
335        if row < 1 || row > sheet.max_rows {
336            return Ok(());
337        }
338
339        // Save row data for undo
340        let sheet_index = self.workbook.get_current_sheet_index();
341        let sheet_name = self.workbook.get_current_sheet_name();
342
343        // Create a copy of the row data before deletion
344        let row_data = if row < sheet.data.len() {
345            sheet.data[row].clone()
346        } else {
347            Vec::new()
348        };
349
350        // Create and add undo action
351        let row_action = RowAction {
352            sheet_index,
353            sheet_name,
354            row,
355            row_data,
356        };
357
358        self.undo_history.push(ActionCommand::Row(row_action));
359        self.workbook.delete_row(row)?;
360
361        self.workbook.recalculate_max_rows();
362        self.workbook.recalculate_max_cols();
363        let sheet = self.workbook.get_current_sheet();
364
365        if self.selected_cell.0 > sheet.max_rows {
366            self.selected_cell.0 = sheet.max_rows.max(1);
367        }
368
369        self.handle_scrolling();
370        self.search_results.clear();
371        self.current_search_idx = None;
372
373        self.add_notification(format!("Deleted row {row}"));
374        Ok(())
375    }
376
377    pub fn delete_rows(&mut self, start_row: usize, end_row: usize) -> Result<()> {
378        if start_row == end_row {
379            return self.delete_row(start_row);
380        }
381
382        let sheet = self.workbook.get_current_sheet();
383
384        // If the entire range is outside the valid range, return success
385        if start_row < 1 || start_row > sheet.max_rows || start_row > end_row {
386            return Ok(());
387        }
388
389        // If start_row is valid but end_row exceeds max_rows, adjust end_row to max_rows
390        let effective_end_row = end_row.min(sheet.max_rows);
391
392        // Save all row data for batch undo
393        let sheet_index = self.workbook.get_current_sheet_index();
394        let sheet_name = self.workbook.get_current_sheet_name();
395
396        // Save row data in the original order from top to bottom
397        let rows_to_save = effective_end_row - start_row + 1;
398        let mut rows_data = Vec::with_capacity(rows_to_save);
399
400        for row in start_row..=effective_end_row {
401            if row < sheet.data.len() {
402                rows_data.push(sheet.data[row].clone());
403            } else {
404                rows_data.push(Vec::new());
405            }
406        }
407
408        // Create and add batch undo action
409        let multi_row_action = MultiRowAction {
410            sheet_index,
411            sheet_name,
412            start_row,
413            end_row: effective_end_row,
414            rows_data,
415        };
416
417        self.undo_history
418            .push(ActionCommand::MultiRow(multi_row_action));
419        self.workbook.delete_rows(start_row, effective_end_row)?;
420
421        self.workbook.recalculate_max_rows();
422        self.workbook.recalculate_max_cols();
423        let sheet = self.workbook.get_current_sheet();
424
425        if self.selected_cell.0 > sheet.max_rows {
426            self.selected_cell.0 = sheet.max_rows.max(1);
427        }
428
429        self.handle_scrolling();
430        self.search_results.clear();
431        self.current_search_idx = None;
432
433        self.add_notification(format!("Deleted rows {start_row} to {effective_end_row}"));
434        Ok(())
435    }
436
437    pub fn delete_current_column(&mut self) -> Result<()> {
438        let col = self.selected_cell.1;
439        let sheet = self.workbook.get_current_sheet();
440
441        // If column is outside the valid range, return success
442        if col < 1 || col > sheet.max_cols {
443            return Ok(());
444        }
445
446        // Save column data for undo
447        let sheet_index = self.workbook.get_current_sheet_index();
448        let sheet_name = self.workbook.get_current_sheet_name();
449
450        // Extract the column data from each row
451        let mut column_data = Vec::with_capacity(sheet.data.len());
452        for row in &sheet.data {
453            if col < row.len() {
454                column_data.push(row[col].clone());
455            } else {
456                column_data.push(crate::excel::Cell::empty());
457            }
458        }
459
460        // Save the column width
461        let column_width = if col < self.column_widths.len() {
462            self.column_widths[col]
463        } else {
464            15 // Default width
465        };
466
467        let column_action = ColumnAction {
468            sheet_index,
469            sheet_name,
470            col,
471            column_data,
472            column_width,
473        };
474
475        self.undo_history.push(ActionCommand::Column(column_action));
476        self.workbook.delete_column(col)?;
477
478        self.workbook.recalculate_max_rows();
479        self.workbook.recalculate_max_cols();
480        let sheet = self.workbook.get_current_sheet();
481
482        if col > sheet.max_cols {
483            self.selected_cell.1 = sheet.max_cols.max(1);
484        }
485
486        if self.selected_cell.0 > sheet.max_rows {
487            self.selected_cell.0 = sheet.max_rows.max(1);
488        }
489
490        if self.column_widths.len() > col {
491            self.column_widths.remove(col);
492        }
493
494        self.adjust_column_widths(sheet.max_cols);
495
496        self.handle_scrolling();
497        self.search_results.clear();
498        self.current_search_idx = None;
499
500        let col_name = index_to_col_name(col);
501        self.add_notification(format!("Deleted column {col_name}"));
502        Ok(())
503    }
504
505    pub fn delete_column(&mut self, col: usize) -> Result<()> {
506        let sheet = self.workbook.get_current_sheet();
507
508        // If column is outside the valid range, return success
509        if col < 1 || col > sheet.max_cols {
510            return Ok(());
511        }
512
513        // Save column data for undo
514        let sheet_index = self.workbook.get_current_sheet_index();
515        let sheet_name = self.workbook.get_current_sheet_name();
516
517        // Extract the column data from each row
518        let mut column_data = Vec::with_capacity(sheet.data.len());
519        for row in &sheet.data {
520            if col < row.len() {
521                column_data.push(row[col].clone());
522            } else {
523                column_data.push(crate::excel::Cell::empty());
524            }
525        }
526
527        // Save the column width
528        let column_width = if col < self.column_widths.len() {
529            self.column_widths[col]
530        } else {
531            15 // Default width
532        };
533
534        let column_action = ColumnAction {
535            sheet_index,
536            sheet_name,
537            col,
538            column_data,
539            column_width,
540        };
541
542        self.undo_history.push(ActionCommand::Column(column_action));
543        self.workbook.delete_column(col)?;
544
545        self.workbook.recalculate_max_rows();
546        self.workbook.recalculate_max_cols();
547        let sheet = self.workbook.get_current_sheet();
548
549        if self.selected_cell.1 > sheet.max_cols {
550            self.selected_cell.1 = sheet.max_cols.max(1);
551        }
552
553        if self.selected_cell.0 > sheet.max_rows {
554            self.selected_cell.0 = sheet.max_rows.max(1);
555        }
556
557        if self.column_widths.len() > col {
558            self.column_widths.remove(col);
559        }
560
561        self.adjust_column_widths(sheet.max_cols);
562
563        self.handle_scrolling();
564        self.search_results.clear();
565        self.current_search_idx = None;
566
567        let col_name = index_to_col_name(col);
568        self.add_notification(format!("Deleted column {col_name}"));
569        Ok(())
570    }
571
572    pub fn delete_columns(&mut self, start_col: usize, end_col: usize) -> Result<()> {
573        if start_col == end_col {
574            return self.delete_column(start_col);
575        }
576
577        let sheet = self.workbook.get_current_sheet();
578
579        // If the entire range is outside the valid range, return success
580        if start_col < 1 || start_col > sheet.max_cols || start_col > end_col {
581            return Ok(());
582        }
583
584        // If start_col is valid but end_col exceeds max_cols, adjust end_col to max_cols
585        let effective_end_col = end_col.min(sheet.max_cols);
586
587        // For multiple columns, save all column data for batch undo
588        let sheet_index = self.workbook.get_current_sheet_index();
589        let sheet_name = self.workbook.get_current_sheet_name();
590
591        // Save column data and widths for batch undo
592        let cols_to_save = effective_end_col - start_col + 1;
593        let mut columns_data = Vec::with_capacity(cols_to_save);
594        let mut column_widths = Vec::with_capacity(cols_to_save);
595
596        for col in start_col..=effective_end_col {
597            // Extract the column data from each row
598            let mut column_data = Vec::with_capacity(sheet.data.len());
599            for row in &sheet.data {
600                if col < row.len() {
601                    column_data.push(row[col].clone());
602                } else {
603                    column_data.push(crate::excel::Cell::empty());
604                }
605            }
606            columns_data.push(column_data);
607
608            // Save the column width
609            let column_width = if col < self.column_widths.len() {
610                self.column_widths[col]
611            } else {
612                15 // Default width
613            };
614            column_widths.push(column_width);
615        }
616
617        // Create and add batch undo action
618        let multi_column_action = MultiColumnAction {
619            sheet_index,
620            sheet_name,
621            start_col,
622            end_col: effective_end_col,
623            columns_data,
624            column_widths,
625        };
626
627        self.undo_history
628            .push(ActionCommand::MultiColumn(multi_column_action));
629        self.workbook.delete_columns(start_col, effective_end_col)?;
630
631        self.workbook.recalculate_max_rows();
632        self.workbook.recalculate_max_cols();
633        let sheet = self.workbook.get_current_sheet();
634
635        if self.selected_cell.1 > sheet.max_cols {
636            self.selected_cell.1 = sheet.max_cols.max(1);
637        }
638
639        if self.selected_cell.0 > sheet.max_rows {
640            self.selected_cell.0 = sheet.max_rows.max(1);
641        }
642
643        for col in (start_col..=effective_end_col).rev() {
644            if self.column_widths.len() > col {
645                self.column_widths.remove(col);
646            }
647        }
648
649        self.adjust_column_widths(sheet.max_cols);
650
651        self.handle_scrolling();
652        self.search_results.clear();
653        self.current_search_idx = None;
654
655        self.add_notification(format!(
656            "Deleted columns {} to {}",
657            index_to_col_name(start_col),
658            index_to_col_name(effective_end_col)
659        ));
660        Ok(())
661    }
662
663    pub fn auto_adjust_column_width(&mut self, col: Option<usize>) {
664        // Get sheet information before any mutable operations
665        let is_loaded = self.workbook.get_current_sheet().is_loaded;
666        let max_cols = self.workbook.get_current_sheet().max_cols;
667        let default_min_width = 5;
668
669        if !is_loaded && max_cols == 0 {
670            self.add_notification(
671                "Cannot adjust column widths in lazy loading mode until sheet is loaded"
672                    .to_string(),
673            );
674            return;
675        }
676
677        match col {
678            // Adjust specific column
679            Some(column) => {
680                // Ensure column_widths is large enough
681                self.ensure_column_widths();
682
683                if column < self.column_widths.len() {
684                    // Calculate and set new column width
685                    let width = self.calculate_column_width(column);
686                    self.column_widths[column] = width.max(default_min_width);
687
688                    self.ensure_column_visible(column);
689
690                    self.add_notification(format!(
691                        "Column {} width adjusted",
692                        index_to_col_name(column)
693                    ));
694                }
695            }
696            // Adjust all columns
697            None => {
698                // Ensure column_widths is large enough
699                self.ensure_column_widths();
700
701                // Only process columns if there are any
702                if max_cols > 0 {
703                    for col_idx in 1..=max_cols {
704                        let width = self.calculate_column_width(col_idx);
705                        self.column_widths[col_idx] = width.max(default_min_width);
706                    }
707
708                    let column = self.selected_cell.1;
709                    self.ensure_column_visible(column);
710
711                    self.add_notification("All column widths adjusted".to_string());
712                }
713            }
714        }
715    }
716
717    fn calculate_column_width(&self, col: usize) -> usize {
718        let sheet = self.workbook.get_current_sheet();
719
720        // Start with minimum width and header width
721        let col_name = index_to_col_name(col);
722        let mut max_width = 3.max(col_name.len());
723
724        // Calculate max width from all cells in the column
725        for row in 1..=sheet.max_rows {
726            if row >= sheet.data.len() || col >= sheet.data[row].len() {
727                continue;
728            }
729
730            let content = &sheet.data[row][col].value;
731            if content.is_empty() {
732                continue;
733            }
734
735            let mut display_width = 0;
736
737            for c in content.chars() {
738                if c.is_ascii() {
739                    display_width += 1;
740                } else {
741                    display_width += 2;
742                }
743            }
744
745            max_width = max_width.max(display_width);
746        }
747        max_width
748    }
749
750    pub fn get_column_width(&self, col: usize) -> usize {
751        if col < self.column_widths.len() {
752            self.column_widths[col]
753        } else {
754            15 // Default width
755        }
756    }
757
758    pub fn ensure_column_widths(&mut self) {
759        let sheet = self.workbook.get_current_sheet();
760        self.adjust_column_widths(sheet.max_cols);
761    }
762
763    fn adjust_column_widths(&mut self, max_cols: usize) {
764        match self.column_widths.len().cmp(&(max_cols + 1)) {
765            std::cmp::Ordering::Greater => {
766                self.column_widths.truncate(max_cols + 1);
767            }
768            std::cmp::Ordering::Less => {
769                let additional = max_cols + 1 - self.column_widths.len();
770                self.column_widths.extend(vec![15; additional]);
771            }
772            std::cmp::Ordering::Equal => {
773                // Column widths are already correct, do nothing
774            }
775        }
776    }
777}
778
779#[cfg(test)]
780mod tests {
781    use crate::app::AppState;
782    use crate::excel::{Sheet, Workbook};
783    use std::path::PathBuf;
784
785    #[test]
786    fn create_sheet_can_be_undone_and_redone() {
787        let workbook = Workbook::from_sheets_for_test(vec![Sheet::blank("Sheet1".to_string())]);
788        let mut app = AppState::new(workbook, PathBuf::from("test.xlsx")).unwrap();
789
790        app.create_sheet("Report");
791        assert_eq!(app.workbook.get_sheet_names(), vec!["Sheet1", "Report"]);
792        assert_eq!(app.workbook.get_current_sheet_name(), "Report");
793        assert!(app.workbook.is_modified());
794
795        app.undo().unwrap();
796        assert_eq!(app.workbook.get_sheet_names(), vec!["Sheet1"]);
797        assert_eq!(app.workbook.get_current_sheet_name(), "Sheet1");
798        assert!(!app.workbook.is_modified());
799
800        app.redo().unwrap();
801        assert_eq!(app.workbook.get_sheet_names(), vec!["Sheet1", "Report"]);
802        assert_eq!(app.workbook.get_current_sheet_name(), "Report");
803        assert!(app.workbook.is_modified());
804    }
805}