1use std::cell::RefCell;
2
3use display::DisplayColor;
4use unicode_segmentation::UnicodeSegmentation;
5use unicode_width::UnicodeWidthStr;
6use xi_unicode::EmojiExt;
7
8fn unicode_column_width(s: &str) -> usize {
9 s.graphemes(true).map(grapheme_column_width).sum()
10}
11
12fn grapheme_column_width(s: &str) -> usize {
13 for c in s.chars() {
14 if c.is_emoji_modifier_base() || c.is_emoji_modifier() {
15 return 2;
16 }
17 }
18 UnicodeWidthStr::width(s)
19}
20
21pub(crate) struct SegmentPartial {
22 content: String,
23 length: usize,
24}
25
26impl SegmentPartial {
27 fn new(content: &str, length: usize) -> Self {
28 Self {
29 content: String::from(content),
30 length,
31 }
32 }
33
34 pub(crate) fn get_content(&self) -> &str {
35 self.content.as_str()
36 }
37
38 pub(crate) const fn get_length(&self) -> usize {
39 self.length
40 }
41}
42
43#[derive(Clone, Debug)]
45pub struct LineSegment {
46 color: DisplayColor,
47 dim: bool,
48 reverse: bool,
49 text: String,
50 length: usize,
51 underline: bool,
52}
53
54impl LineSegment {
55 #[must_use]
57 #[inline]
58 pub fn new(text: &str) -> Self {
59 Self::new_with_color_and_style(text, DisplayColor::Normal, false, false, false)
60 }
61
62 #[must_use]
64 #[inline]
65 pub fn new_with_color(text: &str, color: DisplayColor) -> Self {
66 Self::new_with_color_and_style(text, color, false, false, false)
67 }
68
69 #[must_use]
71 #[inline]
72 pub fn new_with_color_and_style(
73 text: &str,
74 color: DisplayColor,
75 dim: bool,
76 underline: bool,
77 reverse: bool,
78 ) -> Self {
79 Self {
80 text: String::from(text),
81 color,
82 reverse,
83 dim,
84 length: unicode_column_width(text),
85 underline,
86 }
87 }
88
89 pub(crate) fn get_content(&self) -> &str {
90 self.text.as_str()
91 }
92
93 pub(crate) const fn get_color(&self) -> DisplayColor {
94 self.color
95 }
96
97 pub(crate) const fn is_dimmed(&self) -> bool {
98 self.dim
99 }
100
101 pub(crate) const fn is_underlined(&self) -> bool {
102 self.underline
103 }
104
105 pub(crate) const fn is_reversed(&self) -> bool {
106 self.reverse
107 }
108
109 pub(crate) const fn get_length(&self) -> usize {
110 self.length
111 }
112
113 pub(crate) fn get_partial_segment(&self, left: usize, max_width: usize) -> SegmentPartial {
114 let segment_length = unicode_column_width(self.text.as_str());
115
116 if segment_length <= left {
118 SegmentPartial::new("", 0)
119 }
120 else {
121 let graphemes = UnicodeSegmentation::graphemes(self.text.as_str(), true);
122
123 let skip_length = RefCell::new(0);
124 let graphemes_itr = graphemes.skip_while(|v| {
125 let len = grapheme_column_width(v);
126 let value = *skip_length.borrow();
127 if value + len > left {
128 false
129 }
130 else {
131 _ = skip_length.replace(value + len);
132 true
133 }
134 });
135
136 if segment_length - *skip_length.borrow() >= max_width {
137 let take_length = RefCell::new(0);
138 let partial_line = graphemes_itr
139 .take_while(|v| {
140 let len = grapheme_column_width(v);
141 let value = *take_length.borrow();
142 if value + len > max_width {
143 false
144 }
145 else {
146 _ = take_length.replace(value + len);
147 true
148 }
149 })
150 .collect::<String>();
151 SegmentPartial::new(partial_line.as_str(), take_length.into_inner())
152 }
153 else {
154 let partial_line = graphemes_itr.collect::<String>();
155 SegmentPartial::new(partial_line.as_str(), segment_length - skip_length.into_inner())
156 }
157 }
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn line_segment_case_new() {
167 let line_segment = LineSegment::new("D'fhuascail Íosa, Úrmhac na hÓighe Beannaithe, pór Éava agus Ádhaimh");
168
169 assert_eq!(line_segment.get_color(), DisplayColor::Normal);
170 assert!(!line_segment.is_dimmed());
171 assert!(!line_segment.is_underlined());
172 assert!(!line_segment.is_reversed());
173 assert_eq!(line_segment.get_length(), 68);
174 }
175
176 #[test]
177 fn line_segment_case_new_with_color() {
178 let line_segment = LineSegment::new_with_color("Árvíztűrő tükörfúrógép", DisplayColor::IndicatorColor);
179
180 assert_eq!(line_segment.get_color(), DisplayColor::IndicatorColor);
181 assert!(!line_segment.is_dimmed());
182 assert!(!line_segment.is_underlined());
183 assert!(!line_segment.is_reversed());
184 assert_eq!(line_segment.get_length(), 22);
185 }
186
187 #[test]
188 fn line_segment_case_new_with_color_and_style_all_styles_enabled() {
189 let line_segment = LineSegment::new_with_color_and_style(
190 "Sævör grét áðan því úlpan var ónýt",
191 DisplayColor::IndicatorColor,
192 true,
193 true,
194 true,
195 );
196
197 assert_eq!(line_segment.get_color(), DisplayColor::IndicatorColor);
198 assert!(line_segment.is_dimmed());
199 assert!(line_segment.is_underlined());
200 assert!(line_segment.is_reversed());
201 assert_eq!(line_segment.get_length(), 34);
202 }
203
204 #[test]
205 fn line_segment_case_new_with_color_and_style_all_styles_disabled() {
206 let line_segment = LineSegment::new_with_color_and_style(
207 "? דג סקרן שט בים מאוכזב ולפתע מצא לו חברה איך הקליטה",
208 DisplayColor::IndicatorColor,
209 false,
210 false,
211 false,
212 );
213
214 assert_eq!(line_segment.get_color(), DisplayColor::IndicatorColor);
215 assert!(!line_segment.is_dimmed());
216 assert!(!line_segment.is_underlined());
217 assert!(!line_segment.is_reversed());
218 assert_eq!(line_segment.get_length(), 52);
219 }
220
221 #[test]
222 fn line_segment_case_new_with_color_and_style_all_styles_dimmed() {
223 let line_segment =
224 LineSegment::new_with_color_and_style("Test String", DisplayColor::IndicatorColor, true, false, false);
225
226 assert!(line_segment.is_dimmed());
227 assert!(!line_segment.is_underlined());
228 assert!(!line_segment.is_reversed());
229 }
230
231 #[test]
232 fn line_segment_case_new_with_color_and_style_all_styles_underlined() {
233 let line_segment =
234 LineSegment::new_with_color_and_style("Test String", DisplayColor::IndicatorColor, false, true, false);
235
236 assert!(!line_segment.is_dimmed());
237 assert!(line_segment.is_underlined());
238 assert!(!line_segment.is_reversed());
239 }
240
241 #[test]
242 fn line_segment_case_new_with_color_and_style_all_styles_reversed() {
243 let line_segment =
244 LineSegment::new_with_color_and_style("Test String", DisplayColor::IndicatorColor, false, false, true);
245
246 assert!(!line_segment.is_dimmed());
247 assert!(!line_segment.is_underlined());
248 assert!(line_segment.is_reversed());
249 }
250
251 #[test]
252 fn line_segment_case_get_content() {
253 let line_segment = LineSegment::new("1234567890");
254
255 assert_eq!(line_segment.get_content(), "1234567890");
256 }
257
258 #[test]
259 fn line_segment_case_get_partial_segment_full_segment_exact_fit() {
260 let line_segment = LineSegment::new("1234567890");
261
262 let partial = line_segment.get_partial_segment(0, 10);
263
264 assert_eq!(partial.get_content(), "1234567890");
265 assert_eq!(partial.get_length(), 10);
266 }
267
268 #[test]
269 fn line_segment_case_get_partial_segment_full_segment_width_one_over() {
270 let line_segment = LineSegment::new("1234567890");
271
272 let partial = line_segment.get_partial_segment(0, 11);
273
274 assert_eq!(partial.get_content(), "1234567890");
275 assert_eq!(partial.get_length(), 10);
276 }
277
278 #[test]
279 fn line_segment_case_get_partial_segment_partial_segment_width_one_less() {
280 let line_segment = LineSegment::new("1234567890");
281
282 let partial = line_segment.get_partial_segment(0, 9);
283
284 assert_eq!(partial.get_content(), "123456789");
285 assert_eq!(partial.get_length(), 9);
286 }
287
288 #[test]
289 fn line_segment_case_get_partial_segment_partial_segment_width_left_one() {
290 let line_segment = LineSegment::new("1234567890");
291
292 let partial = line_segment.get_partial_segment(1, 9);
293
294 assert_eq!(partial.get_content(), "234567890");
295 assert_eq!(partial.get_length(), 9);
296 }
297
298 #[test]
299 fn line_segment_case_get_partial_segment_partial_segment_width_left_middle() {
300 let line_segment = LineSegment::new("1234567890");
301
302 let partial = line_segment.get_partial_segment(5, 5);
303
304 assert_eq!(partial.get_content(), "67890");
305 assert_eq!(partial.get_length(), 5);
306 }
307
308 #[test]
309 fn line_segment_case_get_partial_segment_partial_segment_width_left_middle_and_extra_max_width() {
310 let line_segment = LineSegment::new("1234567890");
311
312 let partial = line_segment.get_partial_segment(5, 6);
313
314 assert_eq!(partial.get_content(), "67890");
315 assert_eq!(partial.get_length(), 5);
316 }
317
318 #[test]
319 fn line_segment_case_get_partial_segment_partial_segment_width_one_left_in_segment() {
320 let line_segment = LineSegment::new("1234567890");
321
322 let partial = line_segment.get_partial_segment(9, 1);
323
324 assert_eq!(partial.get_content(), "0");
325 assert_eq!(partial.get_length(), 1);
326 }
327
328 #[test]
329 fn line_segment_case_get_partial_segment_partial_segment_width_one_left_in_segment_with_extra_max_width() {
330 let line_segment = LineSegment::new("1234567890");
331
332 let partial = line_segment.get_partial_segment(9, 2);
333
334 assert_eq!(partial.get_content(), "0");
335 assert_eq!(partial.get_length(), 1);
336 }
337
338 #[test]
339 fn line_segment_case_get_partial_segment_partial_segment_one_max_width() {
340 let line_segment = LineSegment::new("1234567890");
341
342 let partial = line_segment.get_partial_segment(0, 1);
343
344 assert_eq!(partial.get_content(), "1");
345 assert_eq!(partial.get_length(), 1);
346 }
347
348 #[test]
349 fn line_segment_case_get_partial_segment_partial_segment_two_max_width() {
350 let line_segment = LineSegment::new("1234567890");
351
352 let partial = line_segment.get_partial_segment(0, 2);
353
354 assert_eq!(partial.get_content(), "12");
355 assert_eq!(partial.get_length(), 2);
356 }
357
358 #[test]
359 fn line_segment_case_get_partial_segment_partial_segment_no_max_width() {
360 let line_segment = LineSegment::new("1234567890");
361
362 let partial = line_segment.get_partial_segment(0, 0);
363
364 assert_eq!(partial.get_content(), "");
365 assert_eq!(partial.get_length(), 0);
366 }
367
368 #[test]
369 fn line_segment_case_get_partial_segment_partial_segment_left_at_segment_length() {
370 let line_segment = LineSegment::new("1234567890");
371
372 let partial = line_segment.get_partial_segment(10, 1);
373
374 assert_eq!(partial.get_content(), "");
375 assert_eq!(partial.get_length(), 0);
376 }
377
378 #[test]
379 fn line_segment_case_get_partial_segment_partial_segment_left_after_segment_length() {
380 let line_segment = LineSegment::new("1234567890");
381
382 let partial = line_segment.get_partial_segment(11, 1);
383
384 assert_eq!(partial.get_content(), "");
385 assert_eq!(partial.get_length(), 0);
386 }
387
388 #[test]
389 fn line_segment_case_get_partial_segment_partial_segment_left_with_emoji() {
390 let line_segment = LineSegment::new("1😊2😊3😊4567890");
391
392 let partial = line_segment.get_partial_segment(0, 10);
393
394 assert_eq!(partial.get_content(), "1😊2😊3😊4");
395 assert_eq!(partial.get_length(), 10);
396 }
397
398 #[test]
399 fn line_segment_case_get_partial_segment_partial_segment_left_with_emoji_split_at_length() {
400 let line_segment = LineSegment::new("123456789😊");
401
402 let partial = line_segment.get_partial_segment(0, 10);
403
404 assert_eq!(partial.get_content(), "123456789");
405 assert_eq!(partial.get_length(), 9);
406 }
407}