1use crate::actions::{
2 ActionCommand, ColumnAction, MultiColumnAction, MultiRowAction, RowAction, SheetAction,
3};
4use crate::app::AppState;
5use crate::utils::index_to_col_name;
6use anyhow::Result;
7
8impl AppState<'_> {
9 pub fn next_sheet(&mut self) -> Result<()> {
10 let sheet_count = self.workbook.get_sheet_names().len();
11 let current_index = self.workbook.get_current_sheet_index();
12
13 if current_index >= sheet_count - 1 {
14 self.add_notification("Already at the last sheet".to_string());
15 return Ok(());
16 }
17
18 self.switch_sheet_by_index(current_index + 1)
19 }
20
21 pub fn prev_sheet(&mut self) -> Result<()> {
22 let current_index = self.workbook.get_current_sheet_index();
23
24 if current_index == 0 {
25 self.add_notification("Already at the first sheet".to_string());
26 return Ok(());
27 }
28
29 self.switch_sheet_by_index(current_index - 1)
30 }
31
32 pub fn switch_sheet_by_index(&mut self, index: usize) -> Result<()> {
33 let current_sheet_name = self.workbook.get_current_sheet_name();
34
35 if !self.sheet_column_widths.contains_key(¤t_sheet_name)
37 || self.sheet_column_widths[¤t_sheet_name] != self.column_widths
38 {
39 self.sheet_column_widths
40 .insert(current_sheet_name.clone(), self.column_widths.clone());
41 }
42
43 let current_position = crate::app::CellPosition {
45 selected: self.selected_cell,
46 view: (self.start_row, self.start_col),
47 };
48 self.sheet_cell_positions
49 .insert(current_sheet_name, current_position);
50
51 self.workbook.switch_sheet(index)?;
52
53 let new_sheet_name = self.workbook.get_current_sheet_name();
54
55 if let Some(saved_widths) = self.sheet_column_widths.get(&new_sheet_name) {
57 if &self.column_widths != saved_widths {
58 self.column_widths = saved_widths.clone();
59 }
60 } else {
61 let max_cols = self.workbook.get_current_sheet().max_cols;
62 let default_width = 15;
63 self.column_widths = vec![default_width; max_cols + 1];
64
65 self.sheet_column_widths
66 .insert(new_sheet_name.clone(), self.column_widths.clone());
67 }
68
69 if let Some(saved_position) = self.sheet_cell_positions.get(&new_sheet_name) {
71 let sheet = self.workbook.get_current_sheet();
73 let valid_row = saved_position.selected.0.min(sheet.max_rows.max(1));
74 let valid_col = saved_position.selected.1.min(sheet.max_cols.max(1));
75
76 self.selected_cell = (valid_row, valid_col);
77 self.start_row = saved_position.view.0;
78 self.start_col = saved_position.view.1;
79
80 self.handle_scrolling();
82 } else {
83 self.selected_cell = (1, 1);
85 self.start_row = 1;
86 self.start_col = 1;
87 }
88
89 if !self.search_results.is_empty() {
91 self.search_results.clear();
92 self.current_search_idx = None;
93 }
94
95 self.update_row_number_width();
96
97 let is_lazy_loading = self.workbook.is_lazy_loading();
99 let is_sheet_loaded = self.workbook.is_sheet_loaded(index);
100
101 if is_lazy_loading && !is_sheet_loaded {
102 self.input_mode = crate::app::InputMode::LazyLoading;
104 self.add_notification(format!(
105 "Switched to sheet: {new_sheet_name} (press Enter to load)"
106 ));
107 } else {
108 self.add_notification(format!("Switched to sheet: {new_sheet_name}"));
109 }
110
111 Ok(())
112 }
113
114 pub fn switch_to_sheet(&mut self, name_or_index: &str) {
115 let sheet_names = self.workbook.get_sheet_names();
117
118 if let Ok(index) = name_or_index.parse::<usize>() {
120 let zero_based_index = index.saturating_sub(1);
122
123 if zero_based_index < sheet_names.len() {
124 match self.switch_sheet_by_index(zero_based_index) {
125 Ok(()) => return,
126 Err(e) => {
127 self.add_notification(format!("Failed to switch to sheet {index}: {e}"));
128 return;
129 }
130 }
131 }
132 }
133
134 for (i, name) in sheet_names.iter().enumerate() {
136 if name.eq_ignore_ascii_case(name_or_index) {
137 match self.switch_sheet_by_index(i) {
138 Ok(()) => return,
139 Err(e) => {
140 self.add_notification(format!(
141 "Failed to switch to sheet '{name_or_index}': {e}"
142 ));
143 return;
144 }
145 }
146 }
147 }
148
149 self.add_notification(format!("Sheet '{name_or_index}' not found"));
151 }
152
153 pub fn delete_current_sheet(&mut self) {
154 let current_sheet_name = self.workbook.get_current_sheet_name();
155 let sheet_index = self.workbook.get_current_sheet_index();
156
157 let sheet_data = self.workbook.get_current_sheet().clone();
159 let column_widths = self.column_widths.clone();
160
161 match self.workbook.delete_current_sheet() {
162 Ok(()) => {
163 let sheet_action = SheetAction {
165 sheet_index,
166 sheet_name: current_sheet_name.clone(),
167 sheet_data,
168 column_widths,
169 };
170
171 self.undo_history.push(ActionCommand::Sheet(sheet_action));
172 self.sheet_column_widths.remove(¤t_sheet_name);
173 self.sheet_cell_positions.remove(¤t_sheet_name);
174
175 let new_sheet_name = self.workbook.get_current_sheet_name();
176 let new_sheet_index = self.workbook.get_current_sheet_index();
177 let is_new_sheet_loaded = self.workbook.is_sheet_loaded(new_sheet_index);
178
179 if let Some(saved_position) = self.sheet_cell_positions.get(&new_sheet_name) {
181 let sheet = self.workbook.get_current_sheet();
183 let valid_row = saved_position.selected.0.min(sheet.max_rows.max(1));
184 let valid_col = saved_position.selected.1.min(sheet.max_cols.max(1));
185
186 self.selected_cell = (valid_row, valid_col);
187 self.start_row = saved_position.view.0;
188 self.start_col = saved_position.view.1;
189
190 self.handle_scrolling();
192 } else {
193 self.selected_cell = (1, 1);
195 self.start_row = 1;
196 self.start_col = 1;
197 }
198
199 if let Some(saved_widths) = self.sheet_column_widths.get(&new_sheet_name) {
200 self.column_widths = saved_widths.clone();
201 } else {
202 let max_cols = self.workbook.get_current_sheet().max_cols;
203 let default_width = 15;
204 self.column_widths = vec![default_width; max_cols + 1];
205
206 self.sheet_column_widths
207 .insert(new_sheet_name.clone(), self.column_widths.clone());
208 }
209
210 self.search_results.clear();
212 self.current_search_idx = None;
213
214 if self.workbook.is_lazy_loading() && !is_new_sheet_loaded {
216 self.input_mode = crate::app::InputMode::LazyLoading;
218 self.add_notification(format!(
219 "Deleted sheet: {current_sheet_name}. Switched to sheet: {new_sheet_name} (press Enter to load)"
220 ));
221 } else {
222 self.add_notification(format!("Deleted sheet: {current_sheet_name}"));
223 }
224 }
225 Err(e) => {
226 self.add_notification(format!("Failed to delete sheet: {e}"));
227 }
228 }
229 }
230
231 pub fn delete_current_row(&mut self) -> Result<()> {
232 let row = self.selected_cell.0;
233 let sheet = self.workbook.get_current_sheet();
234
235 if row < 1 || row > sheet.max_rows {
237 return Ok(());
238 }
239
240 let sheet_index = self.workbook.get_current_sheet_index();
242 let sheet_name = self.workbook.get_current_sheet_name();
243
244 let row_data = if row < sheet.data.len() {
246 sheet.data[row].clone()
247 } else {
248 Vec::new()
249 };
250
251 let row_action = RowAction {
253 sheet_index,
254 sheet_name,
255 row,
256 row_data,
257 };
258
259 self.undo_history.push(ActionCommand::Row(row_action));
260 self.workbook.delete_row(row)?;
261
262 self.workbook.recalculate_max_rows();
263 self.workbook.recalculate_max_cols();
264 let sheet = self.workbook.get_current_sheet();
265
266 if self.selected_cell.0 > sheet.max_rows {
267 self.selected_cell.0 = sheet.max_rows.max(1);
268 }
269
270 self.handle_scrolling();
271 self.search_results.clear();
272 self.current_search_idx = None;
273
274 self.add_notification(format!("Deleted row {row}"));
275 Ok(())
276 }
277
278 pub fn delete_row(&mut self, row: usize) -> Result<()> {
279 let sheet = self.workbook.get_current_sheet();
280
281 if row < 1 || row > sheet.max_rows {
283 return Ok(());
284 }
285
286 let sheet_index = self.workbook.get_current_sheet_index();
288 let sheet_name = self.workbook.get_current_sheet_name();
289
290 let row_data = if row < sheet.data.len() {
292 sheet.data[row].clone()
293 } else {
294 Vec::new()
295 };
296
297 let row_action = RowAction {
299 sheet_index,
300 sheet_name,
301 row,
302 row_data,
303 };
304
305 self.undo_history.push(ActionCommand::Row(row_action));
306 self.workbook.delete_row(row)?;
307
308 self.workbook.recalculate_max_rows();
309 self.workbook.recalculate_max_cols();
310 let sheet = self.workbook.get_current_sheet();
311
312 if self.selected_cell.0 > sheet.max_rows {
313 self.selected_cell.0 = sheet.max_rows.max(1);
314 }
315
316 self.handle_scrolling();
317 self.search_results.clear();
318 self.current_search_idx = None;
319
320 self.add_notification(format!("Deleted row {row}"));
321 Ok(())
322 }
323
324 pub fn delete_rows(&mut self, start_row: usize, end_row: usize) -> Result<()> {
325 if start_row == end_row {
326 return self.delete_row(start_row);
327 }
328
329 let sheet = self.workbook.get_current_sheet();
330
331 if start_row < 1 || start_row > sheet.max_rows || start_row > end_row {
333 return Ok(());
334 }
335
336 let effective_end_row = end_row.min(sheet.max_rows);
338
339 let sheet_index = self.workbook.get_current_sheet_index();
341 let sheet_name = self.workbook.get_current_sheet_name();
342
343 let rows_to_save = effective_end_row - start_row + 1;
345 let mut rows_data = Vec::with_capacity(rows_to_save);
346
347 for row in start_row..=effective_end_row {
348 if row < sheet.data.len() {
349 rows_data.push(sheet.data[row].clone());
350 } else {
351 rows_data.push(Vec::new());
352 }
353 }
354
355 let multi_row_action = MultiRowAction {
357 sheet_index,
358 sheet_name,
359 start_row,
360 end_row: effective_end_row,
361 rows_data,
362 };
363
364 self.undo_history
365 .push(ActionCommand::MultiRow(multi_row_action));
366 self.workbook.delete_rows(start_row, effective_end_row)?;
367
368 self.workbook.recalculate_max_rows();
369 self.workbook.recalculate_max_cols();
370 let sheet = self.workbook.get_current_sheet();
371
372 if self.selected_cell.0 > sheet.max_rows {
373 self.selected_cell.0 = sheet.max_rows.max(1);
374 }
375
376 self.handle_scrolling();
377 self.search_results.clear();
378 self.current_search_idx = None;
379
380 self.add_notification(format!("Deleted rows {start_row} to {effective_end_row}"));
381 Ok(())
382 }
383
384 pub fn delete_current_column(&mut self) -> Result<()> {
385 let col = self.selected_cell.1;
386 let sheet = self.workbook.get_current_sheet();
387
388 if col < 1 || col > sheet.max_cols {
390 return Ok(());
391 }
392
393 let sheet_index = self.workbook.get_current_sheet_index();
395 let sheet_name = self.workbook.get_current_sheet_name();
396
397 let mut column_data = Vec::with_capacity(sheet.data.len());
399 for row in &sheet.data {
400 if col < row.len() {
401 column_data.push(row[col].clone());
402 } else {
403 column_data.push(crate::excel::Cell::empty());
404 }
405 }
406
407 let column_width = if col < self.column_widths.len() {
409 self.column_widths[col]
410 } else {
411 15 };
413
414 let column_action = ColumnAction {
415 sheet_index,
416 sheet_name,
417 col,
418 column_data,
419 column_width,
420 };
421
422 self.undo_history.push(ActionCommand::Column(column_action));
423 self.workbook.delete_column(col)?;
424
425 self.workbook.recalculate_max_rows();
426 self.workbook.recalculate_max_cols();
427 let sheet = self.workbook.get_current_sheet();
428
429 if col > sheet.max_cols {
430 self.selected_cell.1 = sheet.max_cols.max(1);
431 }
432
433 if self.selected_cell.0 > sheet.max_rows {
434 self.selected_cell.0 = sheet.max_rows.max(1);
435 }
436
437 if self.column_widths.len() > col {
438 self.column_widths.remove(col);
439 }
440
441 self.adjust_column_widths(sheet.max_cols);
442
443 self.handle_scrolling();
444 self.search_results.clear();
445 self.current_search_idx = None;
446
447 let col_name = index_to_col_name(col);
448 self.add_notification(format!("Deleted column {col_name}"));
449 Ok(())
450 }
451
452 pub fn delete_column(&mut self, col: usize) -> Result<()> {
453 let sheet = self.workbook.get_current_sheet();
454
455 if col < 1 || col > sheet.max_cols {
457 return Ok(());
458 }
459
460 let sheet_index = self.workbook.get_current_sheet_index();
462 let sheet_name = self.workbook.get_current_sheet_name();
463
464 let mut column_data = Vec::with_capacity(sheet.data.len());
466 for row in &sheet.data {
467 if col < row.len() {
468 column_data.push(row[col].clone());
469 } else {
470 column_data.push(crate::excel::Cell::empty());
471 }
472 }
473
474 let column_width = if col < self.column_widths.len() {
476 self.column_widths[col]
477 } else {
478 15 };
480
481 let column_action = ColumnAction {
482 sheet_index,
483 sheet_name,
484 col,
485 column_data,
486 column_width,
487 };
488
489 self.undo_history.push(ActionCommand::Column(column_action));
490 self.workbook.delete_column(col)?;
491
492 self.workbook.recalculate_max_rows();
493 self.workbook.recalculate_max_cols();
494 let sheet = self.workbook.get_current_sheet();
495
496 if self.selected_cell.1 > sheet.max_cols {
497 self.selected_cell.1 = sheet.max_cols.max(1);
498 }
499
500 if self.selected_cell.0 > sheet.max_rows {
501 self.selected_cell.0 = sheet.max_rows.max(1);
502 }
503
504 if self.column_widths.len() > col {
505 self.column_widths.remove(col);
506 }
507
508 self.adjust_column_widths(sheet.max_cols);
509
510 self.handle_scrolling();
511 self.search_results.clear();
512 self.current_search_idx = None;
513
514 let col_name = index_to_col_name(col);
515 self.add_notification(format!("Deleted column {col_name}"));
516 Ok(())
517 }
518
519 pub fn delete_columns(&mut self, start_col: usize, end_col: usize) -> Result<()> {
520 if start_col == end_col {
521 return self.delete_column(start_col);
522 }
523
524 let sheet = self.workbook.get_current_sheet();
525
526 if start_col < 1 || start_col > sheet.max_cols || start_col > end_col {
528 return Ok(());
529 }
530
531 let effective_end_col = end_col.min(sheet.max_cols);
533
534 let sheet_index = self.workbook.get_current_sheet_index();
536 let sheet_name = self.workbook.get_current_sheet_name();
537
538 let cols_to_save = effective_end_col - start_col + 1;
540 let mut columns_data = Vec::with_capacity(cols_to_save);
541 let mut column_widths = Vec::with_capacity(cols_to_save);
542
543 for col in start_col..=effective_end_col {
544 let mut column_data = Vec::with_capacity(sheet.data.len());
546 for row in &sheet.data {
547 if col < row.len() {
548 column_data.push(row[col].clone());
549 } else {
550 column_data.push(crate::excel::Cell::empty());
551 }
552 }
553 columns_data.push(column_data);
554
555 let column_width = if col < self.column_widths.len() {
557 self.column_widths[col]
558 } else {
559 15 };
561 column_widths.push(column_width);
562 }
563
564 let multi_column_action = MultiColumnAction {
566 sheet_index,
567 sheet_name,
568 start_col,
569 end_col: effective_end_col,
570 columns_data,
571 column_widths,
572 };
573
574 self.undo_history
575 .push(ActionCommand::MultiColumn(multi_column_action));
576 self.workbook.delete_columns(start_col, effective_end_col)?;
577
578 self.workbook.recalculate_max_rows();
579 self.workbook.recalculate_max_cols();
580 let sheet = self.workbook.get_current_sheet();
581
582 if self.selected_cell.1 > sheet.max_cols {
583 self.selected_cell.1 = sheet.max_cols.max(1);
584 }
585
586 if self.selected_cell.0 > sheet.max_rows {
587 self.selected_cell.0 = sheet.max_rows.max(1);
588 }
589
590 for col in (start_col..=effective_end_col).rev() {
591 if self.column_widths.len() > col {
592 self.column_widths.remove(col);
593 }
594 }
595
596 self.adjust_column_widths(sheet.max_cols);
597
598 self.handle_scrolling();
599 self.search_results.clear();
600 self.current_search_idx = None;
601
602 self.add_notification(format!(
603 "Deleted columns {} to {}",
604 index_to_col_name(start_col),
605 index_to_col_name(effective_end_col)
606 ));
607 Ok(())
608 }
609
610 pub fn auto_adjust_column_width(&mut self, col: Option<usize>) {
611 let is_loaded = self.workbook.get_current_sheet().is_loaded;
613 let max_cols = self.workbook.get_current_sheet().max_cols;
614 let default_min_width = 5;
615
616 if !is_loaded && max_cols == 0 {
617 self.add_notification(
618 "Cannot adjust column widths in lazy loading mode until sheet is loaded"
619 .to_string(),
620 );
621 return;
622 }
623
624 match col {
625 Some(column) => {
627 self.ensure_column_widths();
629
630 if column < self.column_widths.len() {
631 let width = self.calculate_column_width(column);
633 self.column_widths[column] = width.max(default_min_width);
634
635 self.ensure_column_visible(column);
636
637 self.add_notification(format!(
638 "Column {} width adjusted",
639 index_to_col_name(column)
640 ));
641 }
642 }
643 None => {
645 self.ensure_column_widths();
647
648 if max_cols > 0 {
650 for col_idx in 1..=max_cols {
651 let width = self.calculate_column_width(col_idx);
652 self.column_widths[col_idx] = width.max(default_min_width);
653 }
654
655 let column = self.selected_cell.1;
656 self.ensure_column_visible(column);
657
658 self.add_notification("All column widths adjusted".to_string());
659 }
660 }
661 }
662 }
663
664 fn calculate_column_width(&self, col: usize) -> usize {
665 let sheet = self.workbook.get_current_sheet();
666
667 let col_name = index_to_col_name(col);
669 let mut max_width = 3.max(col_name.len());
670
671 for row in 1..=sheet.max_rows {
673 if row >= sheet.data.len() || col >= sheet.data[row].len() {
674 continue;
675 }
676
677 let content = &sheet.data[row][col].value;
678 if content.is_empty() {
679 continue;
680 }
681
682 let mut display_width = 0;
683
684 for c in content.chars() {
685 if c.is_ascii() {
686 display_width += 1;
687 } else {
688 display_width += 2;
689 }
690 }
691
692 max_width = max_width.max(display_width);
693 }
694 max_width
695 }
696
697 pub fn get_column_width(&self, col: usize) -> usize {
698 if col < self.column_widths.len() {
699 self.column_widths[col]
700 } else {
701 15 }
703 }
704
705 pub fn ensure_column_widths(&mut self) {
706 let sheet = self.workbook.get_current_sheet();
707 self.adjust_column_widths(sheet.max_cols);
708 }
709
710 fn adjust_column_widths(&mut self, max_cols: usize) {
711 match self.column_widths.len().cmp(&(max_cols + 1)) {
712 std::cmp::Ordering::Greater => {
713 self.column_widths.truncate(max_cols + 1);
714 }
715 std::cmp::Ordering::Less => {
716 let additional = max_cols + 1 - self.column_widths.len();
717 self.column_widths.extend(vec![15; additional]);
718 }
719 std::cmp::Ordering::Equal => {
720 }
722 }
723 }
724}