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 if !self.sheet_column_widths.contains_key(¤t_sheet_name)
38 || self.sheet_column_widths[¤t_sheet_name] != self.column_widths
39 {
40 self.sheet_column_widths
41 .insert(current_sheet_name.clone(), self.column_widths.clone());
42 }
43
44 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 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 if let Some(saved_position) = self.sheet_cell_positions.get(&new_sheet_name) {
72 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 self.handle_scrolling();
83 } else {
84 self.selected_cell = (1, 1);
86 self.start_row = 1;
87 self.start_col = 1;
88 }
89
90 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 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 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 let sheet_names = self.workbook.get_sheet_names();
118
119 if let Ok(index) = name_or_index.parse::<usize>() {
121 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 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 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 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 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(¤t_sheet_name);
225 self.sheet_cell_positions.remove(¤t_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 if let Some(saved_position) = self.sheet_cell_positions.get(&new_sheet_name) {
233 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 self.handle_scrolling();
244 } else {
245 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 self.search_results.clear();
264 self.current_search_idx = None;
265 self.update_row_number_width();
266
267 if self.workbook.is_lazy_loading() && !is_new_sheet_loaded {
269 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 < 1 || row > sheet.max_rows {
290 return Ok(());
291 }
292
293 let sheet_index = self.workbook.get_current_sheet_index();
295 let sheet_name = self.workbook.get_current_sheet_name();
296
297 let row_data = if row < sheet.data.len() {
299 sheet.data[row].clone()
300 } else {
301 Vec::new()
302 };
303
304 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 < 1 || row > sheet.max_rows {
336 return Ok(());
337 }
338
339 let sheet_index = self.workbook.get_current_sheet_index();
341 let sheet_name = self.workbook.get_current_sheet_name();
342
343 let row_data = if row < sheet.data.len() {
345 sheet.data[row].clone()
346 } else {
347 Vec::new()
348 };
349
350 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 start_row < 1 || start_row > sheet.max_rows || start_row > end_row {
386 return Ok(());
387 }
388
389 let effective_end_row = end_row.min(sheet.max_rows);
391
392 let sheet_index = self.workbook.get_current_sheet_index();
394 let sheet_name = self.workbook.get_current_sheet_name();
395
396 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 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 col < 1 || col > sheet.max_cols {
443 return Ok(());
444 }
445
446 let sheet_index = self.workbook.get_current_sheet_index();
448 let sheet_name = self.workbook.get_current_sheet_name();
449
450 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 let column_width = if col < self.column_widths.len() {
462 self.column_widths[col]
463 } else {
464 15 };
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 col < 1 || col > sheet.max_cols {
510 return Ok(());
511 }
512
513 let sheet_index = self.workbook.get_current_sheet_index();
515 let sheet_name = self.workbook.get_current_sheet_name();
516
517 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 let column_width = if col < self.column_widths.len() {
529 self.column_widths[col]
530 } else {
531 15 };
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 start_col < 1 || start_col > sheet.max_cols || start_col > end_col {
581 return Ok(());
582 }
583
584 let effective_end_col = end_col.min(sheet.max_cols);
586
587 let sheet_index = self.workbook.get_current_sheet_index();
589 let sheet_name = self.workbook.get_current_sheet_name();
590
591 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 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 let column_width = if col < self.column_widths.len() {
610 self.column_widths[col]
611 } else {
612 15 };
614 column_widths.push(column_width);
615 }
616
617 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 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 Some(column) => {
680 self.ensure_column_widths();
682
683 if column < self.column_widths.len() {
684 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 None => {
698 self.ensure_column_widths();
700
701 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 let col_name = index_to_col_name(col);
722 let mut max_width = 3.max(col_name.len());
723
724 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 }
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 }
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}