1fn buf_trim_trailing(buf: &[u8]) -> Vec<&[u8]> {
3 buf.split(|c| *c == b'\n')
4 .map(|line| line.trim_ascii_end())
5 .collect()
6}
7
8fn drop_last_empty_lines<'a>(lines: &[&'a [u8]]) -> Vec<&'a [u8]> {
10 if let Some(last) = lines.iter().rposition(|line| !line.is_empty()) {
11 lines[0..=last].to_vec()
12 } else {
13 lines.to_vec()
14 }
15}
16
17pub fn cleanup_captured_buffer(buffer: &[u8], drop_n_last_lines: usize) -> Vec<u8> {
38 let trimmed_lines: Vec<&[u8]> = buf_trim_trailing(buffer);
39 let mut buffer: Vec<&[u8]> = drop_last_empty_lines(&trimmed_lines);
40 buffer.truncate(buffer.len() - drop_n_last_lines);
41
42 let mut final_buffer: Vec<u8> = Vec::with_capacity(buffer.len());
44 for (idx, &line) in buffer.iter().enumerate() {
45 final_buffer.extend_from_slice(line);
46
47 let is_last_line = idx == buffer.len() - 1;
48 if is_last_line {
49 let reset = "\u{001b}[0m".as_bytes();
50 final_buffer.extend_from_slice(reset);
51 final_buffer.push(b'\n');
52 } else {
53 final_buffer.push(b'\n');
54 }
55 }
56
57 final_buffer
58}
59
60#[cfg(test)]
61mod tests {
62 use super::{buf_trim_trailing, cleanup_captured_buffer, drop_last_empty_lines};
63
64 #[test]
65 fn trims_trailing_whitespaces() {
66 let input = " text ".as_bytes();
67 let expected = " text".as_bytes();
68
69 let actual = input.trim_ascii_end();
70 assert_eq!(actual, expected);
71 }
72
73 #[test]
74 fn trims_whitespaces() {
75 let input = " text ".as_bytes();
76 let expected = "text".as_bytes();
77
78 let actual = input.trim_ascii();
79 assert_eq!(actual, expected);
80 }
81
82 #[test]
83 fn test_buf_trim_trailing() {
84 let text = "line1\n\nline3 ";
85 let actual = buf_trim_trailing(text.as_bytes());
86 let expected = vec!["line1".as_bytes(), "".as_bytes(), "line3".as_bytes()];
87 assert_eq!(actual, expected);
88 }
89
90 #[test]
91 fn test_buf_drop_last_empty_lines() {
92 let text = "line1\nline2\n\nline3 ";
93
94 let trimmed_lines = buf_trim_trailing(text.as_bytes());
95 let actual = drop_last_empty_lines(&trimmed_lines);
96 let expected = trimmed_lines;
97 assert_eq!(actual, expected);
98
99 let text = "line1\nline2\n\n\n ";
102
103 let trimmed_lines = buf_trim_trailing(text.as_bytes());
104 let actual = drop_last_empty_lines(&trimmed_lines);
105 let expected = vec!["line1".as_bytes(), "line2".as_bytes()];
106 assert_eq!(actual, expected);
107 }
108
109 #[test]
110 fn test_trim_only_whitespace() {
111 let input = " \t ".as_bytes();
112 assert_eq!(input.trim_ascii(), &[]);
113 assert_eq!(input.trim_ascii_end(), &[]);
114 }
115
116 #[test]
117 fn test_trim_empty() {
118 let input = "".as_bytes();
119 assert_eq!(input.trim_ascii(), &[]);
120 assert_eq!(input.trim_ascii_end(), &[]);
121 }
122
123 #[test]
124 fn test_trim_tabs() {
125 let input = "\t\ttext\t\t".as_bytes();
126 assert_eq!(input.trim_ascii(), "text".as_bytes());
127 assert_eq!(input.trim_ascii_end(), "\t\ttext".as_bytes());
128 }
129
130 #[test]
131 fn test_cleanup_captured_buffer_basic() {
132 let input = "line1\nline2\n";
133 let result = cleanup_captured_buffer(input.as_bytes(), 0);
134
135 let result_str = String::from_utf8(result).unwrap();
137 assert!(result_str.contains("line1\n"));
138 assert!(result_str.contains("line2"));
139 assert!(result_str.contains("\u{001b}[0m")); }
141
142 #[test]
143 fn test_cleanup_captured_buffer_trims_trailing_spaces() {
144 let input = "line1 \nline2 \n";
145 let result = cleanup_captured_buffer(input.as_bytes(), 0);
146
147 let result_str = String::from_utf8(result).unwrap();
148 assert!(result_str.starts_with("line1\n"));
150 assert!(result_str.contains("line2\u{001b}[0m\n"));
151 }
152
153 #[test]
154 fn test_cleanup_captured_buffer_drops_empty_trailing_lines() {
155 let input = "line1\nline2\n\n\n \n";
156 let result = cleanup_captured_buffer(input.as_bytes(), 0);
157
158 let result_str = String::from_utf8(result).unwrap();
159 assert_eq!(result_str, "line1\nline2\u{001b}[0m\n");
161 }
162
163 #[test]
164 fn test_cleanup_captured_buffer_drop_n_last_lines() {
165 let input = "line1\nline2\nline3\nline4\n";
166 let result = cleanup_captured_buffer(input.as_bytes(), 2);
167
168 let result_str = String::from_utf8(result).unwrap();
169 assert_eq!(result_str, "line1\nline2\u{001b}[0m\n");
171 }
172
173 #[test]
174 fn test_cleanup_captured_buffer_single_line() {
175 let input = "single line \n";
176 let result = cleanup_captured_buffer(input.as_bytes(), 0);
177
178 let result_str = String::from_utf8(result).unwrap();
179 assert_eq!(result_str, "single line\u{001b}[0m\n");
180 }
181
182 #[test]
183 fn test_cleanup_captured_buffer_preserves_escape_codes() {
184 let input = "\u{001b}[32mgreen text\u{001b}[0m\n";
186 let result = cleanup_captured_buffer(input.as_bytes(), 0);
187
188 let result_str = String::from_utf8(result).unwrap();
189 assert!(result_str.contains("\u{001b}[32m"));
191 assert!(result_str.ends_with("\u{001b}[0m\n"));
192 }
193
194 #[test]
195 fn test_drop_last_empty_lines_all_empty() {
196 let lines: Vec<&[u8]> = vec![b"", b"", b""];
197 let result = drop_last_empty_lines(&lines);
198 assert_eq!(result, lines);
200 }
201
202 #[test]
203 fn test_drop_last_empty_lines_no_empty() {
204 let lines: Vec<&[u8]> = vec![b"a", b"b", b"c"];
205 let result = drop_last_empty_lines(&lines);
206 assert_eq!(result, lines);
207 }
208
209 #[test]
210 fn test_cleanup_captured_buffer_tabs_are_trimmed() {
211 let input = "line1\t\t\nline2\t\n";
212 let result = cleanup_captured_buffer(input.as_bytes(), 0);
213 let result_str = String::from_utf8(result).unwrap();
214 assert_eq!(result_str, "line1\nline2\u{001b}[0m\n");
215 }
216
217 #[test]
218 fn test_cleanup_captured_buffer_mixed_trailing_whitespace() {
219 let input = "line1 \t \nline2\t \t\n";
220 let result = cleanup_captured_buffer(input.as_bytes(), 0);
221 let result_str = String::from_utf8(result).unwrap();
222 assert_eq!(result_str, "line1\nline2\u{001b}[0m\n");
223 }
224
225 #[test]
226 fn test_cleanup_captured_buffer_no_trailing_newline() {
227 let input = "line1\nline2";
228 let result = cleanup_captured_buffer(input.as_bytes(), 0);
229 let result_str = String::from_utf8(result).unwrap();
230 assert_eq!(result_str, "line1\nline2\u{001b}[0m\n");
231 }
232
233 #[test]
234 fn test_buf_trim_trailing_preserves_leading_whitespace() {
235 let text = " indented\n\tnested\n";
236 let actual = buf_trim_trailing(text.as_bytes());
237 assert_eq!(actual[0], " indented".as_bytes());
238 assert_eq!(actual[1], "\tnested".as_bytes());
239 }
240}