1use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
7
8use crate::cmdline::ExCommand;
9use crate::state::{AppState, Focus};
10
11#[derive(Clone, Debug, PartialEq)]
14pub enum Command {
15 Quit,
16 Help,
17 Reload,
18 CycleFocus,
19 Open,
20 Back,
21 MoveDown,
22 MoveUp,
23 MoveTop,
24 MoveBottom,
25 HalfPageDown,
26 HalfPageUp,
27 NextProfile,
28 StartCommand(&'static str),
30 Backlinks,
31 MoreResults,
33 ToggleExpand,
35 Edit,
38 Ex(ExCommand),
39}
40
41#[derive(Clone, Copy, PartialEq, Eq)]
42pub enum Bar {
43 Global,
44 Context,
45}
46
47#[derive(Clone, Copy)]
48pub struct KeyChord {
49 pub code: KeyCode,
50 pub mods: KeyModifiers,
51}
52
53impl KeyChord {
54 fn matches(&self, key: &KeyEvent) -> bool {
55 if self.code != key.code {
56 return false;
57 }
58 let mods = match key.code {
61 KeyCode::Char(_) => key.modifiers.difference(KeyModifiers::SHIFT),
62 _ => key.modifiers,
63 };
64 mods == self.mods
65 }
66}
67
68#[derive(Clone, Copy)]
72pub struct When {
73 pub focus: Option<Focus>,
75 pub results_only: bool,
77}
78
79impl When {
80 fn matches(&self, state: &AppState) -> bool {
81 self.focus.is_none_or(|f| f == state.focus)
82 && (!self.results_only || !state.source.is_all())
83 }
84}
85
86pub struct Binding {
87 pub chord: KeyChord,
88 pub when: When,
89 pub cmd: Command,
90 pub bar: Option<Bar>,
92 pub key_label: &'static str,
93 pub action_label: &'static str,
94}
95
96const NONE: KeyModifiers = KeyModifiers::NONE;
97const CTRL: KeyModifiers = KeyModifiers::CONTROL;
98const ANY: When = When {
99 focus: None,
100 results_only: false,
101};
102const LIST: When = When {
103 focus: Some(Focus::List),
104 results_only: false,
105};
106const VIEW: When = When {
107 focus: Some(Focus::View),
108 results_only: false,
109};
110const LIST_RESULTS: When = When {
111 focus: Some(Focus::List),
112 results_only: true,
113};
114
115macro_rules! bind {
116 ($code:expr, $mods:expr, $when:expr, $cmd:expr, $bar:expr, $key:expr, $action:expr) => {
117 Binding {
118 chord: KeyChord {
119 code: $code,
120 mods: $mods,
121 },
122 when: $when,
123 cmd: $cmd,
124 bar: $bar,
125 key_label: $key,
126 action_label: $action,
127 }
128 };
129}
130
131use Command as C;
132use KeyCode::*;
133
134pub static KEYMAP: &[Binding] = &[
137 bind!(
139 Char('q'),
140 NONE,
141 ANY,
142 C::Quit,
143 Some(Bar::Global),
144 "q",
145 "quit"
146 ),
147 bind!(
148 Char(':'),
149 NONE,
150 ANY,
151 C::StartCommand(""),
152 Some(Bar::Global),
153 ":",
154 "cmd"
155 ),
156 bind!(
157 Char('/'),
158 NONE,
159 ANY,
160 C::StartCommand("search "),
161 Some(Bar::Global),
162 "/",
163 "search"
164 ),
165 bind!(
166 Char('?'),
167 NONE,
168 ANY,
169 C::Help,
170 Some(Bar::Global),
171 "?",
172 "help"
173 ),
174 bind!(
175 Char('p'),
176 NONE,
177 ANY,
178 C::NextProfile,
179 Some(Bar::Global),
180 "p",
181 "profile"
182 ),
183 bind!(
184 Char('r'),
185 NONE,
186 ANY,
187 C::Reload,
188 Some(Bar::Global),
189 "r",
190 "reload"
191 ),
192 bind!(
193 Tab,
194 NONE,
195 ANY,
196 C::CycleFocus,
197 Some(Bar::Global),
198 "Tab",
199 "focus"
200 ),
201 bind!(Esc, NONE, VIEW, C::Back, None, "Esc", "back"),
203 bind!(
204 Esc,
205 NONE,
206 LIST_RESULTS,
207 C::Back,
208 Some(Bar::Context),
209 "Esc",
210 "all jots"
211 ),
212 bind!(
214 Char('j'),
215 NONE,
216 LIST,
217 C::MoveDown,
218 Some(Bar::Context),
219 "j/k",
220 "move"
221 ),
222 bind!(Char('k'), NONE, LIST, C::MoveUp, None, "k", "up"),
223 bind!(
224 Char('j'),
225 NONE,
226 VIEW,
227 C::MoveDown,
228 Some(Bar::Context),
229 "j/k",
230 "scroll"
231 ),
232 bind!(Char('k'), NONE, VIEW, C::MoveUp, None, "k", "up"),
233 bind!(Down, NONE, ANY, C::MoveDown, None, "↓", "down"),
234 bind!(Up, NONE, ANY, C::MoveUp, None, "↑", "up"),
235 bind!(
236 Char('g'),
237 NONE,
238 ANY,
239 C::MoveTop,
240 Some(Bar::Context),
241 "g/G",
242 "top/end"
243 ),
244 bind!(Char('G'), NONE, ANY, C::MoveBottom, None, "G", "end"),
245 bind!(Home, NONE, ANY, C::MoveTop, None, "Home", "top"),
246 bind!(End, NONE, ANY, C::MoveBottom, None, "End", "end"),
247 bind!(
248 Char('d'),
249 NONE,
250 ANY,
251 C::HalfPageDown,
252 Some(Bar::Context),
253 "d/u",
254 "½page"
255 ),
256 bind!(Char('u'), NONE, ANY, C::HalfPageUp, None, "u", "½page up"),
257 bind!(Char('d'), CTRL, ANY, C::HalfPageDown, None, "C-d", "½page"),
258 bind!(Char('u'), CTRL, ANY, C::HalfPageUp, None, "C-u", "½page up"),
259 bind!(PageDown, NONE, ANY, C::HalfPageDown, None, "PgDn", "½page"),
260 bind!(PageUp, NONE, ANY, C::HalfPageUp, None, "PgUp", "½page up"),
261 bind!(Enter, NONE, LIST, C::Open, Some(Bar::Context), "⏎", "open"),
263 bind!(Char('l'), NONE, LIST, C::Open, None, "l", "open"),
264 bind!(Right, NONE, LIST, C::Open, None, "→", "open"),
265 bind!(
266 Char('h'),
267 NONE,
268 VIEW,
269 C::Back,
270 Some(Bar::Context),
271 "h",
272 "back"
273 ),
274 bind!(Left, NONE, VIEW, C::Back, None, "←", "back"),
275 bind!(
276 Char('f'),
277 NONE,
278 LIST,
279 C::StartCommand("find "),
280 Some(Bar::Context),
281 "f",
282 "find"
283 ),
284 bind!(
285 Char('b'),
286 NONE,
287 ANY,
288 C::Backlinks,
289 Some(Bar::Context),
290 "b",
291 "backlinks"
292 ),
293 bind!(
294 Char('m'),
295 NONE,
296 LIST_RESULTS,
297 C::MoreResults,
298 Some(Bar::Context),
299 "m",
300 "more"
301 ),
302 bind!(
303 Char('x'),
304 NONE,
305 VIEW,
306 C::ToggleExpand,
307 Some(Bar::Context),
308 "x",
309 "expand"
310 ),
311 bind!(
312 Char('e'),
313 NONE,
314 ANY,
315 C::Edit,
316 Some(Bar::Context),
317 "e",
318 "edit"
319 ),
320];
321
322pub fn lookup(state: &AppState, key: &KeyEvent) -> Option<Command> {
325 KEYMAP
326 .iter()
327 .find(|b| b.when.matches(state) && b.chord.matches(key))
328 .map(|b| b.cmd.clone())
329}
330
331pub fn bar_items(state: &AppState, bar: Bar) -> Vec<(&'static str, &'static str)> {
335 KEYMAP
336 .iter()
337 .filter(|b| b.bar == Some(bar) && b.when.matches(state))
338 .map(|b| (b.key_label, b.action_label))
339 .collect()
340}