1use crate::cells::cell_len;
11use crate::style::Style;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct Segment {
16 pub text: String,
17 pub style: Option<Style>,
18 pub control: bool,
20}
21
22impl Segment {
23 pub fn new(text: impl Into<String>, style: Option<Style>) -> Self {
25 Segment {
26 text: text.into(),
27 style,
28 control: false,
29 }
30 }
31
32 pub fn line() -> Self {
34 Segment {
35 text: "\n".to_string(),
36 style: None,
37 control: false,
38 }
39 }
40
41 pub fn control(text: impl Into<String>) -> Self {
43 Segment {
44 text: text.into(),
45 style: None,
46 control: true,
47 }
48 }
49
50 pub fn cell_length(&self) -> usize {
52 if self.control {
53 0
54 } else {
55 cell_len(&self.text)
56 }
57 }
58
59 pub fn simplify(segments: &[Segment]) -> Vec<Segment> {
62 let mut out: Vec<Segment> = Vec::with_capacity(segments.len());
63 for segment in segments {
64 match out.last_mut() {
65 Some(last) if last.style == segment.style && last.control == segment.control => {
66 last.text.push_str(&segment.text);
67 }
68 _ => out.push(segment.clone()),
69 }
70 }
71 out
72 }
73
74 pub fn apply_style(segments: &[Segment], style: &Style) -> Vec<Segment> {
82 segments
83 .iter()
84 .map(|segment| {
85 if segment.control || segment.text == "\n" {
86 segment.clone()
87 } else {
88 let combined = match &segment.style {
89 Some(own) => style.combine(own),
90 None => style.clone(),
91 };
92 Segment {
93 text: segment.text.clone(),
94 style: Some(combined),
95 control: false,
96 }
97 }
98 })
99 .collect()
100 }
101
102 pub fn split_lines(segments: &[Segment]) -> Vec<Vec<Segment>> {
108 let mut lines: Vec<Vec<Segment>> = Vec::new();
109 let mut current: Vec<Segment> = Vec::new();
110 for segment in segments {
111 if segment.control || !segment.text.contains('\n') {
112 if !segment.text.is_empty() {
113 current.push(segment.clone());
114 }
115 continue;
116 }
117 let mut parts = segment.text.split('\n').peekable();
118 while let Some(part) = parts.next() {
119 if !part.is_empty() {
120 current.push(Segment::new(part, segment.style.clone()));
121 }
122 if parts.peek().is_some() {
123 lines.push(std::mem::take(&mut current));
125 }
126 }
127 }
128 if !current.is_empty() {
129 lines.push(current);
130 }
131 lines
132 }
133
134 pub fn set_shape(lines: Vec<Vec<Segment>>, width: usize, height: usize) -> Vec<Vec<Segment>> {
138 let mut shaped: Vec<Vec<Segment>> = lines
139 .into_iter()
140 .take(height)
141 .map(|line| Segment::adjust_line_length(&line, width, None))
142 .collect();
143 while shaped.len() < height {
144 shaped.push(vec![Segment::new(" ".repeat(width), None)]);
145 }
146 shaped
147 }
148
149 pub fn crop_lines(segments: &[Segment], width: usize) -> Vec<Segment> {
160 let mut result: Vec<Segment> = Vec::with_capacity(segments.len());
161 let mut used = 0usize;
162 for segment in segments {
163 if segment.control {
164 result.push(segment.clone());
165 continue;
166 }
167 if segment.text == "\n" {
168 used = 0;
169 result.push(segment.clone());
170 continue;
171 }
172 let length = segment.cell_length();
173 if used + length <= width {
174 used += length;
175 result.push(segment.clone());
176 } else if used < width {
177 result.push(Segment::new(
181 crate::cells::set_cell_size(&segment.text, width - used),
182 segment.style.clone(),
183 ));
184 used = width;
185 }
186 }
188 result
189 }
190
191 pub fn adjust_line_length(
194 line: &[Segment],
195 length: usize,
196 style: Option<Style>,
197 ) -> Vec<Segment> {
198 let line_length: usize = line.iter().map(Segment::cell_length).sum();
199 if line_length == length {
200 line.to_vec()
201 } else if line_length < length {
202 let mut new_line = line.to_vec();
203 new_line.push(Segment::new(" ".repeat(length - line_length), style));
204 new_line
205 } else {
206 let mut new_line: Vec<Segment> = Vec::new();
208 let mut remaining = length;
209 for segment in line {
210 let seg_len = segment.cell_length();
211 if seg_len <= remaining {
212 new_line.push(segment.clone());
213 remaining -= seg_len;
214 } else {
215 let cropped = crate::cells::set_cell_size(&segment.text, remaining);
216 new_line.push(Segment::new(cropped, segment.style.clone()));
217 break;
218 }
219 }
220 new_line
221 }
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
232 fn crop_lines_cuts_each_line_independently() {
233 let segments = vec![
234 Segment::new("hello world", None),
235 Segment::line(),
236 Segment::new("hi", None),
237 Segment::line(),
238 Segment::control("\x1b[2A"),
239 Segment::new("abcdefgh", None),
240 ];
241 let cropped = Segment::crop_lines(&segments, 5);
242 let texts: Vec<&str> = cropped.iter().map(|s| s.text.as_str()).collect();
243 assert_eq!(texts, vec!["hello", "\n", "hi", "\n", "\x1b[2A", "abcde"]);
244 }
245
246 #[test]
249 fn crop_lines_pads_a_split_wide_character() {
250 let segments = vec![Segment::new("aa你好", None)];
251 let cropped = Segment::crop_lines(&segments, 5);
252 assert_eq!(cropped[0].text, "aa你 ");
253 }
254
255 #[test]
258 fn crop_lines_preserves_styles_and_drops_the_tail() {
259 let bold = Style::parse("bold").unwrap();
260 let segments = vec![
261 Segment::new("abc", Some(bold.clone())),
262 Segment::new("defgh", None),
263 ];
264 let cropped = Segment::crop_lines(&segments, 3);
265 assert_eq!(cropped.len(), 1);
266 assert_eq!(cropped[0].text, "abc");
267 assert_eq!(cropped[0].style, Some(bold));
268 }
269
270 #[test]
271 fn cell_length_ignores_control() {
272 assert_eq!(Segment::new("abc", None).cell_length(), 3);
273 assert_eq!(Segment::control("\x1b[2J").cell_length(), 0);
274 }
275}