Skip to main content

binocular/input/vim/
preview_controller.rs

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}