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