pub fn sanitize(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c == '\u{1b}' {
match chars.peek() {
Some('[') => {
chars.next();
while let Some(&n) = chars.peek() {
chars.next();
if ('\u{40}'..='\u{7e}').contains(&n) {
break;
}
}
}
Some(']') => {
chars.next();
while let Some(n) = chars.next() {
if n == '\u{07}' {
break;
}
if n == '\u{1b}' {
if chars.peek() == Some(&'\\') {
chars.next();
}
break;
}
}
}
Some(_) => {
chars.next();
}
None => {}
}
} else if !c.is_control() || c == '\n' || c == '\t' {
out.push(c);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn strips_ansi_sequences_whole_and_control_bytes() {
assert_eq!(sanitize("\u{1b}[31mred\u{1b}[0m ok\u{7}"), "red ok");
assert_eq!(sanitize("\u{1b}]0;title\u{7}after"), "after");
assert_eq!(sanitize("\u{1b}]0;t\u{1b}\\after"), "after");
assert_eq!(sanitize("\u{1b}cwiped"), "wiped");
assert_eq!(sanitize("a\u{0}b\u{7f}c\u{9b}d"), "abcd");
assert_eq!(sanitize("tail\u{1b}"), "tail");
}
#[test]
fn keeps_newlines_tabs_and_printable_unicode() {
assert_eq!(sanitize("a\nb\tc"), "a\nb\tc");
assert_eq!(sanitize("café → ok"), "café → ok");
assert_eq!(sanitize(""), "");
}
}