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