1use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
2use tui_textarea::{Input, Key, TextArea};
3
4use crate::app::{AppState, InputMode};
5
6pub fn handle_key_event(app_state: &mut AppState, key: KeyEvent) {
7 match app_state.input_mode {
8 InputMode::Normal => {
9 if key.modifiers.contains(KeyModifiers::CONTROL)
10 || key.modifiers.contains(KeyModifiers::SUPER)
11 {
12 handle_ctrl_key(app_state, key.code);
13 } else {
14 handle_normal_mode(app_state, key.code);
15 }
16 }
17 InputMode::Editing => handle_editing_mode(app_state, key),
18 InputMode::Command => handle_command_mode(app_state, key.code),
19 InputMode::CommandInLazyLoading => handle_command_in_lazy_loading_mode(app_state, key.code),
20 InputMode::SearchForward => handle_search_mode(app_state, key.code),
21 InputMode::SearchBackward => handle_search_mode(app_state, key.code),
22 InputMode::Help => handle_help_mode(app_state, key.code),
23 InputMode::LazyLoading => handle_lazy_loading_mode(app_state, key.code),
24 }
25}
26
27fn handle_ctrl_key(app_state: &mut AppState, key_code: KeyCode) {
29 match key_code {
30 KeyCode::Left => {
31 app_state.jump_to_prev_non_empty_cell_left();
32 }
33 KeyCode::Right => {
34 app_state.jump_to_prev_non_empty_cell_right();
35 }
36 KeyCode::Up => {
37 app_state.jump_to_prev_non_empty_cell_up();
38 }
39 KeyCode::Down => {
40 app_state.jump_to_prev_non_empty_cell_down();
41 }
42 KeyCode::Char('r') => {
43 if let Err(e) = app_state.redo() {
44 app_state.add_notification(format!("Redo failed: {e}"));
45 }
46 }
47 _ => {}
48 }
49}
50
51fn handle_command_mode(app_state: &mut AppState, key_code: KeyCode) {
52 match key_code {
53 KeyCode::Enter => app_state.execute_command(),
54 KeyCode::Esc => app_state.cancel_input(),
55 KeyCode::Backspace => app_state.delete_char_from_input(),
56 KeyCode::Char(c) => app_state.add_char_to_input(c),
57 _ => {}
58 }
59}
60
61fn handle_command_in_lazy_loading_mode(app_state: &mut AppState, key_code: KeyCode) {
62 match key_code {
63 KeyCode::Enter => {
64 let current_index = app_state.workbook.get_current_sheet_index();
66 let is_sheet_loaded = app_state.workbook.is_sheet_loaded(current_index);
67
68 app_state.execute_command();
70
71 if !is_sheet_loaded
73 && !app_state
74 .workbook
75 .is_sheet_loaded(app_state.workbook.get_current_sheet_index())
76 && matches!(app_state.input_mode, InputMode::Normal)
77 {
78 app_state.input_mode = InputMode::LazyLoading;
79 }
80 }
81 KeyCode::Esc => {
82 app_state.input_mode = InputMode::LazyLoading;
84 app_state.input_buffer = String::new();
85 }
86 KeyCode::Backspace => app_state.delete_char_from_input(),
87 KeyCode::Char(c) => app_state.add_char_to_input(c),
88 _ => {}
89 }
90}
91
92fn handle_normal_mode(app_state: &mut AppState, key_code: KeyCode) {
93 match key_code {
94 KeyCode::Enter => {
95 app_state.g_pressed = false;
96
97 let index = app_state.workbook.get_current_sheet_index();
99 let sheet_name = app_state.workbook.get_current_sheet_name();
100
101 if app_state.workbook.is_lazy_loading() && !app_state.workbook.is_sheet_loaded(index) {
102 if let Err(e) = app_state.workbook.ensure_sheet_loaded(index, &sheet_name) {
104 app_state.add_notification(format!("Failed to load sheet: {e}"));
105 } else {
106 app_state.start_editing();
107 }
108 } else {
109 app_state.start_editing();
110 }
111 }
112 KeyCode::Char('h') => {
113 app_state.g_pressed = false;
114 app_state.move_cursor(0, -1);
115 }
116 KeyCode::Char('j') => {
117 app_state.g_pressed = false;
118 app_state.move_cursor(1, 0);
119 }
120 KeyCode::Char('k') => {
121 app_state.g_pressed = false;
122 app_state.move_cursor(-1, 0);
123 }
124 KeyCode::Char('l') => {
125 app_state.g_pressed = false;
126 app_state.move_cursor(0, 1);
127 }
128 KeyCode::Char('u') => {
129 app_state.g_pressed = false;
130 if let Err(e) = app_state.undo() {
131 app_state.add_notification(format!("Undo failed: {e}"));
132 }
133 }
134 KeyCode::Char('=' | '+') => {
135 app_state.g_pressed = false;
136 app_state.adjust_info_panel_height(1);
137 }
138 KeyCode::Char('-') => {
139 app_state.g_pressed = false;
140 app_state.adjust_info_panel_height(-1);
141 }
142 KeyCode::Char('[') => {
143 app_state.g_pressed = false;
144 if let Err(e) = app_state.prev_sheet() {
145 app_state.add_notification(format!("Failed to switch to previous sheet: {e}"));
146 }
147 }
148 KeyCode::Char(']') => {
149 app_state.g_pressed = false;
150 if let Err(e) = app_state.next_sheet() {
151 app_state.add_notification(format!("Failed to switch to next sheet: {e}"));
152 }
153 }
154 KeyCode::Char('g') => {
155 if app_state.g_pressed {
156 app_state.jump_to_first_row();
157 app_state.g_pressed = false;
158 } else {
159 app_state.g_pressed = true;
160 }
161 }
162 KeyCode::Char('G') => {
163 app_state.g_pressed = false;
164 app_state.jump_to_last_row();
165 }
166 KeyCode::Char('0') => {
167 app_state.g_pressed = false;
168 app_state.jump_to_first_column();
169 }
170 KeyCode::Char('^') => {
171 app_state.g_pressed = false;
172 app_state.jump_to_first_non_empty_column();
173 }
174 KeyCode::Char('$') => {
175 app_state.g_pressed = false;
176 app_state.jump_to_last_column();
177 }
178 KeyCode::Char('y') => {
179 app_state.g_pressed = false;
180 app_state.copy_cell();
181 }
182 KeyCode::Char('d') => {
183 app_state.g_pressed = false;
184 if let Err(e) = app_state.cut_cell() {
185 app_state.add_notification(format!("Cut failed: {e}"));
186 }
187 }
188 KeyCode::Char('p') => {
189 app_state.g_pressed = false;
190 if let Err(e) = app_state.paste_cell() {
191 app_state.add_notification(format!("Paste failed: {e}"));
192 }
193 }
194 KeyCode::Char(':') => {
195 app_state.g_pressed = false;
196 app_state.start_command_mode();
197 }
198 KeyCode::Char('/') => {
199 app_state.g_pressed = false;
200 app_state.start_search_forward();
201 }
202 KeyCode::Char('?') => {
203 app_state.g_pressed = false;
204 app_state.start_search_backward();
205 }
206 KeyCode::Char('n') => {
207 app_state.g_pressed = false;
208 if !app_state.search_results.is_empty() {
209 app_state.jump_to_next_search_result();
210 } else if !app_state.search_query.is_empty() {
211 app_state.search_results = app_state.find_all_matches(&app_state.search_query);
213 if !app_state.search_results.is_empty() {
214 app_state.jump_to_next_search_result();
215 }
216 }
217 }
218
219 KeyCode::Char('N') => {
220 app_state.g_pressed = false;
221 if !app_state.search_results.is_empty() {
222 app_state.jump_to_prev_search_result();
223 } else if !app_state.search_query.is_empty() {
224 app_state.search_results = app_state.find_all_matches(&app_state.search_query);
226 if !app_state.search_results.is_empty() {
227 app_state.jump_to_prev_search_result();
228 }
229 }
230 }
231
232 KeyCode::Left => {
233 app_state.g_pressed = false;
234 app_state.move_cursor(0, -1);
235 }
236 KeyCode::Right => {
237 app_state.g_pressed = false;
238 app_state.move_cursor(0, 1);
239 }
240 KeyCode::Up => {
241 app_state.g_pressed = false;
242 app_state.move_cursor(-1, 0);
243 }
244 KeyCode::Down => {
245 app_state.g_pressed = false;
246 app_state.move_cursor(1, 0);
247 }
248 _ => {
249 app_state.g_pressed = false;
250 }
251 }
252}
253
254fn handle_editing_mode(app_state: &mut AppState, key: KeyEvent) {
255 let input = Input {
257 key: key_code_to_tui_key(key.code),
258 ctrl: key.modifiers.contains(KeyModifiers::CONTROL),
259 alt: key.modifiers.contains(KeyModifiers::ALT),
260 shift: key.modifiers.contains(KeyModifiers::SHIFT),
261 };
262
263 if let Err(e) = app_state.handle_vim_input(input) {
264 app_state.add_notification(format!("Vim input error: {e}"));
265 }
266}
267
268fn handle_search_mode(app_state: &mut AppState, key_code: KeyCode) {
269 match key_code {
270 KeyCode::Enter => app_state.execute_search(),
271 KeyCode::Esc => {
272 app_state.input_mode = InputMode::Normal;
273 app_state.input_buffer = String::new();
274 app_state.text_area = TextArea::default();
275 }
276 _ => {
277 let input = Input {
278 key: key_code_to_tui_key(key_code),
279 ctrl: false,
280 alt: false,
281 shift: false,
282 };
283 app_state.text_area.input(input);
284 }
285 }
286}
287
288fn key_code_to_tui_key(key_code: KeyCode) -> Key {
290 match key_code {
291 KeyCode::Backspace => Key::Backspace,
292 KeyCode::Enter => Key::Enter,
293 KeyCode::Left => Key::Left,
294 KeyCode::Right => Key::Right,
295 KeyCode::Up => Key::Up,
296 KeyCode::Down => Key::Down,
297 KeyCode::Home => Key::Home,
298 KeyCode::End => Key::End,
299 KeyCode::PageUp => Key::PageUp,
300 KeyCode::PageDown => Key::PageDown,
301 KeyCode::Tab => Key::Tab,
302 KeyCode::Delete => Key::Delete,
303 KeyCode::BackTab | KeyCode::Insert => Key::Null,
305 KeyCode::Esc => Key::Esc,
306 KeyCode::Char(c) => Key::Char(c),
307 KeyCode::F(n) => Key::F(n),
308 _ => Key::Null,
309 }
310}
311
312fn handle_lazy_loading_mode(app_state: &mut AppState, key_code: KeyCode) {
313 match key_code {
314 KeyCode::Enter => {
315 let index = app_state.workbook.get_current_sheet_index();
316 let sheet_name = app_state.workbook.get_current_sheet_name();
317
318 if let Err(e) = app_state.workbook.ensure_sheet_loaded(index, &sheet_name) {
320 app_state.add_notification(format!("Failed to load sheet: {e}"));
321 }
322
323 app_state.input_mode = InputMode::Normal;
324 }
325 KeyCode::Char('[') => {
326 let current_index = app_state.workbook.get_current_sheet_index();
328
329 if current_index == 0 {
330 app_state.add_notification("Already at the first sheet".to_string());
331 } else {
332 if let Err(e) = app_state.switch_sheet_by_index(current_index - 1) {
334 app_state.add_notification(format!("Failed to switch to previous sheet: {e}"));
335 }
336 }
337 }
338 KeyCode::Char(']') => {
339 let current_index = app_state.workbook.get_current_sheet_index();
341 let sheet_count = app_state.workbook.get_sheet_names().len();
342
343 if current_index >= sheet_count - 1 {
344 app_state.add_notification("Already at the last sheet".to_string());
345 } else {
346 if let Err(e) = app_state.switch_sheet_by_index(current_index + 1) {
348 app_state.add_notification(format!("Failed to switch to next sheet: {e}"));
349 }
350 }
351 }
352 KeyCode::Char(':') => {
353 app_state.start_command_in_lazy_loading_mode();
355 }
356 _ => {
357 app_state.add_notification(
358 "Press Enter to load the sheet data, or use [ and ] to switch sheets".to_string(),
359 );
360 }
361 }
362}
363
364fn handle_help_mode(app_state: &mut AppState, key_code: KeyCode) {
365 let line_count = app_state.help_text.lines().count();
366
367 let visible_lines = app_state.help_visible_lines;
368
369 let max_scroll = line_count.saturating_sub(visible_lines).max(0);
370
371 match key_code {
372 KeyCode::Enter | KeyCode::Esc => {
373 app_state.input_mode = InputMode::Normal;
374 }
375 KeyCode::Char('j') | KeyCode::Down => {
376 app_state.help_scroll = (app_state.help_scroll + 1).min(max_scroll);
378 }
379 KeyCode::Char('k') | KeyCode::Up => {
380 app_state.help_scroll = app_state.help_scroll.saturating_sub(1);
382 }
383 KeyCode::Home => {
384 app_state.help_scroll = 0;
386 }
387 KeyCode::End => {
388 app_state.help_scroll = max_scroll;
390 }
391 _ => {}
392 }
393}