pub enum Piece {
Static { ansi: String, plain: String },
Arg(String),
}
pub fn split(input: &str, bleed: bool) -> Result<Vec<Piece>, farben_core::errors::LexError> {
farben_core::clear_active_stack();
let mut pieces: Vec<Piece> = Vec::new();
let mut static_buf = String::new();
let mut chars = input.chars().peekable();
while let Some(c) = chars.next() {
match c {
'{' => {
if let Some('{') = chars.peek() {
chars.next();
static_buf.push('{');
} else {
flush_static(&mut static_buf, &mut pieces, false)?;
let mut spec = String::new();
let mut closed = false;
for inner in chars.by_ref() {
if inner == '}' {
closed = true;
break;
}
spec.push(inner);
}
if closed {
pieces.push(Piece::Arg(spec));
} else {
static_buf.push('{');
static_buf.push_str(&spec);
}
}
}
'}' => match chars.peek() {
Some('}') => {
chars.next();
static_buf.push('}');
}
_ => static_buf.push('}'),
},
other => static_buf.push(other),
}
}
flush_static(&mut static_buf, &mut pieces, !bleed)?;
if !bleed {
ensure_trailing_reset(&mut pieces);
}
Ok(pieces)
}
fn flush_static(
buf: &mut String,
pieces: &mut Vec<Piece>,
append_reset: bool,
) -> Result<(), farben_core::errors::LexError> {
if buf.is_empty() && !append_reset {
return Ok(());
}
let tokens = farben_core::lexer::tokenize(buf.as_str())?;
let mut ansi = farben_core::parser::render_forced(tokens);
if append_reset {
ansi.push_str("\x1b[0m");
}
let plain = farben_core::strip::strip_ansi(&ansi);
if !ansi.is_empty() || !plain.is_empty() {
pieces.push(Piece::Static { ansi, plain });
}
buf.clear();
Ok(())
}
fn ensure_trailing_reset(pieces: &mut Vec<Piece>) {
let needs_reset = match pieces.last() {
Some(Piece::Static { ansi, .. }) => !ansi.ends_with("\x1b[0m"),
Some(Piece::Arg(_)) => true,
None => false,
};
if needs_reset {
pieces.push(Piece::Static {
ansi: "\x1b[0m".to_string(),
plain: String::new(),
});
}
}