excel_cli/app/
state.rs

1use anyhow::Result;
2use ratatui_textarea::TextArea;
3use std::collections::HashMap;
4use std::path::PathBuf;
5
6use crate::actions::UndoHistory;
7use crate::excel::Workbook;
8
9pub enum InputMode {
10    Normal,
11    Editing,
12    Command,
13    SearchForward,
14    SearchBackward,
15    Help,
16}
17
18pub struct AppState<'a> {
19    pub workbook: Workbook,
20    pub file_path: PathBuf,
21    pub selected_cell: (usize, usize), // (row, col)
22    pub start_row: usize,
23    pub start_col: usize,
24    pub visible_rows: usize,
25    pub visible_cols: usize,
26    pub input_mode: InputMode,
27    pub input_buffer: String,
28    pub text_area: TextArea<'a>,
29    pub should_quit: bool,
30    pub column_widths: Vec<usize>, // Store width for current sheet's columns
31    pub sheet_column_widths: HashMap<String, Vec<usize>>, // Store column widths for each sheet
32    pub clipboard: Option<String>, // Store copied/cut cell content
33    pub g_pressed: bool,           // Track if 'g' was pressed for 'gg' command
34    pub search_query: String,      // Current search query
35    pub search_results: Vec<(usize, usize)>, // List of cells matching the search query
36    pub current_search_idx: Option<usize>, // Index of current search result
37    pub search_direction: bool,    // true for forward, false for backward
38    pub highlight_enabled: bool,   // Control whether search results are highlighted
39    pub info_panel_height: usize,
40    pub notification_messages: Vec<String>,
41    pub max_notifications: usize,
42    pub help_text: String,
43    pub help_scroll: usize,
44    pub help_visible_lines: usize,
45    pub undo_history: UndoHistory,
46}
47
48impl AppState<'_> {
49    pub fn new(workbook: Workbook, file_path: PathBuf) -> Result<Self> {
50        // Initialize default column widths for current sheet
51        let max_cols = workbook.get_current_sheet().max_cols;
52        let default_width = 15;
53        let column_widths = vec![default_width; max_cols + 1];
54
55        // Initialize column widths for all sheets
56        let mut sheet_column_widths = HashMap::with_capacity(workbook.get_sheet_names().len());
57        let sheet_names = workbook.get_sheet_names();
58
59        for (i, name) in sheet_names.iter().enumerate() {
60            if i == workbook.get_current_sheet_index() {
61                sheet_column_widths.insert(name.clone(), column_widths.clone());
62            } else {
63                let sheet_max_cols = if let Some(sheet) = workbook.get_sheet_by_index(i) {
64                    sheet.max_cols
65                } else {
66                    max_cols // Fallback to current sheet's max_cols
67                };
68                sheet_column_widths.insert(name.clone(), vec![default_width; sheet_max_cols + 1]);
69            }
70        }
71
72        // Initialize TextArea
73        let text_area = TextArea::default();
74
75        Ok(Self {
76            workbook,
77            file_path,
78            selected_cell: (1, 1), // Excel uses 1-based indexing
79            start_row: 1,
80            start_col: 1,
81            visible_rows: 30, // Default values, will be adjusted based on window size
82            visible_cols: 15, // Default values, will be adjusted based on window size
83            input_mode: InputMode::Normal,
84            input_buffer: String::new(),
85            text_area,
86            should_quit: false,
87            column_widths,
88            sheet_column_widths,
89            clipboard: None,
90            g_pressed: false,
91            search_query: String::new(),
92            search_results: Vec::new(),
93            current_search_idx: None,
94            search_direction: true,  // Default to forward search
95            highlight_enabled: true, // Default to showing highlights
96            info_panel_height: 10,
97            notification_messages: Vec::new(),
98            max_notifications: 5,
99            help_text: String::new(),
100            help_scroll: 0,
101            help_visible_lines: 20,
102            undo_history: UndoHistory::new(),
103        })
104    }
105
106    pub fn add_notification(&mut self, message: String) {
107        self.notification_messages.push(message);
108
109        if self.notification_messages.len() > self.max_notifications {
110            self.notification_messages.remove(0);
111        }
112    }
113
114    pub fn adjust_info_panel_height(&mut self, delta: isize) {
115        let new_height = (self.info_panel_height as isize + delta).clamp(6, 16) as usize;
116        if new_height != self.info_panel_height {
117            self.info_panel_height = new_height;
118            self.add_notification(format!("Info panel height: {}", self.info_panel_height));
119        }
120    }
121
122    pub fn get_cell_content(&self, row: usize, col: usize) -> String {
123        let sheet = self.workbook.get_current_sheet();
124
125        if row < sheet.data.len() && col < sheet.data[0].len() {
126            let cell = &sheet.data[row][col];
127            if cell.is_formula {
128                let mut result = String::with_capacity(9 + cell.value.len());
129                result.push_str("Formula: ");
130                result.push_str(&cell.value);
131                result
132            } else {
133                cell.value.clone()
134            }
135        } else {
136            String::new()
137        }
138    }
139
140    pub fn get_cell_content_mut(&mut self, row: usize, col: usize) -> String {
141        self.workbook.ensure_cell_exists(row, col);
142
143        self.ensure_column_widths();
144
145        let sheet = self.workbook.get_current_sheet();
146        let cell = &sheet.data[row][col];
147
148        if cell.is_formula {
149            let mut result = String::with_capacity(9 + cell.value.len());
150            result.push_str("Formula: ");
151            result.push_str(&cell.value);
152            result
153        } else {
154            cell.value.clone()
155        }
156    }
157
158    pub fn cancel_input(&mut self) {
159        // If in help mode, just close the help window
160        if let InputMode::Help = self.input_mode {
161            self.input_mode = InputMode::Normal;
162            return;
163        }
164
165        // Otherwise, cancel the current input
166        self.input_mode = InputMode::Normal;
167        self.input_buffer = String::new();
168        self.text_area = TextArea::default();
169    }
170
171    pub fn add_char_to_input(&mut self, c: char) {
172        self.input_buffer.push(c);
173    }
174
175    pub fn delete_char_from_input(&mut self) {
176        self.input_buffer.pop();
177    }
178
179    pub fn start_command_mode(&mut self) {
180        self.input_mode = InputMode::Command;
181        self.input_buffer = String::new();
182    }
183}