use std::fmt;
use super::{Color, Filled, Line, Paint};
use unicode_display_width as unicode;
use unicode_segmentation::UnicodeSegmentation as _;
pub trait Cell: fmt::Display {
type Truncated: Cell;
type Padded: Cell;
fn width(&self) -> usize;
fn background(&self) -> Color {
Color::Unset
}
#[must_use]
fn truncate(&self, width: usize, delim: &str) -> Self::Truncated;
#[must_use]
fn pad(&self, width: usize) -> Self::Padded;
}
impl Cell for Paint<String> {
type Truncated = Self;
type Padded = Self;
fn width(&self) -> usize {
Cell::width(self.content())
}
fn background(&self) -> Color {
self.style.background
}
fn truncate(&self, width: usize, delim: &str) -> Self {
Self {
item: self.item.truncate(width, delim),
style: self.style,
}
}
fn pad(&self, width: usize) -> Self {
Self {
item: self.item.pad(width),
style: self.style,
}
}
}
impl Cell for Line {
type Truncated = Line;
type Padded = Line;
fn width(&self) -> usize {
Line::width(self)
}
fn pad(&self, width: usize) -> Self::Padded {
let mut line = self.clone();
Line::pad(&mut line, width);
line
}
fn truncate(&self, width: usize, delim: &str) -> Self::Truncated {
let mut line = self.clone();
Line::truncate(&mut line, width, delim);
line
}
}
impl Cell for Paint<&str> {
type Truncated = Paint<String>;
type Padded = Paint<String>;
fn width(&self) -> usize {
Cell::width(self.item)
}
fn background(&self) -> Color {
self.style.background
}
fn truncate(&self, width: usize, delim: &str) -> Paint<String> {
Paint {
item: self.item.truncate(width, delim),
style: self.style,
}
}
fn pad(&self, width: usize) -> Paint<String> {
Paint {
item: self.item.pad(width),
style: self.style,
}
}
}
impl Cell for String {
type Truncated = Self;
type Padded = Self;
fn width(&self) -> usize {
Cell::width(self.as_str())
}
fn truncate(&self, width: usize, delim: &str) -> Self {
self.as_str().truncate(width, delim)
}
fn pad(&self, width: usize) -> Self {
self.as_str().pad(width)
}
}
impl Cell for str {
type Truncated = String;
type Padded = String;
fn width(&self) -> usize {
self.graphemes(true)
.map(|g| unicode::width(g) as usize)
.sum()
}
fn truncate(&self, width: usize, delim: &str) -> String {
if width < Cell::width(self) {
let d = Cell::width(delim);
if width < d {
return String::new();
}
let mut cols = 0; let mut boundary = 0; for g in self.graphemes(true) {
let c = Cell::width(g);
if cols + c + d > width {
break;
}
boundary += g.len();
cols += c;
}
if self[boundary..].trim().is_empty() {
self[..boundary + 1].to_owned()
} else {
format!("{}{delim}", &self[..boundary])
}
} else {
self.to_owned()
}
}
fn pad(&self, max: usize) -> String {
let width = Cell::width(self);
if width < max {
format!("{self}{}", " ".repeat(max - width))
} else {
self.to_owned()
}
}
}
impl<T: Cell + ?Sized> Cell for &T {
type Truncated = T::Truncated;
type Padded = T::Padded;
fn width(&self) -> usize {
T::width(self)
}
fn truncate(&self, width: usize, delim: &str) -> Self::Truncated {
T::truncate(self, width, delim)
}
fn pad(&self, width: usize) -> Self::Padded {
T::pad(self, width)
}
}
impl<T: Cell + fmt::Display> Cell for Filled<T> {
type Truncated = T::Truncated;
type Padded = T::Padded;
fn width(&self) -> usize {
T::width(&self.item)
}
fn background(&self) -> Color {
self.color
}
fn truncate(&self, width: usize, delim: &str) -> Self::Truncated {
T::truncate(&self.item, width, delim)
}
fn pad(&self, width: usize) -> Self::Padded {
T::pad(&self.item, width)
}
}
#[cfg(test)]
mod test {
#[test]
fn test_width() {
assert_eq!(unicode_display_width::width("❤️"), 2);
assert_eq!(unicode_display_width::width("🪵"), 2);
}
}