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}