1use anyhow::Result;
2use std::collections::HashMap;
3use std::path::PathBuf;
4use tui_textarea::TextArea;
5
6use crate::actions::UndoHistory;
7use crate::app::VimState;
8use crate::excel::{Workbook, EXCEL_MAX_COLS, EXCEL_MAX_ROWS};
9
10#[derive(Clone, Copy)]
12pub struct CellPosition {
13 pub selected: (usize, usize),
15 pub view: (usize, usize),
17}
18
19pub enum InputMode {
20 Normal,
21 Editing,
22 Command,
23 SearchForward,
24 SearchBackward,
25 Help,
26 LazyLoading,
27 CommandInLazyLoading,
28}
29
30pub struct AppState<'a> {
31 pub workbook: Workbook,
32 pub file_path: PathBuf,
33 pub selected_cell: (usize, usize), pub start_row: usize,
35 pub start_col: usize,
36 pub visible_rows: usize,
37 pub visible_cols: usize,
38 pub input_mode: InputMode,
39 pub input_buffer: String,
40 pub text_area: TextArea<'a>,
41 pub should_quit: bool,
42 pub column_widths: Vec<usize>, pub sheet_column_widths: HashMap<String, Vec<usize>>, pub sheet_cell_positions: HashMap<String, CellPosition>, pub clipboard: Option<String>, pub g_pressed: bool, pub row_number_width: usize, pub search_query: String, pub search_results: Vec<(usize, usize)>, pub current_search_idx: Option<usize>, pub search_direction: bool, pub highlight_enabled: bool, pub info_panel_height: usize,
54 pub notification_messages: Vec<String>,
55 pub max_notifications: usize,
56 pub help_text: String,
57 pub help_scroll: usize,
58 pub help_visible_lines: usize,
59 pub help_total_lines: usize,
60 pub undo_history: UndoHistory,
61 pub vim_state: Option<VimState>,
62}
63
64impl AppState<'_> {
65 pub fn new(workbook: Workbook, file_path: PathBuf) -> Result<Self> {
66 let max_cols = workbook.get_current_sheet().max_cols;
68 let default_width = 15;
69 let column_widths = vec![default_width; max_cols + 1];
70
71 let mut sheet_column_widths = HashMap::with_capacity(workbook.get_sheet_names().len());
73 let mut sheet_cell_positions = HashMap::with_capacity(workbook.get_sheet_names().len());
74 let sheet_names = workbook.get_sheet_names();
75
76 for (i, name) in sheet_names.iter().enumerate() {
77 if i == workbook.get_current_sheet_index() {
78 sheet_column_widths.insert(name.clone(), column_widths.clone());
79 sheet_cell_positions.insert(
81 name.clone(),
82 CellPosition {
83 selected: (1, 1),
84 view: (1, 1),
85 },
86 );
87 } else {
88 let sheet_max_cols = if let Some(sheet) = workbook.get_sheet_by_index(i) {
89 sheet.max_cols
90 } else {
91 max_cols };
93 sheet_column_widths.insert(name.clone(), vec![default_width; sheet_max_cols + 1]);
94 sheet_cell_positions.insert(
96 name.clone(),
97 CellPosition {
98 selected: (1, 1),
99 view: (1, 1),
100 },
101 );
102 }
103 }
104
105 let text_area = TextArea::default();
107
108 let max_rows = workbook.get_current_sheet().max_rows;
110 let row_number_width = if max_rows < 10 {
111 1
112 } else {
113 max_rows.to_string().len()
114 };
115 let row_number_width = row_number_width.max(4);
117
118 let is_lazy_loading = workbook.is_lazy_loading() && !workbook.is_sheet_loaded(0);
120
121 let initial_input_mode = if is_lazy_loading {
123 InputMode::LazyLoading
124 } else {
125 InputMode::Normal
126 };
127
128 Ok(Self {
129 workbook,
130 file_path,
131 selected_cell: (1, 1), start_row: 1,
133 start_col: 1,
134 visible_rows: 30, visible_cols: 15, input_mode: initial_input_mode,
137 input_buffer: String::new(),
138 text_area,
139 should_quit: false,
140 column_widths,
141 sheet_column_widths,
142 sheet_cell_positions,
143 clipboard: None,
144 g_pressed: false,
145 row_number_width,
146 search_query: String::new(),
147 search_results: Vec::new(),
148 current_search_idx: None,
149 search_direction: true, highlight_enabled: true, info_panel_height: 10,
152 notification_messages: Vec::new(),
153 max_notifications: 5,
154 help_text: String::new(),
155 help_scroll: 0,
156 help_visible_lines: 20,
157 help_total_lines: 0,
158 undo_history: UndoHistory::new(),
159 vim_state: None,
160 })
161 }
162
163 pub fn add_notification(&mut self, message: String) {
164 self.notification_messages.push(message);
165
166 if self.notification_messages.len() > self.max_notifications {
167 self.notification_messages.remove(0);
168 }
169 }
170
171 pub fn update_row_number_width(&mut self) {
173 let max_rows = self
174 .workbook
175 .get_current_sheet()
176 .max_rows
177 .max(self.selected_cell.0)
178 .max(self.start_row)
179 .clamp(1, EXCEL_MAX_ROWS);
180 let width = max_rows.to_string().len();
181 self.row_number_width = width.max(4);
183 }
184
185 pub fn clamp_cell_to_excel_bounds((row, col): (usize, usize)) -> (usize, usize) {
186 (row.clamp(1, EXCEL_MAX_ROWS), col.clamp(1, EXCEL_MAX_COLS))
187 }
188
189 pub fn clamp_selected_cell_to_excel_bounds(&mut self) {
190 self.selected_cell = Self::clamp_cell_to_excel_bounds(self.selected_cell);
191 self.start_row = self.start_row.clamp(1, EXCEL_MAX_ROWS);
192 self.start_col = self.start_col.clamp(1, EXCEL_MAX_COLS);
193 }
194
195 pub fn adjust_info_panel_height(&mut self, delta: isize) {
196 let new_height = (self.info_panel_height as isize + delta).clamp(6, 16) as usize;
197 if new_height != self.info_panel_height {
198 self.info_panel_height = new_height;
199 self.add_notification(format!("Info panel height: {}", self.info_panel_height));
200 }
201 }
202
203 pub fn get_cell_content(&self, row: usize, col: usize) -> String {
204 let sheet = self.workbook.get_current_sheet();
205
206 if row < sheet.data.len() && col < sheet.data[0].len() {
207 let cell = &sheet.data[row][col];
208 if cell.is_formula {
209 let mut result = String::with_capacity(9 + cell.value.len());
210 result.push_str("Formula: ");
211 result.push_str(&cell.value);
212 result
213 } else {
214 cell.value.clone()
215 }
216 } else {
217 String::new()
218 }
219 }
220
221 pub fn get_cell_content_mut(&mut self, row: usize, col: usize) -> String {
222 self.workbook.ensure_cell_exists(row, col);
223
224 self.ensure_column_widths();
225
226 let sheet = self.workbook.get_current_sheet();
227 let cell = &sheet.data[row][col];
228
229 if cell.is_formula {
230 let mut result = String::with_capacity(9 + cell.value.len());
231 result.push_str("Formula: ");
232 result.push_str(&cell.value);
233 result
234 } else {
235 cell.value.clone()
236 }
237 }
238
239 pub fn cancel_input(&mut self) {
240 match self.input_mode {
241 InputMode::Help => {
242 self.input_mode = InputMode::Normal;
243 return;
244 }
245 InputMode::CommandInLazyLoading => {
246 self.input_mode = InputMode::LazyLoading;
247 self.input_buffer = String::new();
248 self.text_area = TextArea::default();
249 return;
250 }
251 _ => {}
252 }
253
254 self.input_mode = InputMode::Normal;
256 self.input_buffer = String::new();
257 self.text_area = TextArea::default();
258 }
259
260 pub fn add_char_to_input(&mut self, c: char) {
261 self.input_buffer.push(c);
262 }
263
264 pub fn delete_char_from_input(&mut self) {
265 self.input_buffer.pop();
266 }
267
268 pub fn start_command_mode(&mut self) {
269 self.input_mode = InputMode::Command;
270 self.input_buffer = String::new();
271 }
272
273 pub fn start_command_in_lazy_loading_mode(&mut self) {
274 self.input_mode = InputMode::CommandInLazyLoading;
275 self.input_buffer = String::new();
276 }
277}