1pub fn is_csi_final_byte(byte: u8) -> bool {
3 (0x40..=0x7e).contains(&byte)
4}
5
6pub fn strip_ansi(input: &str) -> String {
9 let mut out = String::with_capacity(input.len());
10 let mut chars = input.chars().peekable();
11 while let Some(character) = chars.next() {
12 if character != '\x1b' {
13 out.push(character);
14 continue;
15 }
16 match chars.next() {
17 Some('[') => {
18 for next in chars.by_ref() {
19 if next.is_ascii() && is_csi_final_byte(next as u8) {
20 break;
21 }
22 }
23 }
24 Some(kind @ (']' | 'P' | 'X' | '^' | '_')) => {
25 while let Some(next) = chars.next() {
26 if kind == ']' && next == '\x07' {
27 break;
28 }
29 if next == '\x1b' && chars.peek() == Some(&'\\') {
30 chars.next();
31 break;
32 }
33 }
34 }
35 Some(' '..='/') => {
36 for next in chars.by_ref() {
37 if ('0'..='~').contains(&next) {
38 break;
39 }
40 }
41 }
42 _ => {}
43 }
44 }
45 out
46}
47
48pub fn truncate_utf8(value: &mut String, limit: usize) {
50 if value.len() <= limit {
51 return;
52 }
53 let mut end = limit;
54 while !value.is_char_boundary(end) {
55 end -= 1;
56 }
57 value.truncate(end);
58}
59
60#[cfg(test)]
61#[path = "tests/text/mod.rs"]
62mod tests;
63
64pub fn push_bounded(output: &mut String, value: &str, maximum_bytes: usize) -> bool {
66 let available = maximum_bytes.saturating_sub(output.len());
67 if value.len() <= available {
68 output.push_str(value);
69 return true;
70 }
71 let mut keep = available.min(value.len());
72 while keep > 0 && !value.is_char_boundary(keep) {
73 keep -= 1;
74 }
75 output.push_str(&value[..keep]);
76 false
77}
78
79pub fn append_truncation_marker(output: &mut String, marker: &str, maximum_bytes: usize) {
81 if marker.len() > maximum_bytes {
82 return;
83 }
84 if output.len().saturating_add(marker.len()) > maximum_bytes {
85 let mut keep = maximum_bytes - marker.len();
86 while keep > 0 && !output.is_char_boundary(keep) {
87 keep -= 1;
88 }
89 output.truncate(keep);
90 }
91 output.push_str(marker);
92}
93
94pub fn utf8_prefix(value: &str, limit: usize) -> &str {
96 let mut end = limit.min(value.len());
97 while !value.is_char_boundary(end) {
98 end -= 1;
99 }
100 &value[..end]
101}
102
103#[cfg(test)]
104#[path = "tests/text_bounded/mod.rs"]
105mod bounded_tests;