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 GoTop,
17 GoBottom,
18 Quit,
19 Resize(u16, u16),
20 Refresh,
21 ToggleLineNumbers,
22 ToggleChop,
23 ToggleFollow,
24 SearchForward,
26 SearchBackward,
28 NextMatch,
30 PreviousMatch,
32 OptionPrefix,
35 Reload,
38 TogglePrettify,
41 SetPrettifyMode(PrettifyMode),
44 RedetectPrettify,
46 Noop,
47}
48
49pub fn translate(event: Event) -> Command {
50 match event {
51 Event::Resize(c, r) => Command::Resize(c, r),
52 Event::Key(KeyEvent { code, modifiers, .. }) => translate_key(code, modifiers),
53 _ => Command::Noop,
54 }
55}
56
57fn translate_key(code: KeyCode, mods: KeyModifiers) -> Command {
58 use KeyCode::*;
59 let ctrl = mods.contains(KeyModifiers::CONTROL);
60 match (code, ctrl) {
61 (Char('q'), false) | (Char('Q'), false) => Command::Quit,
62 (Char('c'), true) => Command::Quit,
63 (Down, _) | (Char('j'), false) | (Char('e'), false) | (Char('e'), true) | (Enter, _) => Command::ScrollLines(1),
64 (Char('y'), false) | (Char('y'), true) | (Up, _) | (Char('k'), false) => Command::ScrollLines(-1),
65 (Char('J'), false) => Command::ScrollLogicalLines(1),
66 (Char('K'), false) => Command::ScrollLogicalLines(-1),
67 (Char(' '), false) | (Char('f'), false) | (Char('f'), true) | (PageDown, _) => Command::PageDown,
68 (Char('b'), false) | (Char('b'), true) | (PageUp, _) => Command::PageUp,
69 (Char('d'), false) | (Char('d'), true) => Command::HalfPageDown,
70 (Char('u'), false) | (Char('u'), true) => Command::HalfPageUp,
71 (Char('g'), false) | (Char('<'), false) | (Home, _) => Command::GoTop,
72 (Char('G'), false) | (Char('>'), false) | (End, _) => Command::GoBottom,
73 (Char('r'), false) | (Char('l'), true) => Command::Refresh,
74 (Char('R'), false) => Command::Reload,
75 (Char('P'), false) => Command::TogglePrettify,
76 (Char('-'), false) => Command::OptionPrefix,
77 (Char('F'), false) => Command::ToggleFollow,
78 (Char('/'), false) => Command::SearchForward,
79 (Char('?'), false) => Command::SearchBackward,
80 (Char('n'), false) => Command::NextMatch,
81 (Char('N'), false) => Command::PreviousMatch,
82 _ => Command::Noop,
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use crossterm::event::{KeyCode, KeyEventKind, KeyEventState};
90
91 fn key(code: KeyCode, mods: KeyModifiers) -> Event {
92 Event::Key(KeyEvent {
93 code, modifiers: mods,
94 kind: KeyEventKind::Press, state: KeyEventState::NONE,
95 })
96 }
97
98 #[test]
99 fn arrow_down_scrolls_one() {
100 assert_eq!(translate(key(KeyCode::Down, KeyModifiers::NONE)), Command::ScrollLines(1));
101 }
102
103 #[test]
104 fn j_scrolls_one() {
105 assert_eq!(translate(key(KeyCode::Char('j'), KeyModifiers::NONE)), Command::ScrollLines(1));
106 }
107
108 #[test]
109 fn space_pages_down() {
110 assert_eq!(translate(key(KeyCode::Char(' '), KeyModifiers::NONE)), Command::PageDown);
111 }
112
113 #[test]
114 fn ctrl_c_quits() {
115 assert_eq!(translate(key(KeyCode::Char('c'), KeyModifiers::CONTROL)), Command::Quit);
116 }
117
118 #[test]
119 fn capital_g_goes_to_bottom() {
120 assert_eq!(translate(key(KeyCode::Char('G'), KeyModifiers::SHIFT)), Command::GoBottom);
121 }
122
123 #[test]
124 fn capital_j_jumps_one_logical_line_forward() {
125 assert_eq!(translate(key(KeyCode::Char('J'), KeyModifiers::SHIFT)), Command::ScrollLogicalLines(1));
126 }
127
128 #[test]
129 fn capital_k_jumps_one_logical_line_backward() {
130 assert_eq!(translate(key(KeyCode::Char('K'), KeyModifiers::SHIFT)), Command::ScrollLogicalLines(-1));
131 }
132
133 #[test]
134 fn capital_f_toggles_follow() {
135 assert_eq!(translate(key(KeyCode::Char('F'), KeyModifiers::SHIFT)), Command::ToggleFollow);
136 }
137
138 #[test]
139 fn lowercase_f_still_pages_down() {
140 assert_eq!(translate(key(KeyCode::Char('f'), KeyModifiers::NONE)), Command::PageDown);
141 }
142
143 #[test]
144 fn slash_opens_forward_search() {
145 assert_eq!(translate(key(KeyCode::Char('/'), KeyModifiers::NONE)), Command::SearchForward);
146 }
147
148 #[test]
149 fn question_mark_opens_backward_search() {
150 assert_eq!(translate(key(KeyCode::Char('?'), KeyModifiers::SHIFT)), Command::SearchBackward);
152 }
153
154 #[test]
155 fn n_repeats_match_forward() {
156 assert_eq!(translate(key(KeyCode::Char('n'), KeyModifiers::NONE)), Command::NextMatch);
157 }
158
159 #[test]
160 fn capital_n_repeats_match_backward() {
161 assert_eq!(translate(key(KeyCode::Char('N'), KeyModifiers::SHIFT)), Command::PreviousMatch);
162 }
163
164 #[test]
165 fn capital_r_triggers_reload() {
166 assert_eq!(translate(key(KeyCode::Char('R'), KeyModifiers::SHIFT)), Command::Reload);
167 }
168
169 #[test]
170 fn lowercase_r_still_refreshes() {
171 assert_eq!(translate(key(KeyCode::Char('r'), KeyModifiers::NONE)), Command::Refresh);
172 }
173
174 #[test]
175 fn capital_p_toggles_prettify() {
176 assert_eq!(translate(key(KeyCode::Char('P'), KeyModifiers::SHIFT)), Command::TogglePrettify);
177 }
178
179 #[test]
180 fn lowercase_p_remains_unbound() {
181 assert_eq!(translate(key(KeyCode::Char('p'), KeyModifiers::NONE)), Command::Noop);
182 }
183
184 #[test]
185 fn dash_is_option_prefix() {
186 assert_eq!(translate(key(KeyCode::Char('-'), KeyModifiers::NONE)), Command::OptionPrefix);
187 }
188
189 #[test]
190 fn resize_event() {
191 assert_eq!(translate(Event::Resize(80, 24)), Command::Resize(80, 24));
192 }
193}