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.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                            // Maybe removing the popup is not a good idea, more testing needed
435                            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                    // if no file is open, close the program instead of the popup
647                    {
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}