1pub const EOF: char = '\x04';
8
9pub const ESCAPE: char = '\x1b';
11
12#[cfg(unix)]
16pub const DELETE: char = RUBOUT;
17
18#[cfg(windows)]
22pub const DELETE: char = '\x08';
23
24pub const RUBOUT: char = '\x7f';
26
27pub fn parse_char_name(name: &str) -> Option<String> {
31 let name_lc = name.to_lowercase();
32
33 let is_ctrl = contains_any(&name_lc, &["c-", "ctrl-", "control-"]);
34 let is_meta = contains_any(&name_lc, &["m-", "meta-"]);
35
36 let name = match name_lc.rfind('-') {
37 Some(pos) => &name_lc[pos + 1..],
38 None => &name_lc[..],
39 };
40
41 let ch = match name {
42 "del" | "rubout" => DELETE,
43 "esc" | "escape" => ESCAPE,
44 "lfd" | "newline" => '\n',
45 "ret" | "return" => '\r',
46 "spc" | "space" => ' ',
47 "tab" => '\t',
48 s if !s.is_empty() => s.chars().next().unwrap(),
49 _ => return None,
50 };
51
52 let ch = match (is_ctrl, is_meta) {
53 (true, true) => meta(ctrl(ch)),
54 (true, false) => ctrl(ch).to_string(),
55 (false, true) => meta(ch),
56 (false, false) => ch.to_string(),
57 };
58
59 Some(ch)
60}
61
62pub fn escape_sequence(s: &str) -> String {
67 let mut res = String::with_capacity(s.len());
68
69 for ch in s.chars() {
70 match ch {
71 ESCAPE => res.push_str(r"\e"),
72 RUBOUT => res.push_str(r"\C-?"),
73 '\\' => res.push_str(r"\\"),
74 '\'' => res.push_str(r"\'"),
75 '"' => res.push_str(r#"\""#),
76 ch if is_ctrl(ch) => {
77 res.push_str(r"\C-");
78 res.push(unctrl_lower(ch));
79 }
80 ch => res.push(ch),
81 }
82 }
83
84 res
85}
86
87pub fn meta(ch: char) -> String {
89 let mut s = String::with_capacity(ch.len_utf8() + 1);
90 s.push(ESCAPE);
91 s.push(ch);
92 s
93}
94
95fn contains_any(s: &str, strs: &[&str]) -> bool {
96 strs.iter().any(|a| s.contains(a))
97}
98
99pub fn is_printable(c: char) -> bool {
103 c == '\t' || c == '\n' || !(c == '\0' || is_ctrl(c))
104}
105
106const CTRL_BIT: u8 = 0x40;
107const CTRL_MASK: u8 = 0x1f;
108
109pub fn is_ctrl(c: char) -> bool {
111 const CTRL_MAX: u32 = 0x1f;
112
113 c != '\0' && c as u32 <= CTRL_MAX
114}
115
116pub fn ctrl(c: char) -> char {
118 ((c as u8) & CTRL_MASK) as char
119}
120
121pub fn unctrl(c: char) -> char {
123 ((c as u8) | CTRL_BIT) as char
124}
125
126pub fn unctrl_lower(c: char) -> char {
128 unctrl(c).to_ascii_lowercase()
129}
130
131#[cfg(test)]
132mod test {
133 use super::{ctrl, escape_sequence, parse_char_name, unctrl, unctrl_lower};
134
135 #[test]
136 fn test_ctrl() {
137 assert_eq!(ctrl('A'), '\x01');
138 assert_eq!(ctrl('I'), '\t');
139 assert_eq!(ctrl('J'), '\n');
140 assert_eq!(ctrl('M'), '\r');
141
142 assert_eq!(unctrl('\x01'), 'A');
143 assert_eq!(unctrl('\t'), 'I');
144 assert_eq!(unctrl('\n'), 'J');
145 assert_eq!(unctrl('\r'), 'M');
146 }
147
148 #[test]
149 fn test_unctrl() {
150 assert_eq!(unctrl('\x1d'), ']');
151 assert_eq!(unctrl_lower('\x1d'), ']');
152 }
153
154 #[test]
155 fn test_escape() {
156 assert_eq!(escape_sequence("\x1b\x7f"), r"\e\C-?");
157 }
158
159 #[test]
160 fn test_parse_char() {
161 assert_eq!(parse_char_name("Escape"), Some("\x1b".to_owned()));
162 assert_eq!(parse_char_name("Control-u"), Some("\x15".to_owned()));
163 assert_eq!(parse_char_name("Meta-tab"), Some("\x1b\t".to_owned()));
164 }
165}