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