use std::io::Write;
use std::sync::{Mutex, MutexGuard};
pub struct StripAnsiWriter<W> {
pub(crate) inner: Mutex<W>,
}
impl<W> StripAnsiWriter<W> {
pub fn new(inner: W) -> Self {
Self {
inner: Mutex::new(inner),
}
}
}
pub(crate) fn strip_ansi_and_write<W: Write>(writer: &mut W, buf: &[u8]) -> std::io::Result<usize> {
let input_len = buf.len();
let Some(first_esc) = memchr::memchr(0x1b, buf) else {
writer.write_all(buf)?;
return Ok(input_len);
};
let mut output = Vec::with_capacity(input_len);
output.extend_from_slice(&buf[..first_esc]);
let mut i = first_esc;
while i < buf.len() {
if buf[i] == 0x1b && i + 1 < buf.len() && buf[i + 1] == b'[' {
i += 2;
while i < buf.len() {
let c = buf[i];
i += 1;
if c == b'm' {
break;
}
if !c.is_ascii_digit() && c != b';' {
break;
}
}
} else {
output.push(buf[i]);
i += 1;
}
}
writer.write_all(&output)?;
Ok(input_len)
}
pub struct StripAnsiWriterGuard<'a, W> {
guard: MutexGuard<'a, W>,
}
impl<W: Write> Write for StripAnsiWriterGuard<'_, W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
strip_ansi_and_write(&mut *self.guard, buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.guard.flush()
}
}
impl<'a, W: Write + 'a> tracing_subscriber::fmt::MakeWriter<'a> for StripAnsiWriter<W> {
type Writer = StripAnsiWriterGuard<'a, W>;
fn make_writer(&'a self) -> Self::Writer {
StripAnsiWriterGuard {
guard: self.inner.lock().unwrap_or_else(|e| e.into_inner()),
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use tracing_subscriber::fmt::MakeWriter;
#[test]
fn strip_ansi_fast_path_no_escape() {
let mut output = Vec::new();
let input = b"Hello, world!";
let written = strip_ansi_and_write(&mut output, input).unwrap();
assert_eq!(written, input.len());
assert_eq!(output, input);
}
#[test]
fn strip_ansi_removes_sgr_sequence() {
let mut output = Vec::new();
let input = b"\x1b[32mgreen\x1b[0m";
let written = strip_ansi_and_write(&mut output, input).unwrap();
assert_eq!(written, input.len());
assert_eq!(output, b"green");
}
#[test]
fn strip_ansi_removes_multiple_sequences() {
let mut output = Vec::new();
let input = b"\x1b[1m\x1b[31mBold Red\x1b[0m Normal";
let written = strip_ansi_and_write(&mut output, input).unwrap();
assert_eq!(written, input.len());
assert_eq!(output, b"Bold Red Normal");
}
#[test]
fn strip_ansi_handles_complex_sgr() {
let mut output = Vec::new();
let input = b"\x1b[1;31;42mStyled\x1b[0m";
let written = strip_ansi_and_write(&mut output, input).unwrap();
assert_eq!(written, input.len());
assert_eq!(output, b"Styled");
}
#[test]
fn strip_ansi_preserves_non_sgr_escape() {
let mut output = Vec::new();
let input = b"Hello\x1bWorld";
let written = strip_ansi_and_write(&mut output, input).unwrap();
assert_eq!(written, input.len());
assert_eq!(output, b"Hello\x1bWorld");
}
#[test]
fn strip_ansi_handles_escape_at_end() {
let mut output = Vec::new();
let input = b"Hello\x1b";
let written = strip_ansi_and_write(&mut output, input).unwrap();
assert_eq!(written, input.len());
assert_eq!(output, b"Hello\x1b");
}
#[test]
fn strip_ansi_handles_incomplete_sequence() {
let mut output = Vec::new();
let input = b"Hello\x1b[31";
let written = strip_ansi_and_write(&mut output, input).unwrap();
assert_eq!(written, input.len());
assert_eq!(output, b"Hello");
}
#[test]
fn strip_ansi_writer_works() {
let inner = Vec::new();
let writer = StripAnsiWriter::new(inner);
{
let mut guard = writer.make_writer();
guard.write_all(b"\x1b[32mtest\x1b[0m").unwrap();
}
let result = writer.inner.lock().unwrap();
assert_eq!(&*result, b"test");
}
#[test]
fn strip_ansi_empty_input() {
let mut output = Vec::new();
let input = b"";
let written = strip_ansi_and_write(&mut output, input).unwrap();
assert_eq!(written, 0);
assert_eq!(output, b"");
}
#[test]
fn strip_ansi_only_escape_sequences() {
let mut output = Vec::new();
let input = b"\x1b[31m\x1b[0m";
let written = strip_ansi_and_write(&mut output, input).unwrap();
assert_eq!(written, input.len());
assert_eq!(output, b"");
}
}