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.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 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 {
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}