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