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 let end = end.min(self.plain.len());
407 if start < end && start < self.plain.len() {
408 self.spans.push(Span::new(start, end, style));
409 }
410 }
411
412 pub fn stylize_before(&mut self, style: Style, start: usize, end: Option<usize>) {
415 let end = end.unwrap_or(self.plain.len());
416 if start < end && start < self.plain.len() {
417 self.spans.insert(0, Span::new(start, end, style));
418 }
419 }
420
421 pub fn apply_meta(&mut self, meta: Vec<u8>, spans: &[Span]) {
424 for span in spans {
425 let start = span.start;
426 let end = span.end;
427 for existing in &mut self.spans {
429 if existing.start < end && existing.end > start {
430 existing.style.meta = Some(meta.clone());
431 }
432 }
433 self.spans.push(Span::new(start, end, {
435 let mut s = Style::new();
436 s.meta = Some(meta.clone());
437 s
438 }));
439 }
440 }
441
442 pub fn highlight_regex(&mut self, pattern: &str, style: Style) -> usize {
445 let re = regex::Regex::new(pattern);
446 let re = match re {
447 Ok(r) => r,
448 Err(_) => return 0,
449 };
450
451 let mut count = 0usize;
452 let matches: Vec<(usize, usize)> = re
454 .find_iter(&self.plain)
455 .map(|m| (m.start(), m.end()))
456 .collect();
457
458 for (start, end) in matches {
459 self.spans.push(Span::new(start, end, style.clone()));
460 count += 1;
461 }
462 count
463 }
464
465 pub fn blank_copy(&self) -> Self {
467 Self {
468 plain: String::new(),
469 spans: Vec::new(),
470 style: self.style.clone(),
471 justify: self.justify,
472 end: self.end.clone(),
473 overflow: self.overflow,
474 no_wrap: self.no_wrap,
475 tab_size: self.tab_size,
476 indent_guides: self.indent_guides,
477 }
478 }
479
480 pub fn copy_styles(&self) -> Self {
482 Self::new(self.plain.clone())
483 }
484
485 pub fn detect_indentation(&self) -> (String, usize) {
489 let trimmed_start = self.plain.len() - self.plain.trim_start().len();
490 if trimmed_start == 0 {
491 return (String::new(), 0);
492 }
493 let indent_str = self.plain[..trimmed_start].to_string();
494 let first_char = indent_str.chars().next().unwrap_or(' ');
495 let indent_count = indent_str.chars().filter(|&c| c == first_char).count();
496 (indent_str, indent_count)
497 }
498
499 pub fn divide(&self, offsets: &[usize]) -> Vec<Text> {
502 let mut result: Vec<Text> = Vec::new();
503 let mut prev = 0usize;
504 for &offset in offsets {
505 if offset <= prev || offset > self.plain.len() {
506 continue;
507 }
508 result.push(self.slice(prev, offset));
509 prev = offset;
510 }
511 if prev < self.plain.len() {
512 result.push(self.slice(prev, self.plain.len()));
513 }
514 result
515 }
516
517 fn slice(&self, start: usize, end: usize) -> Text {
519 let piece = self.plain[start..end].to_string();
520 let mut text = Text::new(piece);
521 text.style = self.style.clone();
522 for span in &self.spans {
523 if span.start < end && span.end > start {
524 let s_start = if span.start > start { span.start - start } else { 0 };
525 let s_end = if span.end < end { span.end - start } else { end - start };
526 if s_start < s_end {
527 text.spans.push(Span::new(s_start, s_end, span.style.clone()));
528 }
529 }
530 }
531 text
532 }
533
534 pub fn extend_style(&mut self, style: Style) {
536 if !self.plain.is_empty() {
537 self.spans.push(Span::new(0, self.plain.len(), style));
538 }
539 }
540
541 pub fn fit(&self, width: usize) -> Text {
544 let mut copy = self.clone();
545 let cell_len = copy.cell_len();
546 if cell_len > width {
547 copy.truncate(width, OverflowMethod::Crop);
548 } else if cell_len < width {
549 copy.align(AlignMethod::Left, width);
550 }
551 copy
552 }
553
554 pub fn set_length(&mut self, length: usize) {
556 let cell_len = self.cell_len();
557 if cell_len > length {
558 self.truncate(length, OverflowMethod::Crop);
559 } else if cell_len < length {
560 self.pad_right(length - cell_len, ' ');
561 }
562 }
563
564 pub fn remove_suffix(&mut self, suffix: &str) -> bool {
566 if self.plain.ends_with(suffix) {
567 let end = self.plain.len() - suffix.len();
568 self.plain.truncate(end);
569 self.spans.retain(|s| s.start < end);
570 for s in &mut self.spans {
571 if s.end > end {
572 s.end = end;
573 }
574 }
575 true
576 } else {
577 false
578 }
579 }
580
581 pub fn rstrip_end(&mut self, end: &str) -> bool {
583 self.remove_suffix(end)
584 }
585
586 pub fn right_crop(&mut self, offset: usize) -> Text {
588 if offset >= self.plain.len() {
589 return Text::new("");
591 }
592
593 let cropped_text = self.plain[offset..].to_string();
594 let mut cropped = Text::new(&*cropped_text);
595 cropped.style = self.style.clone();
596
597 self.plain.truncate(offset);
598
599 let mut kept_spans: Vec<Span> = Vec::new();
601 let mut cropped_spans: Vec<Span> = Vec::new();
602 for span in &self.spans {
603 if span.start < offset {
604 let mut s = span.clone();
605 if s.end > offset {
606 cropped_spans.push(Span::new(0, s.end - offset, span.style.clone()));
608 s.end = offset;
609 }
610 kept_spans.push(s);
611 } else {
612 cropped_spans.push(Span::new(
614 span.start - offset,
615 span.end - offset,
616 span.style.clone(),
617 ));
618 }
619 }
620 self.spans = kept_spans;
621 cropped.spans = cropped_spans;
622 cropped
623 }
624
625 pub fn rstrip(&mut self) -> &mut Self {
627 let trimmed_end = self.plain.len() - self.plain.trim_end().len();
628 if trimmed_end > 0 {
629 let new_len = self.plain.len() - trimmed_end;
630 self.plain.truncate(new_len);
631 self.spans.retain(|s| s.start < new_len);
632 for s in &mut self.spans {
633 if s.end > new_len {
634 s.end = new_len;
635 }
636 }
637 }
638 self
639 }
640
641 pub fn split(&self) -> Vec<Text> {
643 let mut result: Vec<Text> = Vec::new();
644 let mut byte_pos = 0usize;
645 for ch in self.plain.chars() {
646 let ch_len = ch.len_utf8();
647 let ch_str = &self.plain[byte_pos..byte_pos + ch_len];
648 let mut text = Text::new(ch_str.to_string());
649 text.style = self.style_at(byte_pos);
650 result.push(text);
651 byte_pos += ch_len;
652 }
653 result
654 }
655
656 pub fn wrap(&self, width: usize) -> Vec<Text> {
660 let mut lines: Vec<Text> = Vec::new();
661 let mut current = Text::new("");
662
663 for word in self.plain.split_whitespace() {
664 let word_w = unicode_width::UnicodeWidthStr::width(word);
665 let cur_w = current.cell_len();
666
667 if cur_w == 0 {
668 current = Text::new(word);
670 } else if cur_w + 1 + word_w <= width {
671 current.plain.push(' ');
673 current.plain.push_str(word);
674 } else {
675 if !current.plain.is_empty() {
677 lines.push(current);
678 }
679 current = Text::new(word);
680 }
681 }
682
683 if !current.plain.is_empty() {
684 lines.push(current);
685 }
686
687 lines
688 }
689
690 pub fn render(&self) -> String {
692 if self.spans.is_empty() && self.style.is_plain() {
694 return self.plain.clone();
695 }
696
697 let mut out = String::new();
698 let chars: Vec<(usize, char)> = self.plain.char_indices().collect();
699 let default_ansi = self.style.to_ansi();
700 let reset = if default_ansi.is_empty() { "" } else { "\x1b[0m" };
701
702 if !default_ansi.is_empty() {
703 out.push_str(&default_ansi);
704 }
705
706 for (byte_pos, ch) in &chars {
707 let mut applied = String::new();
709 for span in &self.spans {
710 if span.start == *byte_pos {
711 applied.push_str(&span.style.to_ansi());
712 }
713 }
714 out.push_str(&applied);
715
716 out.push(*ch);
718
719 let char_end = byte_pos + ch.len_utf8();
721 let mut ended = false;
722 for span in &self.spans {
723 if span.end == char_end {
724 out.push_str("\x1b[0m");
725 ended = true;
726 }
727 }
728 if ended && !default_ansi.is_empty() {
730 out.push_str(&default_ansi);
731 }
732 }
733
734 if !reset.is_empty() {
735 out.push_str(reset);
736 }
737
738 out
739 }
740
741 pub fn markup(&self) -> String {
744 if self.spans.is_empty() {
745 return crate::markup::escape(&self.plain);
746 }
747
748 let mut sorted: Vec<&Span> = self.spans.iter().collect();
749 sorted.sort_by_key(|s| (s.start, s.end));
750
751 let mut result = String::new();
752 let mut pos = 0usize;
753
754 for span in &sorted {
755 if span.start > pos {
756 result.push_str(&crate::markup::escape(&self.plain[pos..span.start]));
757 }
758 if span.start < span.end {
759 let style_str = span.style.to_string();
760 if style_str != "none" && !style_str.is_empty() {
761 result.push_str(&format!("[{}]", style_str));
762 result.push_str(&crate::markup::escape(&self.plain[span.start..span.end]));
763 result.push_str("[/]");
764 } else {
765 result.push_str(&crate::markup::escape(&self.plain[span.start..span.end]));
766 }
767 }
768 pos = pos.max(span.end);
769 }
770
771 if pos < self.plain.len() {
772 result.push_str(&crate::markup::escape(&self.plain[pos..]));
773 }
774
775 result
776 }
777
778 pub fn pad(&mut self, count: usize, character: char) {
780 self.plain = format!(
781 "{}{}{}",
782 character.to_string().repeat(count),
783 self.plain,
784 character.to_string().repeat(count)
785 );
786 for span in &mut self.spans {
788 span.start += count;
789 span.end += count;
790 }
791 }
792
793 pub fn pad_left(&mut self, count: usize, character: char) {
795 self.plain = format!("{}{}", character.to_string().repeat(count), self.plain);
796 for span in &mut self.spans {
797 span.start += count;
798 span.end += count;
799 }
800 }
801
802 pub fn pad_right(&mut self, count: usize, character: char) {
804 self.plain = format!("{}{}", self.plain, character.to_string().repeat(count));
805 }
806
807 pub fn align(&mut self, method: AlignMethod, width: usize) {
809 let current = self.cell_len();
810 if current >= width {
811 return;
812 }
813 let padding = width - current;
814 match method {
815 AlignMethod::Left => self.pad_right(padding, ' '),
816 AlignMethod::Right => self.pad_left(padding, ' '),
817 AlignMethod::Center => {
818 let left = padding / 2;
819 self.pad_left(left, ' ');
820 self.pad_right(padding - left, ' ');
821 }
822 AlignMethod::Full => {} }
824 }
825}
826
827impl Default for Text {
828 fn default() -> Self {
829 Self::new("")
830 }
831}
832
833impl fmt::Display for Text {
834 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
835 write!(f, "{}", self.render())
836 }
837}
838
839impl From<&str> for Text {
840 fn from(s: &str) -> Self {
841 Self::new(s)
842 }
843}
844
845impl From<String> for Text {
846 fn from(s: String) -> Self {
847 Self::new(s)
848 }
849}
850
851#[derive(Debug, Clone)]
857pub enum TextType {
858 Plain(String),
859 Rich(Text),
860}
861
862impl TextType {
863 pub fn render(&self) -> String {
864 match self {
865 Self::Plain(s) => s.clone(),
866 Self::Rich(t) => t.render(),
867 }
868 }
869}
870
871impl From<&str> for TextType {
872 fn from(s: &str) -> Self {
873 Self::Plain(s.to_string())
874 }
875}
876
877impl From<String> for TextType {
878 fn from(s: String) -> Self {
879 Self::Plain(s)
880 }
881}
882
883impl From<Text> for TextType {
884 fn from(t: Text) -> Self {
885 Self::Rich(t)
886 }
887}
888
889impl fmt::Display for TextType {
890 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
891 match self {
892 Self::Plain(s) => write!(f, "{s}"),
893 Self::Rich(t) => write!(f, "{t}"),
894 }
895 }
896}
897
898fn apply_sgr(style: &mut Style, params: &str) {
905 if params.is_empty() || params == "0" {
906 *style = Style::new();
908 return;
909 }
910
911 let parts: Vec<&str> = params.split(';').collect();
912 let mut i = 0usize;
913 while i < parts.len() {
914 match parts[i] {
915 "1" => { *style = style.clone().bold(true); }
916 "2" => { *style = style.clone().dim(true); }
917 "3" => { *style = style.clone().italic(true); }
918 "4" => { *style = style.clone().underline(true); }
919 "5" => { *style = style.clone().blink(true); }
920 "6" => { *style = style.clone().blink2(true); }
921 "7" => { *style = style.clone().reverse(true); }
922 "8" => { *style = style.clone().conceal(true); }
923 "9" => { *style = style.clone().strike(true); }
924 "21" => { *style = style.clone().underline2(true); }
925 "22" => { *style = style.clone().bold(false).dim(false); }
926 "23" => { *style = style.clone().italic(false); }
927 "24" => { *style = style.clone().underline(false); }
928 "25" => { *style = style.clone().blink(false).blink2(false); }
929 "27" => { *style = style.clone().reverse(false); }
930 "28" => { *style = style.clone().conceal(false); }
931 "29" => { *style = style.clone().strike(false); }
932 "51" => { *style = style.clone().frame(true); }
933 "52" => { *style = style.clone().encircle(true); }
934 "53" => { *style = style.clone().overline(true); }
935 "54" => { *style = style.clone().frame(false).encircle(false); }
936 "55" => { *style = style.clone().overline(false); }
937 "38" => {
938 if i + 1 < parts.len() {
940 match parts[i + 1] {
941 "5" => {
942 if i + 2 < parts.len() {
943 if let Ok(n) = parts[i + 2].parse::<u8>() {
944 let c = crate::color::Color::from_8bit(n);
945 *style = style.clone().color(c);
946 }
947 i += 2;
948 }
949 }
950 "2" => {
951 if i + 4 < parts.len() {
952 let r = parts[i + 2].parse::<u8>().unwrap_or(0);
953 let g = parts[i + 3].parse::<u8>().unwrap_or(0);
954 let b = parts[i + 4].parse::<u8>().unwrap_or(0);
955 *style = style.clone().color(crate::color::Color::from_rgb(r, g, b));
956 i += 4;
957 }
958 }
959 _ => {}
960 }
961 }
962 }
963 "48" => {
964 if i + 1 < parts.len() {
966 match parts[i + 1] {
967 "5" => {
968 if i + 2 < parts.len() {
969 if let Ok(n) = parts[i + 2].parse::<u8>() {
970 let c = crate::color::Color::from_8bit(n);
971 *style = style.clone().bgcolor(c);
972 }
973 i += 2;
974 }
975 }
976 "2" => {
977 if i + 4 < parts.len() {
978 let r = parts[i + 2].parse::<u8>().unwrap_or(0);
979 let g = parts[i + 3].parse::<u8>().unwrap_or(0);
980 let b = parts[i + 4].parse::<u8>().unwrap_or(0);
981 *style = style.clone().bgcolor(crate::color::Color::from_rgb(r, g, b));
982 i += 4;
983 }
984 }
985 _ => {}
986 }
987 }
988 }
989 "39" => { style.color = None; }
990 "49" => { style.bgcolor = None; }
991 n => {
992 if let Ok(num) = n.parse::<u8>() {
994 match num {
995 30..=37 => {
996 let c = crate::color::Color::from_8bit(num - 30);
997 *style = style.clone().color(c);
998 }
999 40..=47 => {
1000 let c = crate::color::Color::from_8bit(num - 40);
1001 *style = style.clone().bgcolor(c);
1002 }
1003 90..=97 => {
1004 let c = crate::color::Color::from_8bit(num - 82);
1005 *style = style.clone().color(c);
1006 }
1007 100..=107 => {
1008 let c = crate::color::Color::from_8bit(num - 92);
1009 *style = style.clone().bgcolor(c);
1010 }
1011 _ => {}
1012 }
1013 }
1014 }
1015 }
1016 i += 1;
1017 }
1018}
1019
1020#[cfg(test)]
1021mod tests {
1022 use super::*;
1023 use crate::color::Color;
1024
1025 #[test]
1026 fn test_text_append() {
1027 let mut t = Text::new("Hello");
1028 t.append_styled(" World", Style::new().bold(true));
1029 assert_eq!(t.plain, "Hello World");
1030 assert_eq!(t.spans.len(), 1);
1031 assert_eq!(t.spans[0].start, 5);
1032 assert_eq!(t.spans[0].end, 11);
1033 }
1034
1035 #[test]
1036 fn test_text_truncate() {
1037 let mut t = Text::new("Hello World");
1038 t.truncate(5, OverflowMethod::Ellipsis);
1039 assert!(t.plain.contains('\u{2026}'));
1040 }
1041
1042 #[test]
1043 fn test_styled_constructor() {
1044 let style = Style::new().bold(true).color(Color::parse("red").unwrap());
1045 let t = Text::styled(style.clone());
1046 assert_eq!(t.plain, "");
1047 assert_eq!(t.style, style);
1048 assert!(t.spans.is_empty());
1049 }
1050
1051 #[test]
1052 fn test_from_ansi_empty() {
1053 let t = Text::from_ansi("");
1054 assert_eq!(t.plain, "");
1055 assert!(t.spans.is_empty());
1056 }
1057
1058 #[test]
1059 fn test_from_ansi_no_escapes() {
1060 let t = Text::from_ansi("hello world");
1061 assert_eq!(t.plain, "hello world");
1062 assert!(t.spans.is_empty());
1063 }
1064
1065 #[test]
1066 fn test_from_ansi_bold() {
1067 let t = Text::from_ansi("\x1b[1mbold\x1b[0m");
1068 assert_eq!(t.plain, "bold");
1069 assert!(!t.spans.is_empty());
1070 }
1071
1072 #[test]
1073 fn test_style_getter() {
1074 let style = Style::new().bold(true);
1075 let t = Text::styled(style.clone());
1076 assert_eq!(t.get_style(), &style);
1077 }
1078
1079 #[test]
1080 fn test_style_mut_getter() {
1081 let mut t = Text::new("hello");
1082 t.style = Style::new().bold(true);
1083 assert_eq!(t.get_style().get_bold(), Some(true));
1084 }
1085
1086 #[test]
1087 fn test_spans_getter() {
1088 let mut t = Text::new("hello");
1089 t.stylize(Style::new().bold(true), 0, Some(3));
1090 assert_eq!(t.spans().len(), 1);
1091 }
1092
1093 #[test]
1094 fn test_from_markup() {
1095 let t = Text::from_markup("[bold]hello[/bold]");
1096 assert_eq!(t.plain, "hello");
1097 assert!(!t.spans.is_empty());
1098 }
1099
1100 #[test]
1101 fn test_append_tokens() {
1102 let mut t = Text::new("");
1103 let tokens = vec![
1104 ("Hello ".to_string(), Style::new().bold(true)),
1105 ("World".to_string(), Style::new().italic(true)),
1106 ];
1107 t.append_tokens(tokens);
1108 assert_eq!(t.plain, "Hello World");
1109 assert_eq!(t.spans.len(), 2);
1110 }
1111
1112 #[test]
1113 fn test_stylize_before() {
1114 let mut t = Text::new("hello");
1115 t.stylize(Style::new().bold(true), 0, Some(5));
1116 t.stylize_before(Style::new().italic(true), 0, Some(5));
1117 assert_eq!(t.spans.len(), 2);
1119 assert_eq!(t.spans[0].style.get_italic(), Some(true));
1120 }
1121
1122 #[test]
1123 fn test_blank_copy() {
1124 let mut t = Text::new("hello");
1125 t.stylize(Style::new().bold(true), 0, Some(3));
1126 t.justify = JustifyMethod::Center;
1127 let blank = t.blank_copy();
1128 assert_eq!(blank.plain, "");
1129 assert!(blank.spans.is_empty());
1130 assert_eq!(blank.justify, JustifyMethod::Center);
1131 }
1132
1133 #[test]
1134 fn test_copy_styles() {
1135 let mut t = Text::new("hello");
1136 t.stylize(Style::new().bold(true), 0, Some(3));
1137 let copy = t.copy_styles();
1138 assert_eq!(copy.plain, "hello");
1139 assert!(copy.spans.is_empty());
1140 }
1141
1142 #[test]
1143 fn test_detect_indentation() {
1144 let t = Text::new(" hello");
1145 let (indent, count) = t.detect_indentation();
1146 assert_eq!(indent, " ");
1147 assert_eq!(count, 2);
1148 }
1149
1150 #[test]
1151 fn test_detect_indentation_none() {
1152 let t = Text::new("hello");
1153 let (indent, count) = t.detect_indentation();
1154 assert_eq!(indent, "");
1155 assert_eq!(count, 0);
1156 }
1157
1158 #[test]
1159 fn test_get_style_at_offset() {
1160 let mut t = Text::new("hello world");
1161 t.stylize(Style::new().bold(true), 0, Some(5));
1162 let s0 = t.get_style_at_offset(0);
1163 assert_eq!(s0.get_bold(), Some(true));
1164 let s6 = t.get_style_at_offset(6);
1165 assert_eq!(s6.get_bold(), None); }
1167
1168 #[test]
1169 fn test_divide() {
1170 let mut t = Text::new("abcdef");
1171 t.stylize(Style::new().bold(true), 0, Some(3));
1172 let parts = t.divide(&[2, 4]);
1173 assert_eq!(parts.len(), 3);
1174 assert_eq!(parts[0].plain, "ab");
1175 assert_eq!(parts[1].plain, "cd");
1176 assert_eq!(parts[2].plain, "ef");
1177 }
1178
1179 #[test]
1180 fn test_extend_style() {
1181 let mut t = Text::new("hello");
1182 t.extend_style(Style::new().bold(true));
1183 assert_eq!(t.spans.len(), 1);
1184 assert_eq!(t.spans[0].start, 0);
1185 assert_eq!(t.spans[0].end, 5);
1186 }
1187
1188 #[test]
1189 fn test_fit_truncate() {
1190 let t = Text::new("hello world");
1191 let fitted = t.fit(5);
1192 assert_eq!(fitted.cell_len(), 5);
1193 }
1194
1195 #[test]
1196 fn test_fit_pad() {
1197 let t = Text::new("hi");
1198 let fitted = t.fit(10);
1199 assert_eq!(fitted.cell_len(), 10);
1200 }
1201
1202 #[test]
1203 fn test_set_length_truncate() {
1204 let mut t = Text::new("hello world");
1205 t.set_length(5);
1206 assert_eq!(t.cell_len(), 5);
1207 }
1208
1209 #[test]
1210 fn test_set_length_pad() {
1211 let mut t = Text::new("hi");
1212 t.set_length(10);
1213 assert_eq!(t.cell_len(), 10);
1214 }
1215
1216 #[test]
1217 fn test_remove_suffix() {
1218 let mut t = Text::new("hello.txt");
1219 assert!(t.remove_suffix(".txt"));
1220 assert_eq!(t.plain, "hello");
1221 }
1222
1223 #[test]
1224 fn test_remove_suffix_not_found() {
1225 let mut t = Text::new("hello");
1226 assert!(!t.remove_suffix(".txt"));
1227 assert_eq!(t.plain, "hello");
1228 }
1229
1230 #[test]
1231 fn test_right_crop() {
1232 let mut t = Text::new("hello world");
1233 let cropped = t.right_crop(5);
1234 assert_eq!(t.plain, "hello");
1235 assert_eq!(cropped.plain, " world");
1236 }
1237
1238 #[test]
1239 fn test_rstrip() {
1240 let mut t = Text::new("hello ");
1241 t.rstrip();
1242 assert_eq!(t.plain, "hello");
1243 }
1244
1245 #[test]
1246 fn test_rstrip_end() {
1247 let mut t = Text::new("hello\n");
1248 assert!(t.rstrip_end("\n"));
1249 assert_eq!(t.plain, "hello");
1250 }
1251
1252 #[test]
1253 fn test_split_chars() {
1254 let t = Text::new("abc");
1255 let chars = t.split();
1256 assert_eq!(chars.len(), 3);
1257 assert_eq!(chars[0].plain, "a");
1258 assert_eq!(chars[1].plain, "b");
1259 assert_eq!(chars[2].plain, "c");
1260 }
1261
1262 #[test]
1263 fn test_markup() {
1264 let mut t = Text::new("hello world");
1265 t.stylize(Style::new().bold(true), 0, Some(5));
1266 let markup = t.markup();
1267 assert!(markup.contains("[bold]"));
1268 assert!(markup.contains("[/]"));
1269 assert!(markup.contains("hello"));
1270 assert!(markup.contains(" world"));
1271 }
1272
1273 #[test]
1274 fn test_markup_no_spans() {
1275 let t = Text::new("hello");
1276 assert_eq!(t.markup(), "hello");
1277 }
1278
1279 #[test]
1280 fn test_no_wrap_builder() {
1281 let t = Text::new("hello").no_wrap(true);
1282 assert!(t.no_wrap);
1283 }
1284
1285 #[test]
1286 fn test_tab_size_builder() {
1287 let t = Text::new("hello").tab_size(4);
1288 assert_eq!(t.tab_size, 4);
1289 }
1290
1291 #[test]
1292 fn test_with_indent_guides_builder() {
1293 let t = Text::new("hello").with_indent_guides(true);
1294 assert!(t.indent_guides);
1295 }
1296
1297 #[test]
1298 fn test_expand_tabs_with_custom_size() {
1299 let mut t = Text::new("\thello").tab_size(4);
1300 t.expand_tabs();
1301 assert_eq!(t.plain, " hello");
1302 }
1303
1304 #[test]
1305 fn test_apply_meta() {
1306 let mut t = Text::new("hello world");
1307 let meta = vec![1u8, 2u8, 3u8];
1308 let spans = vec![Span::new(0, 5, Style::new())];
1309 t.apply_meta(meta, &spans);
1310 assert!(t.spans.iter().any(|s| s.style.meta.is_some()));
1312 }
1313
1314 #[test]
1315 fn test_default_impl() {
1316 let t: Text = Default::default();
1317 assert_eq!(t.plain, "");
1318 assert_eq!(t.tab_size, 8);
1319 }
1320}