1use crate::actions::{
2 ActionCommand, ActionExecutor, ActionType, CellAction, ColumnAction, MultiColumnAction,
3 MultiRowAction, RowAction, SheetAction, SheetOperation,
4};
5use crate::app::AppState;
6use crate::utils::index_to_col_name;
7use anyhow::Result;
8use std::rc::Rc;
9
10impl AppState<'_> {
11 pub fn undo(&mut self) -> Result<()> {
12 if let Some(action) = self.undo_history.undo() {
13 self.apply_action(&action, true)?;
14
15 self.workbook.recalculate_max_rows();
16 self.workbook.recalculate_max_cols();
17 self.ensure_column_widths();
18
19 let sheet = self.workbook.get_current_sheet();
21 if self.selected_cell.0 > sheet.max_rows {
22 self.selected_cell.0 = sheet.max_rows.max(1);
23 }
24 if self.selected_cell.1 > sheet.max_cols {
25 self.selected_cell.1 = sheet.max_cols.max(1);
26 }
27
28 if self.undo_history.all_undone() {
29 self.workbook.set_modified(false);
30 } else {
31 self.workbook.set_modified(true);
32 }
33 } else {
34 self.add_notification("No operations to undo".to_string());
35 }
36 Ok(())
37 }
38
39 pub fn redo(&mut self) -> Result<()> {
40 if let Some(action) = self.undo_history.redo() {
41 self.apply_action(&action, false)?;
42
43 self.workbook.recalculate_max_rows();
44 self.workbook.recalculate_max_cols();
45 self.ensure_column_widths();
46
47 let sheet = self.workbook.get_current_sheet();
49 if self.selected_cell.0 > sheet.max_rows {
50 self.selected_cell.0 = sheet.max_rows.max(1);
51 }
52 if self.selected_cell.1 > sheet.max_cols {
53 self.selected_cell.1 = sheet.max_cols.max(1);
54 }
55
56 self.workbook.set_modified(true);
57 } else {
58 self.add_notification("No operations to redo".to_string());
59 }
60 Ok(())
61 }
62
63 fn apply_action(&mut self, action: &Rc<ActionCommand>, is_undo: bool) -> Result<()> {
64 match action.as_ref() {
65 ActionCommand::Cell(cell_action) => {
66 let value = if is_undo {
67 &cell_action.old_value
68 } else {
69 &cell_action.new_value
70 };
71 self.apply_cell_action(cell_action, value, is_undo, &cell_action.action_type)?;
72 }
73 ActionCommand::Row(row_action) => {
74 self.apply_row_action(row_action, is_undo)?;
75 }
76 ActionCommand::Column(column_action) => {
77 self.apply_column_action(column_action, is_undo)?;
78 }
79 ActionCommand::Sheet(sheet_action) => {
80 self.apply_sheet_action(sheet_action, is_undo)?;
81 }
82 ActionCommand::MultiRow(multi_row_action) => {
83 self.apply_multi_row_action(multi_row_action, is_undo)?;
84 }
85 ActionCommand::MultiColumn(multi_column_action) => {
86 self.apply_multi_column_action(multi_column_action, is_undo)?;
87 }
88 }
89 Ok(())
90 }
91
92 fn apply_cell_action(
93 &mut self,
94 cell_action: &CellAction,
95 value: &crate::excel::Cell,
96 is_undo: bool,
97 action_type: &ActionType,
98 ) -> Result<()> {
99 let current_sheet_index = self.workbook.get_current_sheet_index();
100
101 if current_sheet_index != cell_action.sheet_index {
102 if let Err(e) = self.switch_sheet_by_index(cell_action.sheet_index) {
103 self.add_notification(format!(
104 "Cannot switch to sheet {}: {}",
105 cell_action.sheet_name, e
106 ));
107 return Ok(());
108 }
109 }
110
111 self.workbook.get_current_sheet_mut().data[cell_action.row][cell_action.col] =
112 value.clone();
113
114 self.selected_cell = (cell_action.row, cell_action.col);
115 self.handle_scrolling();
116
117 let cell_ref = format!(
118 "{}{}",
119 crate::utils::index_to_col_name(cell_action.col),
120 cell_action.row
121 );
122
123 let operation_text = match action_type {
124 ActionType::Edit => "edit",
125 ActionType::Cut => "cut",
126 ActionType::Paste => "paste",
127 _ => "cell operation",
128 };
129
130 if current_sheet_index != cell_action.sheet_index {
131 let action_word = if is_undo { "Undid" } else { "Redid" };
132 self.add_notification(format!(
133 "{} {} operation on cell {} in sheet {}",
134 action_word, operation_text, cell_ref, cell_action.sheet_name
135 ));
136 } else {
137 let action_word = if is_undo { "Undid" } else { "Redid" };
138 self.add_notification(format!(
139 "{} {} operation on cell {}",
140 action_word, operation_text, cell_ref
141 ));
142 }
143
144 Ok(())
145 }
146
147 fn apply_row_action(&mut self, row_action: &RowAction, is_undo: bool) -> Result<()> {
148 let current_sheet_index = self.workbook.get_current_sheet_index();
149
150 if current_sheet_index != row_action.sheet_index {
151 if let Err(e) = self.switch_sheet_by_index(row_action.sheet_index) {
152 self.add_notification(format!(
153 "Cannot switch to sheet {}: {}",
154 row_action.sheet_name, e
155 ));
156 return Ok(());
157 }
158 }
159
160 let sheet = self.workbook.get_current_sheet_mut();
161
162 if is_undo {
163 sheet
164 .data
165 .insert(row_action.row, row_action.row_data.clone());
166
167 sheet.max_rows = sheet.max_rows.saturating_add(1);
168
169 self.workbook.recalculate_max_cols();
172
173 self.add_notification(format!("Undid row {} deletion", row_action.row));
174 } else if row_action.row < sheet.data.len() {
175 sheet.data.remove(row_action.row);
176 sheet.max_rows = sheet.max_rows.saturating_sub(1);
177
178 if self.selected_cell.0 > sheet.max_rows {
179 self.selected_cell.0 = sheet.max_rows.max(1);
180 }
181
182 self.add_notification(format!("Redid row {} deletion", row_action.row));
183 }
184
185 self.handle_scrolling();
186 self.search_results.clear();
187 self.current_search_idx = None;
188
189 Ok(())
190 }
191
192 fn apply_column_action(&mut self, column_action: &ColumnAction, is_undo: bool) -> Result<()> {
193 let current_sheet_index = self.workbook.get_current_sheet_index();
194
195 if current_sheet_index != column_action.sheet_index {
196 if let Err(e) = self.switch_sheet_by_index(column_action.sheet_index) {
197 self.add_notification(format!(
198 "Cannot switch to sheet {}: {}",
199 column_action.sheet_name, e
200 ));
201 return Ok(());
202 }
203 }
204
205 let sheet = self.workbook.get_current_sheet_mut();
206 let col = column_action.col;
207
208 if is_undo {
209 let column_data = &column_action.column_data;
210
211 for (i, row) in sheet.data.iter_mut().enumerate() {
212 if i < column_data.len() {
213 if col <= row.len() {
214 row.insert(col, column_data[i].clone());
215 } else {
216 while row.len() < col {
217 row.push(crate::excel::Cell::empty());
218 }
219 row.push(column_data[i].clone());
220 }
221 }
222 }
223
224 sheet.max_cols = sheet.max_cols.saturating_add(1);
226
227 self.workbook.recalculate_max_rows();
230
231 if col < self.column_widths.len() {
232 self.column_widths.insert(col, column_action.column_width);
233 if !self.column_widths.is_empty() {
234 self.column_widths.pop();
235 }
236 } else {
237 while self.column_widths.len() < col {
238 self.column_widths.push(15); }
240 self.column_widths.push(column_action.column_width);
241 }
242
243 self.ensure_column_visible(col);
244 self.add_notification(format!("Undid column {} deletion", index_to_col_name(col)));
245 } else {
246 for row in sheet.data.iter_mut() {
247 if col < row.len() {
248 row.remove(col);
249 }
250 }
251
252 sheet.max_cols = sheet.max_cols.saturating_sub(1);
253
254 if self.column_widths.len() > col {
255 self.column_widths.remove(col);
256 self.column_widths.push(15);
257 }
258
259 if self.selected_cell.1 > sheet.max_cols {
260 self.selected_cell.1 = sheet.max_cols.max(1);
261 }
262
263 self.add_notification(format!("Redid column {} deletion", index_to_col_name(col)));
264 }
265
266 self.handle_scrolling();
267 self.search_results.clear();
268 self.current_search_idx = None;
269
270 Ok(())
271 }
272
273 fn apply_sheet_action(&mut self, sheet_action: &SheetAction, is_undo: bool) -> Result<()> {
274 match (sheet_action.operation, is_undo) {
275 (SheetOperation::Delete, true) => {
276 self.restore_sheet_from_action(
277 sheet_action,
278 format!("Undid sheet {} deletion", sheet_action.sheet_name),
279 );
280 }
281 (SheetOperation::Delete, false) => {
282 self.delete_sheet_from_action(
283 sheet_action,
284 format!("Redid deletion of sheet {}", sheet_action.sheet_name),
285 );
286 }
287 (SheetOperation::Create, true) => {
288 self.delete_sheet_from_action(
289 sheet_action,
290 format!("Undid creation of sheet {}", sheet_action.sheet_name),
291 );
292 }
293 (SheetOperation::Create, false) => {
294 self.restore_sheet_from_action(
295 sheet_action,
296 format!("Redid creation of sheet {}", sheet_action.sheet_name),
297 );
298 }
299 }
300
301 Ok(())
302 }
303
304 fn cleanup_after_sheet_deletion(&mut self, sheet_name: &str) {
305 self.sheet_column_widths.remove(sheet_name);
306 self.sheet_cell_positions.remove(sheet_name);
307
308 let new_sheet_name = self.workbook.get_current_sheet_name();
309
310 if let Some(saved_position) = self.sheet_cell_positions.get(&new_sheet_name) {
312 let sheet = self.workbook.get_current_sheet();
314 let valid_row = saved_position.selected.0.min(sheet.max_rows.max(1));
315 let valid_col = saved_position.selected.1.min(sheet.max_cols.max(1));
316
317 self.selected_cell = (valid_row, valid_col);
318 self.start_row = saved_position.view.0;
319 self.start_col = saved_position.view.1;
320
321 self.handle_scrolling();
323 } else {
324 self.selected_cell = (1, 1);
326 self.start_row = 1;
327 self.start_col = 1;
328 }
329
330 if let Some(saved_widths) = self.sheet_column_widths.get(&new_sheet_name) {
331 self.column_widths = saved_widths.clone();
332 } else {
333 let max_cols = self.workbook.get_current_sheet().max_cols;
334 let default_width = 15;
335 self.column_widths = vec![default_width; max_cols + 1];
336
337 self.sheet_column_widths
338 .insert(new_sheet_name.clone(), self.column_widths.clone());
339 }
340
341 self.search_results.clear();
342 self.current_search_idx = None;
343 self.update_row_number_width();
344
345 let new_sheet_index = self.workbook.get_current_sheet_index();
346 if self.workbook.is_lazy_loading() && !self.workbook.is_sheet_loaded(new_sheet_index) {
347 self.input_mode = crate::app::InputMode::LazyLoading;
348 } else {
349 self.input_mode = crate::app::InputMode::Normal;
350 }
351 }
352
353 fn restore_sheet_from_action(&mut self, sheet_action: &SheetAction, notification: String) {
354 let sheet_index = sheet_action.sheet_index;
355
356 if let Err(e) = self
357 .workbook
358 .insert_sheet_at_index(sheet_action.sheet_data.clone(), sheet_index)
359 {
360 self.add_notification(format!(
361 "Failed to restore sheet {}: {}",
362 sheet_action.sheet_name, e
363 ));
364 return;
365 }
366
367 self.sheet_column_widths.insert(
368 sheet_action.sheet_name.clone(),
369 sheet_action.column_widths.clone(),
370 );
371
372 self.sheet_cell_positions.insert(
373 sheet_action.sheet_name.clone(),
374 crate::app::CellPosition {
375 selected: (1, 1),
376 view: (1, 1),
377 },
378 );
379
380 if let Err(e) = self.switch_sheet_by_index(sheet_index) {
381 self.add_notification(format!(
382 "Restored sheet {} but couldn't switch to it: {}",
383 sheet_action.sheet_name, e
384 ));
385 return;
386 }
387
388 self.notification_messages.pop();
389 self.add_notification(notification);
390 }
391
392 fn delete_sheet_from_action(&mut self, sheet_action: &SheetAction, notification: String) {
393 if let Err(e) = self.switch_sheet_by_index(sheet_action.sheet_index) {
394 self.add_notification(format!(
395 "Cannot switch to sheet {} to delete it: {}",
396 sheet_action.sheet_name, e
397 ));
398 return;
399 }
400
401 self.notification_messages.pop();
402
403 if let Err(e) = self.workbook.delete_current_sheet() {
404 self.add_notification(format!("Failed to delete sheet: {e}"));
405 return;
406 }
407
408 self.cleanup_after_sheet_deletion(&sheet_action.sheet_name);
409 self.add_notification(notification);
410 }
411
412 fn apply_multi_row_action(
413 &mut self,
414 multi_row_action: &MultiRowAction,
415 is_undo: bool,
416 ) -> Result<()> {
417 let current_sheet_index = self.workbook.get_current_sheet_index();
418
419 if current_sheet_index != multi_row_action.sheet_index {
420 if let Err(e) = self.switch_sheet_by_index(multi_row_action.sheet_index) {
421 self.add_notification(format!(
422 "Cannot switch to sheet {}: {}",
423 multi_row_action.sheet_name, e
424 ));
425 return Ok(());
426 }
427 }
428
429 let start_row = multi_row_action.start_row;
430 let end_row = multi_row_action.end_row;
431 let rows_to_restore = end_row - start_row + 1;
432
433 if is_undo {
434 let rows_data = &multi_row_action.rows_data;
435 let sheet = self.workbook.get_current_sheet_mut();
436
437 Self::restore_rows(sheet, start_row, rows_data);
439
440 sheet.max_rows = sheet.max_rows.saturating_add(rows_to_restore);
441
442 self.workbook.recalculate_max_cols();
444
445 self.add_notification(format!("Undid rows {} to {} deletion", start_row, end_row));
446 } else {
447 self.workbook.delete_rows(start_row, end_row)?;
448
449 let sheet = self.workbook.get_current_sheet();
450
451 if self.selected_cell.0 > sheet.max_rows {
452 self.selected_cell.0 = sheet.max_rows.max(1);
453 }
454
455 self.add_notification(format!("Redid rows {} to {} deletion", start_row, end_row));
456 }
457
458 self.handle_scrolling();
459 self.search_results.clear();
460 self.current_search_idx = None;
461
462 Ok(())
463 }
464
465 fn apply_multi_column_action(
466 &mut self,
467 multi_column_action: &MultiColumnAction,
468 is_undo: bool,
469 ) -> Result<()> {
470 let current_sheet_index = self.workbook.get_current_sheet_index();
471
472 if current_sheet_index != multi_column_action.sheet_index {
473 if let Err(e) = self.switch_sheet_by_index(multi_column_action.sheet_index) {
474 self.add_notification(format!(
475 "Cannot switch to sheet {}: {}",
476 multi_column_action.sheet_name, e
477 ));
478 return Ok(());
479 }
480 }
481
482 let start_col = multi_column_action.start_col;
483 let end_col = multi_column_action.end_col;
484 let cols_to_restore = end_col - start_col + 1;
485
486 if is_undo {
487 let columns_data = &multi_column_action.columns_data;
488 let column_widths = &multi_column_action.column_widths;
489
490 let sheet = self.workbook.get_current_sheet_mut();
491
492 for col_idx in (0..cols_to_restore).rev() {
493 if col_idx < columns_data.len() {
494 let column_data = &columns_data[col_idx];
495 Self::restore_column_at_position(sheet, start_col, column_data);
496
497 Self::restore_column_width(
498 &mut self.column_widths,
499 start_col,
500 col_idx,
501 column_widths,
502 );
503 }
504 }
505
506 sheet.max_cols = sheet.max_cols.saturating_add(cols_to_restore);
507
508 self.workbook.recalculate_max_rows();
510
511 Self::trim_column_widths(&mut self.column_widths, cols_to_restore);
512 self.ensure_column_visible(start_col);
513
514 self.add_notification(format!(
515 "Undid columns {} to {} deletion",
516 index_to_col_name(start_col),
517 index_to_col_name(end_col)
518 ));
519 } else {
520 self.workbook.delete_columns(start_col, end_col)?;
521
522 let sheet = self.workbook.get_current_sheet();
523 Self::remove_column_widths(&mut self.column_widths, start_col, end_col);
524
525 if self.selected_cell.1 > sheet.max_cols {
526 self.selected_cell.1 = sheet.max_cols.max(1);
527 }
528
529 self.add_notification(format!(
530 "Redid columns {} to {} deletion",
531 index_to_col_name(start_col),
532 index_to_col_name(end_col)
533 ));
534 }
535
536 self.handle_scrolling();
537 self.search_results.clear();
538 self.current_search_idx = None;
539
540 Ok(())
541 }
542
543 fn restore_rows(
544 sheet: &mut crate::excel::Sheet,
545 position: usize,
546 rows_data: &[Vec<crate::excel::Cell>],
547 ) {
548 for row_data in rows_data.iter().rev() {
550 sheet.data.insert(position, row_data.clone());
551 }
552 }
553
554 fn restore_column_at_position(
555 sheet: &mut crate::excel::Sheet,
556 position: usize,
557 column_data: &[crate::excel::Cell],
558 ) {
559 for (i, row) in sheet.data.iter_mut().enumerate() {
560 if i < column_data.len() {
561 if position <= row.len() {
562 row.insert(position, column_data[i].clone());
563 } else {
564 let additional = position - row.len();
565 row.reserve(additional + 1);
566 while row.len() < position {
567 row.push(crate::excel::Cell::empty());
568 }
569 row.push(column_data[i].clone());
570 }
571 }
572 }
573 }
574
575 fn restore_column_width(
576 column_widths: &mut Vec<usize>,
577 position: usize,
578 col_idx: usize,
579 width_values: &[usize],
580 ) {
581 if position < column_widths.len() {
582 let width = if col_idx < width_values.len() {
583 width_values[col_idx]
584 } else {
585 15 };
587 column_widths.insert(position, width);
588 }
589 }
590
591 fn trim_column_widths(column_widths: &mut Vec<usize>, count: usize) {
592 if count >= column_widths.len() {
593 return;
594 }
595 column_widths.truncate(column_widths.len() - count);
596 }
597
598 fn remove_column_widths(column_widths: &mut Vec<usize>, start_col: usize, end_col: usize) {
599 let cols_to_remove = end_col - start_col + 1;
600
601 column_widths.reserve(cols_to_remove);
603
604 for col in (start_col..=end_col).rev() {
605 if column_widths.len() > col {
606 column_widths.remove(col);
607 }
608 }
609
610 let mut defaults = vec![15; cols_to_remove];
612 column_widths.append(&mut defaults);
613 }
614}
615
616impl ActionExecutor for AppState<'_> {
617 fn execute_action(&mut self, action: &ActionCommand) -> Result<()> {
618 match action {
619 ActionCommand::Cell(action) => self.execute_cell_action(action),
620 ActionCommand::Row(action) => self.execute_row_action(action),
621 ActionCommand::Column(action) => self.execute_column_action(action),
622 ActionCommand::Sheet(action) => self.execute_sheet_action(action),
623 ActionCommand::MultiRow(action) => self.execute_multi_row_action(action),
624 ActionCommand::MultiColumn(action) => self.execute_multi_column_action(action),
625 }
626 }
627
628 fn execute_cell_action(&mut self, action: &CellAction) -> Result<()> {
629 self.workbook
630 .set_cell_value(action.row, action.col, action.new_value.value.clone())
631 }
632
633 fn execute_row_action(&mut self, action: &RowAction) -> Result<()> {
634 self.workbook.delete_row(action.row)
635 }
636
637 fn execute_column_action(&mut self, action: &ColumnAction) -> Result<()> {
638 self.workbook.delete_column(action.col)
639 }
640
641 fn execute_sheet_action(&mut self, action: &SheetAction) -> Result<()> {
642 match action.operation {
643 SheetOperation::Create => {
644 self.workbook
645 .insert_sheet_at_index(action.sheet_data.clone(), action.sheet_index)?;
646 self.sheet_column_widths
647 .insert(action.sheet_name.clone(), action.column_widths.clone());
648 self.sheet_cell_positions.insert(
649 action.sheet_name.clone(),
650 crate::app::CellPosition {
651 selected: (1, 1),
652 view: (1, 1),
653 },
654 );
655 self.switch_sheet_by_index(action.sheet_index)
656 }
657 SheetOperation::Delete => {
658 self.switch_sheet_by_index(action.sheet_index)?;
659 self.workbook.delete_current_sheet()?;
660 self.cleanup_after_sheet_deletion(&action.sheet_name);
661 Ok(())
662 }
663 }
664 }
665
666 fn execute_multi_row_action(&mut self, action: &MultiRowAction) -> Result<()> {
667 self.workbook.delete_rows(action.start_row, action.end_row)
668 }
669
670 fn execute_multi_column_action(&mut self, action: &MultiColumnAction) -> Result<()> {
671 self.workbook
672 .delete_columns(action.start_col, action.end_col)
673 }
674}