1use crate::style::Style;
4use alloc::string::String;
5use alloc::vec::Vec;
6use unicode_width::UnicodeWidthStr;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
24pub struct Span {
25 pub content: String,
27 pub style: Style,
29}
30
31impl Span {
32 #[must_use]
34 pub fn raw(content: impl Into<String>) -> Self {
35 Self {
36 content: content.into(),
37 style: Style::default(),
38 }
39 }
40
41 #[must_use]
43 pub fn styled(content: impl Into<String>, style: Style) -> Self {
44 Self {
45 content: content.into(),
46 style,
47 }
48 }
49
50 #[must_use]
52 pub fn width(&self) -> usize {
53 self.content.as_str().width()
54 }
55}
56
57impl<S: Into<String>> From<S> for Span {
58 fn from(s: S) -> Self {
59 Self::raw(s)
60 }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Default)]
79pub struct Line {
80 pub spans: Vec<Span>,
82}
83
84impl Line {
85 #[must_use]
87 pub fn new() -> Self {
88 Self::default()
89 }
90
91 #[must_use]
93 pub fn raw(content: impl Into<String>) -> Self {
94 Self {
95 spans: alloc::vec![Span::raw(content)],
96 }
97 }
98
99 #[must_use]
103 pub fn width(&self) -> usize {
104 self.spans.iter().map(Span::width).sum()
105 }
106}
107
108impl From<&str> for Line {
109 fn from(s: &str) -> Self {
110 Self::raw(s)
111 }
112}
113
114impl From<String> for Line {
115 fn from(s: String) -> Self {
116 Self::raw(s)
117 }
118}
119
120impl From<Span> for Line {
121 fn from(span: Span) -> Self {
122 Self {
123 spans: alloc::vec![span],
124 }
125 }
126}
127
128impl From<Vec<Span>> for Line {
129 fn from(spans: Vec<Span>) -> Self {
130 Self { spans }
131 }
132}
133
134#[macro_export]
155macro_rules! spans {
156 ($(($style:expr, $text:expr)),* $(,)?) => {
157 $crate::text::Line::from(alloc::vec![
158 $($crate::text::Span::styled($text, $style)),*
159 ])
160 };
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166 use crate::color::Color;
167
168 #[test]
169 fn test_span_raw() {
170 let s = Span::raw("hello");
171 assert_eq!(s.content, "hello");
172 assert_eq!(s.style, Style::default());
173 assert_eq!(s.width(), 5);
174 }
175
176 #[test]
177 fn test_span_styled() {
178 let style = Style::new().fg(Color::RED);
179 let s = Span::styled("hi", style);
180 assert_eq!(s.content, "hi");
181 assert_eq!(s.style, style);
182 }
183
184 #[test]
185 fn test_span_width_wide_chars() {
186 let s = Span::raw("中文"); assert_eq!(s.width(), 4);
188 }
189
190 #[test]
191 fn test_line_from_str() {
192 let line = Line::from("hello");
193 assert_eq!(line.spans.len(), 1);
194 assert_eq!(line.width(), 5);
195 }
196
197 #[test]
198 fn test_line_from_spans() {
199 let line = Line::from(vec![
200 Span::raw("HP: "),
201 Span::styled("100", Style::new().fg(Color::GREEN)),
202 ]);
203 assert_eq!(line.width(), 7);
204 }
205
206 #[test]
207 fn test_line_width_wide_chars() {
208 let line = Line::from(vec![Span::raw("中"), Span::raw("x")]);
209 assert_eq!(line.width(), 3); }
211
212 #[test]
213 fn test_line_empty() {
214 let line = Line::new();
215 assert_eq!(line.width(), 0);
216 }
217}