1use std::fmt;
7use winit::keyboard::{KeyCode, NamedKey};
8
9#[derive(Debug, Clone)]
11pub struct ParseError(String);
12
13impl fmt::Display for ParseError {
14 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15 write!(f, "{}", self.0)
16 }
17}
18
19impl std::error::Error for ParseError {}
20
21#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
23pub struct Modifiers {
24 pub ctrl: bool,
25 pub alt: bool,
26 pub shift: bool,
27 pub super_key: bool,
28 pub cmd_or_ctrl: bool,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Hash)]
34pub struct KeyCombo {
35 pub modifiers: Modifiers,
36 pub key: ParsedKey,
37}
38
39impl fmt::Display for KeyCombo {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 let mut parts = Vec::new();
42
43 if self.modifiers.cmd_or_ctrl {
44 parts.push("CmdOrCtrl".to_string());
45 }
46 if self.modifiers.ctrl {
47 parts.push("Ctrl".to_string());
48 }
49 if self.modifiers.alt {
50 parts.push("Alt".to_string());
51 }
52 if self.modifiers.shift {
53 parts.push("Shift".to_string());
54 }
55 if self.modifiers.super_key {
56 parts.push("Super".to_string());
57 }
58
59 match &self.key {
60 ParsedKey::Character(c) => parts.push(c.to_string()),
61 ParsedKey::Named(n) => parts.push(format!("{:?}", n)),
62 ParsedKey::Physical(k) => parts.push(format!("[{:?}]", k)),
63 }
64
65 write!(f, "{}", parts.join("+"))
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, Hash)]
71pub enum ParsedKey {
72 Character(char),
74 Named(NamedKey),
76 Physical(KeyCode),
79}
80
81pub fn parse_key_combo(s: &str) -> Result<KeyCombo, ParseError> {
96 let parts: Vec<&str> = s.split('+').map(str::trim).collect();
97
98 if parts.is_empty() {
99 return Err(ParseError("Empty key combination".to_string()));
100 }
101
102 let mut modifiers = Modifiers::default();
103 let mut key_part = None;
104
105 for (i, part) in parts.iter().enumerate() {
106 let is_last = i == parts.len() - 1;
107 let part_lower = part.to_lowercase();
108
109 let is_modifier = match part_lower.as_str() {
111 "ctrl" | "control" => {
112 modifiers.ctrl = true;
113 true
114 }
115 "alt" | "option" => {
116 modifiers.alt = true;
117 true
118 }
119 "shift" => {
120 modifiers.shift = true;
121 true
122 }
123 "super" | "cmd" | "command" | "meta" | "win" => {
124 modifiers.super_key = true;
125 true
126 }
127 "cmdorctrl" => {
128 modifiers.cmd_or_ctrl = true;
129 true
130 }
131 _ => false,
132 };
133
134 if !is_modifier {
135 if key_part.is_some() {
136 return Err(ParseError(format!(
137 "Multiple keys specified: already have key, found '{}'",
138 part
139 )));
140 }
141 key_part = Some(*part);
142 } else if is_last {
143 return Err(ParseError(
145 "Key combination ends with modifier, no key specified".to_string(),
146 ));
147 }
148 }
149
150 let key_str = key_part.ok_or_else(|| ParseError("No key specified".to_string()))?;
151 let key = parse_key(key_str)?;
152
153 Ok(KeyCombo { modifiers, key })
154}
155
156fn parse_key(s: &str) -> Result<ParsedKey, ParseError> {
158 if s.starts_with('[') && s.ends_with(']') {
160 let code_str = &s[1..s.len() - 1];
161 if let Some(code) = parse_physical_key_code(code_str) {
162 return Ok(ParsedKey::Physical(code));
163 }
164 return Err(ParseError(format!(
165 "Unknown physical key code: '{}'",
166 code_str
167 )));
168 }
169
170 if let Some(named) = parse_named_key(s) {
172 return Ok(ParsedKey::Named(named));
173 }
174
175 let chars: Vec<char> = s.chars().collect();
177 if chars.len() == 1 {
178 return Ok(ParsedKey::Character(chars[0].to_ascii_uppercase()));
179 }
180
181 Err(ParseError(format!("Unknown key: '{}'", s)))
182}
183
184fn parse_physical_key_code(s: &str) -> Option<KeyCode> {
187 match s.to_lowercase().as_str() {
188 "keya" => Some(KeyCode::KeyA),
190 "keyb" => Some(KeyCode::KeyB),
191 "keyc" => Some(KeyCode::KeyC),
192 "keyd" => Some(KeyCode::KeyD),
193 "keye" => Some(KeyCode::KeyE),
194 "keyf" => Some(KeyCode::KeyF),
195 "keyg" => Some(KeyCode::KeyG),
196 "keyh" => Some(KeyCode::KeyH),
197 "keyi" => Some(KeyCode::KeyI),
198 "keyj" => Some(KeyCode::KeyJ),
199 "keyk" => Some(KeyCode::KeyK),
200 "keyl" => Some(KeyCode::KeyL),
201 "keym" => Some(KeyCode::KeyM),
202 "keyn" => Some(KeyCode::KeyN),
203 "keyo" => Some(KeyCode::KeyO),
204 "keyp" => Some(KeyCode::KeyP),
205 "keyq" => Some(KeyCode::KeyQ),
206 "keyr" => Some(KeyCode::KeyR),
207 "keys" => Some(KeyCode::KeyS),
208 "keyt" => Some(KeyCode::KeyT),
209 "keyu" => Some(KeyCode::KeyU),
210 "keyv" => Some(KeyCode::KeyV),
211 "keyw" => Some(KeyCode::KeyW),
212 "keyx" => Some(KeyCode::KeyX),
213 "keyy" => Some(KeyCode::KeyY),
214 "keyz" => Some(KeyCode::KeyZ),
215
216 "digit0" => Some(KeyCode::Digit0),
218 "digit1" => Some(KeyCode::Digit1),
219 "digit2" => Some(KeyCode::Digit2),
220 "digit3" => Some(KeyCode::Digit3),
221 "digit4" => Some(KeyCode::Digit4),
222 "digit5" => Some(KeyCode::Digit5),
223 "digit6" => Some(KeyCode::Digit6),
224 "digit7" => Some(KeyCode::Digit7),
225 "digit8" => Some(KeyCode::Digit8),
226 "digit9" => Some(KeyCode::Digit9),
227
228 "minus" => Some(KeyCode::Minus),
230 "equal" => Some(KeyCode::Equal),
231 "bracketleft" => Some(KeyCode::BracketLeft),
232 "bracketright" => Some(KeyCode::BracketRight),
233 "backslash" => Some(KeyCode::Backslash),
234 "semicolon" => Some(KeyCode::Semicolon),
235 "quote" => Some(KeyCode::Quote),
236 "backquote" => Some(KeyCode::Backquote),
237 "comma" => Some(KeyCode::Comma),
238 "period" => Some(KeyCode::Period),
239 "slash" => Some(KeyCode::Slash),
240
241 "f1" => Some(KeyCode::F1),
243 "f2" => Some(KeyCode::F2),
244 "f3" => Some(KeyCode::F3),
245 "f4" => Some(KeyCode::F4),
246 "f5" => Some(KeyCode::F5),
247 "f6" => Some(KeyCode::F6),
248 "f7" => Some(KeyCode::F7),
249 "f8" => Some(KeyCode::F8),
250 "f9" => Some(KeyCode::F9),
251 "f10" => Some(KeyCode::F10),
252 "f11" => Some(KeyCode::F11),
253 "f12" => Some(KeyCode::F12),
254
255 "arrowup" => Some(KeyCode::ArrowUp),
257 "arrowdown" => Some(KeyCode::ArrowDown),
258 "arrowleft" => Some(KeyCode::ArrowLeft),
259 "arrowright" => Some(KeyCode::ArrowRight),
260 "home" => Some(KeyCode::Home),
261 "end" => Some(KeyCode::End),
262 "pageup" => Some(KeyCode::PageUp),
263 "pagedown" => Some(KeyCode::PageDown),
264 "insert" => Some(KeyCode::Insert),
265 "delete" => Some(KeyCode::Delete),
266
267 "enter" => Some(KeyCode::Enter),
269 "escape" => Some(KeyCode::Escape),
270 "space" => Some(KeyCode::Space),
271 "tab" => Some(KeyCode::Tab),
272 "backspace" => Some(KeyCode::Backspace),
273
274 _ => None,
275 }
276}
277
278fn parse_named_key(s: &str) -> Option<NamedKey> {
280 match s.to_lowercase().as_str() {
281 "f1" => Some(NamedKey::F1),
283 "f2" => Some(NamedKey::F2),
284 "f3" => Some(NamedKey::F3),
285 "f4" => Some(NamedKey::F4),
286 "f5" => Some(NamedKey::F5),
287 "f6" => Some(NamedKey::F6),
288 "f7" => Some(NamedKey::F7),
289 "f8" => Some(NamedKey::F8),
290 "f9" => Some(NamedKey::F9),
291 "f10" => Some(NamedKey::F10),
292 "f11" => Some(NamedKey::F11),
293 "f12" => Some(NamedKey::F12),
294
295 "enter" | "return" => Some(NamedKey::Enter),
297 "escape" | "esc" => Some(NamedKey::Escape),
298 "space" => Some(NamedKey::Space),
299 "tab" => Some(NamedKey::Tab),
300 "backspace" => Some(NamedKey::Backspace),
301 "delete" | "del" => Some(NamedKey::Delete),
302 "insert" | "ins" => Some(NamedKey::Insert),
303 "home" => Some(NamedKey::Home),
304 "end" => Some(NamedKey::End),
305 "pageup" | "pgup" => Some(NamedKey::PageUp),
306 "pagedown" | "pgdn" => Some(NamedKey::PageDown),
307
308 "up" | "arrowup" => Some(NamedKey::ArrowUp),
310 "down" | "arrowdown" => Some(NamedKey::ArrowDown),
311 "left" | "arrowleft" => Some(NamedKey::ArrowLeft),
312 "right" | "arrowright" => Some(NamedKey::ArrowRight),
313
314 _ => None,
315 }
316}
317
318pub fn key_combo_to_bytes(combo: &KeyCombo) -> Result<Vec<u8>, String> {
326 let has_ctrl = combo.modifiers.ctrl || combo.modifiers.cmd_or_ctrl;
327 let has_alt = combo.modifiers.alt;
328
329 match &combo.key {
330 ParsedKey::Character(c) => {
331 if has_ctrl {
332 let upper = c.to_ascii_uppercase();
334 if upper.is_ascii_uppercase() {
335 let code = upper as u8 - b'A' + 1;
336 let bytes = vec![code];
337 if has_alt {
338 let mut result = vec![0x1b];
340 result.extend_from_slice(&bytes);
341 Ok(result)
342 } else {
343 Ok(bytes)
344 }
345 } else {
346 Err(format!("Cannot compute Ctrl code for '{}'", c))
347 }
348 } else if has_alt {
349 let mut bytes = vec![0x1b];
351 let mut buf = [0u8; 4];
352 let encoded = c.encode_utf8(&mut buf);
353 bytes.extend_from_slice(encoded.as_bytes());
354 Ok(bytes)
355 } else {
356 let mut buf = [0u8; 4];
358 let encoded = c.encode_utf8(&mut buf);
359 Ok(encoded.as_bytes().to_vec())
360 }
361 }
362 ParsedKey::Named(named) => {
363 let seq: &[u8] = match named {
364 NamedKey::Enter => b"\r",
365 NamedKey::Tab => b"\t",
366 NamedKey::Space => b" ",
367 NamedKey::Backspace => b"\x7f",
368 NamedKey::Escape => b"\x1b",
369 NamedKey::Insert => b"\x1b[2~",
370 NamedKey::Delete => b"\x1b[3~",
371 NamedKey::ArrowUp => b"\x1b[A",
372 NamedKey::ArrowDown => b"\x1b[B",
373 NamedKey::ArrowRight => b"\x1b[C",
374 NamedKey::ArrowLeft => b"\x1b[D",
375 NamedKey::Home => b"\x1b[H",
376 NamedKey::End => b"\x1b[F",
377 NamedKey::PageUp => b"\x1b[5~",
378 NamedKey::PageDown => b"\x1b[6~",
379 NamedKey::F1 => b"\x1bOP",
380 NamedKey::F2 => b"\x1bOQ",
381 NamedKey::F3 => b"\x1bOR",
382 NamedKey::F4 => b"\x1bOS",
383 NamedKey::F5 => b"\x1b[15~",
384 NamedKey::F6 => b"\x1b[17~",
385 NamedKey::F7 => b"\x1b[18~",
386 NamedKey::F8 => b"\x1b[19~",
387 NamedKey::F9 => b"\x1b[20~",
388 NamedKey::F10 => b"\x1b[21~",
389 NamedKey::F11 => b"\x1b[23~",
390 NamedKey::F12 => b"\x1b[24~",
391 _ => return Err(format!("Unsupported named key: {:?}", named)),
392 };
393 let bytes = seq.to_vec();
394 if has_alt {
395 let mut result = vec![0x1b];
396 result.extend_from_slice(&bytes);
397 Ok(result)
398 } else {
399 Ok(bytes)
400 }
401 }
402 ParsedKey::Physical(_code) => {
403 Err("Physical key codes cannot be converted to bytes without a keyboard layout".into())
404 }
405 }
406}
407
408pub fn parse_key_sequence(keys: &str) -> Result<Vec<Vec<u8>>, String> {
416 let trimmed = keys.trim();
417 if trimmed.is_empty() {
418 return Err("Empty key sequence".to_string());
419 }
420
421 let parts: Vec<&str> = trimmed.split_whitespace().collect();
422 let mut result = Vec::with_capacity(parts.len());
423
424 for part in parts {
425 let combo = parse_key_combo(part).map_err(|e| format!("'{}': {}", part, e))?;
426 let bytes = key_combo_to_bytes(&combo)?;
427 result.push(bytes);
428 }
429
430 Ok(result)
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436
437 #[test]
438 fn test_simple_key() {
439 let combo = parse_key_combo("A").unwrap();
440 assert!(!combo.modifiers.ctrl);
441 assert!(!combo.modifiers.shift);
442 assert_eq!(combo.key, ParsedKey::Character('A'));
443 }
444
445 #[test]
446 fn test_ctrl_key() {
447 let combo = parse_key_combo("Ctrl+A").unwrap();
448 assert!(combo.modifiers.ctrl);
449 assert!(!combo.modifiers.shift);
450 assert_eq!(combo.key, ParsedKey::Character('A'));
451 }
452
453 #[test]
454 fn test_ctrl_shift_key() {
455 let combo = parse_key_combo("Ctrl+Shift+B").unwrap();
456 assert!(combo.modifiers.ctrl);
457 assert!(combo.modifiers.shift);
458 assert_eq!(combo.key, ParsedKey::Character('B'));
459 }
460
461 #[test]
462 fn test_cmd_or_ctrl() {
463 let combo = parse_key_combo("CmdOrCtrl+Shift+B").unwrap();
464 assert!(combo.modifiers.cmd_or_ctrl);
465 assert!(combo.modifiers.shift);
466 assert!(!combo.modifiers.ctrl);
467 assert_eq!(combo.key, ParsedKey::Character('B'));
468 }
469
470 #[test]
471 fn test_function_key() {
472 let combo = parse_key_combo("F5").unwrap();
473 assert!(!combo.modifiers.ctrl);
474 assert_eq!(combo.key, ParsedKey::Named(NamedKey::F5));
475 }
476
477 #[test]
478 fn test_ctrl_function_key() {
479 let combo = parse_key_combo("Ctrl+F12").unwrap();
480 assert!(combo.modifiers.ctrl);
481 assert_eq!(combo.key, ParsedKey::Named(NamedKey::F12));
482 }
483
484 #[test]
485 fn test_case_insensitive() {
486 let combo = parse_key_combo("ctrl+shift+a").unwrap();
487 assert!(combo.modifiers.ctrl);
488 assert!(combo.modifiers.shift);
489 assert_eq!(combo.key, ParsedKey::Character('A'));
490 }
491
492 #[test]
493 fn test_modifier_aliases() {
494 let combo = parse_key_combo("Control+A").unwrap();
496 assert!(combo.modifiers.ctrl);
497
498 let combo = parse_key_combo("Option+A").unwrap();
500 assert!(combo.modifiers.alt);
501
502 let combo = parse_key_combo("Cmd+A").unwrap();
504 assert!(combo.modifiers.super_key);
505
506 let combo = parse_key_combo("Command+A").unwrap();
507 assert!(combo.modifiers.super_key);
508
509 let combo = parse_key_combo("Meta+A").unwrap();
510 assert!(combo.modifiers.super_key);
511 }
512
513 #[test]
514 fn test_named_key_aliases() {
515 let combo = parse_key_combo("Enter").unwrap();
516 assert_eq!(combo.key, ParsedKey::Named(NamedKey::Enter));
517
518 let combo = parse_key_combo("Return").unwrap();
519 assert_eq!(combo.key, ParsedKey::Named(NamedKey::Enter));
520
521 let combo = parse_key_combo("Esc").unwrap();
522 assert_eq!(combo.key, ParsedKey::Named(NamedKey::Escape));
523
524 let combo = parse_key_combo("PgUp").unwrap();
525 assert_eq!(combo.key, ParsedKey::Named(NamedKey::PageUp));
526 }
527
528 #[test]
529 fn test_invalid_empty() {
530 assert!(parse_key_combo("").is_err());
531 }
532
533 #[test]
534 fn test_invalid_modifier_only() {
535 assert!(parse_key_combo("Ctrl").is_err());
536 assert!(parse_key_combo("Ctrl+Shift").is_err());
537 }
538
539 #[test]
540 fn test_invalid_unknown_key() {
541 assert!(parse_key_combo("Ctrl+UnknownKey").is_err());
542 }
543
544 #[test]
545 fn test_display() {
546 let combo = parse_key_combo("Ctrl+Shift+B").unwrap();
547 let display = format!("{}", combo);
548 assert!(display.contains("Ctrl"));
549 assert!(display.contains("Shift"));
550 assert!(display.contains("B"));
551 }
552
553 #[test]
554 fn test_physical_key() {
555 let combo = parse_key_combo("Ctrl+[KeyZ]").unwrap();
556 assert!(combo.modifiers.ctrl);
557 assert_eq!(combo.key, ParsedKey::Physical(KeyCode::KeyZ));
558 }
559
560 #[test]
561 fn test_physical_key_case_insensitive() {
562 let combo = parse_key_combo("Ctrl+[keya]").unwrap();
563 assert!(combo.modifiers.ctrl);
564 assert_eq!(combo.key, ParsedKey::Physical(KeyCode::KeyA));
565 }
566
567 #[test]
568 fn test_physical_key_display() {
569 let combo = parse_key_combo("Ctrl+[KeyZ]").unwrap();
570 let display = format!("{}", combo);
571 assert!(display.contains("Ctrl"));
572 assert!(display.contains("[KeyZ]"));
573 }
574
575 #[test]
576 fn test_invalid_physical_key() {
577 assert!(parse_key_combo("Ctrl+[Unknown]").is_err());
578 }
579
580 #[test]
583 fn test_key_combo_to_bytes_enter() {
584 let combo = parse_key_combo("Enter").unwrap();
585 let bytes = key_combo_to_bytes(&combo).unwrap();
586 assert_eq!(bytes, b"\r");
587 }
588
589 #[test]
590 fn test_key_combo_to_bytes_tab() {
591 let combo = parse_key_combo("Tab").unwrap();
592 let bytes = key_combo_to_bytes(&combo).unwrap();
593 assert_eq!(bytes, b"\t");
594 }
595
596 #[test]
597 fn test_key_combo_to_bytes_ctrl_c() {
598 let combo = parse_key_combo("Ctrl+C").unwrap();
599 let bytes = key_combo_to_bytes(&combo).unwrap();
600 assert_eq!(bytes, vec![0x03]); }
602
603 #[test]
604 fn test_key_combo_to_bytes_ctrl_a() {
605 let combo = parse_key_combo("Ctrl+A").unwrap();
606 let bytes = key_combo_to_bytes(&combo).unwrap();
607 assert_eq!(bytes, vec![0x01]); }
609
610 #[test]
611 fn test_key_combo_to_bytes_ctrl_z() {
612 let combo = parse_key_combo("Ctrl+Z").unwrap();
613 let bytes = key_combo_to_bytes(&combo).unwrap();
614 assert_eq!(bytes, vec![0x1a]); }
616
617 #[test]
618 fn test_key_combo_to_bytes_arrow_up() {
619 let combo = parse_key_combo("Up").unwrap();
620 let bytes = key_combo_to_bytes(&combo).unwrap();
621 assert_eq!(bytes, b"\x1b[A");
622 }
623
624 #[test]
625 fn test_key_combo_to_bytes_arrow_down() {
626 let combo = parse_key_combo("Down").unwrap();
627 let bytes = key_combo_to_bytes(&combo).unwrap();
628 assert_eq!(bytes, b"\x1b[B");
629 }
630
631 #[test]
632 fn test_key_combo_to_bytes_f5() {
633 let combo = parse_key_combo("F5").unwrap();
634 let bytes = key_combo_to_bytes(&combo).unwrap();
635 assert_eq!(bytes, b"\x1b[15~");
636 }
637
638 #[test]
639 fn test_key_combo_to_bytes_escape() {
640 let combo = parse_key_combo("Escape").unwrap();
641 let bytes = key_combo_to_bytes(&combo).unwrap();
642 assert_eq!(bytes, b"\x1b");
643 }
644
645 #[test]
646 fn test_key_combo_to_bytes_plain_char() {
647 let combo = parse_key_combo("A").unwrap();
648 let bytes = key_combo_to_bytes(&combo).unwrap();
649 assert_eq!(bytes, b"A");
650 }
651
652 #[test]
653 fn test_key_combo_to_bytes_alt_key() {
654 let combo = parse_key_combo("Alt+A").unwrap();
655 let bytes = key_combo_to_bytes(&combo).unwrap();
656 assert_eq!(bytes, vec![0x1b, b'A']);
657 }
658
659 #[test]
660 fn test_parse_key_sequence_single() {
661 let seqs = parse_key_sequence("Enter").unwrap();
662 assert_eq!(seqs.len(), 1);
663 assert_eq!(seqs[0], b"\r");
664 }
665
666 #[test]
667 fn test_parse_key_sequence_ctrl_c() {
668 let seqs = parse_key_sequence("Ctrl+C").unwrap();
669 assert_eq!(seqs.len(), 1);
670 assert_eq!(seqs[0], vec![0x03]);
671 }
672
673 #[test]
674 fn test_parse_key_sequence_multi_keys() {
675 let seqs = parse_key_sequence("Up Up Down Down").unwrap();
676 assert_eq!(seqs.len(), 4);
677 assert_eq!(seqs[0], b"\x1b[A");
678 assert_eq!(seqs[1], b"\x1b[A");
679 assert_eq!(seqs[2], b"\x1b[B");
680 assert_eq!(seqs[3], b"\x1b[B");
681 }
682
683 #[test]
684 fn test_parse_key_sequence_empty() {
685 assert!(parse_key_sequence("").is_err());
686 assert!(parse_key_sequence(" ").is_err());
687 }
688
689 #[test]
690 fn test_parse_key_sequence_invalid_key() {
691 assert!(parse_key_sequence("InvalidKey").is_err());
692 }
693
694 #[test]
695 fn test_key_combo_to_bytes_physical_key_error() {
696 let combo = parse_key_combo("Ctrl+[KeyZ]").unwrap();
697 assert!(key_combo_to_bytes(&combo).is_err());
698 }
699}