use std::fmt;
use thag_styling::{ColorInitStrategy, Role, Style, Styleable, Styler, TermAttributes};
#[derive(Clone, Debug)]
pub struct StyledString {
content: String,
style: Style,
}
impl StyledString {
pub fn new(content: String, style: Style) -> Self {
Self { content, style }
}
fn has_inner_resets(&self) -> bool {
self.content.contains("\x1b[0m")
}
fn escape_inner_resets(&self) -> String {
if !self.has_inner_resets() {
return self.content.clone();
}
let reset = "\x1b[0m";
let outer_style_codes = self.style.to_ansi_codes();
let reset_positions: Vec<usize> = self
.content
.match_indices(reset)
.map(|(idx, _)| idx)
.collect();
if reset_positions.is_empty() {
return self.content.clone();
}
let mut result = self.content.clone();
result.reserve(reset_positions.len() * outer_style_codes.len());
for &pos in reset_positions.iter().rev() {
let insert_pos = pos + reset.len();
result.insert_str(insert_pos, &outer_style_codes);
}
result
}
pub fn to_styled(&self) -> String {
let escaped_content = self.escape_inner_resets();
self.style.paint(escaped_content)
}
pub fn bold(self) -> Self {
Self {
content: self.content,
style: self.style.bold(),
}
}
pub fn italic(self) -> Self {
Self {
content: self.content,
style: self.style.italic(),
}
}
}
impl fmt::Display for StyledString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_styled())
}
}
trait StyledPrint {
fn styled_error(&self) -> StyledString;
fn styled_warning(&self) -> StyledString;
fn styled_success(&self) -> StyledString;
fn styled_info(&self) -> StyledString;
fn styled_with(&self, styler: impl Styler) -> StyledString;
}
impl StyledPrint for str {
fn styled_error(&self) -> StyledString {
StyledString::new(self.to_string(), Style::from(Role::Error))
}
fn styled_warning(&self) -> StyledString {
StyledString::new(self.to_string(), Style::from(Role::Warning))
}
fn styled_success(&self) -> StyledString {
StyledString::new(self.to_string(), Style::from(Role::Success))
}
fn styled_info(&self) -> StyledString {
StyledString::new(self.to_string(), Style::from(Role::Info))
}
fn styled_with(&self, styler: impl Styler) -> StyledString {
StyledString::new(self.to_string(), styler.to_style())
}
}
impl StyledPrint for String {
fn styled_error(&self) -> StyledString {
StyledString::new(self.clone(), Style::from(Role::Error))
}
fn styled_warning(&self) -> StyledString {
StyledString::new(self.clone(), Style::from(Role::Warning))
}
fn styled_success(&self) -> StyledString {
StyledString::new(self.clone(), Style::from(Role::Success))
}
fn styled_info(&self) -> StyledString {
StyledString::new(self.clone(), Style::from(Role::Info))
}
fn styled_with(&self, styler: impl Styler) -> StyledString {
StyledString::new(self.clone(), styler.to_style())
}
}
trait StyleAnsiExt {
fn to_ansi_codes(&self) -> String;
}
impl StyleAnsiExt for Style {
fn to_ansi_codes(&self) -> String {
let mut codes = String::new();
if let Some(color_info) = &self.foreground {
let ansi = color_info.to_ansi_for_support(TermAttributes::get_or_init().color_support);
codes.push_str(&ansi);
}
if self.bold {
codes.push_str("\x1b[1m");
}
if self.italic {
codes.push_str("\x1b[3m");
}
if self.dim {
codes.push_str("\x1b[2m");
}
if self.underline {
codes.push_str("\x1b[4m");
}
codes
}
}
fn main() {
TermAttributes::get_or_init_with_strategy(&ColorInitStrategy::Match);
println!("=== StyledString Concept Demo ===\n");
println!("1. Simple usage (like current approach):");
let simple = "Simple error message".styled_error();
println!(" {}", simple);
println!("\n2. The nesting problem with current approach:");
let inner1 = "Heading1 text".style_with(Role::Heading1.underline());
let inner2 = "Heading2 text".style_with(Role::Heading2.italic());
let broken_embed = format!("Error {} error {} error", inner1, inner2).error();
println!("broken_embed= {}", broken_embed);
println!("broken_embed= {:?}", broken_embed);
let broken_result = format!("Warning {} warning", broken_embed).warning();
println!("broken_result= {}", broken_result);
println!("broken_result= {:?}", broken_result);
println!(" ❌ Problem: Warning color likely lost after inner resets");
println!("\n3. StyledString with inner reset handling:");
let smart_inner1 = "Heading1 text".styled_with(Role::Heading1.underline());
let smart_inner2 = "Heading2 text".styled_with(Role::Heading2.italic());
let smart_embed = format!("Error {} error {} error", smart_inner1, smart_inner2).styled_error();
println!("smart_embed= {}", smart_embed);
println!("smart_embed= {:?}", smart_embed);
let smart_result = format!("Warning {} warning", smart_embed).styled_warning();
println!("smart_result= {}", smart_result);
println!("smart_result= {:?}", smart_result);
println!(" ✅ Should work: Warning color restored after each inner reset");
println!("\n4. Chaining with StyledString:");
let chained = "Bold italic warning".styled_warning().bold().italic();
println!(" {}", chained);
println!("\n5. Debug: Show raw ANSI codes:");
let debug_inner = "red text".styled_error();
let debug_outer = format!("blue {} blue", debug_inner).styled_info();
println!(" Raw output: {:?}", debug_outer.to_styled());
println!("\n=== Key Advantages of StyledString ===");
println!("✅ Automatic context preservation like colored");
println!("✅ Chainable styling methods");
println!("✅ Works with format! macro seamlessly");
println!("✅ No need for explicit embedding syntax");
println!("✅ Drop-in replacement for current string methods");
println!("\n=== StyledString Benefits ===");
println!("✅ Natural Rust syntax with format! and method chaining");
println!("✅ Automatic context preservation across unlimited nesting levels");
println!("✅ Perfect attribute reset handling prevents bleeding");
println!("✅ No special macros or embed arrays needed");
println!("✅ More performant - no macro overhead");
println!();
println!("Example:");
println!(" format!(\"Warning {{}} warning\", \"error\".error()).warning().println();");
println!(" // Clean, readable, and just works!");
}