Skip to main content

stynx_code_tui/
util.rs

1
2pub fn strip_ansi(input: &str) -> String {
3    let bytes = input.as_bytes();
4    let mut out = String::with_capacity(bytes.len());
5    let mut i = 0;
6    while i < bytes.len() {
7        let b = bytes[i];
8        if b == 0x1b && i + 1 < bytes.len() {
9            let next = bytes[i + 1];
10
11            if next == b'[' {
12                i += 2;
13                while i < bytes.len() {
14                    let c = bytes[i];
15                    if (0x40..=0x7e).contains(&c) {
16                        i += 1;
17                        break;
18                    }
19                    i += 1;
20                }
21                continue;
22            }
23
24            if next == b']' {
25                i += 2;
26                while i < bytes.len() {
27                    let c = bytes[i];
28                    if c == 0x07 {
29                        i += 1;
30                        break;
31                    }
32                    if c == 0x1b && i + 1 < bytes.len() && bytes[i + 1] == b'\\' {
33                        i += 2;
34                        break;
35                    }
36                    i += 1;
37                }
38                continue;
39            }
40
41            if matches!(next, b'(' | b')' | b'*' | b'+' | b'#') && i + 2 < bytes.len() {
42                i += 3;
43                continue;
44            }
45
46            i += 1;
47            continue;
48        }
49
50        if b < 0x20 && b != b'\n' && b != b'\t' && b != b'\r' {
51            i += 1;
52            continue;
53        }
54
55        let char_end = utf8_char_end(bytes, i);
56        out.push_str(&input[i..char_end]);
57        i = char_end;
58    }
59    out
60}
61
62fn utf8_char_end(bytes: &[u8], i: usize) -> usize {
63    let b = bytes[i];
64    let width = if b < 0x80 {
65        1
66    } else if b & 0xe0 == 0xc0 {
67        2
68    } else if b & 0xf0 == 0xe0 {
69        3
70    } else if b & 0xf8 == 0xf0 {
71        4
72    } else {
73        1
74    };
75    (i + width).min(bytes.len())
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn strips_csi() {
84        assert_eq!(strip_ansi("\x1b[31mred\x1b[0m"), "red");
85        assert_eq!(strip_ansi("\x1b[1;33mwarn\x1b[m hi"), "warn hi");
86    }
87
88    #[test]
89    fn strips_osc() {
90        assert_eq!(strip_ansi("\x1b]0;title\x07after"), "after");
91        assert_eq!(strip_ansi("\x1b]0;title\x1b\\after"), "after");
92    }
93
94    #[test]
95    fn keeps_unicode() {
96        assert_eq!(strip_ansi("héllo 🦀"), "héllo 🦀");
97    }
98}