use std::io::Write;
use crate::waybar::WaybarOutput;
pub fn print_pretty(w: &mut impl Write, out: &WaybarOutput) -> std::io::Result<()> {
writeln!(w, "{}", pango_to_ansi(&out.text))?;
writeln!(w)?;
writeln!(w, "{}", pango_to_ansi(&out.tooltip))?;
writeln!(w)?;
let dim = "\x1b[2m";
let reset = "\x1b[0m";
writeln!(w, "{dim}class: {:?}{reset}", out.class)?;
Ok(())
}
pub fn pango_to_ansi(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c == '<' {
let mut tag = String::new();
for nc in chars.by_ref() {
if nc == '>' {
break;
}
tag.push(nc);
}
apply_tag(&tag, &mut out);
} else {
out.push(c);
}
}
out.push_str("\x1b[0m");
out
}
fn apply_tag(tag: &str, out: &mut String) {
if tag.starts_with('/') {
out.push_str("\x1b[0m");
return;
}
let bold = tag.contains("font_weight='bold'") || tag.contains("font_weight=\"bold\"");
let color = extract_attr(tag, "foreground");
if let Some(hex) = color.as_deref().and_then(crate::theme::parse_hex_rgb) {
let (r, g, b) = hex;
out.push_str(&format!("\x1b[38;2;{r};{g};{b}m"));
}
if bold {
out.push_str("\x1b[1m");
}
}
fn extract_attr(tag: &str, key: &str) -> Option<String> {
for delim in ['\'', '"'] {
let needle = format!("{key}={delim}");
if let Some(start) = tag.find(&needle) {
let value_start = start + needle.len();
if let Some(end) = tag[value_start..].find(delim) {
return Some(tag[value_start..value_start + end].to_string());
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plain_text_is_unchanged() {
assert_eq!(pango_to_ansi("hello"), "hello\x1b[0m");
}
#[test]
fn color_span_translates_to_24bit_ansi() {
let s = pango_to_ansi("<span foreground='#ff0000'>red</span>");
assert!(s.starts_with("\x1b[38;2;255;0;0m"));
assert!(s.contains("red"));
assert!(s.contains("\x1b[0m"));
}
#[test]
fn bold_attribute_adds_bold_escape() {
let s = pango_to_ansi("<span font_weight='bold' foreground='#00ff00'>x</span>");
assert!(s.contains("\x1b[38;2;0;255;0m"));
assert!(s.contains("\x1b[1m"));
}
#[test]
fn nested_spans_handled_simply() {
let s = pango_to_ansi(
"<span foreground='#ff0000'>a<span foreground='#00ff00'>b</span>c</span>",
);
assert!(s.contains("\x1b[38;2;255;0;0m"));
assert!(s.contains("\x1b[38;2;0;255;0m"));
assert!(s.contains("a"));
assert!(s.contains("b"));
assert!(s.contains("c"));
}
#[test]
fn unknown_color_is_just_dropped() {
let s = pango_to_ansi("<span foreground='not-hex'>x</span>");
assert!(s.contains("x"));
assert!(!s.contains("\x1b[38;2"));
}
#[test]
fn double_quoted_attributes_also_work() {
let s = pango_to_ansi(r##"<span foreground="#0000ff">b</span>"##);
assert!(s.contains("\x1b[38;2;0;0;255m"));
}
}