1use std::fmt;
7
8use regex::Regex;
9use unicode_width::UnicodeWidthStr;
10
11use crate::align::AlignMethod;
12use crate::style::Style;
13
14#[derive(Debug, Clone, PartialEq)]
20pub struct Span {
21 pub start: usize,
22 pub end: usize,
23 pub style: Style,
24}
25
26impl Span {
27 pub fn new(start: usize, end: usize, style: Style) -> Self {
29 Self { start, end, style }
30 }
31
32 pub fn is_empty(&self) -> bool {
34 self.end <= self.start
35 }
36
37 pub fn split(&self, offset: usize) -> (Self, Option<Self>) {
40 if offset <= self.start || offset >= self.end {
41 return (self.clone(), None);
42 }
43 let span1 = Self::new(self.start, self.end.min(offset), self.style.clone());
44 let span2 = Self::new(span1.end, self.end, self.style.clone());
45 (span1, Some(span2))
46 }
47
48 pub fn move_by(&self, offset: isize) -> Self {
50 let start = (self.start as isize + offset).max(0) as usize;
51 let end = (self.end as isize + offset).max(0) as usize;
52 Self::new(start, end, self.style.clone())
53 }
54
55 pub fn right_crop(&self, offset: usize) -> Self {
57 if offset >= self.end {
58 self.clone()
59 } else {
60 Self::new(self.start, self.end.min(offset), self.style.clone())
61 }
62 }
63}
64
65#[derive(Debug, Clone)]
71pub struct Text {
72 pub plain: String,
74 pub spans: Vec<Span>,
76 pub style: Style,
78 pub justify: JustifyMethod,
80 pub end: String,
82 pub overflow: OverflowMethod,
84 pub no_wrap: bool,
86 pub tab_size: usize,
88 pub indent_guides: bool,
90}
91
92pub type JustifyMethod = crate::align::AlignMethod;
93pub type OverflowMethod = crate::console::OverflowMethod;
94
95impl Text {
96 pub fn new(plain: impl Into<String>) -> Self {
98 Self {
99 plain: plain.into(),
100 spans: Vec::new(),
101 style: Style::new(),
102 justify: JustifyMethod::Left,
103 end: "\n".to_string(),
104 overflow: OverflowMethod::Fold,
105 no_wrap: false,
106 tab_size: 8,
107 indent_guides: false,
108 }
109 }
110
111 pub fn styled(style: Style) -> Self {
113 Self {
114 plain: String::new(),
115 spans: Vec::new(),
116 style,
117 justify: JustifyMethod::Left,
118 end: "\n".to_string(),
119 overflow: OverflowMethod::Fold,
120 no_wrap: false,
121 tab_size: 8,
122 indent_guides: false,
123 }
124 }
125
126 pub fn from_ansi(text: &str) -> Self {
131 let re = Regex::new(r"\x1b\[([\d;]*)([a-zA-Z])").unwrap();
132 let mut plain = String::new();
133 let mut spans: Vec<Span> = Vec::new();
134 let mut current_style = Style::new();
135 let mut last_end = 0usize;
136
137 for cap in re.captures_iter(text) {
138 let m = cap.get(0).unwrap();
139 let match_start = m.start();
140 let match_end = m.end();
141 let cmd = cap.get(2).map_or("", |m| m.as_str());
142
143 if match_start > last_end {
145 let segment = &text[last_end..match_start];
146 let start = plain.len();
147 plain.push_str(segment);
148 let end = plain.len();
149 if !current_style.is_plain() && start < end {
150 spans.push(Span::new(start, end, current_style.clone()));
151 }
152 }
153
154 if cmd == "m" {
156 let params_str = cap.get(1).map_or("", |m| m.as_str());
157 apply_sgr(&mut current_style, params_str);
158 }
159
160 last_end = match_end;
161 }
162
163 if last_end < text.len() {
165 let segment = &text[last_end..];
166 let start = plain.len();
167 plain.push_str(segment);
168 let end = plain.len();
169 if !current_style.is_plain() && start < end {
170 spans.push(Span::new(start, end, current_style.clone()));
171 }
172 }
173
174 Self {
175 plain,
176 spans,
177 style: Style::new(),
178 justify: JustifyMethod::Left,
179 end: "\n".to_string(),
180 overflow: OverflowMethod::Fold,
181 no_wrap: false,
182 tab_size: 8,
183 indent_guides: false,
184 }
185 }
186
187 pub fn from_markup(markup: &str) -> Self {
190 crate::markup::render(markup)
191 }
192
193 pub fn get_style(&self) -> &Style {
195 &self.style
196 }
197
198 pub fn get_style_mut(&mut self) -> &mut Style {
200 &mut self.style
201 }
202
203 pub fn spans(&self) -> &[Span] {
205 &self.spans
206 }
207
208 pub fn style(mut self, style: Style) -> Self {
210 self.style = style;
211 self
212 }
213
214 pub fn justify(mut self, justify: JustifyMethod) -> Self {
216 self.justify = justify;
217 self
218 }
219
220 pub fn end(mut self, end: impl Into<String>) -> Self {
222 self.end = end.into();
223 self
224 }
225
226 pub fn overflow(mut self, overflow: OverflowMethod) -> Self {
228 self.overflow = overflow;
229 self
230 }
231
232 pub fn no_wrap(mut self, value: bool) -> Self {
234 self.no_wrap = value;
235 self
236 }
237
238 pub fn tab_size(mut self, size: usize) -> Self {
240 self.tab_size = size;
241 self
242 }
243
244 pub fn with_indent_guides(mut self, show: bool) -> Self {
246 self.indent_guides = show;
247 self
248 }
249
250 pub fn append(&mut self, text: impl Into<Text>, style: Option<Style>) {
252 let text: Text = text.into();
253 let offset = self.plain.len();
254 self.plain.push_str(&text.plain);
255
256 for span in &text.spans {
258 let mut s = span.clone();
259 s.start += offset;
260 s.end += offset;
261 self.spans.push(s);
262 }
263
264 if let Some(st) = style {
266 self.spans.push(Span::new(
267 offset,
268 offset + text.plain.len(),
269 st,
270 ));
271 }
272 }
273
274 pub fn append_tokens(&mut self, tokens: Vec<(String, Style)>) {
276 for (text, style) in tokens {
277 self.append_styled(text, style);
278 }
279 }
280
281 pub fn append_styled(&mut self, text: impl Into<String>, style: Style) {
283 let text = text.into();
284 let offset = self.plain.len();
285 self.plain.push_str(&text);
286 self.spans.push(Span::new(offset, offset + text.len(), style));
287 }
288
289 pub fn cell_len(&self) -> usize {
291 UnicodeWidthStr::width(self.plain.as_str())
292 }
293
294 pub fn style_at(&self, position: usize) -> Style {
296 let mut style = self.style.clone();
297 for span in &self.spans {
298 if position >= span.start && position < span.end {
299 style = style.combine(&span.style);
300 }
301 }
302 style
303 }
304
305 pub fn get_style_at_offset(&self, offset: usize) -> Style {
308 self.style_at(offset)
309 }
310
311 pub fn truncate(&mut self, max_width: usize, overflow: OverflowMethod) {
313 let w = self.cell_len();
314 if w <= max_width {
315 return;
316 }
317
318 match overflow {
319 OverflowMethod::Ellipsis => {
320 let ellipsis = "\u{2026}";
321 let ellip_w = UnicodeWidthStr::width(ellipsis);
322 if max_width <= ellip_w {
323 self.plain = ellipsis[..max_width].to_string();
324 self.spans.clear();
325 return;
326 }
327 let target = max_width - ellip_w;
329 let mut byte_pos = 0usize;
330 let mut w_count = 0usize;
331 for (i, ch) in self.plain.char_indices() {
332 let cw = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0);
333 if w_count + cw > target {
334 break;
335 }
336 w_count += cw;
337 byte_pos = i + ch.len_utf8();
338 }
339 self.plain.truncate(byte_pos);
340 self.plain.push_str(ellipsis);
341 let crop_at = byte_pos;
343 self.spans.retain(|s| s.start < crop_at);
344 for s in &mut self.spans {
345 if s.end > crop_at {
346 s.end = crop_at;
347 }
348 }
349 }
350 OverflowMethod::Crop => {
351 let mut w_count = 0usize;
353 let mut byte_pos = 0usize;
354 for (i, ch) in self.plain.char_indices() {
355 let cw = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0);
356 if w_count + cw > max_width {
357 break;
358 }
359 w_count += cw;
360 byte_pos = i + ch.len_utf8();
361 }
362 self.plain.truncate(byte_pos);
363 let crop_at = byte_pos;
364 self.spans.retain(|s| s.start < crop_at);
365 for s in &mut self.spans {
366 if s.end > crop_at {
367 s.end = crop_at;
368 }
369 }
370 }
371 _ => {} }
373 }
374
375 pub fn expand_tabs(&mut self) {
377 let tab_width = self.tab_size;
378 let mut result = String::new();
379 let mut col = 0usize;
380 for ch in self.plain.chars() {
381 if ch == '\t' {
382 let spaces = tab_width - (col % tab_width);
383 result.push_str(&" ".repeat(spaces));
384 col += spaces;
385 } else {
386 result.push(ch);
387 col += unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0);
388 }
389 }
390 self.plain = result;
391 }
392
393 pub fn split_lines(&self) -> Vec<Text> {
395 self.plain
396 .split('\n')
397 .map(|line| Text::new(line.to_string()))
398 .collect()
399 }
400
401 pub fn stylize(&mut self, style: Style, start: usize, end: Option<usize>) {
404 let end = end.unwrap_or(self.plain.len());
405 if start < end && start < self.plain.len() {
406 self.spans.push(Span::new(start, end, style));
407 }
408 }
409
410 pub fn stylize_before(&mut self, style: Style, start: usize, end: Option<usize>) {
413 let end = end.unwrap_or(self.plain.len());
414 if start < end && start < self.plain.len() {
415 self.spans.insert(0, Span::new(start, end, style));
416 }
417 }
418
419 pub fn apply_meta(&mut self, meta: Vec<u8>, spans: &[Span]) {
422 for span in spans {
423 let start = span.start;
424 let end = span.end;
425 for existing in &mut self.spans {
427 if existing.start < end && existing.end > start {
428 existing.style.meta = Some(meta.clone());
429 }
430 }
431 self.spans.push(Span::new(start, end, {
433 let mut s = Style::new();
434 s.meta = Some(meta.clone());
435 s
436 }));
437 }
438 }
439
440 pub fn highlight_regex(&mut self, pattern: &str, style: Style) -> usize {
443 let re = regex::Regex::new(pattern);
444 let re = match re {
445 Ok(r) => r,
446 Err(_) => return 0,
447 };
448
449 let mut count = 0usize;
450 let matches: Vec<(usize, usize)> = re
452 .find_iter(&self.plain)
453 .map(|m| (m.start(), m.end()))
454 .collect();
455
456 for (start, end) in matches {
457 self.spans.push(Span::new(start, end, style.clone()));
458 count += 1;
459 }
460 count
461 }
462
463 pub fn blank_copy(&self) -> Self {
465 Self {
466 plain: String::new(),
467 spans: Vec::new(),
468 style: self.style.clone(),
469 justify: self.justify,
470 end: self.end.clone(),
471 overflow: self.overflow,
472 no_wrap: self.no_wrap,
473 tab_size: self.tab_size,
474 indent_guides: self.indent_guides,
475 }
476 }
477
478 pub fn copy_styles(&self) -> Self {
480 Self::new(self.plain.clone())
481 }
482
483 pub fn detect_indentation(&self) -> (String, usize) {
487 let trimmed_start = self.plain.len() - self.plain.trim_start().len();
488 if trimmed_start == 0 {
489 return (String::new(), 0);
490 }
491 let indent_str = self.plain[..trimmed_start].to_string();
492 let first_char = indent_str.chars().next().unwrap_or(' ');
493 let indent_count = indent_str.chars().filter(|&c| c == first_char).count();
494 (indent_str, indent_count)
495 }
496
497 pub fn divide(&self, offsets: &[usize]) -> Vec<Text> {
500 let mut result: Vec<Text> = Vec::new();
501 let mut prev = 0usize;
502 for &offset in offsets {
503 if offset <= prev || offset > self.plain.len() {
504 continue;
505 }
506 result.push(self.slice(prev, offset));
507 prev = offset;
508 }
509 if prev < self.plain.len() {
510 result.push(self.slice(prev, self.plain.len()));
511 }
512 result
513 }
514
515 fn slice(&self, start: usize, end: usize) -> Text {
517 let piece = self.plain[start..end].to_string();
518 let mut text = Text::new(piece);
519 text.style = self.style.clone();
520 for span in &self.spans {
521 if span.start < end && span.end > start {
522 let s_start = if span.start > start { span.start - start } else { 0 };
523 let s_end = if span.end < end { span.end - start } else { end - start };
524 if s_start < s_end {
525 text.spans.push(Span::new(s_start, s_end, span.style.clone()));
526 }
527 }
528 }
529 text
530 }
531
532 pub fn extend_style(&mut self, style: Style) {
534 if !self.plain.is_empty() {
535 self.spans.push(Span::new(0, self.plain.len(), style));
536 }
537 }
538
539 pub fn fit(&self, width: usize) -> Text {
542 let mut copy = self.clone();
543 let cell_len = copy.cell_len();
544 if cell_len > width {
545 copy.truncate(width, OverflowMethod::Crop);
546 } else if cell_len < width {
547 copy.align(AlignMethod::Left, width);
548 }
549 copy
550 }
551
552 pub fn set_length(&mut self, length: usize) {
554 let cell_len = self.cell_len();
555 if cell_len > length {
556 self.truncate(length, OverflowMethod::Crop);
557 } else if cell_len < length {
558 self.pad_right(length - cell_len, ' ');
559 }
560 }
561
562 pub fn remove_suffix(&mut self, suffix: &str) -> bool {
564 if self.plain.ends_with(suffix) {
565 let end = self.plain.len() - suffix.len();
566 self.plain.truncate(end);
567 self.spans.retain(|s| s.start < end);
568 for s in &mut self.spans {
569 if s.end > end {
570 s.end = end;
571 }
572 }
573 true
574 } else {
575 false
576 }
577 }
578
579 pub fn rstrip_end(&mut self, end: &str) -> bool {
581 self.remove_suffix(end)
582 }
583
584 pub fn right_crop(&mut self, offset: usize) -> Text {
586 if offset >= self.plain.len() {
587 return Text::new("");
589 }
590
591 let cropped_text = self.plain[offset..].to_string();
592 let mut cropped = Text::new(&*cropped_text);
593 cropped.style = self.style.clone();
594
595 self.plain.truncate(offset);
596
597 let mut kept_spans: Vec<Span> = Vec::new();
599 let mut cropped_spans: Vec<Span> = Vec::new();
600 for span in &self.spans {
601 if span.start < offset {
602 let mut s = span.clone();
603 if s.end > offset {
604 cropped_spans.push(Span::new(0, s.end - offset, span.style.clone()));
606 s.end = offset;
607 }
608 kept_spans.push(s);
609 } else {
610 cropped_spans.push(Span::new(
612 span.start - offset,
613 span.end - offset,
614 span.style.clone(),
615 ));
616 }
617 }
618 self.spans = kept_spans;
619 cropped.spans = cropped_spans;
620 cropped
621 }
622
623 pub fn rstrip(&mut self) -> &mut Self {
625 let trimmed_end = self.plain.len() - self.plain.trim_end().len();
626 if trimmed_end > 0 {
627 let new_len = self.plain.len() - trimmed_end;
628 self.plain.truncate(new_len);
629 self.spans.retain(|s| s.start < new_len);
630 for s in &mut self.spans {
631 if s.end > new_len {
632 s.end = new_len;
633 }
634 }
635 }
636 self
637 }
638
639 pub fn split(&self) -> Vec<Text> {
641 let mut result: Vec<Text> = Vec::new();
642 let mut byte_pos = 0usize;
643 for ch in self.plain.chars() {
644 let ch_len = ch.len_utf8();
645 let ch_str = &self.plain[byte_pos..byte_pos + ch_len];
646 let mut text = Text::new(ch_str.to_string());
647 text.style = self.style_at(byte_pos);
648 result.push(text);
649 byte_pos += ch_len;
650 }
651 result
652 }
653
654 pub fn wrap(&self, width: usize) -> Vec<Text> {
658 let mut lines: Vec<Text> = Vec::new();
659 let mut current = Text::new("");
660
661 for word in self.plain.split_whitespace() {
662 let word_w = unicode_width::UnicodeWidthStr::width(word);
663 let cur_w = current.cell_len();
664
665 if cur_w == 0 {
666 current = Text::new(word);
668 } else if cur_w + 1 + word_w <= width {
669 current.plain.push(' ');
671 current.plain.push_str(word);
672 } else {
673 if !current.plain.is_empty() {
675 lines.push(current);
676 }
677 current = Text::new(word);
678 }
679 }
680
681 if !current.plain.is_empty() {
682 lines.push(current);
683 }
684
685 lines
686 }
687
688 pub fn render(&self) -> String {
690 if self.spans.is_empty() && self.style.is_plain() {
692 return self.plain.clone();
693 }
694
695 let mut out = String::new();
696 let chars: Vec<(usize, char)> = self.plain.char_indices().collect();
697 let default_ansi = self.style.to_ansi();
698 let reset = if default_ansi.is_empty() { "" } else { "\x1b[0m" };
699
700 if !default_ansi.is_empty() {
701 out.push_str(&default_ansi);
702 }
703
704 for (byte_pos, ch) in &chars {
705 let mut applied = String::new();
707 for span in &self.spans {
708 if span.start == *byte_pos {
709 applied.push_str(&span.style.to_ansi());
710 }
711 }
712 out.push_str(&applied);
713
714 out.push(*ch);
716
717 let char_end = byte_pos + ch.len_utf8();
719 let mut ended = false;
720 for span in &self.spans {
721 if span.end == char_end {
722 out.push_str("\x1b[0m");
723 ended = true;
724 }
725 }
726 if ended && !default_ansi.is_empty() {
728 out.push_str(&default_ansi);
729 }
730 }
731
732 if !reset.is_empty() {
733 out.push_str(reset);
734 }
735
736 out
737 }
738
739 pub fn markup(&self) -> String {
742 if self.spans.is_empty() {
743 return crate::markup::escape(&self.plain);
744 }
745
746 let mut sorted: Vec<&Span> = self.spans.iter().collect();
747 sorted.sort_by_key(|s| (s.start, s.end));
748
749 let mut result = String::new();
750 let mut pos = 0usize;
751
752 for span in &sorted {
753 if span.start > pos {
754 result.push_str(&crate::markup::escape(&self.plain[pos..span.start]));
755 }
756 if span.start < span.end {
757 let style_str = span.style.to_string();
758 if style_str != "none" && !style_str.is_empty() {
759 result.push_str(&format!("[{}]", style_str));
760 result.push_str(&crate::markup::escape(&self.plain[span.start..span.end]));
761 result.push_str("[/]");
762 } else {
763 result.push_str(&crate::markup::escape(&self.plain[span.start..span.end]));
764 }
765 }
766 pos = pos.max(span.end);
767 }
768
769 if pos < self.plain.len() {
770 result.push_str(&crate::markup::escape(&self.plain[pos..]));
771 }
772
773 result
774 }
775
776 pub fn pad(&mut self, count: usize, character: char) {
778 self.plain = format!(
779 "{}{}{}",
780 character.to_string().repeat(count),
781 self.plain,
782 character.to_string().repeat(count)
783 );
784 for span in &mut self.spans {
786 span.start += count;
787 span.end += count;
788 }
789 }
790
791 pub fn pad_left(&mut self, count: usize, character: char) {
793 self.plain = format!("{}{}", character.to_string().repeat(count), self.plain);
794 for span in &mut self.spans {
795 span.start += count;
796 span.end += count;
797 }
798 }
799
800 pub fn pad_right(&mut self, count: usize, character: char) {
802 self.plain = format!("{}{}", self.plain, character.to_string().repeat(count));
803 }
804
805 pub fn align(&mut self, method: AlignMethod, width: usize) {
807 let current = self.cell_len();
808 if current >= width {
809 return;
810 }
811 let padding = width - current;
812 match method {
813 AlignMethod::Left => self.pad_right(padding, ' '),
814 AlignMethod::Right => self.pad_left(padding, ' '),
815 AlignMethod::Center => {
816 let left = padding / 2;
817 self.pad_left(left, ' ');
818 self.pad_right(padding - left, ' ');
819 }
820 AlignMethod::Full => {} }
822 }
823}
824
825impl Default for Text {
826 fn default() -> Self {
827 Self::new("")
828 }
829}
830
831impl fmt::Display for Text {
832 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
833 write!(f, "{}", self.render())
834 }
835}
836
837impl From<&str> for Text {
838 fn from(s: &str) -> Self {
839 Self::new(s)
840 }
841}
842
843impl From<String> for Text {
844 fn from(s: String) -> Self {
845 Self::new(s)
846 }
847}
848
849#[derive(Debug, Clone)]
855pub enum TextType {
856 Plain(String),
857 Rich(Text),
858}
859
860impl TextType {
861 pub fn render(&self) -> String {
862 match self {
863 Self::Plain(s) => s.clone(),
864 Self::Rich(t) => t.render(),
865 }
866 }
867}
868
869impl From<&str> for TextType {
870 fn from(s: &str) -> Self {
871 Self::Plain(s.to_string())
872 }
873}
874
875impl From<String> for TextType {
876 fn from(s: String) -> Self {
877 Self::Plain(s)
878 }
879}
880
881impl From<Text> for TextType {
882 fn from(t: Text) -> Self {
883 Self::Rich(t)
884 }
885}
886
887impl fmt::Display for TextType {
888 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
889 match self {
890 Self::Plain(s) => write!(f, "{s}"),
891 Self::Rich(t) => write!(f, "{t}"),
892 }
893 }
894}
895
896fn apply_sgr(style: &mut Style, params: &str) {
903 if params.is_empty() || params == "0" {
904 *style = Style::new();
906 return;
907 }
908
909 let parts: Vec<&str> = params.split(';').collect();
910 let mut i = 0usize;
911 while i < parts.len() {
912 match parts[i] {
913 "1" => { *style = style.clone().bold(true); }
914 "2" => { *style = style.clone().dim(true); }
915 "3" => { *style = style.clone().italic(true); }
916 "4" => { *style = style.clone().underline(true); }
917 "5" => { *style = style.clone().blink(true); }
918 "6" => { *style = style.clone().blink2(true); }
919 "7" => { *style = style.clone().reverse(true); }
920 "8" => { *style = style.clone().conceal(true); }
921 "9" => { *style = style.clone().strike(true); }
922 "21" => { *style = style.clone().underline2(true); }
923 "22" => { *style = style.clone().bold(false).dim(false); }
924 "23" => { *style = style.clone().italic(false); }
925 "24" => { *style = style.clone().underline(false); }
926 "25" => { *style = style.clone().blink(false).blink2(false); }
927 "27" => { *style = style.clone().reverse(false); }
928 "28" => { *style = style.clone().conceal(false); }
929 "29" => { *style = style.clone().strike(false); }
930 "51" => { *style = style.clone().frame(true); }
931 "52" => { *style = style.clone().encircle(true); }
932 "53" => { *style = style.clone().overline(true); }
933 "54" => { *style = style.clone().frame(false).encircle(false); }
934 "55" => { *style = style.clone().overline(false); }
935 "38" => {
936 if i + 1 < parts.len() {
938 match parts[i + 1] {
939 "5" => {
940 if i + 2 < parts.len() {
941 if let Ok(n) = parts[i + 2].parse::<u8>() {
942 let c = crate::color::Color::from_8bit(n);
943 *style = style.clone().color(c);
944 }
945 i += 2;
946 }
947 }
948 "2" => {
949 if i + 4 < parts.len() {
950 let r = parts[i + 2].parse::<u8>().unwrap_or(0);
951 let g = parts[i + 3].parse::<u8>().unwrap_or(0);
952 let b = parts[i + 4].parse::<u8>().unwrap_or(0);
953 *style = style.clone().color(crate::color::Color::from_rgb(r, g, b));
954 i += 4;
955 }
956 }
957 _ => {}
958 }
959 }
960 }
961 "48" => {
962 if i + 1 < parts.len() {
964 match parts[i + 1] {
965 "5" => {
966 if i + 2 < parts.len() {
967 if let Ok(n) = parts[i + 2].parse::<u8>() {
968 let c = crate::color::Color::from_8bit(n);
969 *style = style.clone().bgcolor(c);
970 }
971 i += 2;
972 }
973 }
974 "2" => {
975 if i + 4 < parts.len() {
976 let r = parts[i + 2].parse::<u8>().unwrap_or(0);
977 let g = parts[i + 3].parse::<u8>().unwrap_or(0);
978 let b = parts[i + 4].parse::<u8>().unwrap_or(0);
979 *style = style.clone().bgcolor(crate::color::Color::from_rgb(r, g, b));
980 i += 4;
981 }
982 }
983 _ => {}
984 }
985 }
986 }
987 "39" => { style.color = None; }
988 "49" => { style.bgcolor = None; }
989 n => {
990 if let Ok(num) = n.parse::<u8>() {
992 match num {
993 30..=37 => {
994 let c = crate::color::Color::from_8bit(num - 30);
995 *style = style.clone().color(c);
996 }
997 40..=47 => {
998 let c = crate::color::Color::from_8bit(num - 40);
999 *style = style.clone().bgcolor(c);
1000 }
1001 90..=97 => {
1002 let c = crate::color::Color::from_8bit(num - 82);
1003 *style = style.clone().color(c);
1004 }
1005 100..=107 => {
1006 let c = crate::color::Color::from_8bit(num - 92);
1007 *style = style.clone().bgcolor(c);
1008 }
1009 _ => {}
1010 }
1011 }
1012 }
1013 }
1014 i += 1;
1015 }
1016}
1017
1018#[cfg(test)]
1019mod tests {
1020 use super::*;
1021 use crate::color::Color;
1022
1023 #[test]
1024 fn test_text_append() {
1025 let mut t = Text::new("Hello");
1026 t.append_styled(" World", Style::new().bold(true));
1027 assert_eq!(t.plain, "Hello World");
1028 assert_eq!(t.spans.len(), 1);
1029 assert_eq!(t.spans[0].start, 5);
1030 assert_eq!(t.spans[0].end, 11);
1031 }
1032
1033 #[test]
1034 fn test_text_truncate() {
1035 let mut t = Text::new("Hello World");
1036 t.truncate(5, OverflowMethod::Ellipsis);
1037 assert!(t.plain.contains('\u{2026}'));
1038 }
1039
1040 #[test]
1041 fn test_styled_constructor() {
1042 let style = Style::new().bold(true).color(Color::parse("red").unwrap());
1043 let t = Text::styled(style.clone());
1044 assert_eq!(t.plain, "");
1045 assert_eq!(t.style, style);
1046 assert!(t.spans.is_empty());
1047 }
1048
1049 #[test]
1050 fn test_from_ansi_empty() {
1051 let t = Text::from_ansi("");
1052 assert_eq!(t.plain, "");
1053 assert!(t.spans.is_empty());
1054 }
1055
1056 #[test]
1057 fn test_from_ansi_no_escapes() {
1058 let t = Text::from_ansi("hello world");
1059 assert_eq!(t.plain, "hello world");
1060 assert!(t.spans.is_empty());
1061 }
1062
1063 #[test]
1064 fn test_from_ansi_bold() {
1065 let t = Text::from_ansi("\x1b[1mbold\x1b[0m");
1066 assert_eq!(t.plain, "bold");
1067 assert!(!t.spans.is_empty());
1068 }
1069
1070 #[test]
1071 fn test_style_getter() {
1072 let style = Style::new().bold(true);
1073 let t = Text::styled(style.clone());
1074 assert_eq!(t.get_style(), &style);
1075 }
1076
1077 #[test]
1078 fn test_style_mut_getter() {
1079 let mut t = Text::new("hello");
1080 t.style = Style::new().bold(true);
1081 assert_eq!(t.get_style().get_bold(), Some(true));
1082 }
1083
1084 #[test]
1085 fn test_spans_getter() {
1086 let mut t = Text::new("hello");
1087 t.stylize(Style::new().bold(true), 0, Some(3));
1088 assert_eq!(t.spans().len(), 1);
1089 }
1090
1091 #[test]
1092 fn test_from_markup() {
1093 let t = Text::from_markup("[bold]hello[/bold]");
1094 assert_eq!(t.plain, "hello");
1095 assert!(!t.spans.is_empty());
1096 }
1097
1098 #[test]
1099 fn test_append_tokens() {
1100 let mut t = Text::new("");
1101 let tokens = vec![
1102 ("Hello ".to_string(), Style::new().bold(true)),
1103 ("World".to_string(), Style::new().italic(true)),
1104 ];
1105 t.append_tokens(tokens);
1106 assert_eq!(t.plain, "Hello World");
1107 assert_eq!(t.spans.len(), 2);
1108 }
1109
1110 #[test]
1111 fn test_stylize_before() {
1112 let mut t = Text::new("hello");
1113 t.stylize(Style::new().bold(true), 0, Some(5));
1114 t.stylize_before(Style::new().italic(true), 0, Some(5));
1115 assert_eq!(t.spans.len(), 2);
1117 assert_eq!(t.spans[0].style.get_italic(), Some(true));
1118 }
1119
1120 #[test]
1121 fn test_blank_copy() {
1122 let mut t = Text::new("hello");
1123 t.stylize(Style::new().bold(true), 0, Some(3));
1124 t.justify = JustifyMethod::Center;
1125 let blank = t.blank_copy();
1126 assert_eq!(blank.plain, "");
1127 assert!(blank.spans.is_empty());
1128 assert_eq!(blank.justify, JustifyMethod::Center);
1129 }
1130
1131 #[test]
1132 fn test_copy_styles() {
1133 let mut t = Text::new("hello");
1134 t.stylize(Style::new().bold(true), 0, Some(3));
1135 let copy = t.copy_styles();
1136 assert_eq!(copy.plain, "hello");
1137 assert!(copy.spans.is_empty());
1138 }
1139
1140 #[test]
1141 fn test_detect_indentation() {
1142 let t = Text::new(" hello");
1143 let (indent, count) = t.detect_indentation();
1144 assert_eq!(indent, " ");
1145 assert_eq!(count, 2);
1146 }
1147
1148 #[test]
1149 fn test_detect_indentation_none() {
1150 let t = Text::new("hello");
1151 let (indent, count) = t.detect_indentation();
1152 assert_eq!(indent, "");
1153 assert_eq!(count, 0);
1154 }
1155
1156 #[test]
1157 fn test_get_style_at_offset() {
1158 let mut t = Text::new("hello world");
1159 t.stylize(Style::new().bold(true), 0, Some(5));
1160 let s0 = t.get_style_at_offset(0);
1161 assert_eq!(s0.get_bold(), Some(true));
1162 let s6 = t.get_style_at_offset(6);
1163 assert_eq!(s6.get_bold(), None); }
1165
1166 #[test]
1167 fn test_divide() {
1168 let mut t = Text::new("abcdef");
1169 t.stylize(Style::new().bold(true), 0, Some(3));
1170 let parts = t.divide(&[2, 4]);
1171 assert_eq!(parts.len(), 3);
1172 assert_eq!(parts[0].plain, "ab");
1173 assert_eq!(parts[1].plain, "cd");
1174 assert_eq!(parts[2].plain, "ef");
1175 }
1176
1177 #[test]
1178 fn test_extend_style() {
1179 let mut t = Text::new("hello");
1180 t.extend_style(Style::new().bold(true));
1181 assert_eq!(t.spans.len(), 1);
1182 assert_eq!(t.spans[0].start, 0);
1183 assert_eq!(t.spans[0].end, 5);
1184 }
1185
1186 #[test]
1187 fn test_fit_truncate() {
1188 let t = Text::new("hello world");
1189 let fitted = t.fit(5);
1190 assert_eq!(fitted.cell_len(), 5);
1191 }
1192
1193 #[test]
1194 fn test_fit_pad() {
1195 let t = Text::new("hi");
1196 let fitted = t.fit(10);
1197 assert_eq!(fitted.cell_len(), 10);
1198 }
1199
1200 #[test]
1201 fn test_set_length_truncate() {
1202 let mut t = Text::new("hello world");
1203 t.set_length(5);
1204 assert_eq!(t.cell_len(), 5);
1205 }
1206
1207 #[test]
1208 fn test_set_length_pad() {
1209 let mut t = Text::new("hi");
1210 t.set_length(10);
1211 assert_eq!(t.cell_len(), 10);
1212 }
1213
1214 #[test]
1215 fn test_remove_suffix() {
1216 let mut t = Text::new("hello.txt");
1217 assert!(t.remove_suffix(".txt"));
1218 assert_eq!(t.plain, "hello");
1219 }
1220
1221 #[test]
1222 fn test_remove_suffix_not_found() {
1223 let mut t = Text::new("hello");
1224 assert!(!t.remove_suffix(".txt"));
1225 assert_eq!(t.plain, "hello");
1226 }
1227
1228 #[test]
1229 fn test_right_crop() {
1230 let mut t = Text::new("hello world");
1231 let cropped = t.right_crop(5);
1232 assert_eq!(t.plain, "hello");
1233 assert_eq!(cropped.plain, " world");
1234 }
1235
1236 #[test]
1237 fn test_rstrip() {
1238 let mut t = Text::new("hello ");
1239 t.rstrip();
1240 assert_eq!(t.plain, "hello");
1241 }
1242
1243 #[test]
1244 fn test_rstrip_end() {
1245 let mut t = Text::new("hello\n");
1246 assert!(t.rstrip_end("\n"));
1247 assert_eq!(t.plain, "hello");
1248 }
1249
1250 #[test]
1251 fn test_split_chars() {
1252 let t = Text::new("abc");
1253 let chars = t.split();
1254 assert_eq!(chars.len(), 3);
1255 assert_eq!(chars[0].plain, "a");
1256 assert_eq!(chars[1].plain, "b");
1257 assert_eq!(chars[2].plain, "c");
1258 }
1259
1260 #[test]
1261 fn test_markup() {
1262 let mut t = Text::new("hello world");
1263 t.stylize(Style::new().bold(true), 0, Some(5));
1264 let markup = t.markup();
1265 assert!(markup.contains("[bold]"));
1266 assert!(markup.contains("[/]"));
1267 assert!(markup.contains("hello"));
1268 assert!(markup.contains(" world"));
1269 }
1270
1271 #[test]
1272 fn test_markup_no_spans() {
1273 let t = Text::new("hello");
1274 assert_eq!(t.markup(), "hello");
1275 }
1276
1277 #[test]
1278 fn test_no_wrap_builder() {
1279 let t = Text::new("hello").no_wrap(true);
1280 assert!(t.no_wrap);
1281 }
1282
1283 #[test]
1284 fn test_tab_size_builder() {
1285 let t = Text::new("hello").tab_size(4);
1286 assert_eq!(t.tab_size, 4);
1287 }
1288
1289 #[test]
1290 fn test_with_indent_guides_builder() {
1291 let t = Text::new("hello").with_indent_guides(true);
1292 assert!(t.indent_guides);
1293 }
1294
1295 #[test]
1296 fn test_expand_tabs_with_custom_size() {
1297 let mut t = Text::new("\thello").tab_size(4);
1298 t.expand_tabs();
1299 assert_eq!(t.plain, " hello");
1300 }
1301
1302 #[test]
1303 fn test_apply_meta() {
1304 let mut t = Text::new("hello world");
1305 let meta = vec![1u8, 2u8, 3u8];
1306 let spans = vec![Span::new(0, 5, Style::new())];
1307 t.apply_meta(meta, &spans);
1308 assert!(t.spans.iter().any(|s| s.style.meta.is_some()));
1310 }
1311
1312 #[test]
1313 fn test_default_impl() {
1314 let t: Text = Default::default();
1315 assert_eq!(t.plain, "");
1316 assert_eq!(t.tab_size, 8);
1317 }
1318}