use std::mem; use vte::{Params, Parser, Perform}; #[derive(Debug, Clone, Copy)] pub(crate) enum Color { Black, Red, Green, Yellow, Blue, Purple, Cyan, White, } #[derive(Debug)] struct Cleaner { current_color: Option, styles: StyledString, current: String, } #[derive(Debug)] struct StyledStringInner { val: String, color: Option, } pub(crate) struct StyledChar { ch: char, color: Option, } impl StyledChar { pub(crate) fn as_char(&self) -> char { self.ch } pub(crate) fn is_bold(&self) -> bool { self.color.is_some() } pub(crate) fn color(&self) -> Option { self.color } } #[derive(Debug)] pub(crate) struct StyledString { inner: Vec, } impl StyledString { fn push(&mut self, val: StyledStringInner) { self.inner.push(val); } pub(crate) fn chars(&self) -> impl Iterator + use<'_> { self.inner.iter().flat_map(|inner| { inner.val.chars().map(|ch| StyledChar { ch, color: inner.color, }) }) } } impl Cleaner { fn reset_color(&mut self) { self.styles.push(StyledStringInner { val: mem::take(&mut self.current), color: mem::take(&mut self.current_color), }); } fn set_color(&mut self, color: Color) { self.current_color = Some(color); } fn add_char(&mut self, c: char) { self.current.push(c); } } impl Perform for Cleaner { fn print(&mut self, c: char) { self.add_char(c); } fn execute(&mut self, byte: u8) { if byte == b'\n' { self.reset_color(); self.add_char('\n'); self.reset_color(); } else { eprintln!("Unknown [execute]: {byte:02x}"); } } fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { eprintln!( "Unknown [hook] params={params:?}, intermediates={intermediates:?}, ignore={ignore:?}, char={c:?}" ); } fn put(&mut self, byte: u8) { eprintln!("Unknonw [put] {byte:02x}"); } fn unhook(&mut self) { eprintln!("Unknown [unhook]"); } fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { eprintln!("Unkown [osc_dispatch] params={params:?} bell_terminated={bell_terminated}"); } fn csi_dispatch(&mut self, params: &Params, _: &[u8], _: bool, c: char) { let params: Vec = params.iter().flatten().copied().collect(); if c != 'm' { return; } // See: https://gist.github.com/JBlond/2fea43a3049b38287e5e9cefc87b2124 match params[..] { [0] => self.reset_color(), // [0, regular] if matches!(regular, 30..=37) => {} [1, bold] if matches!(bold, 30..=37) => match bold { 30 => self.set_color(Color::Black), 31 => self.set_color(Color::Red), 32 => self.set_color(Color::Green), 36 => self.set_color(Color::Yellow), 34 => self.set_color(Color::Blue), 35 => self.set_color(Color::Purple), 33 => self.set_color(Color::Cyan), 37 => self.set_color(Color::White), _ => unreachable!("Was filtered out"), }, // [4, underline] if matches!(underline, 30..=37) => {} // [background] if matches!(background, 40..=47) => {} _ => todo!(), } // println!( // "[csi_dispatch] params={:#?}, intermediates={:?}, ignore={:?}, char={:?}", // params, intermediates, ignore, c // ); } fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { eprintln!( "Unkown [esc_dispatch] intermediates={intermediates:?}, ignore={ignore:?}, byte={byte:02x}" ); } } pub(crate) fn parse(input: &str) -> StyledString { let mut statemachine = Parser::new(); let mut performer = Cleaner { current_color: None, styles: StyledString { inner: vec![] }, current: String::new(), }; let buf: Vec<_> = input.bytes().collect(); statemachine.advance(&mut performer, &buf[..]); assert!(performer.current.is_empty() && performer.current_color.is_none()); performer.styles }