1use crate::app::{App, InputMode};
2use crate::preview::PreviewContent;
3use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
4
5use super::command_parser::{push_count_digit, take_count};
6use super::commands::{execute_command, save_file};
7use super::common_actions::parse_common_normal_action;
8use super::edit::{
9 delete_char, delete_char_under_cursor, delete_line, delete_line_content, delete_range,
10 delete_to_end_of_line, insert_char, insert_newline, open_line_above, open_line_below,
11};
12use super::motion::{
13 move_big_word_backward, move_big_word_end_forward, move_big_word_forward, move_down,
14 move_end_of_line, move_end_of_line_for_insert, move_first_non_blank, move_half_page_down,
15 move_half_page_up, move_left, move_matching_bracket, move_right, move_start_of_line,
16 move_to_end_of_file, move_to_start_of_file, move_up, move_word_backward, move_word_end_forward,
17 move_word_forward, perform_char_search,
18};
19use super::normal_action_handler::{self, CommonActionTarget};
20use super::operator_handler::{self, PendingOperatorResult, PendingOperatorTarget};
21use super::search::perform_search;
22use super::text_objects::{
23 find_inner_pair_range, find_inner_quote_range, find_inner_word_range, select_inner_word,
24};
25use super::undo::{perform_redo, perform_undo};
26use super::utils::{adjust_scroll, ensure_cursor_in_bounds, line_len};
27use super::yank::{yank_line, yank_range_content, yank_selection};
28
29pub fn handle_input(key: KeyEvent, app: &mut App) {
30 if app.preview_session.preview.state.search_active {
31 handle_search_mode(key, app);
32 return;
33 }
34
35 if app.preview_session.preview.state.mode == InputMode::Command {
36 handle_command_mode(key, app);
37 return;
38 }
39
40 if app.preview_session.preview.state.mode == InputMode::Insert {
41 handle_insert_mode(key, app);
42 return;
43 }
44
45 if let Some((forward, stored_count)) = app.preview_session.preview.state.waiting_for_char_search
46 {
47 if let KeyCode::Char(c) = key.code {
48 app.preview_session.preview.state.last_char_search = Some((c, forward));
49 perform_char_search(app, c, forward, stored_count);
50 }
51 app.preview_session.preview.state.waiting_for_char_search = None;
52 return;
53 }
54
55 if push_count_digit(&mut app.preview_session.preview.state.input_buffer, key) {
56 return;
57 }
58
59 let count = take_count(&mut app.preview_session.preview.state.input_buffer);
60
61 if let Some(op) = app.preview_session.preview.state.pending_operator {
62 handle_operator_pending(key, app, op, count);
63 } else {
64 handle_normal_visual_mode(key, app, count);
65 }
66
67 ensure_cursor_in_bounds(app);
68 adjust_scroll(app);
69 refresh_text_preview_if_needed(app);
70}
71
72fn handle_search_mode(key: KeyEvent, app: &mut App) {
73 match key.code {
74 KeyCode::Esc => {
75 app.preview_session.preview.state.search_active = false;
76 app.preview_session.preview.state.search_query.clear();
77 if app.preview_session.preview.state.mode == InputMode::Visual
78 || app.preview_session.preview.state.mode == InputMode::VisualLine
79 {
80 app.preview_session.preview.state.mode = InputMode::Normal;
81 app.preview_session.preview.state.selection_start = None;
82 app.preview_session.preview.state.pending_object_modifier = None;
83 }
84 }
85 KeyCode::Enter => {
86 app.preview_session.preview.state.search_active = false;
87 perform_search(app, true);
88 ensure_cursor_in_bounds(app);
89 adjust_scroll(app);
90 }
91 KeyCode::Char(c) => {
92 app.preview_session.preview.state.search_query.push(c);
93 }
94 KeyCode::Backspace => {
95 app.preview_session.preview.state.search_query.pop();
96 }
97 _ => {}
98 }
99}
100
101fn handle_command_mode(key: KeyEvent, app: &mut App) {
102 match key.code {
103 KeyCode::Esc => {
104 app.preview_session.preview.state.mode = InputMode::Normal;
105 app.preview_session.preview.state.command_buffer.clear();
106 }
107 KeyCode::Enter => {
108 let cmd = app.preview_session.preview.state.command_buffer.clone();
109 app.preview_session.preview.state.command_buffer.clear();
110 app.preview_session.preview.state.mode = InputMode::Normal;
111 execute_command(app, &cmd);
112 }
113 KeyCode::Char(c) => {
114 app.preview_session.preview.state.command_buffer.push(c);
115 }
116 KeyCode::Backspace => {
117 app.preview_session.preview.state.command_buffer.pop();
118 if app.preview_session.preview.state.command_buffer.is_empty() {
119 app.preview_session.preview.state.mode = InputMode::Normal;
120 }
121 }
122 _ => {}
123 }
124}
125
126fn handle_insert_mode(key: KeyEvent, app: &mut App) {
127 match key.code {
128 KeyCode::Esc => {
129 let path = std::path::PathBuf::from(app.preview_file_path().unwrap_or(""));
130 if let Some(PreviewContent::RichText(text_file)) =
131 &mut app.preview_session.preview.content
132 {
133 if text_file.dirty {
134 crate::preview::regenerate_lines(text_file, &path);
135 }
136 }
137 app.preview_session.preview.state.mode = InputMode::Normal;
138 if app.preview_session.preview.state.cursor_char > 0 {
139 app.preview_session.preview.state.cursor_char -= 1;
140 }
141 }
142 KeyCode::Char(c) => {
143 insert_char(app, c);
144 }
145 KeyCode::Enter => {
146 insert_newline(app);
147 }
148 KeyCode::Backspace => {
149 delete_char(app);
150 }
151 KeyCode::Delete => {
152 delete_char_under_cursor(app);
153 }
154 KeyCode::Left => {
155 if app.preview_session.preview.state.cursor_char > 0 {
156 app.preview_session.preview.state.cursor_char -= 1;
157 }
158 }
159 KeyCode::Right => {
160 let len = line_len(app, app.preview_session.preview.state.cursor_line);
161 if app.preview_session.preview.state.cursor_char < len {
162 app.preview_session.preview.state.cursor_char += 1;
163 }
164 }
165 KeyCode::Up => {
166 if app.preview_session.preview.state.cursor_line > 0 {
167 app.preview_session.preview.state.cursor_line -= 1;
168 clamp_cursor_to_line(app);
169 }
170 }
171 KeyCode::Down => {
172 let line_count = super::utils::get_line_count(app);
173 if app.preview_session.preview.state.cursor_line + 1 < line_count {
174 app.preview_session.preview.state.cursor_line += 1;
175 clamp_cursor_to_line(app);
176 }
177 }
178 _ => {}
179 }
180 ensure_cursor_in_bounds(app);
181 adjust_scroll(app);
182}
183
184fn refresh_text_preview_if_needed(app: &mut App) {
185 if app.preview_session.preview.state.mode == InputMode::Insert {
186 return;
187 }
188
189 let path = std::path::PathBuf::from(app.preview_file_path().unwrap_or(""));
190 if let Some(PreviewContent::RichText(text_file)) = &mut app.preview_session.preview.content {
191 if text_file.dirty {
192 crate::preview::regenerate_lines(text_file, &path);
193 }
194 }
195}
196
197fn clamp_cursor_to_line(app: &mut App) {
198 let len = line_len(app, app.preview_session.preview.state.cursor_line);
199 if app.preview_session.preview.state.cursor_char > len {
200 app.preview_session.preview.state.cursor_char = len;
201 }
202}
203
204fn execute_operator_on_range(
205 app: &mut App,
206 op: char,
207 start: (usize, usize),
208 end: (usize, usize),
209 msg: &str,
210) {
211 match op {
212 'd' => {
213 delete_range(app, start, end);
214 app.preview_session.preview.state.status_message =
215 Some((format!("Deleted {}", msg), std::time::Instant::now()));
216 }
217 'c' => {
218 delete_range(app, start, end);
219 app.preview_session.preview.state.mode = InputMode::Insert;
220 app.preview_session.preview.state.status_message =
221 Some((format!("Changed {}", msg), std::time::Instant::now()));
222 }
223 'y' => {
224 yank_range_content(app, start, end);
225 app.preview_session.preview.state.status_message =
226 Some((format!("Yanked {}", msg), std::time::Instant::now()));
227 }
228 _ => return,
229 }
230 app.preview_session.preview.state.pending_operator = None;
231 app.preview_session.preview.state.pending_object_modifier = None;
232}
233
234fn handle_operator_pending(key: KeyEvent, app: &mut App, op: char, count: usize) {
235 if app
236 .preview_session
237 .preview
238 .state
239 .pending_object_modifier
240 .is_none()
241 {
242 let shared_result = {
243 let mut target = PreviewOperatorTarget { app };
244 operator_handler::handle_pending_operator(&mut target, key, op, count)
245 };
246
247 match shared_result {
248 PendingOperatorResult::AwaitingMore
249 | PendingOperatorResult::Applied { .. }
250 | PendingOperatorResult::Cleared => return,
251 PendingOperatorResult::Unhandled => {}
252 }
253 }
254
255 match key.code {
256 KeyCode::Char('\'' | '"' | '`') => {
257 if let KeyCode::Char(quote) = key.code {
258 if let Some((start, end)) = find_inner_quote_range(app, quote) {
259 execute_operator_on_range(app, op, start, end, "inner object");
260 }
261 }
262 }
263 KeyCode::Char(c) => {
264 if app.preview_session.preview.state.pending_object_modifier == Some('i') {
265 let range = match c {
266 'w' => find_inner_word_range(app),
267 '(' | ')' | 'b' => find_inner_pair_range(app, '(', ')'),
268 '[' | ']' => find_inner_pair_range(app, '[', ']'),
269 '{' | '}' | 'B' => find_inner_pair_range(app, '{', '}'),
270 '<' | '>' => find_inner_pair_range(app, '<', '>'),
271 _ => None,
272 };
273 if let Some((start, end)) = range {
274 execute_operator_on_range(app, op, start, end, "inner object");
275 }
276 app.preview_session.preview.state.pending_operator = None;
277 app.preview_session.preview.state.pending_object_modifier = None;
278 } else {
279 app.preview_session.preview.state.pending_operator = None;
280 }
281 }
282 _ => {
283 app.preview_session.preview.state.pending_operator = None;
284 app.preview_session.preview.state.pending_object_modifier = None;
285 }
286 }
287}
288
289fn handle_normal_visual_mode(key: KeyEvent, app: &mut App, count: usize) {
290 if app.preview_session.preview.state.mode == InputMode::Normal {
291 if let Some(action) = parse_common_normal_action(key) {
292 let mut target = PreviewNormalTarget { app };
293 normal_action_handler::apply_common_normal_action(&mut target, action, count);
294 return;
295 }
296 }
297
298 match key.code {
299 KeyCode::Char('y') if !key.modifiers.contains(KeyModifiers::CONTROL) => {
300 if app.preview_session.preview.state.mode == InputMode::Visual
301 || app.preview_session.preview.state.mode == InputMode::VisualLine
302 {
303 yank_selection(app);
304 app.preview_session.preview.state.mode = InputMode::Normal;
305 app.preview_session.preview.state.selection_start = None;
306 app.preview_session.preview.state.pending_object_modifier = None;
307 } else {
308 app.preview_session.preview.state.pending_operator = Some('y');
309 }
310 }
311 KeyCode::Char('s') if key.modifiers.contains(KeyModifiers::CONTROL) => {
312 save_file(app);
313 }
314 KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => {
315 move_half_page_down(app, count)
316 }
317 KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
318 move_half_page_up(app, count)
319 }
320 KeyCode::Char('u') => {
321 for _ in 0..count {
322 perform_undo(app);
323 }
324 }
325 KeyCode::Char('r') if key.modifiers.contains(KeyModifiers::CONTROL) => {
326 for _ in 0..count {
327 perform_redo(app);
328 }
329 }
330 KeyCode::Char('U') => {
331 for _ in 0..count {
332 perform_redo(app);
333 }
334 }
335 KeyCode::Char('j') | KeyCode::Down => move_down(app, count),
336 KeyCode::Char('k') | KeyCode::Up => move_up(app, count),
337 KeyCode::Char('H') => move_start_of_line(app),
338 KeyCode::Char('L') => move_end_of_line(app),
339 KeyCode::Char('G') => move_to_end_of_file(app, count),
340 KeyCode::Char('g') => {
341 app.preview_session.preview.state.pending_operator = Some('g');
342 }
343 KeyCode::Char('/') => {
344 app.preview_session.preview.state.search_active = true;
345 app.preview_session.preview.state.search_query.clear();
346 }
347 KeyCode::Char(':') => {
348 app.preview_session.preview.state.mode = InputMode::Command;
349 app.preview_session.preview.state.command_buffer.clear();
350 }
351 KeyCode::Char('n') => {
352 for _ in 0..count {
353 perform_search(app, true);
354 }
355 }
356 KeyCode::Char('N') => {
357 for _ in 0..count {
358 perform_search(app, false);
359 }
360 }
361 KeyCode::Char('E') => move_big_word_end_forward(app, count),
362 KeyCode::Char('^') | KeyCode::Char('6') if key.modifiers.contains(KeyModifiers::SHIFT) => {
363 move_first_non_blank(app)
364 }
365 KeyCode::Char('f') => {
366 app.preview_session.preview.state.waiting_for_char_search = Some((true, count));
367 }
368 KeyCode::Char('F') => {
369 app.preview_session.preview.state.waiting_for_char_search = Some((false, count));
370 }
371 KeyCode::Char(';') => {
372 if let Some((c, forward)) = app.preview_session.preview.state.last_char_search {
373 perform_char_search(app, c, forward, count);
374 }
375 }
376 KeyCode::Char('%') => move_matching_bracket(app),
377 KeyCode::Char('v') => {
378 if app.preview_session.preview.state.mode == InputMode::Normal {
379 app.preview_session.preview.state.mode = InputMode::Visual;
380 app.preview_session.preview.state.selection_start = Some((
381 app.preview_session.preview.state.cursor_line,
382 app.preview_session.preview.state.cursor_char,
383 ));
384 } else {
385 app.preview_session.preview.state.mode = InputMode::Normal;
386 app.preview_session.preview.state.selection_start = None;
387 app.preview_session.preview.state.pending_object_modifier = None;
388 }
389 }
390 KeyCode::Char('V') => {
391 if app.preview_session.preview.state.mode == InputMode::Normal {
392 app.preview_session.preview.state.mode = InputMode::VisualLine;
393 app.preview_session.preview.state.selection_start =
394 Some((app.preview_session.preview.state.cursor_line, 0));
395 } else {
396 app.preview_session.preview.state.mode = InputMode::Normal;
397 app.preview_session.preview.state.selection_start = None;
398 app.preview_session.preview.state.pending_object_modifier = None;
399 }
400 }
401 KeyCode::Esc => {
402 if app.preview_session.preview.state.mode == InputMode::Visual
403 || app.preview_session.preview.state.mode == InputMode::VisualLine
404 {
405 app.preview_session.preview.state.mode = InputMode::Normal;
406 app.preview_session.preview.state.selection_start = None;
407 app.preview_session.preview.state.pending_object_modifier = None;
408 }
409 }
410 KeyCode::Char('Y') => {
411 if app.preview_session.preview.state.mode == InputMode::Visual
412 || app.preview_session.preview.state.mode == InputMode::VisualLine
413 {
414 yank_selection(app);
415 app.preview_session.preview.state.mode = InputMode::Normal;
416 app.preview_session.preview.state.selection_start = None;
417 app.preview_session.preview.state.pending_object_modifier = None;
418 } else {
419 yank_line(app);
420 }
421 }
422 KeyCode::Char('i') => {
423 if app.preview_session.preview.state.mode == InputMode::Visual {
424 app.preview_session.preview.state.pending_object_modifier = Some('i');
425 }
426 }
427 KeyCode::Char('o') => {
428 if app.preview_session.preview.state.mode == InputMode::Normal {
429 open_line_below(app);
430 app.preview_session.preview.state.mode = InputMode::Insert;
431 }
432 }
433 KeyCode::Char('O') => {
434 if app.preview_session.preview.state.mode == InputMode::Normal {
435 open_line_above(app);
436 app.preview_session.preview.state.mode = InputMode::Insert;
437 }
438 }
439 KeyCode::Char('w') => {
440 if app.preview_session.preview.state.mode == InputMode::Visual
441 && app.preview_session.preview.state.pending_object_modifier == Some('i')
442 {
443 select_inner_word(app);
444 app.preview_session.preview.state.pending_object_modifier = None;
445 } else {
446 move_word_forward(app, count);
447 }
448 }
449 _ => {}
450 }
451}
452
453struct PreviewNormalTarget<'a> {
454 app: &'a mut App,
455}
456
457struct PreviewOperatorTarget<'a> {
458 app: &'a mut App,
459}
460
461impl PendingOperatorTarget for PreviewOperatorTarget<'_> {
462 fn set_modifier(&mut self, modifier: char) -> bool {
463 if modifier == 'i' {
464 self.app
465 .preview_session
466 .preview
467 .state
468 .pending_object_modifier = Some(modifier);
469 true
470 } else {
471 false
472 }
473 }
474
475 fn repeat_operator(&mut self, op: char, count: usize) -> bool {
476 match op {
477 'd' => {
478 delete_line(self.app, count);
479 true
480 }
481 'c' => {
482 delete_line_content(self.app);
483 self.app.preview_session.preview.state.mode = InputMode::Insert;
484 true
485 }
486 'y' => {
487 yank_line(self.app);
488 self.app.preview_session.preview.state.status_message =
489 Some(("Yanked line".to_string(), std::time::Instant::now()));
490 true
491 }
492 'g' => {
493 move_to_start_of_file(self.app, count);
494 true
495 }
496 _ => false,
497 }
498 }
499
500 fn apply_motion(
501 &mut self,
502 _op: char,
503 _motion: super::command_parser::OperatorMotion,
504 _count: usize,
505 ) -> bool {
506 false
507 }
508
509 fn clear_pending(&mut self) {
510 self.app.preview_session.preview.state.pending_operator = None;
511 self.app
512 .preview_session
513 .preview
514 .state
515 .pending_object_modifier = None;
516 }
517}
518
519impl CommonActionTarget for PreviewNormalTarget<'_> {
520 fn move_left(&mut self, count: usize) {
521 move_left(self.app, count);
522 }
523
524 fn move_right(&mut self, count: usize) {
525 move_right(self.app, count);
526 }
527
528 fn move_start_of_line(&mut self) {
529 move_start_of_line(self.app);
530 }
531
532 fn move_end_of_line(&mut self) {
533 move_end_of_line(self.app);
534 }
535
536 fn move_first_non_blank(&mut self) {
537 move_first_non_blank(self.app);
538 }
539
540 fn move_word_forward(&mut self, count: usize) {
541 move_word_forward(self.app, count);
542 }
543
544 fn move_word_end_forward(&mut self, count: usize) {
545 move_word_end_forward(self.app, count);
546 }
547
548 fn move_word_backward(&mut self, count: usize) {
549 move_word_backward(self.app, count);
550 }
551
552 fn move_big_word_forward(&mut self, count: usize) {
553 move_big_word_forward(self.app, count);
554 }
555
556 fn move_big_word_backward(&mut self, count: usize) {
557 move_big_word_backward(self.app, count);
558 }
559
560 fn enter_insert_before(&mut self) {
561 self.app.preview_session.preview.state.mode = InputMode::Insert;
562 }
563
564 fn enter_insert_after(&mut self) {
565 move_right(self.app, 1);
566 self.app.preview_session.preview.state.mode = InputMode::Insert;
567 }
568
569 fn enter_insert_at_end(&mut self) {
570 move_end_of_line_for_insert(self.app);
571 self.app.preview_session.preview.state.mode = InputMode::Insert;
572 }
573
574 fn enter_insert_at_first_non_blank(&mut self) {
575 move_first_non_blank(self.app);
576 self.app.preview_session.preview.state.mode = InputMode::Insert;
577 }
578
579 fn delete_char_under_cursor(&mut self, count: usize) -> bool {
580 for _ in 0..count {
581 delete_char_under_cursor(self.app);
582 }
583 true
584 }
585
586 fn delete_to_end_of_line(&mut self) -> bool {
587 delete_to_end_of_line(self.app);
588 true
589 }
590
591 fn change_to_end_of_line(&mut self) -> bool {
592 delete_to_end_of_line(self.app);
593 self.app.preview_session.preview.state.mode = InputMode::Insert;
594 true
595 }
596
597 fn substitute_char(&mut self) -> bool {
598 delete_char_under_cursor(self.app);
599 self.app.preview_session.preview.state.mode = InputMode::Insert;
600 true
601 }
602
603 fn substitute_line(&mut self) -> bool {
604 delete_line_content(self.app);
605 self.app.preview_session.preview.state.mode = InputMode::Insert;
606 true
607 }
608
609 fn start_operator(&mut self, op: char) {
610 self.app.preview_session.preview.state.pending_operator = Some(op);
611 }
612}