hex_patch/app/
events.rs

1use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyModifiers};
2use ratatui::{backend::Backend, Terminal};
3
4use crate::get_app_context;
5
6use super::{
7    plugins::ui_location::point::Point,
8    popup::{binary_choice::BinaryChoice, popup_state::PopupState, simple_choice::SimpleChoice},
9    settings::key_settings::KeySettings,
10    App,
11};
12
13impl App {
14    fn handle_event_normal(
15        &mut self,
16        event: event::Event,
17    ) -> Result<(), Box<dyn std::error::Error>> {
18        match event {
19            event::Event::Key(event) if event.kind == event::KeyEventKind::Press => {
20                if event == self.settings.key.up {
21                    self.move_cursor_in_selected_panel(0, -1);
22                    // self.move_cursor(0, -1, false);
23                } else if event == self.settings.key.down {
24                    self.move_cursor_in_selected_panel(0, 1);
25                    // self.move_cursor(0, 1, false);
26                } else if event == self.settings.key.left {
27                    self.move_cursor_in_selected_panel(-1, 0);
28                    // self.move_cursor(-1, 0, false);
29                } else if event == self.settings.key.right {
30                    self.move_cursor_in_selected_panel(1, 0);
31                    // self.move_cursor(1, 0, false);
32                } else if event == self.settings.key.next {
33                    match self.info_mode {
34                        super::info_mode::InfoMode::Text => {
35                            self.move_cursor(16, 0, true);
36                        }
37                        super::info_mode::InfoMode::Assembly => {
38                            self.move_cursor_to_near_instruction(1);
39                        }
40                    }
41                } else if event == self.settings.key.previous {
42                    match self.info_mode {
43                        super::info_mode::InfoMode::Text => {
44                            self.move_cursor(-16, 0, true);
45                        }
46                        super::info_mode::InfoMode::Assembly => {
47                            self.move_cursor_to_near_instruction(-1);
48                        }
49                    }
50                } else if event == self.settings.key.page_up {
51                    self.move_cursor_page_up();
52                } else if event == self.settings.key.page_down {
53                    self.move_cursor_page_down();
54                } else if event == self.settings.key.goto_start {
55                    self.move_cursor_to_start();
56                } else if event == self.settings.key.goto_end {
57                    self.move_cursor_to_end();
58                } else if event == self.settings.key.quit {
59                    self.request_quit();
60                } else if event == self.settings.key.save_as {
61                    self.request_popup_save_as();
62                } else if event == self.settings.key.save {
63                    self.request_save();
64                } else if event == self.settings.key.save_and_quit {
65                    self.request_save_and_quit();
66                } else if event == self.settings.key.open {
67                    self.request_open()?;
68                } else if event == self.settings.key.help {
69                    self.request_popup_help();
70                } else if event == self.settings.key.log {
71                    self.request_popup_log();
72                } else if event == self.settings.key.run {
73                    self.request_popup_run();
74                } else if event == self.settings.key.find_text {
75                    self.request_popup_find_text();
76                } else if event == self.settings.key.find_symbol {
77                    self.request_popup_find_symbol();
78                } else if event == self.settings.key.patch_text {
79                    self.request_popup_text();
80                } else if event == self.settings.key.patch_assembly {
81                    self.request_popup_patch();
82                } else if event == self.settings.key.jump {
83                    self.request_popup_jump();
84                } else if event == self.settings.key.change_view {
85                    self.request_view_change();
86                } else if event == self.settings.key.undo {
87                    self.undo();
88                } else if event == self.settings.key.redo {
89                    self.redo();
90                } else if event == self.settings.key.change_selected_pane {
91                    self.switch_selected_pane();
92                } else if event == self.settings.key.fullscreen {
93                    self.switch_fullscreen();
94                } else if let KeyCode::Char(c) = event.code {
95                    match c {
96                        '0'..='9' | 'A'..='F' | 'a'..='f' => {
97                            self.edit_data(c)?;
98                        }
99                        _ => {}
100                    }
101                }
102            }
103            event::Event::Mouse(event) => match event.kind {
104                event::MouseEventKind::ScrollUp => {
105                    self.move_cursor(0, -1, false);
106                }
107                event::MouseEventKind::ScrollDown => {
108                    self.move_cursor(0, 1, false);
109                }
110                event::MouseEventKind::ScrollLeft => {
111                    self.move_cursor(-1, 0, false);
112                }
113                event::MouseEventKind::ScrollRight => {
114                    self.move_cursor(1, 0, false);
115                }
116                _ => {}
117            },
118            event::Event::Resize(width, height) => {
119                self.resize_to_size(width, height);
120            }
121            _ => {}
122        }
123        Ok(())
124    }
125
126    fn handle_string_edit(
127        string: &mut String,
128        cursor: &mut usize,
129        event: &event::Event,
130        charset: Option<&str>,
131        max_len: Option<usize>,
132        multiline: bool,
133        key_settings: &KeySettings,
134    ) -> Result<(), Box<dyn std::error::Error>> {
135        match event {
136            event::Event::Key(event) if event.kind == event::KeyEventKind::Press => {
137                if *event == KeyEvent::new(KeyCode::Backspace, KeyModifiers::empty()) {
138                    if *cursor > 0 {
139                        string.remove(*cursor - 1);
140                        *cursor -= 1;
141                    }
142                } else if *event == KeyEvent::new(KeyCode::Delete, KeyModifiers::empty()) {
143                    if *cursor < string.len() {
144                        string.remove(*cursor);
145                    }
146                } else if *event == KeyEvent::new(KeyCode::Left, KeyModifiers::empty()) {
147                    if *cursor > 0 {
148                        *cursor -= 1;
149                    }
150                } else if *event == KeyEvent::new(KeyCode::Right, KeyModifiers::empty()) {
151                    if *cursor < string.len() {
152                        *cursor += 1;
153                    }
154                } else if *event == KeyEvent::new(KeyCode::Up, KeyModifiers::empty()) && multiline {
155                    let line = string
156                        .chars()
157                        .rev()
158                        .skip(string.len() - *cursor)
159                        .take_while(|c| *c != '\n')
160                        .count();
161                    *cursor = cursor.saturating_sub(line + 1);
162                } else if *event == KeyEvent::new(KeyCode::Down, KeyModifiers::empty()) && multiline
163                {
164                    let line = string
165                        .chars()
166                        .skip(*cursor)
167                        .take_while(|c| *c != '\n')
168                        .count();
169                    *cursor = cursor.saturating_add(line + 1).min(string.len());
170                } else if *event == KeyEvent::new(KeyCode::Home, KeyModifiers::empty()) {
171                    *cursor = 0;
172                } else if *event == KeyEvent::new(KeyCode::End, KeyModifiers::empty()) {
173                    *cursor = string.len();
174                } else if *event == key_settings.new_line && multiline {
175                    string.insert(*cursor, '\n');
176                    *cursor += 1;
177                } else if let KeyCode::Char(c) = event.code {
178                    if (max_len.is_none() || string.len() < max_len.expect("Just checked"))
179                        && (charset.is_none() || charset.expect("Just checked").contains(c))
180                    {
181                        string.insert(*cursor, c);
182                        *cursor += 1;
183                    }
184                }
185            }
186            _ => {}
187        }
188        Ok(())
189    }
190
191    fn handle_popup_scroll(scroll: &mut usize, len: usize, lines: Option<usize>, direction: i8) {
192        if direction > 0 {
193            *scroll = (*scroll).saturating_add(1);
194            if let Some(lines) = lines {
195                if *scroll as isize >= len as isize - lines as isize {
196                    *scroll = len.saturating_sub(lines);
197                }
198            } else if *scroll as isize >= len as isize {
199                *scroll = len.saturating_sub(1);
200            }
201        } else {
202            *scroll = (*scroll).saturating_sub(1);
203        }
204    }
205
206    fn handle_event_popup<B: Backend>(
207        &mut self,
208        event: event::Event,
209        terminal: &mut Terminal<B>,
210    ) -> Result<(), Box<dyn std::error::Error>> {
211        let mut popup = self.popup.clone();
212        match &mut popup {
213            Some(PopupState::Open {
214                currently_open_path,
215                path,
216                cursor,
217                results,
218                scroll: _scroll,
219            }) => {
220                let old_path = path.clone();
221                Self::handle_string_edit(
222                    path,
223                    cursor,
224                    &event,
225                    None,
226                    None,
227                    false,
228                    &self.settings.key,
229                )?;
230                if old_path != *path || results.is_empty() {
231                    *results =
232                        Self::find_dir_contents(currently_open_path, path, &self.filesystem)?;
233                }
234            }
235            Some(PopupState::Run {
236                command,
237                cursor,
238                results,
239                scroll: _scroll,
240            }) => {
241                let old_command = command.clone();
242                Self::handle_string_edit(
243                    command,
244                    cursor,
245                    &event,
246                    None,
247                    None,
248                    false,
249                    &self.settings.key,
250                )?;
251                if old_command != *command || results.is_empty() {
252                    *results = self.find_commands(command);
253                }
254            }
255            Some(PopupState::FindText { text, cursor }) => {
256                Self::handle_string_edit(
257                    text,
258                    cursor,
259                    &event,
260                    None,
261                    None,
262                    false,
263                    &self.settings.key,
264                )?;
265            }
266            Some(PopupState::FindSymbol {
267                filter,
268                symbols,
269                cursor,
270                scroll: _scroll,
271            }) => {
272                let old_filter = filter.clone();
273                Self::handle_string_edit(
274                    filter,
275                    cursor,
276                    &event,
277                    None,
278                    None,
279                    false,
280                    &self.settings.key,
281                )?;
282                if old_filter != *filter || symbols.is_empty() {
283                    *symbols = self.find_symbols(filter);
284                }
285            }
286            Some(PopupState::InsertText { text, cursor }) => {
287                Self::handle_string_edit(
288                    text,
289                    cursor,
290                    &event,
291                    None,
292                    None,
293                    true,
294                    &self.settings.key,
295                )?;
296            }
297            Some(PopupState::Patch {
298                assembly,
299                preview,
300                cursor,
301            }) => {
302                Self::handle_string_edit(
303                    assembly,
304                    cursor,
305                    &event,
306                    None,
307                    None,
308                    true,
309                    &self.settings.key,
310                )?;
311                if let Some(current_instruction) = self.get_current_instruction() {
312                    *preview =
313                        self.bytes_from_assembly(assembly, current_instruction.virtual_address());
314                }
315            }
316            Some(PopupState::JumpToAddress {
317                location: address,
318                cursor,
319            }) => {
320                Self::handle_string_edit(
321                    address,
322                    cursor,
323                    &event,
324                    None,
325                    None,
326                    false,
327                    &self.settings.key,
328                )?;
329            }
330            Some(PopupState::SaveAs { path, cursor }) => {
331                Self::handle_string_edit(
332                    path,
333                    cursor,
334                    &event,
335                    None,
336                    None,
337                    false,
338                    &self.settings.key,
339                )?;
340            }
341            _ => {}
342        }
343
344        match event {
345            event::Event::Key(event) if event.kind == event::KeyEventKind::Press => {
346                if event == self.settings.key.right {
347                    match &mut popup {
348                        Some(PopupState::Save(choice)) | Some(PopupState::SaveAndQuit(choice)) => {
349                            *choice = choice.next();
350                        }
351                        Some(PopupState::QuitDirtySave(choice)) => {
352                            *choice = choice.next();
353                        }
354                        _ => {}
355                    }
356                } else if event == self.settings.key.left {
357                    match &mut popup {
358                        Some(PopupState::Save(choice)) | Some(PopupState::SaveAndQuit(choice)) => {
359                            *choice = choice.previous();
360                        }
361                        Some(PopupState::QuitDirtySave(choice)) => {
362                            *choice = choice.previous();
363                        }
364                        _ => {}
365                    }
366                } else if event == self.settings.key.confirm {
367                    match &mut popup {
368                        Some(PopupState::Open {
369                            currently_open_path,
370                            path,
371                            cursor: _cursor,
372                            results: _results,
373                            scroll,
374                        }) => {
375                            let mut new_popup = None;
376                            self.go_to_path(
377                                currently_open_path,
378                                path,
379                                *scroll,
380                                &mut new_popup,
381                                terminal,
382                            )?;
383                            popup = new_popup;
384                        }
385                        Some(PopupState::Run {
386                            command,
387                            cursor: _cursor,
388                            results: _results,
389                            scroll,
390                        }) => {
391                            self.run_command(command, *scroll)?;
392                            popup.clone_from(&self.popup);
393                        }
394                        Some(PopupState::FindText {
395                            text,
396                            cursor: _cursor,
397                        }) => {
398                            self.find_text(text);
399                            // Maybe removing the popup is not a good idea, more testing needed
400                            popup = None;
401                        }
402                        Some(PopupState::FindSymbol {
403                            filter,
404                            symbols,
405                            cursor: _cursor,
406                            scroll,
407                        }) => {
408                            self.jump_to_fuzzy_symbol(filter, symbols, *scroll);
409                            popup = None;
410                        }
411                        Some(PopupState::Log(_)) => {
412                            popup = None;
413                        }
414                        Some(PopupState::InsertText {
415                            text,
416                            cursor: _cursor,
417                        }) => {
418                            self.insert_text(text);
419                            popup = None;
420                        }
421                        Some(PopupState::Patch {
422                            assembly,
423                            preview: _preview,
424                            cursor: _cursor,
425                        }) => {
426                            self.patch(assembly);
427                            popup = None;
428                        }
429                        Some(PopupState::JumpToAddress {
430                            location,
431                            cursor: _cursor,
432                        }) => {
433                            self.jump_to_symbol(location);
434                            popup = None;
435                        }
436                        Some(PopupState::SaveAs { path, cursor: _ }) => {
437                            self.save_file_as(path)?;
438                            popup = None;
439                        }
440                        Some(PopupState::Save(choice)) => {
441                            if *choice == BinaryChoice::Yes {
442                                self.save_file()?;
443                            }
444                            popup = None;
445                        }
446                        Some(PopupState::SaveAndQuit(choice)) => {
447                            if *choice == BinaryChoice::Yes {
448                                self.save_file()?;
449                                self.needs_to_exit = true;
450                            }
451                            popup = None;
452                        }
453                        Some(PopupState::QuitDirtySave(choice)) => {
454                            match choice {
455                                SimpleChoice::Yes => {
456                                    self.save_file()?;
457                                    self.needs_to_exit = true;
458                                }
459                                SimpleChoice::No => {
460                                    self.needs_to_exit = true;
461                                }
462                                SimpleChoice::Cancel => {}
463                            }
464                            popup = None;
465                        }
466                        Some(PopupState::Help(_)) => {
467                            popup = None;
468                        }
469                        Some(PopupState::Custom {
470                            plugin_index: _,
471                            callback: _,
472                        }) => {}
473                        None => {}
474                    }
475                } else if event == self.settings.key.down {
476                    match &mut popup {
477                        Some(PopupState::Open {
478                            currently_open_path: _currently_open_path,
479                            path: _path,
480                            cursor: _cursor,
481                            results,
482                            scroll,
483                        }) => {
484                            Self::handle_popup_scroll(scroll, results.len(), None, 1);
485                        }
486                        Some(PopupState::Run {
487                            command: _command,
488                            cursor: _cursor,
489                            results,
490                            scroll,
491                        }) => {
492                            Self::handle_popup_scroll(scroll, results.len(), None, 1);
493                        }
494                        Some(PopupState::FindSymbol {
495                            filter: _filter,
496                            symbols,
497                            cursor: _cursor,
498                            scroll,
499                        }) => {
500                            if symbols.is_empty() {
501                                if let Some(symbols) = self.header.get_symbols() {
502                                    Self::handle_popup_scroll(scroll, symbols.len(), None, 1);
503                                } else {
504                                    *scroll = 0;
505                                }
506                            } else {
507                                Self::handle_popup_scroll(scroll, symbols.len(), None, 1);
508                            }
509                        }
510                        Some(PopupState::Log(scroll)) => {
511                            Self::handle_popup_scroll(
512                                scroll,
513                                self.logger.len(),
514                                Some(self.get_scrollable_popup_line_count()),
515                                -1,
516                            );
517                        }
518                        Some(PopupState::Help(scroll)) => {
519                            Self::handle_popup_scroll(
520                                scroll,
521                                self.help_list.len(),
522                                Some(self.get_scrollable_popup_line_count()),
523                                1,
524                            );
525                        }
526                        _ => {}
527                    }
528                } else if event == self.settings.key.up {
529                    match &mut popup {
530                        Some(PopupState::Open {
531                            currently_open_path: _currently_open_path,
532                            path: _path,
533                            cursor: _cursor,
534                            results,
535                            scroll,
536                        }) => {
537                            Self::handle_popup_scroll(scroll, results.len(), None, -1);
538                        }
539                        Some(PopupState::Run {
540                            command: _command,
541                            cursor: _cursor,
542                            results: _results,
543                            scroll,
544                        }) => {
545                            Self::handle_popup_scroll(scroll, _results.len(), None, -1);
546                        }
547                        Some(PopupState::FindSymbol {
548                            filter: _filter,
549                            symbols,
550                            cursor: _cursor,
551                            scroll,
552                        }) => {
553                            Self::handle_popup_scroll(scroll, symbols.len(), None, -1);
554                        }
555                        Some(PopupState::Log(scroll)) => {
556                            Self::handle_popup_scroll(
557                                scroll,
558                                self.logger.len(),
559                                Some(self.get_scrollable_popup_line_count()),
560                                1,
561                            );
562                        }
563                        Some(PopupState::Help(scroll)) => {
564                            Self::handle_popup_scroll(
565                                scroll,
566                                self.help_list.len(),
567                                Some(self.get_scrollable_popup_line_count()),
568                                -1,
569                            );
570                        }
571                        _ => {}
572                    }
573                } else if event == self.settings.key.close_popup {
574                    if self.filesystem.is_file(self.filesystem.pwd())
575                    // if no file is open, close the program instead of the popup
576                    {
577                        popup = None;
578                    } else {
579                        self.needs_to_exit = true;
580                    }
581                } else if event == self.settings.key.clear_log {
582                    if let Some(PopupState::Log(scroll)) = &mut popup {
583                        *scroll = 0;
584                        self.logger.clear();
585                    }
586                } else if let KeyCode::Char(_) | KeyCode::Backspace | KeyCode::Delete = event.code {
587                    if event.modifiers.is_empty() {
588                        match &mut popup {
589                            Some(PopupState::Open {
590                                currently_open_path: _currently_open_path,
591                                path: _path,
592                                cursor: _cursor,
593                                results: _results,
594                                scroll,
595                            }) => {
596                                *scroll = 0;
597                            }
598                            Some(PopupState::Run {
599                                command: _command,
600                                cursor: _cursor,
601                                results: _results,
602                                scroll,
603                            }) => {
604                                *scroll = 0;
605                            }
606                            Some(PopupState::FindSymbol {
607                                filter: _,
608                                symbols: _,
609                                cursor: _,
610                                scroll,
611                            }) => {
612                                *scroll = 0;
613                            }
614                            _ => {}
615                        }
616                    }
617                }
618            }
619            event::Event::Resize(width, height) => {
620                Self::resize_popup_if_needed(&mut popup);
621                self.resize_to_size(width, height);
622            }
623            _ => {}
624        }
625        self.popup = popup;
626        Ok(())
627    }
628
629    fn handle_plugin_events(&mut self, event: &Event) -> Result<(), Box<dyn std::error::Error>> {
630        match event {
631            event::Event::FocusGained => {
632                let mut app_context = get_app_context!(self);
633                self.plugin_manager.on_focus(&mut app_context);
634            }
635            event::Event::FocusLost => {
636                let mut app_context = get_app_context!(self);
637                self.plugin_manager.on_blur(&mut app_context);
638            }
639            event::Event::Key(ke) => {
640                let mut app_context = get_app_context!(self);
641                self.plugin_manager.on_key(*ke, &mut app_context);
642            }
643            event::Event::Mouse(me) => {
644                let location = self.get_ui_location(Point::new(me.column, me.row));
645                let mut app_context = get_app_context!(self);
646                self.plugin_manager
647                    .on_mouse(*me, location, &mut app_context);
648            }
649            event::Event::Paste(s) => {
650                let mut app_context = get_app_context!(self);
651                self.plugin_manager.on_paste(s, &mut app_context);
652            }
653            event::Event::Resize(rows, cols) => {
654                let mut app_context = get_app_context!(self);
655                self.plugin_manager
656                    .on_resize(*rows, *cols, &mut app_context);
657            }
658        }
659        Ok(())
660    }
661
662    pub(super) fn handle_event<B: Backend>(
663        &mut self,
664        event: event::Event,
665        terminal: &mut Terminal<B>,
666    ) -> Result<(), Box<dyn std::error::Error>> {
667        self.handle_plugin_events(&event)?;
668        if self.popup.is_some() {
669            self.handle_event_popup(event, terminal)?;
670        } else {
671            self.handle_event_normal(event)?;
672        }
673
674        Ok(())
675    }
676}
677
678#[cfg(test)]
679mod tests {
680    use super::*;
681
682    #[test]
683    fn test_handle_string_edit() {
684        let mut string = String::from("ABCDEFGHIJKLM");
685        let mut cursor = 13;
686        let event = Event::Key(KeyEvent::new(KeyCode::Backspace, KeyModifiers::empty()));
687        let charset = None;
688        let max_len = None;
689        let multiline = true;
690        let key_settings = KeySettings::default();
691        App::handle_string_edit(
692            &mut string,
693            &mut cursor,
694            &event,
695            charset,
696            max_len,
697            multiline,
698            &key_settings,
699        )
700        .unwrap();
701        assert_eq!(string, "ABCDEFGHIJKL");
702        assert_eq!(cursor, 12);
703
704        let event = Event::Key(KeyEvent::new(KeyCode::Left, KeyModifiers::empty()));
705        App::handle_string_edit(
706            &mut string,
707            &mut cursor,
708            &event,
709            charset,
710            max_len,
711            multiline,
712            &key_settings,
713        )
714        .unwrap();
715        assert_eq!(string, "ABCDEFGHIJKL");
716        assert_eq!(cursor, 11);
717
718        let event = Event::Key(KeyEvent::new(KeyCode::Delete, KeyModifiers::empty()));
719        App::handle_string_edit(
720            &mut string,
721            &mut cursor,
722            &event,
723            charset,
724            max_len,
725            multiline,
726            &key_settings,
727        )
728        .unwrap();
729        assert_eq!(string, "ABCDEFGHIJK");
730        assert_eq!(cursor, 11);
731
732        let event = Event::Key(KeyEvent::new(KeyCode::Left, KeyModifiers::empty()));
733        App::handle_string_edit(
734            &mut string,
735            &mut cursor,
736            &event,
737            charset,
738            max_len,
739            multiline,
740            &key_settings,
741        )
742        .unwrap();
743        assert_eq!(string, "ABCDEFGHIJK");
744        assert_eq!(cursor, 10);
745
746        let event = Event::Key(KeyEvent::new(KeyCode::Right, KeyModifiers::empty()));
747        App::handle_string_edit(
748            &mut string,
749            &mut cursor,
750            &event,
751            charset,
752            max_len,
753            multiline,
754            &key_settings,
755        )
756        .unwrap();
757        assert_eq!(string, "ABCDEFGHIJK");
758        assert_eq!(cursor, 11);
759
760        let event = Event::Key(KeyEvent::new(KeyCode::Up, KeyModifiers::empty()));
761        App::handle_string_edit(
762            &mut string,
763            &mut cursor,
764            &event,
765            charset,
766            max_len,
767            multiline,
768            &key_settings,
769        )
770        .unwrap();
771        assert_eq!(string, "ABCDEFGHIJK");
772        assert_eq!(cursor, 0);
773
774        let event = Event::Key(KeyEvent::new(KeyCode::Down, KeyModifiers::empty()));
775        App::handle_string_edit(
776            &mut string,
777            &mut cursor,
778            &event,
779            charset,
780            max_len,
781            multiline,
782            &key_settings,
783        )
784        .unwrap();
785        assert_eq!(string, "ABCDEFGHIJK");
786        assert_eq!(cursor, 11);
787    }
788}