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#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::color::Color;
138
139 #[test]
140 fn test_span_raw() {
141 let s = Span::raw("hello");
142 assert_eq!(s.content, "hello");
143 assert_eq!(s.style, Style::default());
144 assert_eq!(s.width(), 5);
145 }
146
147 #[test]
148 fn test_span_styled() {
149 let style = Style::new().fg(Color::RED);
150 let s = Span::styled("hi", style);
151 assert_eq!(s.content, "hi");
152 assert_eq!(s.style, style);
153 }
154
155 #[test]
156 fn test_span_width_wide_chars() {
157 let s = Span::raw("中文"); assert_eq!(s.width(), 4);
159 }
160
161 #[test]
162 fn test_line_from_str() {
163 let line = Line::from("hello");
164 assert_eq!(line.spans.len(), 1);
165 assert_eq!(line.width(), 5);
166 }
167
168 #[test]
169 fn test_line_from_spans() {
170 let line = Line::from(vec![
171 Span::raw("HP: "),
172 Span::styled("100", Style::new().fg(Color::GREEN)),
173 ]);
174 assert_eq!(line.width(), 7);
175 }
176
177 #[test]
178 fn test_line_width_wide_chars() {
179 let line = Line::from(vec![Span::raw("中"), Span::raw("x")]);
180 assert_eq!(line.width(), 3); }
182
183 #[test]
184 fn test_line_empty() {
185 let line = Line::new();
186 assert_eq!(line.width(), 0);
187 }
188}