1use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
2
3use crate::prettify::PrettifyMode;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum Command {
7 ScrollLines(i64),
8 ScrollLogicalLines(i64),
12 PageDown,
13 PageUp,
14 HalfPageDown,
15 HalfPageUp,
16 HScrollLeft,
17 HScrollRight,
18 HScrollLeftStep,
19 HScrollRightStep,
20 Quit,
21 Resize(u16, u16),
22 Refresh,
23 ToggleLineNumbers,
24 ToggleChop,
25 ToggleFollow,
26 SearchForward,
28 SearchBackward,
30 NextMatch,
32 PreviousMatch,
34 OptionPrefix,
37 Reload,
40 TogglePrettify,
43 SetPrettifyMode(PrettifyMode),
46 RedetectPrettify,
48 Digit(u8),
51 GotoLine,
54 GotoRecord,
57 GotoPercent,
60 Cancel,
62 MarkSet,
65 MarkJump,
68 CtrlXPrefix,
71 JumpPrevious,
74 ShellEscape,
76 ColonPrompt,
78 YankLine,
82 AnimPause,
84 AnimStepForward,
86 AnimStepBack,
88 AnimRestart,
90 TagPrompt,
92 TagPop,
94 OpenPicker,
96 OpenTagPicker,
99 OpenHelp,
101 SelectFile(usize),
104 DropFileAt(usize),
107 SelectTagMatch(usize),
110 MouseEvent(crossterm::event::MouseEvent),
114 FocusOtherPane,
116 Noop,
117}
118
119pub fn translate(event: Event) -> Command {
120 match event {
121 Event::Resize(c, r) => Command::Resize(c, r),
122 Event::Key(KeyEvent { code, modifiers, .. }) => translate_key(code, modifiers),
123 Event::Mouse(m) => Command::MouseEvent(m),
124 _ => Command::Noop,
125 }
126}
127
128fn translate_key(code: KeyCode, mods: KeyModifiers) -> Command {
129 use KeyCode::*;
130 let ctrl = mods.contains(KeyModifiers::CONTROL);
131 match (code, ctrl) {
132 (Char('q'), false) | (Char('Q'), false) => Command::Quit,
133 (Char('c'), true) => Command::Quit,
134 (Down, _) | (Char('j'), false) | (Char('e'), false) | (Char('e'), true) | (Enter, _) => Command::ScrollLines(1),
135 (Char('y'), false) | (Char('y'), true) | (Up, _) | (Char('k'), false) => Command::ScrollLines(-1),
136 (Char('J'), false) => Command::ScrollLogicalLines(1),
137 (Char('K'), false) => Command::ScrollLogicalLines(-1),
138 (Char(' '), false) | (Char('f'), false) | (Char('f'), true) | (PageDown, _) => Command::PageDown,
139 (Char('b'), false) | (Char('b'), true) | (PageUp, _) => Command::PageUp,
140 (Char('d'), false) | (Char('d'), true) => Command::HalfPageDown,
141 (Char('u'), false) | (Char('u'), true) => Command::HalfPageUp,
142 (Char('0'), false) => Command::Digit(0),
143 (Char('1'), false) => Command::Digit(1),
144 (Char('2'), false) => Command::Digit(2),
145 (Char('3'), false) => Command::Digit(3),
146 (Char('4'), false) => Command::Digit(4),
147 (Char('5'), false) => Command::Digit(5),
148 (Char('6'), false) => Command::Digit(6),
149 (Char('7'), false) => Command::Digit(7),
150 (Char('8'), false) => Command::Digit(8),
151 (Char('9'), false) => Command::Digit(9),
152 (Char('g'), false) | (Char('<'), false) | (Home, _) => Command::GotoLine,
153 (Char('G'), false) | (Char('>'), false) | (End, _) => Command::GotoRecord,
154 (Char('%'), false) => Command::GotoPercent,
155 (Esc, _) => Command::Cancel,
156 (Char('r'), false) | (Char('l'), true) => Command::Refresh,
157 (Char('R'), false) => Command::Reload,
158 (Char('P'), false) => Command::TogglePrettify,
159 (Char('-'), false) => Command::OptionPrefix,
160 (Char('F'), false) => Command::ToggleFollow,
161 (Char('/'), false) => Command::SearchForward,
162 (Char('?'), false) => Command::SearchBackward,
163 (Char('n'), false) => Command::NextMatch,
164 (Char('N'), false) => Command::PreviousMatch,
165 (Char('m'), false) => Command::MarkSet,
166 (Char('\''), false) => Command::MarkJump,
167 (Char('!'), false) => Command::ShellEscape,
168 (Char('x'), true) => Command::CtrlXPrefix,
169 (Char(':'), false) => Command::ColonPrompt,
170 (Char(']'), true) => Command::TagPrompt,
171 (Char('t'), true) => Command::TagPop,
172 (F(1), _) => Command::OpenHelp,
173 (Left, false) if mods.contains(KeyModifiers::SHIFT) => Command::HScrollLeftStep,
174 (Right, false) if mods.contains(KeyModifiers::SHIFT) => Command::HScrollRightStep,
175 (Left, false) => Command::HScrollLeft,
176 (Right, false) => Command::HScrollRight,
177 (Char('p'), false) => Command::AnimPause,
178 (Char('.'), false) => Command::AnimStepForward,
179 (Char(','), false) => Command::AnimStepBack,
180 (Backspace, _) => Command::AnimRestart,
181 (Tab, _) => Command::FocusOtherPane,
182 _ => Command::Noop,
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189 use crossterm::event::{KeyCode, KeyEventKind, KeyEventState};
190
191 fn key(code: KeyCode, mods: KeyModifiers) -> Event {
192 Event::Key(KeyEvent {
193 code, modifiers: mods,
194 kind: KeyEventKind::Press, state: KeyEventState::NONE,
195 })
196 }
197
198 #[test]
199 fn arrow_down_scrolls_one() {
200 assert_eq!(translate(key(KeyCode::Down, KeyModifiers::NONE)), Command::ScrollLines(1));
201 }
202
203 #[test]
204 fn j_scrolls_one() {
205 assert_eq!(translate(key(KeyCode::Char('j'), KeyModifiers::NONE)), Command::ScrollLines(1));
206 }
207
208 #[test]
209 fn space_pages_down() {
210 assert_eq!(translate(key(KeyCode::Char(' '), KeyModifiers::NONE)), Command::PageDown);
211 }
212
213 #[test]
214 fn ctrl_c_quits() {
215 assert_eq!(translate(key(KeyCode::Char('c'), KeyModifiers::CONTROL)), Command::Quit);
216 }
217
218 #[test]
219 fn capital_g_goes_to_record() {
220 assert_eq!(translate(key(KeyCode::Char('G'), KeyModifiers::SHIFT)), Command::GotoRecord);
221 }
222
223 #[test]
224 fn lowercase_g_goes_to_line() {
225 assert_eq!(translate(key(KeyCode::Char('g'), KeyModifiers::NONE)), Command::GotoLine);
226 }
227
228 #[test]
229 fn percent_goes_to_percent() {
230 assert_eq!(translate(key(KeyCode::Char('%'), KeyModifiers::NONE)), Command::GotoPercent);
231 }
232
233 #[test]
234 fn digit_keys_produce_digit_commands() {
235 for d in 0u8..=9 {
236 let ch = char::from_digit(d as u32, 10).unwrap();
237 assert_eq!(
238 translate(key(KeyCode::Char(ch), KeyModifiers::NONE)),
239 Command::Digit(d),
240 );
241 }
242 }
243
244 #[test]
245 fn anim_keys_translate() {
246 assert_eq!(translate(key(KeyCode::Char('p'), KeyModifiers::NONE)), Command::AnimPause);
247 assert_eq!(translate(key(KeyCode::Char('.'), KeyModifiers::NONE)), Command::AnimStepForward);
248 assert_eq!(translate(key(KeyCode::Char(','), KeyModifiers::NONE)), Command::AnimStepBack);
249 assert_eq!(translate(key(KeyCode::Backspace, KeyModifiers::NONE)), Command::AnimRestart);
250 }
251
252 #[test]
253 fn esc_produces_cancel() {
254 assert_eq!(translate(key(KeyCode::Esc, KeyModifiers::NONE)), Command::Cancel);
255 }
256
257 #[test]
258 fn capital_j_jumps_one_logical_line_forward() {
259 assert_eq!(translate(key(KeyCode::Char('J'), KeyModifiers::SHIFT)), Command::ScrollLogicalLines(1));
260 }
261
262 #[test]
263 fn capital_k_jumps_one_logical_line_backward() {
264 assert_eq!(translate(key(KeyCode::Char('K'), KeyModifiers::SHIFT)), Command::ScrollLogicalLines(-1));
265 }
266
267 #[test]
268 fn capital_f_toggles_follow() {
269 assert_eq!(translate(key(KeyCode::Char('F'), KeyModifiers::SHIFT)), Command::ToggleFollow);
270 }
271
272 #[test]
273 fn lowercase_f_still_pages_down() {
274 assert_eq!(translate(key(KeyCode::Char('f'), KeyModifiers::NONE)), Command::PageDown);
275 }
276
277 #[test]
278 fn slash_opens_forward_search() {
279 assert_eq!(translate(key(KeyCode::Char('/'), KeyModifiers::NONE)), Command::SearchForward);
280 }
281
282 #[test]
283 fn question_mark_opens_backward_search() {
284 assert_eq!(translate(key(KeyCode::Char('?'), KeyModifiers::SHIFT)), Command::SearchBackward);
286 }
287
288 #[test]
289 fn n_repeats_match_forward() {
290 assert_eq!(translate(key(KeyCode::Char('n'), KeyModifiers::NONE)), Command::NextMatch);
291 }
292
293 #[test]
294 fn capital_n_repeats_match_backward() {
295 assert_eq!(translate(key(KeyCode::Char('N'), KeyModifiers::SHIFT)), Command::PreviousMatch);
296 }
297
298 #[test]
299 fn capital_r_triggers_reload() {
300 assert_eq!(translate(key(KeyCode::Char('R'), KeyModifiers::SHIFT)), Command::Reload);
301 }
302
303 #[test]
304 fn lowercase_r_still_refreshes() {
305 assert_eq!(translate(key(KeyCode::Char('r'), KeyModifiers::NONE)), Command::Refresh);
306 }
307
308 #[test]
309 fn capital_p_toggles_prettify() {
310 assert_eq!(translate(key(KeyCode::Char('P'), KeyModifiers::SHIFT)), Command::TogglePrettify);
311 }
312
313 #[test]
314 fn lowercase_p_is_anim_pause() {
315 assert_eq!(translate(key(KeyCode::Char('p'), KeyModifiers::NONE)), Command::AnimPause);
316 }
317
318 #[test]
319 fn dash_is_option_prefix() {
320 assert_eq!(translate(key(KeyCode::Char('-'), KeyModifiers::NONE)), Command::OptionPrefix);
321 }
322
323 #[test]
324 fn resize_event() {
325 assert_eq!(translate(Event::Resize(80, 24)), Command::Resize(80, 24));
326 }
327
328 #[test]
329 fn m_key_produces_mark_set_command() {
330 let evt = key(KeyCode::Char('m'), KeyModifiers::NONE);
331 assert_eq!(translate(evt), Command::MarkSet);
332 }
333
334 #[test]
335 fn single_quote_key_produces_mark_jump_command() {
336 let evt = key(KeyCode::Char('\''), KeyModifiers::NONE);
337 assert_eq!(translate(evt), Command::MarkJump);
338 }
339
340 #[test]
341 fn ctrl_x_produces_ctrl_x_prefix_command() {
342 let evt = key(KeyCode::Char('x'), KeyModifiers::CONTROL);
343 assert_eq!(translate(evt), Command::CtrlXPrefix);
344 }
345
346 #[test]
347 fn bang_produces_shell_escape_command() {
348 let evt = key(KeyCode::Char('!'), KeyModifiers::NONE);
349 assert_eq!(translate(evt), Command::ShellEscape);
350 }
351
352 #[test]
353 fn colon_produces_colon_prompt_command() {
354 let evt = Event::Key(KeyEvent::new(KeyCode::Char(':'), KeyModifiers::NONE));
355 assert_eq!(translate(evt), Command::ColonPrompt);
356 }
357
358 #[test]
359 fn ctrl_close_bracket_produces_tag_prompt() {
360 let evt = Event::Key(KeyEvent::new(KeyCode::Char(']'), KeyModifiers::CONTROL));
361 assert_eq!(translate(evt), Command::TagPrompt);
362 }
363
364 #[test]
365 fn ctrl_t_produces_tag_pop() {
366 let evt = Event::Key(KeyEvent::new(KeyCode::Char('t'), KeyModifiers::CONTROL));
367 assert_eq!(translate(evt), Command::TagPop);
368 }
369
370 #[test]
371 fn f1_opens_help() {
372 let evt = key(KeyCode::F(1), KeyModifiers::NONE);
373 assert_eq!(translate(evt), Command::OpenHelp);
374 }
375
376 #[test]
377 fn tab_is_focus_other_pane() {
378 assert_eq!(translate(key(KeyCode::Tab, KeyModifiers::NONE)), Command::FocusOtherPane);
379 }
380
381 #[test]
382 fn arrows_translate_to_hscroll() {
383 assert_eq!(translate(key(KeyCode::Right, KeyModifiers::NONE)), Command::HScrollRight);
384 assert_eq!(translate(key(KeyCode::Left, KeyModifiers::NONE)), Command::HScrollLeft);
385 assert_eq!(translate(key(KeyCode::Right, KeyModifiers::SHIFT)), Command::HScrollRightStep);
386 assert_eq!(translate(key(KeyCode::Left, KeyModifiers::SHIFT)), Command::HScrollLeftStep);
387 }
388}