stylish_stringlike/text/spans/
span.rs

1use super::{
2    BoundedWidth, Expandable, HasWidth, Joinable, Paintable, Pushable, RawText, Sliceable, Spans,
3    Width,
4};
5#[cfg(test)]
6use ansi_term::{ANSIString, Style};
7use regex::Captures;
8use std::borrow::Cow;
9use std::fmt;
10use std::ops::Deref;
11use std::ops::RangeBounds;
12use unicode_width::UnicodeWidthStr;
13
14/// A span of text having a single style.
15#[derive(Clone, Debug, Default, PartialEq)]
16pub struct Span<'a, T: Clone> {
17    style: Cow<'a, T>,
18    content: Cow<'a, str>,
19}
20
21impl<'a, T: Clone> Span<'a, T> {
22    pub fn style(&self) -> &Cow<'a, T> {
23        &self.style
24    }
25    pub fn new(style: Cow<'a, T>, content: Cow<'a, str>) -> Span<'a, T> {
26        Span { style, content }
27    }
28    pub fn borrowed(style: &'a T, content: &'a str) -> Span<'a, T> {
29        Span {
30            style: Cow::Borrowed(style),
31            content: Cow::Borrowed(content),
32        }
33    }
34}
35impl<'a, T: Paintable + Clone> fmt::Display for Span<'a, T> {
36    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
37        self.style.paint(self.content.as_ref()).fmt(fmt)
38    }
39}
40
41#[cfg(test)]
42impl<'a> From<&Span<'a, Style>> for ANSIString<'a> {
43    fn from(span: &Span<'a, Style>) -> ANSIString<'a> {
44        span.style.paint(span.content.clone())
45    }
46}
47#[cfg(test)]
48impl<'a> From<Span<'a, Style>> for ANSIString<'a> {
49    fn from(span: Span<'a, Style>) -> ANSIString<'a> {
50        span.style.paint(span.content)
51    }
52}
53#[cfg(test)]
54impl<'a> From<&'a ANSIString<'a>> for Span<'a, Style> {
55    fn from(string: &'a ANSIString<'a>) -> Self {
56        let style = Cow::Borrowed(string.style_ref());
57        let content = Cow::Borrowed(string.deref());
58        Span::new(style, content)
59    }
60}
61#[cfg(test)]
62impl<'a> From<ANSIString<'_>> for Span<'a, Style> {
63    fn from(string: ANSIString<'_>) -> Self {
64        let style = Cow::Owned(*string.style_ref());
65        let content = Cow::Owned(string.deref().to_string());
66        Span::new(style, content)
67    }
68}
69
70impl<'a, T: Clone + Default + PartialEq> Joinable<Span<'a, T>> for Span<'a, T> {
71    type Output = Spans<T>;
72    fn join(&self, other: &Span<T>) -> Self::Output {
73        let mut res: Spans<T> = Default::default();
74        res.push(self);
75        res.push(other);
76        res
77    }
78}
79impl<'a, T: Clone> Pushable<str> for Span<'a, T> {
80    fn push(&mut self, other: &str) {
81        self.content.to_mut().push_str(other);
82    }
83}
84impl<'a, T: Clone> Sliceable for Span<'a, T> {
85    fn slice<R>(&self, range: R) -> Option<Self>
86    where
87        R: RangeBounds<usize> + Clone,
88    {
89        self.content
90            .deref()
91            .slice(range)
92            .map(|ref s| Span::new(self.style.clone(), Cow::Owned(s.to_string())))
93    }
94}
95impl<'a, T: Clone> RawText for Span<'a, T> {
96    fn raw(&self) -> String {
97        self.content.to_string()
98    }
99    fn raw_ref(&self) -> &str {
100        &self.content
101    }
102}
103impl<'a, T: Clone> BoundedWidth for Span<'a, T> {
104    fn bounded_width(&self) -> usize {
105        self.content.width()
106    }
107}
108impl<'a, T: Clone> HasWidth for Span<'a, T> {
109    fn width(&self) -> Width {
110        Width::Bounded(self.bounded_width())
111    }
112}
113impl<'a, T: Clone> Expandable for Span<'a, T> {
114    fn expand(&self, capture: &Captures) -> Span<'a, T> {
115        let new_content = self.raw().expand(capture);
116        Span {
117            style: self.style.clone(),
118            content: Cow::Owned(new_content),
119        }
120    }
121}
122#[cfg(test)]
123mod test {
124    use super::*;
125    use crate::text::{Sliceable, WidthSliceable};
126    use ansi_term::Color;
127
128    #[test]
129    fn convert() {
130        let style = Style::new();
131        let span = Span::borrowed(&style, "foo");
132        let actual: ANSIString = (&span).into();
133        let expected = Style::new().paint("foo");
134        assert_eq!(expected, actual);
135    }
136    #[test]
137    fn fmt() {
138        let style = Style::new();
139        let span = Span::borrowed(&style, "foo");
140        let foo: ANSIString = (&span).into();
141        let actual = format!("{}", span);
142        let expected = format!("{}", foo);
143        assert_eq!(expected, actual);
144    }
145    #[test]
146    fn slice() {
147        let span = Span::<Style>::new(
148            Cow::Owned(Color::Black.normal()),
149            Cow::Owned(String::from("012345678")),
150        );
151        let res = span.slice(1..8);
152        let actual = format!("{}", res.unwrap());
153        let expected = format!("{}", Color::Black.paint("1234567"));
154        assert_eq!(expected, actual);
155    }
156    #[test]
157    fn slice_width_middle() {
158        let span = Span::<Style>::new(
159            Cow::Owned(Color::Black.normal()),
160            Cow::Owned(String::from("012345678")),
161        );
162        let res = span.slice_width(1..2);
163        let actual = format!("{}", res.unwrap());
164        let expected = format!("{}", Color::Black.paint("1"));
165        assert_eq!(expected, actual);
166    }
167    #[test]
168    fn slice_width_left() {
169        let span = Span::<Style>::new(
170            Cow::Owned(Color::Black.normal()),
171            Cow::Owned(String::from("012345678")),
172        );
173        let res = span.slice_width(..1);
174        let actual = format!("{}", res.unwrap());
175        let expected = format!("{}", Color::Black.paint("0"));
176        assert_eq!(expected, actual);
177    }
178    #[test]
179    fn slice_width_right() {
180        let span = Span::<Style>::new(
181            Cow::Owned(Color::Black.normal()),
182            Cow::Owned(String::from("012345678")),
183        );
184        let res = span.slice_width(8..);
185        let actual = format!("{}", res.unwrap());
186        let expected = format!("{}", Color::Black.paint("8"));
187        assert_eq!(expected, actual);
188    }
189    #[test]
190    fn slice_width_emoji_left_none() {
191        let span = Span::<Style>::new(
192            Cow::Owned(Color::Black.normal()),
193            Cow::Owned(String::from("πŸ˜ΌπŸ™‹πŸ‘©πŸ“ͺ")),
194        );
195        let actual = span.slice_width(..1);
196        let expected = None;
197        assert_eq!(expected, actual);
198    }
199    #[test]
200    fn slice_width_emoji_left_some() {
201        let span = Span::<Style>::new(
202            Cow::Owned(Color::Black.normal()),
203            Cow::Owned(String::from("πŸ˜ΌπŸ™‹πŸ‘©πŸ“ͺ")),
204        );
205        let res = span.slice_width(..2);
206        let actual = format!("{}", res.unwrap());
207        let expected = format!("{}", Color::Black.paint("😼"));
208        assert_eq!(expected, actual);
209    }
210    #[test]
211    fn slice_width_emoji_left_more() {
212        let span = Span::<Style>::new(
213            Cow::Owned(Color::Black.normal()),
214            Cow::Owned(String::from("πŸ˜ΌπŸ™‹πŸ‘©πŸ“ͺ")),
215        );
216        let res = span.slice_width(..3);
217        let actual = format!("{}", res.unwrap());
218        let expected = format!("{}", Color::Black.paint("😼"));
219        assert_eq!(expected, actual);
220    }
221    #[test]
222    fn slice_width_emoji_left_even_more() {
223        let span = Span::<Style>::new(
224            Cow::Owned(Color::Black.normal()),
225            Cow::Owned(String::from("πŸ˜ΌπŸ™‹πŸ‘©πŸ“ͺ")),
226        );
227        let res = span.slice_width(..4);
228        let actual = format!("{}", res.unwrap());
229        let expected = format!("{}", Color::Black.paint("πŸ˜ΌπŸ™‹"));
230        assert_eq!(expected, actual);
231    }
232    #[test]
233    fn slice_width_emoji_middle_none_less() {
234        let span = Span::<Style>::new(
235            Cow::Owned(Color::Black.normal()),
236            Cow::Owned(String::from("πŸ˜ΌπŸ™‹πŸ‘©πŸ“ͺ")),
237        );
238        let res = span.slice_width(1..2);
239        let actual = res;
240        let expected = None;
241        assert_eq!(expected, actual);
242    }
243    #[test]
244    fn slice_width_emoji_middle_none_more() {
245        let span = Span::<Style>::new(
246            Cow::Owned(Color::Black.normal()),
247            Cow::Owned(String::from("πŸ˜ΌπŸ™‹πŸ‘©πŸ“ͺ")),
248        );
249        let res = span.slice_width(1..3);
250        let actual = res;
251        let expected = None;
252        assert_eq!(expected, actual);
253    }
254    #[test]
255    fn slice_width_emoji_middle_some() {
256        let span = Span::<Style>::new(
257            Cow::Owned(Color::Black.normal()),
258            Cow::Owned(String::from("πŸ˜ΌπŸ™‹πŸ‘©πŸ“ͺ")),
259        );
260        let res = span.slice_width(1..4);
261        let actual = format!("{}", res.unwrap());
262        let expected = format!("{}", Color::Black.paint("πŸ™‹"));
263        assert_eq!(expected, actual);
264    }
265    #[test]
266    fn slice_width_emoji_middle_more() {
267        let span = Span::<Style>::new(
268            Cow::Owned(Color::Black.normal()),
269            Cow::Owned(String::from("πŸ˜ΌπŸ™‹πŸ‘©πŸ“ͺ")),
270        );
271        let res = span.slice_width(1..6);
272        let actual = format!("{}", res.unwrap());
273        let expected = format!("{}", Color::Black.paint("πŸ™‹πŸ‘©"));
274        assert_eq!(expected, actual);
275    }
276    #[test]
277    fn slice_width_emoji_right_none_trivial() {
278        let span = Span::<Style>::new(
279            Cow::Owned(Color::Black.normal()),
280            Cow::Owned(String::from("πŸ˜ΌπŸ™‹πŸ‘©πŸ“ͺ")),
281        );
282        let actual = span.slice_width(8..);
283        let expected = None;
284        assert_eq!(expected, actual);
285    }
286    #[test]
287    fn slice_width_emoji_right_none_simple() {
288        let span = Span::<Style>::new(
289            Cow::Owned(Color::Black.normal()),
290            Cow::Owned(String::from("πŸ˜ΌπŸ™‹πŸ‘©πŸ“ͺ")),
291        );
292        let actual = span.slice_width(7..);
293        let expected = None;
294        assert_eq!(expected, actual);
295    }
296    #[test]
297    fn slice_width_emoji_right_some() {
298        let span = Span::<Style>::new(
299            Cow::Owned(Color::Black.normal()),
300            Cow::Owned(String::from("πŸ˜ΌπŸ™‹πŸ‘©πŸ“ͺ")),
301        );
302        let res = span.slice_width(6..);
303        let actual = format!("{}", res.unwrap());
304        let expected = format!("{}", Color::Black.paint("πŸ“ͺ"));
305        assert_eq!(expected, actual);
306    }
307    #[test]
308    fn slice_width_full() {
309        let span = Span::<Style>::new(
310            Cow::Owned(Color::Black.normal()),
311            Cow::Owned(String::from("πŸ˜ΌπŸ™‹πŸ‘©πŸ“ͺ")),
312        );
313        let res = span.slice_width(..);
314        let actual = format!("{}", res.unwrap());
315        let expected = format!("{}", Color::Black.paint("πŸ˜ΌπŸ™‹πŸ‘©πŸ“ͺ"));
316        assert_eq!(expected, actual);
317    }
318}