use colored::Colorize;
use crate::text::style::{Color, Style};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct StyledString {
tokens: Vec<Token>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct Token {
content: String,
style: Style,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Format {
Plain,
Ansi,
}
impl StyledString {
pub fn new() -> StyledString {
StyledString { tokens: vec![] }
}
pub fn push(&mut self, content: &str) {
self.push_with(content, Style::new());
}
pub fn push_with(&mut self, content: &str, style: Style) {
let token = Token::new(content, style);
self.push_token(token);
}
fn push_token(&mut self, token: Token) {
if let Some(last) = self.tokens.last_mut()
&& last.style == token.style
{
last.content.push_str(&token.content);
return;
}
self.tokens.push(token);
}
pub fn to_string(&self, format: Format) -> String {
self.tokens
.iter()
.map(|token| token.to_string(format))
.collect::<Vec<String>>()
.join("")
}
pub fn append(&mut self, other: StyledString) {
for token in other.tokens {
self.push_token(token);
}
}
pub fn split(&self, delimiter: char) -> Vec<StyledString> {
let mut items = vec![];
let mut item = StyledString::new();
for token in &self.tokens {
let mut substrings = token.content.split(delimiter).collect::<Vec<&str>>();
let first = substrings.remove(0);
if !first.is_empty() {
item.push_with(first, token.style);
}
for substring in substrings {
items.push(item);
item = StyledString::new();
if !substring.is_empty() {
item.push_with(substring, token.style);
}
}
}
items.push(item);
items
}
pub fn ends_with(&self, value: &str) -> bool {
self.to_string(Format::Plain).ends_with(value)
}
pub fn len(&self) -> usize {
self.tokens.iter().fold(0, |acc, t| acc + t.content.len())
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn wrap(&self, max_width: usize) -> StyledString {
let mut string = StyledString::new();
let mut width = 0;
for token in &self.tokens {
let mut chunk = String::new();
let mut it = token.content.chars().peekable();
while let Some(c) = it.next() {
chunk.push(c);
width += 1;
if width >= max_width {
let token = Token::new(&chunk, token.style);
string.push_token(token);
if it.peek().is_some() {
let nl = Token::new("\n", Style::new());
string.push_token(nl);
}
chunk = String::new();
width = 0;
}
}
if !chunk.is_empty() {
let token = Token::new(&chunk, token.style);
string.push_token(token);
}
}
string
}
pub fn clear(&mut self) {
self.tokens.clear();
}
}
impl Token {
fn new(content: &str, style: Style) -> Token {
let content = content.to_string();
Token { content, style }
}
fn to_string(&self, format: Format) -> String {
match format {
Format::Plain => self.plain(),
Format::Ansi => self.ansi(),
}
}
fn plain(&self) -> String {
self.content.to_string()
}
fn ansi(&self) -> String {
let mut s = self.content.to_string();
if let Some(color) = &self.style.fg {
s = match color {
Color::Blue => {
if self.style.bold {
s.blue().bold().to_string()
} else {
s.blue().to_string()
}
}
Color::BrightBlack => {
if self.style.bold {
s.bright_black().bold().to_string()
} else {
s.bright_black().to_string()
}
}
Color::Cyan => {
if self.style.bold {
s.cyan().bold().to_string()
} else {
s.cyan().to_string()
}
}
Color::Green => {
if self.style.bold {
s.green().bold().to_string()
} else {
s.green().to_string()
}
}
Color::Magenta => {
if self.style.bold {
s.magenta().bold().to_string()
} else {
s.magenta().to_string()
}
}
Color::Purple => {
if self.style.bold {
s.purple().bold().to_string()
} else {
s.purple().to_string()
}
}
Color::Red => {
if self.style.bold {
s.red().bold().to_string()
} else {
s.red().to_string()
}
}
Color::Yellow => {
if self.style.bold {
s.yellow().bold().to_string()
} else {
s.yellow().to_string()
}
}
};
} else if self.style.bold {
s = s.bold().to_string();
}
s
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hello() {
crate::text::init_crate_colored();
let mut message = StyledString::new();
message.push("Hello ");
message.push_with("Bob", Style::new().red());
message.push("!");
assert_eq!(message.to_string(Format::Plain), "Hello Bob!");
assert_eq!(
message.to_string(Format::Ansi),
"Hello \u{1b}[31mBob\u{1b}[0m!"
);
}
#[test]
fn test_push() {
let mut message = StyledString::new();
message.push("Hello");
message.push(" ");
message.push_with("Bob", Style::new().red());
message.push("!");
assert_eq!(
message,
StyledString {
tokens: vec![
Token {
content: "Hello ".to_string(),
style: Style::new()
},
Token {
content: "Bob".to_string(),
style: Style::new().red()
},
Token {
content: "!".to_string(),
style: Style::new()
},
],
}
);
}
#[test]
fn test_append() {
let mut message1 = StyledString::new();
message1.push("Hello ");
message1.push_with("Bob", Style::new().red());
message1.push("!");
let mut message2 = StyledString::new();
message2.push("Hi ");
message2.push_with("Bill", Style::new().red());
message2.push("!");
let mut messages = StyledString::new();
messages.push("Hello ");
messages.push_with("Bob", Style::new().red());
messages.push("!");
messages.push("Hi ");
messages.push_with("Bill", Style::new().red());
messages.push("!");
message1.append(message2);
assert_eq!(message1, messages);
}
#[test]
fn test_split() {
let mut line = StyledString::new();
line.push("Hello,Hi,");
line.push_with("Hola", Style::new().red());
line.push(",Bye,");
line.push_with("Adios", Style::new().red());
let mut item0 = StyledString::new();
item0.push("Hello");
let mut item1 = StyledString::new();
item1.push("Hi");
let mut item2 = StyledString::new();
item2.push_with("Hola", Style::new().red());
let mut item3 = StyledString::new();
item3.push("Bye");
let mut item4 = StyledString::new();
item4.push_with("Adios", Style::new().red());
assert_eq!(line.split(','), vec![item0, item1, item2, item3, item4]);
let mut line = StyledString::new();
line.push("0,,2,");
let mut item0 = StyledString::new();
item0.push("0");
let item1 = StyledString::new();
let mut item2 = StyledString::new();
item2.push("2");
let item3 = StyledString::new();
assert_eq!(line.split(','), vec![item0, item1, item2, item3]);
}
#[test]
fn test_ends_with() {
let mut line = StyledString::new();
line.push("Hello,Hi,");
assert!(line.ends_with(","));
assert!(!line.ends_with("\n"));
}
#[test]
fn compare_with_crate_colored() {
let mut message = StyledString::new();
message.push_with("foo", Style::new().red().bold());
assert_eq!(
"foo".red().bold().to_string(),
message.to_string(Format::Ansi),
);
let mut message = StyledString::new();
message.push_with("bar", Style::new().bold());
assert_eq!("bar".bold().to_string(), message.to_string(Format::Ansi),);
}
#[test]
fn wrap_single_plain_token() {
let mut line = StyledString::new();
line.push("aaaabbbbcccc");
let mut wrapped = StyledString::new();
wrapped.push("aaaa\nbbbb\ncccc");
assert_eq!(line.wrap(4), wrapped);
assert_eq!(line.len(), 12);
assert_eq!(line.wrap(4).len(), 14);
}
#[test]
fn wrap_single_styled_token() {
let mut line = StyledString::new();
line.push_with("aaaabbbbcccc", Style::new().blue());
let mut wrapped = StyledString::new();
wrapped.push_with("aaaa", Style::new().blue());
wrapped.push("\n");
wrapped.push_with("bbbb", Style::new().blue());
wrapped.push("\n");
wrapped.push_with("cccc", Style::new().blue());
assert_eq!(line.wrap(4), wrapped);
assert_eq!(line.len(), 12);
assert_eq!(line.wrap(4).len(), 14);
}
#[test]
fn wrap_multi_styled_token() {
let mut line = StyledString::new();
line.push_with("aaa", Style::new().blue());
line.push_with("ab", Style::new().green());
line.push_with("bbbccc", Style::new().yellow());
line.push_with("cee", Style::new().purple());
let mut wrapped = StyledString::new();
wrapped.push_with("aaa", Style::new().blue());
wrapped.push_with("a", Style::new().green());
wrapped.push("\n");
wrapped.push_with("b", Style::new().green());
wrapped.push_with("bbb", Style::new().yellow());
wrapped.push("\n");
wrapped.push_with("ccc", Style::new().yellow());
wrapped.push_with("c", Style::new().purple());
wrapped.push("\n");
wrapped.push_with("ee", Style::new().purple());
assert_eq!(line.wrap(4), wrapped);
}
}