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
267 .push(Span::new(offset, offset + text.plain.len(), st));
268 }
269 }
270
271 pub fn append_tokens(&mut self, tokens: Vec<(String, Style)>) {
273 for (text, style) in tokens {
274 self.append_styled(text, style);
275 }
276 }
277
278 pub fn append_styled(&mut self, text: impl Into<String>, style: Style) {
280 let text = text.into();
281 let offset = self.plain.len();
282 self.plain.push_str(&text);
283 self.spans
284 .push(Span::new(offset, offset + text.len(), style));
285 }
286
287 pub fn cell_len(&self) -> usize {
289 UnicodeWidthStr::width(self.plain.as_str())
290 }
291
292 pub fn style_at(&self, position: usize) -> Style {
294 let mut style = self.style.clone();
295 for span in &self.spans {
296 if position >= span.start && position < span.end {
297 style = style.combine(&span.style);
298 }
299 }
300 style
301 }
302
303 pub fn get_style_at_offset(&self, offset: usize) -> Style {
306 self.style_at(offset)
307 }
308
309 pub fn truncate(&mut self, max_width: usize, overflow: OverflowMethod) {
311 let w = self.cell_len();
312 if w <= max_width {
313 return;
314 }
315
316 match overflow {
317 OverflowMethod::Ellipsis => {
318 let ellipsis = "\u{2026}";
319 let ellip_w = UnicodeWidthStr::width(ellipsis);
320 if max_width <= ellip_w {
321 self.plain = ellipsis[..max_width].to_string();
322 self.spans.clear();
323 return;
324 }
325 let target = max_width - ellip_w;
327 let mut byte_pos = 0usize;
328 let mut w_count = 0usize;
329 for (i, ch) in self.plain.char_indices() {
330 let cw = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0);
331 if w_count + cw > target {
332 break;
333 }
334 w_count += cw;
335 byte_pos = i + ch.len_utf8();
336 }
337 self.plain.truncate(byte_pos);
338 self.plain.push_str(ellipsis);
339 let crop_at = byte_pos;
341 self.spans.retain(|s| s.start < crop_at);
342 for s in &mut self.spans {
343 if s.end > crop_at {
344 s.end = crop_at;
345 }
346 }
347 }
348 OverflowMethod::Crop => {
349 let mut w_count = 0usize;
351 let mut byte_pos = 0usize;
352 for (i, ch) in self.plain.char_indices() {
353 let cw = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0);
354 if w_count + cw > max_width {
355 break;
356 }
357 w_count += cw;
358 byte_pos = i + ch.len_utf8();
359 }
360 self.plain.truncate(byte_pos);
361 let crop_at = byte_pos;
362 self.spans.retain(|s| s.start < crop_at);
363 for s in &mut self.spans {
364 if s.end > crop_at {
365 s.end = crop_at;
366 }
367 }
368 }
369 _ => {} }
371 }
372
373 pub fn expand_tabs(&mut self) {
375 let tab_width = self.tab_size;
376 let mut result = String::new();
377 let mut col = 0usize;
378 for ch in self.plain.chars() {
379 if ch == '\t' {
380 let spaces = tab_width - (col % tab_width);
381 result.push_str(&" ".repeat(spaces));
382 col += spaces;
383 } else {
384 result.push(ch);
385 col += unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0);
386 }
387 }
388 self.plain = result;
389 }
390
391 pub fn split_lines(&self) -> Vec<Text> {
393 self.plain
394 .split('\n')
395 .map(|line| Text::new(line.to_string()))
396 .collect()
397 }
398
399 pub fn stylize(&mut self, style: Style, start: usize, end: Option<usize>) {
402 let end = end.unwrap_or(self.plain.len());
403 let end = end.min(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 = span.start.saturating_sub(start);
523 let s_end = (span.end.min(end)).saturating_sub(start);
524 if s_start < s_end {
525 text.spans
526 .push(Span::new(s_start, s_end, span.style.clone()));
527 }
528 }
529 }
530 text
531 }
532
533 pub fn extend_style(&mut self, style: Style) {
535 if !self.plain.is_empty() {
536 self.spans.push(Span::new(0, self.plain.len(), style));
537 }
538 }
539
540 pub fn fit(&self, width: usize) -> Text {
543 let mut copy = self.clone();
544 let cell_len = copy.cell_len();
545 if cell_len > width {
546 copy.truncate(width, OverflowMethod::Crop);
547 } else if cell_len < width {
548 copy.align(AlignMethod::Left, width);
549 }
550 copy
551 }
552
553 pub fn set_length(&mut self, length: usize) {
555 let cell_len = self.cell_len();
556 if cell_len > length {
557 self.truncate(length, OverflowMethod::Crop);
558 } else if cell_len < length {
559 self.pad_right(length - cell_len, ' ');
560 }
561 }
562
563 pub fn remove_suffix(&mut self, suffix: &str) -> bool {
565 if self.plain.ends_with(suffix) {
566 let end = self.plain.len() - suffix.len();
567 self.plain.truncate(end);
568 self.spans.retain(|s| s.start < end);
569 for s in &mut self.spans {
570 if s.end > end {
571 s.end = end;
572 }
573 }
574 true
575 } else {
576 false
577 }
578 }
579
580 pub fn rstrip_end(&mut self, end: &str) -> bool {
582 self.remove_suffix(end)
583 }
584
585 pub fn right_crop(&mut self, offset: usize) -> Text {
587 if offset >= self.plain.len() {
588 return Text::new("");
590 }
591
592 let cropped_text = self.plain[offset..].to_string();
593 let mut cropped = Text::new(&*cropped_text);
594 cropped.style = self.style.clone();
595
596 self.plain.truncate(offset);
597
598 let mut kept_spans: Vec<Span> = Vec::new();
600 let mut cropped_spans: Vec<Span> = Vec::new();
601 for span in &self.spans {
602 if span.start < offset {
603 let mut s = span.clone();
604 if s.end > offset {
605 cropped_spans.push(Span::new(0, s.end - offset, span.style.clone()));
607 s.end = offset;
608 }
609 kept_spans.push(s);
610 } else {
611 cropped_spans.push(Span::new(
613 span.start - offset,
614 span.end - offset,
615 span.style.clone(),
616 ));
617 }
618 }
619 self.spans = kept_spans;
620 cropped.spans = cropped_spans;
621 cropped
622 }
623
624 pub fn rstrip(&mut self) -> &mut Self {
626 let trimmed_end = self.plain.len() - self.plain.trim_end().len();
627 if trimmed_end > 0 {
628 let new_len = self.plain.len() - trimmed_end;
629 self.plain.truncate(new_len);
630 self.spans.retain(|s| s.start < new_len);
631 for s in &mut self.spans {
632 if s.end > new_len {
633 s.end = new_len;
634 }
635 }
636 }
637 self
638 }
639
640 pub fn split(&self) -> Vec<Text> {
642 let mut result: Vec<Text> = Vec::new();
643 let mut byte_pos = 0usize;
644 for ch in self.plain.chars() {
645 let ch_len = ch.len_utf8();
646 let ch_str = &self.plain[byte_pos..byte_pos + ch_len];
647 let mut text = Text::new(ch_str.to_string());
648 text.style = self.style_at(byte_pos);
649 result.push(text);
650 byte_pos += ch_len;
651 }
652 result
653 }
654
655 pub fn wrap(&self, width: usize) -> Vec<Text> {
659 let mut lines: Vec<Text> = Vec::new();
660 let mut current = Text::new("");
661
662 for word in self.plain.split_whitespace() {
663 let word_w = unicode_width::UnicodeWidthStr::width(word);
664 let cur_w = current.cell_len();
665
666 if cur_w == 0 {
667 current = Text::new(word);
669 } else if cur_w + 1 + word_w <= width {
670 current.plain.push(' ');
672 current.plain.push_str(word);
673 } else {
674 if !current.plain.is_empty() {
676 lines.push(current);
677 }
678 current = Text::new(word);
679 }
680 }
681
682 if !current.plain.is_empty() {
683 lines.push(current);
684 }
685
686 lines
687 }
688
689 pub fn render(&self) -> String {
691 if self.spans.is_empty() && self.style.is_plain() {
693 return self.plain.clone();
694 }
695
696 let mut out = String::new();
697 let chars: Vec<(usize, char)> = self.plain.char_indices().collect();
698 let default_ansi = self.style.to_ansi();
699 let reset = if default_ansi.is_empty() {
700 ""
701 } else {
702 "\x1b[0m"
703 };
704
705 if !default_ansi.is_empty() {
706 out.push_str(&default_ansi);
707 }
708
709 for (byte_pos, ch) in &chars {
710 let mut applied = String::new();
712 for span in &self.spans {
713 if span.start == *byte_pos {
714 applied.push_str(&span.style.to_ansi());
715 }
716 }
717 out.push_str(&applied);
718
719 out.push(*ch);
721
722 let char_end = byte_pos + ch.len_utf8();
724 let mut ended = false;
725 for span in &self.spans {
726 if span.end == char_end {
727 out.push_str("\x1b[0m");
728 ended = true;
729 }
730 }
731 if ended && !default_ansi.is_empty() {
733 out.push_str(&default_ansi);
734 }
735 }
736
737 if !reset.is_empty() {
738 out.push_str(reset);
739 }
740
741 out
742 }
743
744 pub fn markup(&self) -> String {
747 if self.spans.is_empty() {
748 return crate::markup::escape(&self.plain);
749 }
750
751 let mut sorted: Vec<&Span> = self.spans.iter().collect();
752 sorted.sort_by_key(|s| (s.start, s.end));
753
754 let mut result = String::new();
755 let mut pos = 0usize;
756
757 for span in &sorted {
758 if span.start > pos {
759 result.push_str(&crate::markup::escape(&self.plain[pos..span.start]));
760 }
761 if span.start < span.end {
762 let style_str = span.style.to_string();
763 if style_str != "none" && !style_str.is_empty() {
764 result.push_str(&format!("[{}]", style_str));
765 result.push_str(&crate::markup::escape(&self.plain[span.start..span.end]));
766 result.push_str("[/]");
767 } else {
768 result.push_str(&crate::markup::escape(&self.plain[span.start..span.end]));
769 }
770 }
771 pos = pos.max(span.end);
772 }
773
774 if pos < self.plain.len() {
775 result.push_str(&crate::markup::escape(&self.plain[pos..]));
776 }
777
778 result
779 }
780
781 pub fn pad(&mut self, count: usize, character: char) {
783 self.plain = format!(
784 "{}{}{}",
785 character.to_string().repeat(count),
786 self.plain,
787 character.to_string().repeat(count)
788 );
789 for span in &mut self.spans {
791 span.start += count;
792 span.end += count;
793 }
794 }
795
796 pub fn pad_left(&mut self, count: usize, character: char) {
798 self.plain = format!("{}{}", character.to_string().repeat(count), self.plain);
799 for span in &mut self.spans {
800 span.start += count;
801 span.end += count;
802 }
803 }
804
805 pub fn pad_right(&mut self, count: usize, character: char) {
807 self.plain = format!("{}{}", self.plain, character.to_string().repeat(count));
808 }
809
810 pub fn align(&mut self, method: AlignMethod, width: usize) {
812 let current = self.cell_len();
813 if current >= width {
814 return;
815 }
816 let padding = width - current;
817 match method {
818 AlignMethod::Left => self.pad_right(padding, ' '),
819 AlignMethod::Right => self.pad_left(padding, ' '),
820 AlignMethod::Center => {
821 let left = padding / 2;
822 self.pad_left(left, ' ');
823 self.pad_right(padding - left, ' ');
824 }
825 AlignMethod::Full => {} }
827 }
828}
829
830impl Default for Text {
831 fn default() -> Self {
832 Self::new("")
833 }
834}
835
836impl fmt::Display for Text {
837 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
838 write!(f, "{}", self.render())
839 }
840}
841
842impl From<&str> for Text {
843 fn from(s: &str) -> Self {
844 Self::new(s)
845 }
846}
847
848impl From<String> for Text {
849 fn from(s: String) -> Self {
850 Self::new(s)
851 }
852}
853
854#[derive(Debug, Clone)]
860pub enum TextType {
861 Plain(String),
862 Rich(Text),
863}
864
865impl TextType {
866 pub fn render(&self) -> String {
867 match self {
868 Self::Plain(s) => s.clone(),
869 Self::Rich(t) => t.render(),
870 }
871 }
872}
873
874impl From<&str> for TextType {
875 fn from(s: &str) -> Self {
876 Self::Plain(s.to_string())
877 }
878}
879
880impl From<String> for TextType {
881 fn from(s: String) -> Self {
882 Self::Plain(s)
883 }
884}
885
886impl From<Text> for TextType {
887 fn from(t: Text) -> Self {
888 Self::Rich(t)
889 }
890}
891
892impl fmt::Display for TextType {
893 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
894 match self {
895 Self::Plain(s) => write!(f, "{s}"),
896 Self::Rich(t) => write!(f, "{t}"),
897 }
898 }
899}
900
901fn apply_sgr(style: &mut Style, params: &str) {
908 if params.is_empty() || params == "0" {
909 *style = Style::new();
911 return;
912 }
913
914 let parts: Vec<&str> = params.split(';').collect();
915 let mut i = 0usize;
916 while i < parts.len() {
917 match parts[i] {
918 "1" => {
919 *style = style.clone().bold(true);
920 }
921 "2" => {
922 *style = style.clone().dim(true);
923 }
924 "3" => {
925 *style = style.clone().italic(true);
926 }
927 "4" => {
928 *style = style.clone().underline(true);
929 }
930 "5" => {
931 *style = style.clone().blink(true);
932 }
933 "6" => {
934 *style = style.clone().blink2(true);
935 }
936 "7" => {
937 *style = style.clone().reverse(true);
938 }
939 "8" => {
940 *style = style.clone().conceal(true);
941 }
942 "9" => {
943 *style = style.clone().strike(true);
944 }
945 "21" => {
946 *style = style.clone().underline2(true);
947 }
948 "22" => {
949 *style = style.clone().bold(false).dim(false);
950 }
951 "23" => {
952 *style = style.clone().italic(false);
953 }
954 "24" => {
955 *style = style.clone().underline(false);
956 }
957 "25" => {
958 *style = style.clone().blink(false).blink2(false);
959 }
960 "27" => {
961 *style = style.clone().reverse(false);
962 }
963 "28" => {
964 *style = style.clone().conceal(false);
965 }
966 "29" => {
967 *style = style.clone().strike(false);
968 }
969 "51" => {
970 *style = style.clone().frame(true);
971 }
972 "52" => {
973 *style = style.clone().encircle(true);
974 }
975 "53" => {
976 *style = style.clone().overline(true);
977 }
978 "54" => {
979 *style = style.clone().frame(false).encircle(false);
980 }
981 "55" => {
982 *style = style.clone().overline(false);
983 }
984 "38" => {
985 if i + 1 < parts.len() {
987 match parts[i + 1] {
988 "5" => {
989 if i + 2 < parts.len() {
990 if let Ok(n) = parts[i + 2].parse::<u8>() {
991 let c = crate::color::Color::from_8bit(n);
992 *style = style.clone().color(c);
993 }
994 i += 2;
995 }
996 }
997 "2" if i + 4 < parts.len() => {
998 let r = parts[i + 2].parse::<u8>().unwrap_or(0);
999 let g = parts[i + 3].parse::<u8>().unwrap_or(0);
1000 let b = parts[i + 4].parse::<u8>().unwrap_or(0);
1001 *style = style.clone().color(crate::color::Color::from_rgb(r, g, b));
1002 i += 4;
1003 }
1004 "2" => {}
1005 _ => {}
1006 }
1007 }
1008 }
1009 "48" => {
1010 if i + 1 < parts.len() {
1012 match parts[i + 1] {
1013 "5" => {
1014 if i + 2 < parts.len() {
1015 if let Ok(n) = parts[i + 2].parse::<u8>() {
1016 let c = crate::color::Color::from_8bit(n);
1017 *style = style.clone().bgcolor(c);
1018 }
1019 i += 2;
1020 }
1021 }
1022 "2" if i + 4 < parts.len() => {
1023 let r = parts[i + 2].parse::<u8>().unwrap_or(0);
1024 let g = parts[i + 3].parse::<u8>().unwrap_or(0);
1025 let b = parts[i + 4].parse::<u8>().unwrap_or(0);
1026 *style = style
1027 .clone()
1028 .bgcolor(crate::color::Color::from_rgb(r, g, b));
1029 i += 4;
1030 }
1031 "2" => {}
1032 _ => {}
1033 }
1034 }
1035 }
1036 "39" => {
1037 style.color = None;
1038 }
1039 "49" => {
1040 style.bgcolor = None;
1041 }
1042 n => {
1043 if let Ok(num) = n.parse::<u8>() {
1045 match num {
1046 30..=37 => {
1047 let c = crate::color::Color::from_8bit(num - 30);
1048 *style = style.clone().color(c);
1049 }
1050 40..=47 => {
1051 let c = crate::color::Color::from_8bit(num - 40);
1052 *style = style.clone().bgcolor(c);
1053 }
1054 90..=97 => {
1055 let c = crate::color::Color::from_8bit(num - 82);
1056 *style = style.clone().color(c);
1057 }
1058 100..=107 => {
1059 let c = crate::color::Color::from_8bit(num - 92);
1060 *style = style.clone().bgcolor(c);
1061 }
1062 _ => {}
1063 }
1064 }
1065 }
1066 }
1067 i += 1;
1068 }
1069}
1070
1071#[cfg(test)]
1072mod tests {
1073 use super::*;
1074 use crate::color::Color;
1075
1076 #[test]
1077 fn test_text_append() {
1078 let mut t = Text::new("Hello");
1079 t.append_styled(" World", Style::new().bold(true));
1080 assert_eq!(t.plain, "Hello World");
1081 assert_eq!(t.spans.len(), 1);
1082 assert_eq!(t.spans[0].start, 5);
1083 assert_eq!(t.spans[0].end, 11);
1084 }
1085
1086 #[test]
1087 fn test_text_truncate() {
1088 let mut t = Text::new("Hello World");
1089 t.truncate(5, OverflowMethod::Ellipsis);
1090 assert!(t.plain.contains('\u{2026}'));
1091 }
1092
1093 #[test]
1094 fn test_styled_constructor() {
1095 let style = Style::new().bold(true).color(Color::parse("red").unwrap());
1096 let t = Text::styled(style.clone());
1097 assert_eq!(t.plain, "");
1098 assert_eq!(t.style, style);
1099 assert!(t.spans.is_empty());
1100 }
1101
1102 #[test]
1103 fn test_from_ansi_empty() {
1104 let t = Text::from_ansi("");
1105 assert_eq!(t.plain, "");
1106 assert!(t.spans.is_empty());
1107 }
1108
1109 #[test]
1110 fn test_from_ansi_no_escapes() {
1111 let t = Text::from_ansi("hello world");
1112 assert_eq!(t.plain, "hello world");
1113 assert!(t.spans.is_empty());
1114 }
1115
1116 #[test]
1117 fn test_from_ansi_bold() {
1118 let t = Text::from_ansi("\x1b[1mbold\x1b[0m");
1119 assert_eq!(t.plain, "bold");
1120 assert!(!t.spans.is_empty());
1121 }
1122
1123 #[test]
1124 fn test_style_getter() {
1125 let style = Style::new().bold(true);
1126 let t = Text::styled(style.clone());
1127 assert_eq!(t.get_style(), &style);
1128 }
1129
1130 #[test]
1131 fn test_style_mut_getter() {
1132 let mut t = Text::new("hello");
1133 t.style = Style::new().bold(true);
1134 assert_eq!(t.get_style().get_bold(), Some(true));
1135 }
1136
1137 #[test]
1138 fn test_spans_getter() {
1139 let mut t = Text::new("hello");
1140 t.stylize(Style::new().bold(true), 0, Some(3));
1141 assert_eq!(t.spans().len(), 1);
1142 }
1143
1144 #[test]
1145 fn test_from_markup() {
1146 let t = Text::from_markup("[bold]hello[/bold]");
1147 assert_eq!(t.plain, "hello");
1148 assert!(!t.spans.is_empty());
1149 }
1150
1151 #[test]
1152 fn test_append_tokens() {
1153 let mut t = Text::new("");
1154 let tokens = vec![
1155 ("Hello ".to_string(), Style::new().bold(true)),
1156 ("World".to_string(), Style::new().italic(true)),
1157 ];
1158 t.append_tokens(tokens);
1159 assert_eq!(t.plain, "Hello World");
1160 assert_eq!(t.spans.len(), 2);
1161 }
1162
1163 #[test]
1164 fn test_stylize_before() {
1165 let mut t = Text::new("hello");
1166 t.stylize(Style::new().bold(true), 0, Some(5));
1167 t.stylize_before(Style::new().italic(true), 0, Some(5));
1168 assert_eq!(t.spans.len(), 2);
1170 assert_eq!(t.spans[0].style.get_italic(), Some(true));
1171 }
1172
1173 #[test]
1174 fn test_blank_copy() {
1175 let mut t = Text::new("hello");
1176 t.stylize(Style::new().bold(true), 0, Some(3));
1177 t.justify = JustifyMethod::Center;
1178 let blank = t.blank_copy();
1179 assert_eq!(blank.plain, "");
1180 assert!(blank.spans.is_empty());
1181 assert_eq!(blank.justify, JustifyMethod::Center);
1182 }
1183
1184 #[test]
1185 fn test_copy_styles() {
1186 let mut t = Text::new("hello");
1187 t.stylize(Style::new().bold(true), 0, Some(3));
1188 let copy = t.copy_styles();
1189 assert_eq!(copy.plain, "hello");
1190 assert!(copy.spans.is_empty());
1191 }
1192
1193 #[test]
1194 fn test_detect_indentation() {
1195 let t = Text::new(" hello");
1196 let (indent, count) = t.detect_indentation();
1197 assert_eq!(indent, " ");
1198 assert_eq!(count, 2);
1199 }
1200
1201 #[test]
1202 fn test_detect_indentation_none() {
1203 let t = Text::new("hello");
1204 let (indent, count) = t.detect_indentation();
1205 assert_eq!(indent, "");
1206 assert_eq!(count, 0);
1207 }
1208
1209 #[test]
1210 fn test_get_style_at_offset() {
1211 let mut t = Text::new("hello world");
1212 t.stylize(Style::new().bold(true), 0, Some(5));
1213 let s0 = t.get_style_at_offset(0);
1214 assert_eq!(s0.get_bold(), Some(true));
1215 let s6 = t.get_style_at_offset(6);
1216 assert_eq!(s6.get_bold(), None); }
1218
1219 #[test]
1220 fn test_divide() {
1221 let mut t = Text::new("abcdef");
1222 t.stylize(Style::new().bold(true), 0, Some(3));
1223 let parts = t.divide(&[2, 4]);
1224 assert_eq!(parts.len(), 3);
1225 assert_eq!(parts[0].plain, "ab");
1226 assert_eq!(parts[1].plain, "cd");
1227 assert_eq!(parts[2].plain, "ef");
1228 }
1229
1230 #[test]
1231 fn test_extend_style() {
1232 let mut t = Text::new("hello");
1233 t.extend_style(Style::new().bold(true));
1234 assert_eq!(t.spans.len(), 1);
1235 assert_eq!(t.spans[0].start, 0);
1236 assert_eq!(t.spans[0].end, 5);
1237 }
1238
1239 #[test]
1240 fn test_fit_truncate() {
1241 let t = Text::new("hello world");
1242 let fitted = t.fit(5);
1243 assert_eq!(fitted.cell_len(), 5);
1244 }
1245
1246 #[test]
1247 fn test_fit_pad() {
1248 let t = Text::new("hi");
1249 let fitted = t.fit(10);
1250 assert_eq!(fitted.cell_len(), 10);
1251 }
1252
1253 #[test]
1254 fn test_set_length_truncate() {
1255 let mut t = Text::new("hello world");
1256 t.set_length(5);
1257 assert_eq!(t.cell_len(), 5);
1258 }
1259
1260 #[test]
1261 fn test_set_length_pad() {
1262 let mut t = Text::new("hi");
1263 t.set_length(10);
1264 assert_eq!(t.cell_len(), 10);
1265 }
1266
1267 #[test]
1268 fn test_remove_suffix() {
1269 let mut t = Text::new("hello.txt");
1270 assert!(t.remove_suffix(".txt"));
1271 assert_eq!(t.plain, "hello");
1272 }
1273
1274 #[test]
1275 fn test_remove_suffix_not_found() {
1276 let mut t = Text::new("hello");
1277 assert!(!t.remove_suffix(".txt"));
1278 assert_eq!(t.plain, "hello");
1279 }
1280
1281 #[test]
1282 fn test_right_crop() {
1283 let mut t = Text::new("hello world");
1284 let cropped = t.right_crop(5);
1285 assert_eq!(t.plain, "hello");
1286 assert_eq!(cropped.plain, " world");
1287 }
1288
1289 #[test]
1290 fn test_rstrip() {
1291 let mut t = Text::new("hello ");
1292 t.rstrip();
1293 assert_eq!(t.plain, "hello");
1294 }
1295
1296 #[test]
1297 fn test_rstrip_end() {
1298 let mut t = Text::new("hello\n");
1299 assert!(t.rstrip_end("\n"));
1300 assert_eq!(t.plain, "hello");
1301 }
1302
1303 #[test]
1304 fn test_split_chars() {
1305 let t = Text::new("abc");
1306 let chars = t.split();
1307 assert_eq!(chars.len(), 3);
1308 assert_eq!(chars[0].plain, "a");
1309 assert_eq!(chars[1].plain, "b");
1310 assert_eq!(chars[2].plain, "c");
1311 }
1312
1313 #[test]
1314 fn test_markup() {
1315 let mut t = Text::new("hello world");
1316 t.stylize(Style::new().bold(true), 0, Some(5));
1317 let markup = t.markup();
1318 assert!(markup.contains("[bold]"));
1319 assert!(markup.contains("[/]"));
1320 assert!(markup.contains("hello"));
1321 assert!(markup.contains(" world"));
1322 }
1323
1324 #[test]
1325 fn test_markup_no_spans() {
1326 let t = Text::new("hello");
1327 assert_eq!(t.markup(), "hello");
1328 }
1329
1330 #[test]
1331 fn test_no_wrap_builder() {
1332 let t = Text::new("hello").no_wrap(true);
1333 assert!(t.no_wrap);
1334 }
1335
1336 #[test]
1337 fn test_tab_size_builder() {
1338 let t = Text::new("hello").tab_size(4);
1339 assert_eq!(t.tab_size, 4);
1340 }
1341
1342 #[test]
1343 fn test_with_indent_guides_builder() {
1344 let t = Text::new("hello").with_indent_guides(true);
1345 assert!(t.indent_guides);
1346 }
1347
1348 #[test]
1349 fn test_expand_tabs_with_custom_size() {
1350 let mut t = Text::new("\thello").tab_size(4);
1351 t.expand_tabs();
1352 assert_eq!(t.plain, " hello");
1353 }
1354
1355 #[test]
1356 fn test_apply_meta() {
1357 let mut t = Text::new("hello world");
1358 let meta = vec![1u8, 2u8, 3u8];
1359 let spans = vec![Span::new(0, 5, Style::new())];
1360 t.apply_meta(meta, &spans);
1361 assert!(t.spans.iter().any(|s| s.style.meta.is_some()));
1363 }
1364
1365 #[test]
1366 fn test_default_impl() {
1367 let t: Text = Default::default();
1368 assert_eq!(t.plain, "");
1369 assert_eq!(t.tab_size, 8);
1370 }
1371}