1extern crate self as escriba_keymap;
4
5use std::collections::HashMap;
6
7use escriba_core::{Action, CountedAction, Mode, Motion, Operator};
8use escriba_mode::ModalState;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub enum Key {
13 Char(char),
14 Esc,
15 Enter,
16 Tab,
17 Backspace,
18 Left,
19 Right,
20 Up,
21 Down,
22 PageUp,
23 PageDown,
24 Home,
25 End,
26 Ctrl(char),
27 Alt(char),
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct Binding {
32 pub action: Action,
33 pub description: String,
34}
35
36impl Binding {
37 #[must_use]
38 pub fn new(action: Action, description: impl Into<String>) -> Self {
39 Self {
40 action,
41 description: description.into(),
42 }
43 }
44}
45
46#[derive(Debug, Clone)]
47pub struct Keymap {
48 bindings: HashMap<(Mode, Key), Binding>,
49 sequences: HashMap<(Mode, Vec<Key>), Binding>,
54 leader: Key,
56}
57
58impl Default for Keymap {
59 fn default() -> Self {
60 Self {
61 bindings: HashMap::new(),
62 sequences: HashMap::new(),
63 leader: Key::Char(','),
66 }
67 }
68}
69
70impl Keymap {
71 #[must_use]
72 pub fn new() -> Self {
73 Self::default()
74 }
75
76 #[must_use]
77 pub fn default_vim() -> Self {
78 let mut m = Self::new();
79 let nm = |m: &mut Keymap, k: Key, a: Action, d: &'static str| m.bind(Mode::Normal, k, a, d);
80 nm(
81 &mut m,
82 Key::Char('h'),
83 Action::Move(Motion::Left),
84 "move left",
85 );
86 nm(
87 &mut m,
88 Key::Char('l'),
89 Action::Move(Motion::Right),
90 "move right",
91 );
92 nm(
93 &mut m,
94 Key::Char('j'),
95 Action::Move(Motion::Down),
96 "move down",
97 );
98 nm(&mut m, Key::Char('k'), Action::Move(Motion::Up), "move up");
99 nm(
100 &mut m,
101 Key::Char('w'),
102 Action::Move(Motion::WordStartNext),
103 "word forward",
104 );
105 nm(
106 &mut m,
107 Key::Char('b'),
108 Action::Move(Motion::WordStartPrev),
109 "word back",
110 );
111 nm(
112 &mut m,
113 Key::Char('0'),
114 Action::Move(Motion::LineStart),
115 "line start",
116 );
117 nm(
118 &mut m,
119 Key::Char('$'),
120 Action::Move(Motion::LineEnd),
121 "line end",
122 );
123 nm(
124 &mut m,
125 Key::Char('G'),
126 Action::Move(Motion::DocEnd),
127 "doc end",
128 );
129 nm(
132 &mut m,
133 Key::Char('d'),
134 Action::Operator(Operator::Delete),
135 "delete (operator)",
136 );
137 nm(
138 &mut m,
139 Key::Char('c'),
140 Action::Operator(Operator::Change),
141 "change (operator)",
142 );
143 nm(
144 &mut m,
145 Key::Char('y'),
146 Action::Operator(Operator::Yank),
147 "yank (operator)",
148 );
149 nm(
151 &mut m,
152 Key::Alt('f'),
153 Action::Move(Motion::ForwardSexp),
154 "forward sexp",
155 );
156 nm(
157 &mut m,
158 Key::Alt('b'),
159 Action::Move(Motion::BackwardSexp),
160 "backward sexp",
161 );
162 nm(
163 &mut m,
164 Key::Alt('u'),
165 Action::Move(Motion::UpList),
166 "up list",
167 );
168 nm(
169 &mut m,
170 Key::Alt('d'),
171 Action::Move(Motion::DownList),
172 "down list",
173 );
174 nm(
176 &mut m,
177 Key::Char('i'),
178 Action::ChangeMode(Mode::Insert),
179 "insert",
180 );
181 nm(
182 &mut m,
183 Key::Char('v'),
184 Action::ChangeMode(Mode::Visual),
185 "visual",
186 );
187 nm(
188 &mut m,
189 Key::Char('V'),
190 Action::ChangeMode(Mode::VisualLine),
191 "visual line",
192 );
193 nm(
194 &mut m,
195 Key::Char(':'),
196 Action::ChangeMode(Mode::Command),
197 "command",
198 );
199 nm(&mut m, Key::Char('u'), Action::Undo, "undo");
200 nm(&mut m, Key::Ctrl('r'), Action::Redo, "redo");
201 m.bind(
203 Mode::Insert,
204 Key::Esc,
205 Action::ChangeMode(Mode::Normal),
206 "to normal",
207 );
208 m.bind(
209 Mode::Command,
210 Key::Esc,
211 Action::ChangeMode(Mode::Normal),
212 "abort",
213 );
214 m.bind(Mode::Command, Key::Enter, Action::SubmitCommand, "submit");
215 m.bind(
216 Mode::Visual,
217 Key::Esc,
218 Action::ChangeMode(Mode::Normal),
219 "to normal",
220 );
221 m.bind(
222 Mode::VisualLine,
223 Key::Esc,
224 Action::ChangeMode(Mode::Normal),
225 "to normal",
226 );
227 m
228 }
229
230 pub fn bind(&mut self, mode: Mode, key: Key, action: Action, desc: impl Into<String>) {
231 self.bindings
232 .insert((mode, key), Binding::new(action, desc));
233 }
234
235 #[must_use]
236 pub fn lookup(&self, mode: Mode, key: &Key) -> Option<&Binding> {
237 self.bindings.get(&(mode, key.clone()))
238 }
239
240 #[must_use]
243 pub fn leader(&self) -> &Key {
244 &self.leader
245 }
246
247 pub fn set_leader(&mut self, key: Key) {
250 self.leader = key;
251 }
252
253 pub fn bind_sequence(
259 &mut self,
260 mode: Mode,
261 keys: Vec<Key>,
262 action: Action,
263 desc: impl Into<String>,
264 ) {
265 match keys.as_slice() {
266 [] => {}
267 [single] => self.bind(mode, single.clone(), action, desc),
268 _ => {
269 self.sequences
270 .insert((mode, keys), Binding::new(action, desc));
271 }
272 }
273 }
274
275 #[must_use]
277 pub fn lookup_sequence(&self, mode: Mode, keys: &[Key]) -> Option<&Binding> {
278 self.sequences.get(&(mode, keys.to_vec()))
279 }
280
281 #[must_use]
288 pub fn is_sequence_prefix(&self, mode: Mode, prefix: &[Key]) -> bool {
289 self.sequences
290 .keys()
291 .any(|(m, seq)| *m == mode && seq.len() > prefix.len() && seq.starts_with(prefix))
292 }
293
294 #[must_use]
296 pub fn sequence_len(&self) -> usize {
297 self.sequences.len()
298 }
299
300 #[must_use]
301 pub fn dispatch(&self, state: &ModalState, key: &Key) -> CountedAction {
302 let mode = state.mode();
303 if mode == Mode::Normal {
304 if let Key::Char(c) = key {
305 if c.is_ascii_digit() && *c != '0' {
306 return CountedAction::once(Action::Pending);
307 }
308 if *c == '0' && state.pending_count().is_some() {
309 return CountedAction::once(Action::Pending);
310 }
311 }
312 }
313 if mode == Mode::Insert {
314 if let Key::Char(c) = key {
315 return CountedAction::once(Action::InsertChar(*c));
316 }
317 if matches!(key, Key::Enter) {
318 return CountedAction::once(Action::InsertChar('\n'));
319 }
320 }
321 if mode == Mode::Command {
322 if let Key::Char(c) = key {
323 return CountedAction::once(Action::InsertChar(*c));
324 }
325 }
326 if let Some(b) = self.lookup(mode, key) {
327 return CountedAction::repeated(state.pending_count().unwrap_or(1), b.action.clone());
328 }
329 CountedAction::once(Action::Pending)
330 }
331
332 #[must_use]
333 pub fn len(&self) -> usize {
334 self.bindings.len()
335 }
336
337 #[must_use]
338 pub fn is_empty(&self) -> bool {
339 self.bindings.is_empty()
340 }
341
342 #[must_use]
344 pub fn entries_sorted(&self) -> Vec<(&Mode, &Key, &Binding)> {
345 let mut v: Vec<_> = self.bindings.iter().map(|((m, k), b)| (m, k, b)).collect();
346 v.sort_by(|a, b| {
347 (a.0.as_str(), format!("{:?}", a.1)).cmp(&(b.0.as_str(), format!("{:?}", b.1)))
348 });
349 v
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
358 fn default_vim_has_bindings() {
359 let k = Keymap::default_vim();
360 assert!(k.len() > 10);
361 assert!(k.lookup(Mode::Normal, &Key::Char('h')).is_some());
362 assert!(k.lookup(Mode::Insert, &Key::Esc).is_some());
363 assert!(k.lookup(Mode::Normal, &Key::Alt('f')).is_some());
364 }
365
366 #[test]
367 fn dispatch_normal_motion() {
368 let k = Keymap::default_vim();
369 let s = ModalState::new();
370 let a = k.dispatch(&s, &Key::Char('h'));
371 assert_eq!(a.count, 1);
372 assert_eq!(a.action, Action::Move(Motion::Left));
373 }
374
375 #[test]
376 fn dispatch_count_prefix_pends() {
377 let k = Keymap::default_vim();
378 let s = ModalState::new();
379 assert!(matches!(
380 k.dispatch(&s, &Key::Char('5')).action,
381 Action::Pending
382 ));
383 }
384
385 #[test]
386 fn dispatch_insert_char() {
387 let k = Keymap::default_vim();
388 let mut s = ModalState::new();
389 s.enter(Mode::Insert);
390 let a = k.dispatch(&s, &Key::Char('a'));
391 assert_eq!(a.action, Action::InsertChar('a'));
392 }
393
394 #[test]
395 fn lisp_structural_motions_bound() {
396 let k = Keymap::default_vim();
397 assert_eq!(
398 k.lookup(Mode::Normal, &Key::Alt('f')).unwrap().action,
399 Action::Move(Motion::ForwardSexp)
400 );
401 }
402
403 #[test]
404 fn default_leader_is_comma() {
405 assert_eq!(Keymap::new().leader(), &Key::Char(','));
406 }
407
408 #[test]
409 fn bind_sequence_stores_multikey_and_resolves() {
410 let mut k = Keymap::new();
411 let seq = vec![Key::Char(','), Key::Char('f'), Key::Char('f')];
412 k.bind_sequence(
413 Mode::Normal,
414 seq.clone(),
415 Action::Command {
416 name: "picker.files".into(),
417 args: vec![],
418 },
419 "find files",
420 );
421 let b = k.lookup_sequence(Mode::Normal, &seq).expect("seq bound");
423 assert!(matches!(&b.action, Action::Command { name, .. } if name == "picker.files"));
424 assert!(k.is_sequence_prefix(Mode::Normal, &[Key::Char(',')]));
427 assert!(k.is_sequence_prefix(Mode::Normal, &[Key::Char(','), Key::Char('f')]));
428 assert!(!k.is_sequence_prefix(Mode::Normal, &seq));
429 assert!(!k.is_sequence_prefix(Mode::Insert, &[Key::Char(',')]));
431 assert_eq!(k.sequence_len(), 1);
432 }
433
434 #[test]
435 fn bind_sequence_length_one_delegates_to_single() {
436 let mut k = Keymap::new();
437 k.bind_sequence(
438 Mode::Normal,
439 vec![Key::Char('x')],
440 Action::Undo,
441 "x is undo",
442 );
443 assert_eq!(k.sequence_len(), 0);
445 assert!(k.lookup(Mode::Normal, &Key::Char('x')).is_some());
446 }
447}