1use super::text::{Token, WidthMode, tokenize};
19
20pub fn strip(s: &str) -> String {
26 let mut out = String::with_capacity(s.len());
27 for tok in tokenize(s.as_bytes(), WidthMode::default(), false) {
28 match tok {
29 Token::Text { text, .. } => out.push_str(bs(text)),
30 Token::Control(b) => out.push(b as char),
31 Token::Escape(_) => {}
32 }
33 }
34 out
35}
36
37#[inline]
38fn bs(b: &[u8]) -> &str {
39 unsafe { std::str::from_utf8_unchecked(b) }
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47
48 #[test]
49 fn strip_sgr() {
50 assert_eq!(strip("\x1b[31mhello\x1b[0m"), "hello");
51 }
52
53 #[test]
54 fn strip_osc() {
55 assert_eq!(strip("\x1b]0;title\x07hello"), "hello");
56 assert_eq!(strip("\x1b]0;title\x1b\\hello"), "hello");
57 }
58
59 #[test]
60 fn strip_preserves_newlines() {
61 assert_eq!(strip("a\nb\tc"), "a\nb\tc");
62 }
63
64 #[test]
65 fn strip_unicode() {
66 assert_eq!(strip("\x1b[1m中文\x1b[m"), "中文");
67 }
68
69 #[test]
70 fn strip_empty() {
71 assert_eq!(strip(""), "");
72 }
73
74 #[test]
75 fn strip_only_escapes() {
76 assert_eq!(strip("\x1b[31m\x1b[m"), "");
77 }
78
79 #[test]
80 fn strip_nested_csi() {
81 assert_eq!(strip("a\x1b[1;2;3;4mb\x1b[0;1mc"), "abc");
82 }
83
84 #[test]
85 fn strip_two_byte_esc() {
86 assert_eq!(strip("a\x1b=b"), "ab");
88 }
89}