use vte::{Params, Parser, Perform};
pub struct OutputSanitizer;
impl OutputSanitizer {
pub fn strip_ansi(input: &[u8]) -> String {
let mut extractor = PlainTextExtractor::new();
let mut parser = Parser::new();
parser.advance(&mut extractor, input);
extractor.into_string()
}
pub fn strip_ansi_str(input: &str) -> String {
Self::strip_ansi(input.as_bytes())
}
}
struct PlainTextExtractor {
output: Vec<u8>,
}
impl PlainTextExtractor {
fn new() -> Self {
Self { output: Vec::new() }
}
fn into_string(self) -> String {
String::from_utf8_lossy(&self.output).into_owned()
}
}
impl Perform for PlainTextExtractor {
fn print(&mut self, c: char) {
let mut buf = [0u8; 4];
let encoded = c.encode_utf8(&mut buf);
self.output.extend_from_slice(encoded.as_bytes());
}
fn execute(&mut self, byte: u8) {
match byte {
0x0A | 0x0D | 0x09 => self.output.push(byte),
_ => {}
}
}
fn hook(&mut self, _params: &Params, _intermediates: &[u8], _ignore: bool, _action: char) {
}
fn put(&mut self, _byte: u8) {
}
fn unhook(&mut self) {
}
fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) {
}
fn csi_dispatch(
&mut self,
_params: &Params,
_intermediates: &[u8],
_ignore: bool,
_action: char,
) {
}
fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plain_text() {
let input = b"hello world";
let output = OutputSanitizer::strip_ansi(input);
assert_eq!(output, "hello world");
}
#[test]
fn test_strip_color_codes() {
let input = b"\x1b[31mred\x1b[0m";
let output = OutputSanitizer::strip_ansi(input);
assert_eq!(output, "red");
}
#[test]
fn test_strip_bold() {
let input = b"\x1b[1mbold\x1b[0m";
let output = OutputSanitizer::strip_ansi(input);
assert_eq!(output, "bold");
}
#[test]
fn test_preserve_newlines() {
let input = b"line1\nline2\nline3";
let output = OutputSanitizer::strip_ansi(input);
assert_eq!(output, "line1\nline2\nline3");
}
#[test]
fn test_strip_cursor_movement() {
let input = b"\x1b[2J\x1b[Hcontent";
let output = OutputSanitizer::strip_ansi(input);
assert_eq!(output, "content");
}
#[test]
fn test_complex_sequence() {
let input = b"\x1b[32m\x1b[1mGreen Bold\x1b[0m Normal \x1b[34mBlue\x1b[0m";
let output = OutputSanitizer::strip_ansi(input);
assert_eq!(output, "Green Bold Normal Blue");
}
#[test]
fn test_osc_title() {
let input = b"\x1b]0;Window Title\x07actual content";
let output = OutputSanitizer::strip_ansi(input);
assert_eq!(output, "actual content");
}
#[test]
fn test_strip_ansi_str() {
let input = "\x1b[31mcolored\x1b[0m";
let output = OutputSanitizer::strip_ansi_str(input);
assert_eq!(output, "colored");
}
#[test]
fn test_preserve_tabs() {
let input = b"col1\tcol2\tcol3";
let output = OutputSanitizer::strip_ansi(input);
assert_eq!(output, "col1\tcol2\tcol3");
}
#[test]
fn test_empty_input() {
let output = OutputSanitizer::strip_ansi(b"");
assert_eq!(output, "");
}
#[test]
fn test_only_escape_codes() {
let input = b"\x1b[31m\x1b[0m\x1b[2J";
let output = OutputSanitizer::strip_ansi(input);
assert_eq!(output, "");
}
}