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;
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 undo_history: UndoHistory,
60 pub vim_state: Option<VimState>,
61}
62
63impl AppState<'_> {
64 pub fn new(workbook: Workbook, file_path: PathBuf) -> Result<Self> {
65 let max_cols = workbook.get_current_sheet().max_cols;
67 let default_width = 15;
68 let column_widths = vec![default_width; max_cols + 1];
69
70 let mut sheet_column_widths = HashMap::with_capacity(workbook.get_sheet_names().len());
72 let mut sheet_cell_positions = HashMap::with_capacity(workbook.get_sheet_names().len());
73 let sheet_names = workbook.get_sheet_names();
74
75 for (i, name) in sheet_names.iter().enumerate() {
76 if i == workbook.get_current_sheet_index() {
77 sheet_column_widths.insert(name.clone(), column_widths.clone());
78 sheet_cell_positions.insert(
80 name.clone(),
81 CellPosition {
82 selected: (1, 1),
83 view: (1, 1),
84 },
85 );
86 } else {
87 let sheet_max_cols = if let Some(sheet) = workbook.get_sheet_by_index(i) {
88 sheet.max_cols
89 } else {
90 max_cols };
92 sheet_column_widths.insert(name.clone(), vec![default_width; sheet_max_cols + 1]);
93 sheet_cell_positions.insert(
95 name.clone(),
96 CellPosition {
97 selected: (1, 1),
98 view: (1, 1),
99 },
100 );
101 }
102 }
103
104 let text_area = TextArea::default();
106
107 let max_rows = workbook.get_current_sheet().max_rows;
109 let row_number_width = if max_rows < 10 {
110 1
111 } else {
112 max_rows.to_string().len()
113 };
114 let row_number_width = row_number_width.max(4);
116
117 let is_lazy_loading = workbook.is_lazy_loading() && !workbook.is_sheet_loaded(0);
119
120 let initial_input_mode = if is_lazy_loading {
122 InputMode::LazyLoading
123 } else {
124 InputMode::Normal
125 };
126
127 Ok(Self {
128 workbook,
129 file_path,
130 selected_cell: (1, 1), start_row: 1,
132 start_col: 1,
133 visible_rows: 30, visible_cols: 15, input_mode: initial_input_mode,
136 input_buffer: String::new(),
137 text_area,
138 should_quit: false,
139 column_widths,
140 sheet_column_widths,
141 sheet_cell_positions,
142 clipboard: None,
143 g_pressed: false,
144 row_number_width,
145 search_query: String::new(),
146 search_results: Vec::new(),
147 current_search_idx: None,
148 search_direction: true, highlight_enabled: true, info_panel_height: 10,
151 notification_messages: Vec::new(),
152 max_notifications: 5,
153 help_text: String::new(),
154 help_scroll: 0,
155 help_visible_lines: 20,
156 undo_history: UndoHistory::new(),
157 vim_state: None,
158 })
159 }
160
161 pub fn add_notification(&mut self, message: String) {
162 self.notification_messages.push(message);
163
164 if self.notification_messages.len() > self.max_notifications {
165 self.notification_messages.remove(0);
166 }
167 }
168
169 pub fn update_row_number_width(&mut self) {
171 let max_rows = self.workbook.get_current_sheet().max_rows;
172 let width = max_rows.to_string().len();
173 self.row_number_width = width.max(4);
175 }
176
177 pub fn adjust_info_panel_height(&mut self, delta: isize) {
178 let new_height = (self.info_panel_height as isize + delta).clamp(6, 16) as usize;
179 if new_height != self.info_panel_height {
180 self.info_panel_height = new_height;
181 self.add_notification(format!("Info panel height: {}", self.info_panel_height));
182 }
183 }
184
185 pub fn get_cell_content(&self, row: usize, col: usize) -> String {
186 let sheet = self.workbook.get_current_sheet();
187
188 if row < sheet.data.len() && col < sheet.data[0].len() {
189 let cell = &sheet.data[row][col];
190 if cell.is_formula {
191 let mut result = String::with_capacity(9 + cell.value.len());
192 result.push_str("Formula: ");
193 result.push_str(&cell.value);
194 result
195 } else {
196 cell.value.clone()
197 }
198 } else {
199 String::new()
200 }
201 }
202
203 pub fn get_cell_content_mut(&mut self, row: usize, col: usize) -> String {
204 self.workbook.ensure_cell_exists(row, col);
205
206 self.ensure_column_widths();
207
208 let sheet = self.workbook.get_current_sheet();
209 let cell = &sheet.data[row][col];
210
211 if cell.is_formula {
212 let mut result = String::with_capacity(9 + cell.value.len());
213 result.push_str("Formula: ");
214 result.push_str(&cell.value);
215 result
216 } else {
217 cell.value.clone()
218 }
219 }
220
221 pub fn cancel_input(&mut self) {
222 if let InputMode::Help = self.input_mode {
224 self.input_mode = InputMode::Normal;
225 return;
226 }
227
228 if let InputMode::CommandInLazyLoading = self.input_mode {
230 self.input_mode = InputMode::LazyLoading;
231 self.input_buffer = String::new();
232 self.text_area = TextArea::default();
233 return;
234 }
235
236 self.input_mode = InputMode::Normal;
238 self.input_buffer = String::new();
239 self.text_area = TextArea::default();
240 }
241
242 pub fn add_char_to_input(&mut self, c: char) {
243 self.input_buffer.push(c);
244 }
245
246 pub fn delete_char_from_input(&mut self) {
247 self.input_buffer.pop();
248 }
249
250 pub fn start_command_mode(&mut self) {
251 self.input_mode = InputMode::Command;
252 self.input_buffer = String::new();
253 }
254
255 pub fn start_command_in_lazy_loading_mode(&mut self) {
256 self.input_mode = InputMode::CommandInLazyLoading;
257 self.input_buffer = String::new();
258 }
259}