tauri_plugin_tracing/
strip_ansi.rs1use std::io::Write;
4use std::sync::{Mutex, MutexGuard};
5
6pub struct StripAnsiWriter<W> {
32 pub(crate) inner: Mutex<W>,
33}
34
35impl<W> StripAnsiWriter<W> {
36 pub fn new(inner: W) -> Self {
38 Self {
39 inner: Mutex::new(inner),
40 }
41 }
42}
43
44pub(crate) fn strip_ansi_and_write<W: Write>(writer: &mut W, buf: &[u8]) -> std::io::Result<usize> {
47 let input_len = buf.len();
48
49 let Some(first_esc) = memchr::memchr(0x1b, buf) else {
51 writer.write_all(buf)?;
52 return Ok(input_len);
53 };
54
55 let mut output = Vec::with_capacity(input_len);
58
59 output.extend_from_slice(&buf[..first_esc]);
61 let mut i = first_esc;
62
63 while i < buf.len() {
64 if buf[i] == 0x1b && i + 1 < buf.len() && buf[i + 1] == b'[' {
65 i += 2;
67 while i < buf.len() {
68 let c = buf[i];
69 i += 1;
70 if c == b'm' {
71 break;
72 }
73 if !c.is_ascii_digit() && c != b';' {
74 break;
75 }
76 }
77 } else {
78 output.push(buf[i]);
79 i += 1;
80 }
81 }
82
83 writer.write_all(&output)?;
84 Ok(input_len)
85}
86
87pub struct StripAnsiWriterGuard<'a, W> {
91 guard: MutexGuard<'a, W>,
92}
93
94impl<W: Write> Write for StripAnsiWriterGuard<'_, W> {
95 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
96 strip_ansi_and_write(&mut *self.guard, buf)
97 }
98
99 fn flush(&mut self) -> std::io::Result<()> {
100 self.guard.flush()
101 }
102}
103
104impl<'a, W: Write + 'a> tracing_subscriber::fmt::MakeWriter<'a> for StripAnsiWriter<W> {
106 type Writer = StripAnsiWriterGuard<'a, W>;
107
108 fn make_writer(&'a self) -> Self::Writer {
109 StripAnsiWriterGuard {
110 guard: self.inner.lock().unwrap_or_else(|e| e.into_inner()),
111 }
112 }
113}
114
115#[cfg(test)]
116#[allow(clippy::unwrap_used)]
117mod tests {
118 use super::*;
119 use tracing_subscriber::fmt::MakeWriter;
120
121 #[test]
122 fn strip_ansi_fast_path_no_escape() {
123 let mut output = Vec::new();
124 let input = b"Hello, world!";
125 let written = strip_ansi_and_write(&mut output, input).unwrap();
126 assert_eq!(written, input.len());
127 assert_eq!(output, input);
128 }
129
130 #[test]
131 fn strip_ansi_removes_sgr_sequence() {
132 let mut output = Vec::new();
133 let input = b"\x1b[32mgreen\x1b[0m";
134 let written = strip_ansi_and_write(&mut output, input).unwrap();
135 assert_eq!(written, input.len());
136 assert_eq!(output, b"green");
137 }
138
139 #[test]
140 fn strip_ansi_removes_multiple_sequences() {
141 let mut output = Vec::new();
142 let input = b"\x1b[1m\x1b[31mBold Red\x1b[0m Normal";
143 let written = strip_ansi_and_write(&mut output, input).unwrap();
144 assert_eq!(written, input.len());
145 assert_eq!(output, b"Bold Red Normal");
146 }
147
148 #[test]
149 fn strip_ansi_handles_complex_sgr() {
150 let mut output = Vec::new();
151 let input = b"\x1b[1;31;42mStyled\x1b[0m";
153 let written = strip_ansi_and_write(&mut output, input).unwrap();
154 assert_eq!(written, input.len());
155 assert_eq!(output, b"Styled");
156 }
157
158 #[test]
159 fn strip_ansi_preserves_non_sgr_escape() {
160 let mut output = Vec::new();
161 let input = b"Hello\x1bWorld";
163 let written = strip_ansi_and_write(&mut output, input).unwrap();
164 assert_eq!(written, input.len());
165 assert_eq!(output, b"Hello\x1bWorld");
166 }
167
168 #[test]
169 fn strip_ansi_handles_escape_at_end() {
170 let mut output = Vec::new();
171 let input = b"Hello\x1b";
172 let written = strip_ansi_and_write(&mut output, input).unwrap();
173 assert_eq!(written, input.len());
174 assert_eq!(output, b"Hello\x1b");
175 }
176
177 #[test]
178 fn strip_ansi_handles_incomplete_sequence() {
179 let mut output = Vec::new();
180 let input = b"Hello\x1b[31";
182 let written = strip_ansi_and_write(&mut output, input).unwrap();
183 assert_eq!(written, input.len());
184 assert_eq!(output, b"Hello");
186 }
187
188 #[test]
189 fn strip_ansi_writer_works() {
190 let inner = Vec::new();
191 let writer = StripAnsiWriter::new(inner);
192 {
193 let mut guard = writer.make_writer();
194 guard.write_all(b"\x1b[32mtest\x1b[0m").unwrap();
195 }
196 let result = writer.inner.lock().unwrap();
197 assert_eq!(&*result, b"test");
198 }
199
200 #[test]
201 fn strip_ansi_empty_input() {
202 let mut output = Vec::new();
203 let input = b"";
204 let written = strip_ansi_and_write(&mut output, input).unwrap();
205 assert_eq!(written, 0);
206 assert_eq!(output, b"");
207 }
208
209 #[test]
210 fn strip_ansi_only_escape_sequences() {
211 let mut output = Vec::new();
212 let input = b"\x1b[31m\x1b[0m";
213 let written = strip_ansi_and_write(&mut output, input).unwrap();
214 assert_eq!(written, input.len());
215 assert_eq!(output, b"");
216 }
217}