1use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum Action {
8 Quit,
9 NextTab,
10 PrevTab,
11 JumpTab(usize),
12 Refresh,
13 ToggleHelp,
14 StartFilter,
15 ScrollUp,
16 ScrollDown,
17 ScrollTop,
18 ScrollBottom,
19 PageUp,
20 PageDown,
21 Select,
22 Back,
23 ToggleFollow,
24 Edit,
25 Toggle,
26 Delete,
27 Sort,
28}
29
30pub fn resolve(key: KeyEvent) -> Option<Action> {
32 if key.modifiers.contains(KeyModifiers::CONTROL) {
34 return match key.code {
35 KeyCode::Char('c' | 'q') => Some(Action::Quit),
36 _ => None,
37 };
38 }
39
40 match key.code {
41 KeyCode::Char('q') => Some(Action::Quit),
42 KeyCode::Tab => Some(Action::NextTab),
43 KeyCode::BackTab => Some(Action::PrevTab),
44 KeyCode::Char('1') => Some(Action::JumpTab(0)),
45 KeyCode::Char('2') => Some(Action::JumpTab(1)),
46 KeyCode::Char('3') => Some(Action::JumpTab(2)),
47 KeyCode::Char('4') => Some(Action::JumpTab(3)),
48 KeyCode::Char('5') => Some(Action::JumpTab(4)),
49 KeyCode::Char('6') => Some(Action::JumpTab(5)),
50 KeyCode::Char('7') => Some(Action::JumpTab(6)),
51 KeyCode::Char('8') => Some(Action::JumpTab(7)),
52 KeyCode::Char('9') => Some(Action::JumpTab(8)),
53 KeyCode::Char('0') => Some(Action::JumpTab(9)),
54 KeyCode::Char('r') => Some(Action::Refresh),
55 KeyCode::Char('?') => Some(Action::ToggleHelp),
56 KeyCode::Char('/') => Some(Action::StartFilter),
57 KeyCode::Char('j') | KeyCode::Down => Some(Action::ScrollDown),
58 KeyCode::Char('k') | KeyCode::Up => Some(Action::ScrollUp),
59 KeyCode::Char('g') => Some(Action::ScrollTop),
60 KeyCode::Char('G') => Some(Action::ScrollBottom),
61 KeyCode::PageUp => Some(Action::PageUp),
62 KeyCode::PageDown => Some(Action::PageDown),
63 KeyCode::Enter => Some(Action::Select),
64 KeyCode::Esc => Some(Action::Back),
65 KeyCode::Char('f') => Some(Action::ToggleFollow),
66 KeyCode::Char('e') => Some(Action::Edit),
67 KeyCode::Char('t') => Some(Action::Toggle),
68 KeyCode::Char('d') => Some(Action::Delete),
69 KeyCode::Char('s') => Some(Action::Sort),
70 _ => None,
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77 use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers};
78
79 fn key(code: KeyCode) -> KeyEvent {
81 KeyEvent {
82 code,
83 modifiers: KeyModifiers::NONE,
84 kind: KeyEventKind::Press,
85 state: KeyEventState::NONE,
86 }
87 }
88
89 fn ctrl(code: KeyCode) -> KeyEvent {
91 KeyEvent {
92 code,
93 modifiers: KeyModifiers::CONTROL,
94 kind: KeyEventKind::Press,
95 state: KeyEventState::NONE,
96 }
97 }
98
99 #[test]
100 fn q_maps_to_quit() {
101 assert_eq!(resolve(key(KeyCode::Char('q'))), Some(Action::Quit));
102 }
103
104 #[test]
105 fn ctrl_c_maps_to_quit() {
106 assert_eq!(resolve(ctrl(KeyCode::Char('c'))), Some(Action::Quit));
107 }
108
109 #[test]
110 fn ctrl_q_maps_to_quit() {
111 assert_eq!(resolve(ctrl(KeyCode::Char('q'))), Some(Action::Quit));
112 }
113
114 #[test]
115 fn tab_maps_to_next_tab() {
116 assert_eq!(resolve(key(KeyCode::Tab)), Some(Action::NextTab));
117 }
118
119 #[test]
120 fn backtab_maps_to_prev_tab() {
121 assert_eq!(resolve(key(KeyCode::BackTab)), Some(Action::PrevTab));
122 }
123
124 #[test]
125 fn digit_keys_map_to_jump_tab() {
126 assert_eq!(resolve(key(KeyCode::Char('1'))), Some(Action::JumpTab(0)));
127 assert_eq!(resolve(key(KeyCode::Char('2'))), Some(Action::JumpTab(1)));
128 assert_eq!(resolve(key(KeyCode::Char('3'))), Some(Action::JumpTab(2)));
129 assert_eq!(resolve(key(KeyCode::Char('4'))), Some(Action::JumpTab(3)));
130 assert_eq!(resolve(key(KeyCode::Char('5'))), Some(Action::JumpTab(4)));
131 assert_eq!(resolve(key(KeyCode::Char('6'))), Some(Action::JumpTab(5)));
132 assert_eq!(resolve(key(KeyCode::Char('7'))), Some(Action::JumpTab(6)));
133 assert_eq!(resolve(key(KeyCode::Char('8'))), Some(Action::JumpTab(7)));
134 assert_eq!(resolve(key(KeyCode::Char('9'))), Some(Action::JumpTab(8)));
135 assert_eq!(resolve(key(KeyCode::Char('0'))), Some(Action::JumpTab(9)));
136 }
137
138 #[test]
139 fn r_maps_to_refresh() {
140 assert_eq!(resolve(key(KeyCode::Char('r'))), Some(Action::Refresh));
141 }
142
143 #[test]
144 fn question_mark_maps_to_toggle_help() {
145 assert_eq!(resolve(key(KeyCode::Char('?'))), Some(Action::ToggleHelp));
146 }
147
148 #[test]
149 fn slash_maps_to_start_filter() {
150 assert_eq!(resolve(key(KeyCode::Char('/'))), Some(Action::StartFilter));
151 }
152
153 #[test]
154 fn navigation_keys() {
155 assert_eq!(resolve(key(KeyCode::Char('j'))), Some(Action::ScrollDown));
156 assert_eq!(resolve(key(KeyCode::Down)), Some(Action::ScrollDown));
157 assert_eq!(resolve(key(KeyCode::Char('k'))), Some(Action::ScrollUp));
158 assert_eq!(resolve(key(KeyCode::Up)), Some(Action::ScrollUp));
159 assert_eq!(resolve(key(KeyCode::Char('g'))), Some(Action::ScrollTop));
160 assert_eq!(resolve(key(KeyCode::Char('G'))), Some(Action::ScrollBottom));
161 assert_eq!(resolve(key(KeyCode::PageUp)), Some(Action::PageUp));
162 assert_eq!(resolve(key(KeyCode::PageDown)), Some(Action::PageDown));
163 }
164
165 #[test]
166 fn action_keys() {
167 assert_eq!(resolve(key(KeyCode::Enter)), Some(Action::Select));
168 assert_eq!(resolve(key(KeyCode::Esc)), Some(Action::Back));
169 assert_eq!(resolve(key(KeyCode::Char('f'))), Some(Action::ToggleFollow));
170 assert_eq!(resolve(key(KeyCode::Char('e'))), Some(Action::Edit));
171 assert_eq!(resolve(key(KeyCode::Char('t'))), Some(Action::Toggle));
172 assert_eq!(resolve(key(KeyCode::Char('d'))), Some(Action::Delete));
173 assert_eq!(resolve(key(KeyCode::Char('s'))), Some(Action::Sort));
174 }
175
176 #[test]
177 fn unrecognized_key_returns_none() {
178 assert_eq!(resolve(key(KeyCode::Char('z'))), None);
179 assert_eq!(resolve(key(KeyCode::F(1))), None);
180 assert_eq!(resolve(key(KeyCode::Insert)), None);
181 }
182
183 #[test]
184 fn ctrl_with_unrecognized_char_returns_none() {
185 assert_eq!(resolve(ctrl(KeyCode::Char('x'))), None);
186 assert_eq!(resolve(ctrl(KeyCode::Char('a'))), None);
187 }
188}