1pub fn parse_keys(input: &str) -> Vec<u8> {
5 let chars: Vec<char> = input.chars().collect();
6 let mut result = Vec::with_capacity(input.len());
7 let mut i = 0;
8
9 while i < chars.len() {
10 if chars[i] == '<' {
11 if let Some(end) = chars[i + 1..].iter().position(|&c| c == '>') {
12 let j = i + 1 + end;
13 let key: String = chars[i + 1..j].iter().collect();
14 if let Some(expansion) = expand_key(&key) {
15 result.extend_from_slice(&expansion);
16 i = j + 1;
17 continue;
18 }
19 }
20 }
21 let mut buf = [0u8; 4];
22 let encoded = chars[i].encode_utf8(&mut buf);
23 result.extend_from_slice(encoded.as_bytes());
24 i += 1;
25 }
26 result
27}
28
29fn expand_key(key: &str) -> Option<Vec<u8>> {
30 let u = key.to_uppercase();
31 match u.as_str() {
32 "RET" | "RETURN" | "ENTER" => Some(vec![b'\r']),
33 "TAB" => Some(vec![b'\t']),
34 "BSPC" | "BACKSPACE" => Some(vec![0x7f]),
35 "ESC" | "ESCAPE" => Some(vec![0x1b]),
36 "UP" => Some(vec![0x1b, b'[', b'A']),
37 "DOWN" => Some(vec![0x1b, b'[', b'B']),
38 "RIGHT" => Some(vec![0x1b, b'[', b'C']),
39 "LEFT" => Some(vec![0x1b, b'[', b'D']),
40 "C-C" | "CTRL-C" | "CTRL+C" => Some(vec![0x03]),
41 "C-D" | "CTRL-D" | "CTRL+D" => Some(vec![0x04]),
42 "C-Z" | "CTRL-Z" | "CTRL+Z" => Some(vec![0x1a]),
43 "C-/" | "CTRL-/" | "CTRL+SLASH" | "C-\\" | "CTRL-\\" | "CTRL+\\" => Some(vec![0x1c]),
44 _ => {
45 let letter = u
46 .strip_prefix("CTRL-")
47 .or_else(|| u.strip_prefix("CTRL+"))
48 .or_else(|| u.strip_prefix("C-"))
49 .and_then(|s| s.chars().next())?;
50 if letter.is_ascii_uppercase() {
51 Some(vec![letter as u8 - b'A' + 1])
52 } else if letter.is_ascii_lowercase() {
53 Some(vec![letter.to_ascii_uppercase() as u8 - b'A' + 1])
54 } else {
55 None
56 }
57 }
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn literal_text() {
67 assert_eq!(parse_keys("hello"), b"hello".to_vec());
68 assert_eq!(parse_keys("echo 'hi'\n"), b"echo 'hi'\n".to_vec());
69 }
70
71 #[test]
72 fn ret_and_enter() {
73 assert_eq!(parse_keys("ls<RET>"), b"ls\r".to_vec());
74 assert_eq!(parse_keys("pwd<ENTER>"), b"pwd\r".to_vec());
75 }
76
77 #[test]
78 fn ctrl_keys() {
79 assert_eq!(parse_keys("<C-c>"), vec![0x03]);
80 assert_eq!(parse_keys("<C-d>"), vec![0x04]);
81 assert_eq!(parse_keys("<C-a>"), vec![0x01]);
82 assert_eq!(parse_keys("<C-z>"), vec![0x1a]);
83 assert_eq!(parse_keys("<CTRL+C>"), vec![0x03]);
84 }
85
86 #[test]
87 fn mixed() {
88 assert_eq!(parse_keys("cargo build<RET>"), b"cargo build\r".to_vec());
89 }
90
91 #[test]
92 fn unknown_key_passthrough() {
93 assert_eq!(parse_keys("echo <F5> test"), b"echo <F5> test".to_vec());
94 }
95
96 #[test]
97 fn arrows() {
98 assert_eq!(parse_keys("<UP>"), vec![0x1b, b'[', b'A']);
99 assert_eq!(parse_keys("<DOWN>"), vec![0x1b, b'[', b'B']);
100 assert_eq!(parse_keys("<RIGHT>"), vec![0x1b, b'[', b'C']);
101 assert_eq!(parse_keys("<LEFT>"), vec![0x1b, b'[', b'D']);
102 }
103
104 #[test]
105 fn case_insensitive() {
106 assert_eq!(parse_keys("<ret>"), b"\r".to_vec());
107 assert_eq!(parse_keys("<Ret>"), b"\r".to_vec());
108 }
109}