1use bitflags::bitflags;
27use std::fmt;
28
29bitflags! {
30 #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
36 pub struct Modifiers: u8 {
37 const CTRL = 0b0001;
38 const SHIFT = 0b0010;
39 const ALT = 0b0100;
40 const SUPER = 0b1000;
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
50pub struct KeyChord {
51 pub modifiers: Modifiers,
52 pub key: Key,
53}
54
55impl KeyChord {
56 pub const fn new(modifiers: Modifiers, key: Key) -> Self {
57 Self { modifiers, key }
58 }
59
60 pub const fn plain(key: Key) -> Self {
61 Self {
62 modifiers: Modifiers::empty(),
63 key,
64 }
65 }
66
67 pub const fn char(c: char) -> Self {
68 Self::plain(Key::Char(c))
69 }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
79pub enum Key {
80 Char(char),
81 Named(NamedKey),
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
85pub enum NamedKey {
86 Esc,
87 CR,
89 Tab,
90 BackTab,
93 BS,
94 Space,
95 Up,
96 Down,
97 Left,
98 Right,
99 Home,
100 End,
101 PageUp,
102 PageDown,
103 Insert,
104 Delete,
105 F(u8),
106 Leader,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
112pub enum ParseError {
113 #[error("unknown key name <{0}>")]
114 UnknownNamed(String),
115 #[error("unclosed `<` in key notation")]
116 UnclosedBracket,
117 #[error("empty `<...>` block")]
118 EmptyBracket,
119 #[error("modifier `<{0}->` with no following key")]
120 DanglingModifier(String),
121 #[error("expected exactly one chord, got {0}")]
122 ExpectedOneChord(usize),
123}
124
125pub fn parse_keys(input: &str) -> Result<Vec<KeyChord>, ParseError> {
127 let mut out = Vec::new();
128 let mut chars = input.chars().peekable();
129 while let Some(c) = chars.next() {
130 if c.is_ascii_whitespace() {
133 continue;
134 }
135 if c == '<' {
136 let mut name = String::new();
137 let mut closed = false;
138 for nc in chars.by_ref() {
139 if nc == '>' {
140 closed = true;
141 break;
142 }
143 name.push(nc);
144 }
145 if !closed {
146 return Err(ParseError::UnclosedBracket);
147 }
148 if name.is_empty() {
149 return Err(ParseError::EmptyBracket);
150 }
151 out.push(parse_named(&name)?);
152 } else {
153 out.push(parse_char(c));
154 }
155 }
156 Ok(out)
157}
158
159pub fn parse_key(input: &str) -> Result<KeyChord, ParseError> {
162 let chords = parse_keys(input)?;
163 if chords.len() != 1 {
164 return Err(ParseError::ExpectedOneChord(chords.len()));
165 }
166 Ok(chords.into_iter().next().expect("len checked above"))
167}
168
169fn parse_char(c: char) -> KeyChord {
170 let mut mods = Modifiers::empty();
171 if c.is_ascii_uppercase() {
172 mods |= Modifiers::SHIFT;
173 }
174 KeyChord {
175 modifiers: mods,
176 key: Key::Char(c),
177 }
178}
179
180fn parse_named(raw: &str) -> Result<KeyChord, ParseError> {
181 if raw.eq_ignore_ascii_case("lt") {
183 return Ok(KeyChord::char('<'));
184 }
185
186 let (mods, tail) = parse_modifiers(raw);
187
188 if tail.is_empty() {
189 return Err(ParseError::DanglingModifier(raw.to_string()));
190 }
191
192 let key = if tail.eq_ignore_ascii_case("leader") {
194 Key::Named(NamedKey::Leader)
195 } else if tail.eq_ignore_ascii_case("space") {
196 Key::Char(' ')
201 } else if tail.chars().count() == 1 {
202 let ch = tail.chars().next().expect("len checked above");
205 let ch = if mods.contains(Modifiers::CTRL) {
208 ch.to_ascii_lowercase()
209 } else if mods.contains(Modifiers::SHIFT) && ch.is_ascii_alphabetic() {
210 ch.to_ascii_uppercase()
211 } else {
212 ch
213 };
214 Key::Char(ch)
215 } else {
216 Key::Named(parse_named_key(tail)?)
217 };
218
219 Ok(KeyChord {
220 modifiers: mods,
221 key,
222 })
223}
224
225fn parse_modifiers(raw: &str) -> (Modifiers, &str) {
228 let mut mods = Modifiers::empty();
229 let mut tail = raw;
230 loop {
231 let lower_prefix = tail.get(..2).map(str::to_ascii_lowercase);
232 match lower_prefix.as_deref() {
233 Some("c-") => {
234 mods |= Modifiers::CTRL;
235 tail = &tail[2..];
236 }
237 Some("s-") => {
238 mods |= Modifiers::SHIFT;
239 tail = &tail[2..];
240 }
241 Some("m-") | Some("a-") => {
242 mods |= Modifiers::ALT;
243 tail = &tail[2..];
244 }
245 Some("d-") => {
246 mods |= Modifiers::SUPER;
247 tail = &tail[2..];
248 }
249 _ => return (mods, tail),
250 }
251 }
252}
253
254fn parse_named_key(name: &str) -> Result<NamedKey, ParseError> {
255 let n = name.to_ascii_lowercase();
256 Ok(match n.as_str() {
257 "esc" | "escape" => NamedKey::Esc,
258 "cr" | "enter" | "return" => NamedKey::CR,
259 "bs" | "backspace" => NamedKey::BS,
260 "tab" => NamedKey::Tab,
261 "backtab" => NamedKey::BackTab,
262 "space" => NamedKey::Space,
263 "up" => NamedKey::Up,
264 "down" => NamedKey::Down,
265 "left" => NamedKey::Left,
266 "right" => NamedKey::Right,
267 "home" => NamedKey::Home,
268 "end" => NamedKey::End,
269 "pageup" | "pgup" => NamedKey::PageUp,
270 "pagedown" | "pgdn" => NamedKey::PageDown,
271 "insert" | "ins" => NamedKey::Insert,
272 "delete" | "del" => NamedKey::Delete,
273 s if s.starts_with('f') => {
274 let num: u8 = s[1..]
275 .parse()
276 .map_err(|_| ParseError::UnknownNamed(name.to_string()))?;
277 if !(1..=12).contains(&num) {
278 return Err(ParseError::UnknownNamed(name.to_string()));
279 }
280 NamedKey::F(num)
281 }
282 _ => return Err(ParseError::UnknownNamed(name.to_string())),
283 })
284}
285
286impl fmt::Display for Modifiers {
287 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288 bitflags::parser::to_writer(self, f)
289 }
290}
291
292impl fmt::Display for NamedKey {
293 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294 let s = match self {
295 NamedKey::Esc => "Esc",
296 NamedKey::CR => "CR",
297 NamedKey::Tab => "Tab",
298 NamedKey::BackTab => "S-Tab",
299 NamedKey::BS => "BS",
300 NamedKey::Space => "Space",
301 NamedKey::Up => "Up",
302 NamedKey::Down => "Down",
303 NamedKey::Left => "Left",
304 NamedKey::Right => "Right",
305 NamedKey::Home => "Home",
306 NamedKey::End => "End",
307 NamedKey::PageUp => "PageUp",
308 NamedKey::PageDown => "PageDown",
309 NamedKey::Insert => "Insert",
310 NamedKey::Delete => "Delete",
311 NamedKey::F(n) => return write!(f, "F{n}"),
312 NamedKey::Leader => "leader",
313 };
314 f.write_str(s)
315 }
316}
317
318impl fmt::Display for KeyChord {
319 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324 let mods = self.modifiers;
325 if mods.is_empty() {
327 return match self.key {
328 Key::Char(c) => write!(f, "{c}"),
329 Key::Named(n) => write!(f, "<{n}>"),
330 };
331 }
332 if mods == Modifiers::SHIFT
335 && let Key::Char(c) = self.key
336 && c.is_ascii_uppercase()
337 {
338 return write!(f, "{c}");
339 }
340 write!(f, "<")?;
341 if mods.contains(Modifiers::CTRL) {
342 write!(f, "C-")?;
343 }
344 if mods.contains(Modifiers::SHIFT) {
345 write!(f, "S-")?;
346 }
347 if mods.contains(Modifiers::ALT) {
348 write!(f, "A-")?;
349 }
350 if mods.contains(Modifiers::SUPER) {
351 write!(f, "D-")?;
352 }
353 match self.key {
354 Key::Char(c) => write!(f, "{c}>"),
355 Key::Named(n) => write!(f, "{n}>"),
356 }
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 fn k(ch: char) -> KeyChord {
365 KeyChord::char(ch)
366 }
367
368 fn shift_k(ch: char) -> KeyChord {
369 KeyChord {
370 key: Key::Char(ch),
371 modifiers: Modifiers::SHIFT,
372 }
373 }
374
375 fn ctrl(ch: char) -> KeyChord {
376 KeyChord {
377 key: Key::Char(ch),
378 modifiers: Modifiers::CTRL,
379 }
380 }
381
382 #[test]
383 fn empty_input() {
384 assert_eq!(parse_keys("").unwrap(), vec![]);
385 }
386
387 #[test]
388 fn bare_chars() {
389 assert_eq!(parse_keys("gT").unwrap(), vec![k('g'), shift_k('T')]);
390 }
391
392 #[test]
393 fn ctrl_w_v() {
394 assert_eq!(parse_keys("<C-w>v").unwrap(), vec![ctrl('w'), k('v')]);
395 }
396
397 #[test]
398 fn ctrl_shift_tab() {
399 let chords = parse_keys("<C-S-Tab>").unwrap();
400 assert_eq!(chords.len(), 1);
401 assert_eq!(chords[0].key, Key::Named(NamedKey::Tab));
402 assert!(chords[0].modifiers.contains(Modifiers::CTRL));
403 assert!(chords[0].modifiers.contains(Modifiers::SHIFT));
404 }
405
406 #[test]
407 fn shift_tab_alias() {
408 let a = parse_keys("<S-Tab>").unwrap();
409 let b = parse_keys("<BackTab>").unwrap();
410 assert_eq!(a[0].key, Key::Named(NamedKey::Tab));
414 assert!(a[0].modifiers.contains(Modifiers::SHIFT));
415 assert_eq!(b[0].key, Key::Named(NamedKey::BackTab));
416 }
417
418 #[test]
419 fn meta_alias_for_alt() {
420 let m = parse_keys("<M-x>").unwrap();
421 let a = parse_keys("<A-x>").unwrap();
422 assert_eq!(m, a);
423 assert!(m[0].modifiers.contains(Modifiers::ALT));
424 }
425
426 #[test]
427 fn leader_is_abstract() {
428 let chords = parse_keys("<leader>n").unwrap();
429 assert_eq!(chords[0].key, Key::Named(NamedKey::Leader));
430 assert_eq!(chords[1], k('n'));
431 }
432
433 #[test]
434 fn space_special() {
435 let chords = parse_keys("<Space>x").unwrap();
439 assert_eq!(chords[0].key, Key::Char(' '));
440 assert_eq!(chords[1], k('x'));
441 }
442
443 #[test]
444 fn esc_aliases() {
445 for s in ["<Esc>", "<escape>", "<ESC>"] {
446 assert_eq!(
447 parse_keys(s).unwrap(),
448 vec![KeyChord::plain(Key::Named(NamedKey::Esc))],
449 "alias {s}"
450 );
451 }
452 }
453
454 #[test]
455 fn enter_cr_alias() {
456 let cr = parse_keys("<CR>").unwrap();
457 let enter = parse_keys("<Enter>").unwrap();
458 let ret = parse_keys("<Return>").unwrap();
459 assert_eq!(cr, enter);
460 assert_eq!(cr, ret);
461 }
462
463 #[test]
464 fn bs_aliases() {
465 let bs = parse_keys("<BS>").unwrap();
466 let bksp = parse_keys("<Backspace>").unwrap();
467 assert_eq!(bs, bksp);
468 assert_eq!(bs[0].key, Key::Named(NamedKey::BS));
469 }
470
471 #[test]
472 fn lt_escape() {
473 let chords = parse_keys("<lt>").unwrap();
474 assert_eq!(chords, vec![k('<')]);
475 }
476
477 #[test]
478 fn function_keys() {
479 for n in 1..=12u8 {
480 let s = format!("<F{n}>");
481 let chords = parse_keys(&s).unwrap();
482 assert_eq!(chords[0].key, Key::Named(NamedKey::F(n)));
483 }
484 }
485
486 #[test]
487 fn function_key_out_of_range_errors() {
488 assert!(matches!(
489 parse_keys("<F13>"),
490 Err(ParseError::UnknownNamed(_))
491 ));
492 assert!(matches!(
493 parse_keys("<F0>"),
494 Err(ParseError::UnknownNamed(_))
495 ));
496 }
497
498 #[test]
499 fn unclosed_bracket_errors() {
500 assert_eq!(parse_keys("<C-w").unwrap_err(), ParseError::UnclosedBracket);
501 }
502
503 #[test]
504 fn dangling_modifier_errors() {
505 assert!(matches!(
507 parse_keys("<C->"),
508 Err(ParseError::DanglingModifier(_))
509 ));
510 }
511
512 #[test]
513 fn empty_bracket_errors() {
514 assert!(matches!(parse_keys("<>"), Err(ParseError::EmptyBracket)));
515 }
516
517 #[test]
518 fn unknown_named_errors() {
519 match parse_keys("<NoSuchKey>") {
520 Err(ParseError::UnknownNamed(name)) => {
521 assert!(name.eq_ignore_ascii_case("nosuchkey"));
522 }
523 other => panic!("expected UnknownNamed, got {other:?}"),
524 }
525 }
526
527 #[test]
528 fn whitespace_ignored() {
529 let chords = parse_keys("<C-w> v ").unwrap();
530 assert_eq!(chords, vec![ctrl('w'), k('v')]);
531 }
532
533 #[test]
534 fn modifier_order_independent() {
535 let a = parse_keys("<C-S-Tab>").unwrap();
536 let b = parse_keys("<S-C-Tab>").unwrap();
537 assert_eq!(a, b);
538 }
539
540 #[test]
541 fn shift_letter_normalises_to_uppercase() {
542 let a = parse_keys("<S-a>").unwrap();
543 let b = parse_keys("<S-A>").unwrap();
544 assert_eq!(a, b);
545 assert_eq!(a[0].key, Key::Char('A'));
546 assert!(a[0].modifiers.contains(Modifiers::SHIFT));
547 }
548
549 #[test]
550 fn ctrl_letter_is_case_insensitive() {
551 let a = parse_keys("<C-A>").unwrap();
552 let b = parse_keys("<C-a>").unwrap();
553 assert_eq!(a, b);
554 assert_eq!(a[0].key, Key::Char('a'));
555 }
556
557 #[test]
558 fn punctuation_passes_through() {
559 let chords = parse_keys(":/?,;").unwrap();
560 assert_eq!(chords, vec![k(':'), k('/'), k('?'), k(','), k(';')]);
561 }
562
563 #[test]
564 fn embedded_literals_split_per_char() {
565 let chords = parse_keys("Hello").unwrap();
567 assert_eq!(chords.len(), 5);
568 assert_eq!(chords[0], shift_k('H'));
569 assert_eq!(chords[1], k('e'));
570 assert_eq!(chords[4], k('o'));
571 }
572
573 #[test]
574 fn register_prefix_quote_a() {
575 let chords = parse_keys("\"ay").unwrap();
578 assert_eq!(chords, vec![k('"'), k('a'), k('y')]);
579 }
580
581 #[test]
582 fn parse_key_single() {
583 let kc = parse_key("<C-w>").unwrap();
584 assert_eq!(kc, ctrl('w'));
585 }
586
587 #[test]
588 fn parse_key_residual_errors() {
589 assert!(matches!(
590 parse_key("<C-w>v"),
591 Err(ParseError::ExpectedOneChord(2))
592 ));
593 assert!(matches!(
594 parse_key(""),
595 Err(ParseError::ExpectedOneChord(0))
596 ));
597 }
598}