1use std::fmt;
2
3use super::{Color, Filled, Line, Paint};
4
5use unicode_display_width as unicode;
6use unicode_segmentation::UnicodeSegmentation as _;
7
8pub trait Cell: fmt::Display {
10 type Truncated: Cell;
12 type Padded: Cell;
14
15 fn width(&self) -> usize;
17 fn background(&self) -> Color {
19 Color::Unset
20 }
21 #[must_use]
23 fn truncate(&self, width: usize, delim: &str) -> Self::Truncated;
24 #[must_use]
26 fn pad(&self, width: usize) -> Self::Padded;
27}
28
29impl Cell for Paint<String> {
30 type Truncated = Self;
31 type Padded = Self;
32
33 fn width(&self) -> usize {
34 Cell::width(self.content())
35 }
36
37 fn background(&self) -> Color {
38 self.style.background
39 }
40
41 fn truncate(&self, width: usize, delim: &str) -> Self {
42 Self {
43 item: self.item.truncate(width, delim),
44 style: self.style,
45 }
46 }
47
48 fn pad(&self, width: usize) -> Self {
49 Self {
50 item: self.item.pad(width),
51 style: self.style,
52 }
53 }
54}
55
56impl Cell for Line {
57 type Truncated = Line;
58 type Padded = Line;
59
60 fn width(&self) -> usize {
61 Line::width(self)
62 }
63
64 fn pad(&self, width: usize) -> Self::Padded {
65 let mut line = self.clone();
66 Line::pad(&mut line, width);
67 line
68 }
69
70 fn truncate(&self, width: usize, delim: &str) -> Self::Truncated {
71 let mut line = self.clone();
72 Line::truncate(&mut line, width, delim);
73 line
74 }
75}
76
77impl Cell for Paint<&str> {
78 type Truncated = Paint<String>;
79 type Padded = Paint<String>;
80
81 fn width(&self) -> usize {
82 Cell::width(self.item)
83 }
84
85 fn background(&self) -> Color {
86 self.style.background
87 }
88
89 fn truncate(&self, width: usize, delim: &str) -> Paint<String> {
90 Paint {
91 item: self.item.truncate(width, delim),
92 style: self.style,
93 }
94 }
95
96 fn pad(&self, width: usize) -> Paint<String> {
97 Paint {
98 item: self.item.pad(width),
99 style: self.style,
100 }
101 }
102}
103
104impl Cell for String {
105 type Truncated = Self;
106 type Padded = Self;
107
108 fn width(&self) -> usize {
109 Cell::width(self.as_str())
110 }
111
112 fn truncate(&self, width: usize, delim: &str) -> Self {
113 self.as_str().truncate(width, delim)
114 }
115
116 fn pad(&self, width: usize) -> Self {
117 self.as_str().pad(width)
118 }
119}
120
121impl Cell for str {
122 type Truncated = String;
123 type Padded = String;
124
125 fn width(&self) -> usize {
126 self.graphemes(true)
127 .map(|g| unicode::width(g) as usize)
128 .sum()
129 }
130
131 fn truncate(&self, width: usize, delim: &str) -> String {
132 if width < Cell::width(self) {
133 let d = Cell::width(delim);
134 if width < d {
135 return String::new();
137 }
138 let mut cols = 0; let mut boundary = 0; for g in self.graphemes(true) {
143 let c = Cell::width(g);
144 if cols + c + d > width {
145 break;
146 }
147 boundary += g.len();
148 cols += c;
149 }
150 if self[boundary..].trim().is_empty() {
152 self[..boundary + 1].to_owned()
153 } else {
154 format!("{}{delim}", &self[..boundary])
155 }
156 } else {
157 self.to_owned()
158 }
159 }
160
161 fn pad(&self, max: usize) -> String {
162 let width = Cell::width(self);
163
164 if width < max {
165 format!("{self}{}", " ".repeat(max - width))
166 } else {
167 self.to_owned()
168 }
169 }
170}
171
172impl<T: Cell + ?Sized> Cell for &T {
173 type Truncated = T::Truncated;
174 type Padded = T::Padded;
175
176 fn width(&self) -> usize {
177 T::width(self)
178 }
179
180 fn truncate(&self, width: usize, delim: &str) -> Self::Truncated {
181 T::truncate(self, width, delim)
182 }
183
184 fn pad(&self, width: usize) -> Self::Padded {
185 T::pad(self, width)
186 }
187}
188
189impl<T: Cell + fmt::Display> Cell for Filled<T> {
190 type Truncated = T::Truncated;
191 type Padded = T::Padded;
192
193 fn width(&self) -> usize {
194 T::width(&self.item)
195 }
196
197 fn background(&self) -> Color {
198 self.color
199 }
200
201 fn truncate(&self, width: usize, delim: &str) -> Self::Truncated {
202 T::truncate(&self.item, width, delim)
203 }
204
205 fn pad(&self, width: usize) -> Self::Padded {
206 T::pad(&self.item, width)
207 }
208}
209
210#[cfg(test)]
211mod test {
212 #[test]
213 fn test_width() {
214 assert_eq!(unicode_display_width::width("❤️"), 2);
215 assert_eq!(unicode_display_width::width("🪵"), 2);
216 }
217}