Skip to main content

ctl_text/
span.rs

1//! One run of text in one style.
2
3use std::fmt;
4
5use anstyle::Style;
6
7use crate::cells::text_width;
8use crate::sanitize::{is_clean, span_text};
9
10/// A run of text in one style, on one line.
11///
12/// A span holds no character that [`is_unsafe`](crate::unsafe_chars::is_unsafe)
13/// refuses, so no escape sequence, directional override, or line separator
14/// reaches a terminal through it. It holds no tab either. A cluster never
15/// spans two spans, so a line's width is the sum of its spans' widths.
16///
17/// Its [`Display`](fmt::Display) prints the text without the style.
18#[derive(Clone, Debug, Eq, Hash, PartialEq)]
19pub struct Span {
20    pub(crate) text: String,
21    pub(crate) style: Style,
22}
23
24impl Span {
25    /// A span of `text` in `style`.
26    ///
27    /// Tabs expand to the next multiple of [`TAB_WIDTH`](crate::TAB_WIDTH)
28    /// cells, counted from the start of the span. That is right for a span
29    /// that starts its line.
30    ///
31    /// A span is one line, so every line break becomes a space, and the words
32    /// around it stay apart. The line breaks are newline, carriage return,
33    /// vertical tab, form feed, next line, and the line and paragraph
34    /// separators. Every other unsafe character is removed.
35    ///
36    /// Build text that spans lines, or follows other spans on a line, with
37    /// [`Text`](crate::Text). It splits at each newline and counts tab stops
38    /// from the line start.
39    ///
40    /// To keep an owned `String` as the span's text when it needs no change,
41    /// use `Span::from(string)`.
42    #[must_use]
43    pub fn new(text: impl AsRef<str>, style: Style) -> Self {
44        let text = text.as_ref();
45        let text = if is_clean(text) {
46            text.to_owned()
47        } else {
48            span_text(text)
49        };
50        Self { text, style }
51    }
52
53    /// The text, without escape sequences, tabs, or line breaks.
54    #[must_use]
55    pub fn text(&self) -> &str {
56        &self.text
57    }
58
59    /// The style the text is drawn in. Layout never changes it.
60    #[must_use]
61    pub fn style(&self) -> Style {
62        self.style
63    }
64
65    /// The width of the text in terminal cells.
66    #[must_use]
67    pub fn width(&self) -> u16 {
68        text_width(&self.text)
69    }
70}
71
72impl From<&str> for Span {
73    fn from(text: &str) -> Self {
74        Self::new(text, Style::new())
75    }
76}
77
78impl From<String> for Span {
79    fn from(text: String) -> Self {
80        let text = if is_clean(&text) {
81            text
82        } else {
83            span_text(&text)
84        };
85        Self {
86            text,
87            style: Style::new(),
88        }
89    }
90}
91
92impl fmt::Display for Span {
93    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
94        formatter.write_str(&self.text)
95    }
96}