use std::{
error::Error,
fmt::{Debug, Display},
};
use unicode_segmentation::UnicodeSegmentation;
use crate::{grid::Alignment, process::DrawProcess};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum FormatError<T: TrimStrategy> {
NoSpace(T::Input),
}
impl<T: TrimStrategy> Display for FormatError<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FormatError::NoSpace(value) => write!(f, "No space found for {}", value),
}
}
}
impl<T: TrimStrategy> Error for FormatError<T> {}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Default, Hash)]
pub struct TrimmedText(pub String);
pub trait DisplayAndDebug
where
Self: Display,
Self: Debug,
{
}
impl<T> DisplayAndDebug for T
where
T: Display,
T: Debug,
{
}
pub trait TrimStrategy
where
Self: DisplayAndDebug,
{
type Input: DisplayAndDebug;
fn trim(&mut self, text: Self::Input, chunk: &DrawProcess, a: Alignment) -> Vec<TrimmedText>;
fn back(&mut self, text: Vec<TrimmedText>, _: &DrawProcess, a: Alignment) -> Self::Input;
}
#[derive(Debug)]
pub struct Ignore;
impl Display for Ignore {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", Ignore)
}
}
impl TrimStrategy for Ignore {
type Input = String;
fn trim(&mut self, text: String, _: &DrawProcess, _: Alignment) -> Vec<TrimmedText> {
vec![TrimmedText(text)]
}
fn back(&mut self, text: Vec<TrimmedText>, _: &DrawProcess, _: Alignment) -> Self::Input {
text.into_iter().next().expect("Safe unwrap").0
}
}
#[derive(Debug)]
pub struct Truncate;
impl Display for Truncate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", Ignore)
}
}
impl TrimStrategy for Truncate {
type Input = String;
fn trim(&mut self, text: String, chunk: &DrawProcess, _: Alignment) -> Vec<TrimmedText> {
let blank_space = " ".graphemes(true).cycle();
let res = text.graphemes(true).chain(blank_space).take(chunk.width()).collect();
vec![TrimmedText(res)]
}
fn back(&mut self, text: Vec<TrimmedText>, _: &DrawProcess, _: Alignment) -> Self::Input {
text.into_iter().next().expect("Safe unwrap").0
}
}
#[derive(Debug)]
pub struct Split;
impl Display for Split {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", Ignore)
}
}
impl TrimStrategy for Split {
type Input = String;
fn trim(&mut self, text: String, chunk: &DrawProcess, a: Alignment) -> Vec<TrimmedText> {
let mut v = text.graphemes(true).collect::<Vec<_>>();
if v.is_empty() {
v.push(" ");
} let mut storage: &[&str] = &[];
let mut res: Vec<TrimmedText> = Vec::new();
for line in v.chunks(chunk.width()) {
if !storage.is_empty() {
res.push(TrimmedText(storage.iter().copied().collect::<String>()));
}
storage = line;
}
let blank_space = " ".graphemes(true).cycle();
res.push(TrimmedText(
storage.iter().copied().chain(blank_space).take(chunk.width()).collect::<String>(),
));
if matches!(a, Alignment::Minus) {
res.reverse();
}
res
}
fn back(&mut self, text: Vec<TrimmedText>, _: &DrawProcess, a: Alignment) -> Self::Input {
if text.is_empty() {
panic!("This shouldn't be an error!");
}
let mut res = String::new();
for line in text {
if matches!(a, Alignment::Minus) {
let mut line = line.0;
line.push_str(&res);
res = line;
} else {
res.push_str(&line.0);
}
}
res
}
}