1use egui::{Color32, RichText};
30use regex::Regex;
31use std::sync::LazyLock;
32
33mod color_models;
34
35static CSI_REGEX: LazyLock<Regex> = LazyLock::new(|| {
39 Regex::new(r"\x1b\[([\x30-\x3F]*)([\x40-\x7E])")
44 .expect("Invalid CSI regex pattern")
45});
46
47static OSC_REGEX: LazyLock<Regex> = LazyLock::new(|| {
50 Regex::new(r"\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?")
52 .expect("Invalid OSC regex pattern")
53});
54
55static OTHER_ESC_REGEX: LazyLock<Regex> = LazyLock::new(|| {
58 Regex::new(r"\x1b[\x40-\x5A\x5E\x5F]")
63 .expect("Invalid escape regex pattern")
64});
65
66pub use color_models::*;
68
69#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct ColoredText {
75 pub text: String,
77 pub foreground_color: Option<Color32>,
79 pub background_color: Option<Color32>,
81}
82
83impl ColoredText {
84 #[must_use]
86 pub fn new(text: impl Into<String>) -> Self {
87 Self {
88 text: text.into(),
89 foreground_color: None,
90 background_color: None,
91 }
92 }
93
94 #[must_use]
96 pub fn with_foreground(text: impl Into<String>, color: Color32) -> Self {
97 Self {
98 text: text.into(),
99 foreground_color: Some(color),
100 background_color: None,
101 }
102 }
103
104 #[must_use]
106 pub fn with_background(text: impl Into<String>, color: Color32) -> Self {
107 Self {
108 text: text.into(),
109 foreground_color: None,
110 background_color: Some(color),
111 }
112 }
113
114 #[must_use]
116 pub fn with_colors(
117 text: impl Into<String>,
118 foreground: Option<Color32>,
119 background: Option<Color32>,
120 ) -> Self {
121 Self {
122 text: text.into(),
123 foreground_color: foreground,
124 background_color: background,
125 }
126 }
127}
128
129#[derive(Debug, Clone)]
134pub struct AnsiParser {
135 current_fg: Option<Color32>,
137 current_bg: Option<Color32>,
139}
140
141impl Default for AnsiParser {
142 fn default() -> Self {
143 Self::new()
144 }
145}
146
147impl AnsiParser {
148 pub fn new() -> Self {
150 Self {
151 current_fg: None,
152 current_bg: None,
153 }
154 }
155
156 pub fn parse(&mut self, input: &str) -> Vec<ColoredText> {
164 let cleaned = self.strip_non_sgr_sequences(input);
166 self.parse_sgr(&cleaned)
168 }
169
170 fn strip_non_sgr_sequences(&self, input: &str) -> String {
172 let mut result = input.to_string();
173
174 result = OSC_REGEX.replace_all(&result, "").to_string();
176
177 result = OTHER_ESC_REGEX.replace_all(&result, "").to_string();
179
180 result
181 }
182
183 fn parse_sgr(&mut self, input: &str) -> Vec<ColoredText> {
185 let mut result = Vec::new();
186
187 self.reset_colors();
189
190 let mut last_end = 0;
191
192 for cap in CSI_REGEX.captures_iter(input) {
194 let params = cap.get(1).unwrap().as_str();
195 let final_byte = cap.get(2).unwrap().as_str();
196 let start = cap.get(0).unwrap().start();
197 let end = cap.get(0).unwrap().end();
198
199 if start > last_end {
201 let plain_text = &input[last_end..start];
202 if !plain_text.is_empty() {
203 result.push(ColoredText {
204 text: plain_text.to_string(),
205 foreground_color: self.current_fg,
206 background_color: self.current_bg,
207 });
208 }
209 }
210
211 if final_byte == "m" {
213 if params.is_empty() {
215 self.reset_colors();
216 } else {
217 self.process_sgr_sequence(params);
218 }
219 }
220 last_end = end;
223 }
224
225 if last_end < input.len() {
227 let plain_text = &input[last_end..];
228 if !plain_text.is_empty() {
229 result.push(ColoredText {
230 text: plain_text.to_string(),
231 foreground_color: self.current_fg,
232 background_color: self.current_bg,
233 });
234 }
235 }
236
237 if result.is_empty() {
239 return vec![ColoredText {
240 text: input.to_string(),
241 foreground_color: None,
242 background_color: None,
243 }];
244 }
245
246 result
247 }
248
249 fn reset_colors(&mut self) {
251 self.current_fg = None;
252 self.current_bg = None;
253 }
254
255 fn process_sgr_sequence(&mut self, sequence: &str) {
260 let codes: Vec<&str> = sequence.split(';').collect();
261 let mut i = 0;
262
263 while i < codes.len() {
264 if codes[i].is_empty() {
266 i += 1;
267 continue;
268 }
269
270 match codes[i] {
271 "0" | "" => {
272 self.reset_colors();
274 i += 1;
275 }
276 "1" => {
277 i += 1;
279 }
280 "4" => {
281 i += 1;
283 }
284 "7" => {
285 std::mem::swap(&mut self.current_fg, &mut self.current_bg);
287 i += 1;
288 }
289 "22" => {
290 i += 1;
292 }
293 "24" => {
294 i += 1;
296 }
297 "27" => {
298 i += 1;
300 }
301 "38" => {
302 if i + 2 < codes.len() {
304 match codes[i + 1] {
305 "5" => {
306 if let Ok(color_code) = codes[i + 2].parse::<u8>() {
308 self.current_fg =
309 Some(color_models::eight_bit::ansi_256_to_egui(color_code));
310 }
311 i += 3; }
313 "2" => {
314 if i + 4 < codes.len() {
316 if let (Ok(r), Ok(g), Ok(b)) = (
317 codes[i + 2].parse::<u8>(),
318 codes[i + 3].parse::<u8>(),
319 codes[i + 4].parse::<u8>(),
320 ) {
321 self.current_fg = Some(Color32::from_rgb(r, g, b));
322 }
323 i += 5; } else {
325 i += 1;
326 }
327 }
328 _ => {
329 i += 1;
330 }
331 }
332 } else {
333 i += 1;
334 }
335 }
336 "48" => {
337 if i + 2 < codes.len() {
339 match codes[i + 1] {
340 "5" => {
341 if let Ok(color_code) = codes[i + 2].parse::<u8>() {
343 self.current_bg =
344 Some(color_models::eight_bit::ansi_256_to_egui(color_code));
345 }
346 i += 3; }
348 "2" => {
349 if i + 4 < codes.len() {
351 if let (Ok(r), Ok(g), Ok(b)) = (
352 codes[i + 2].parse::<u8>(),
353 codes[i + 3].parse::<u8>(),
354 codes[i + 4].parse::<u8>(),
355 ) {
356 self.current_bg = Some(Color32::from_rgb(r, g, b));
357 }
358 i += 5; } else {
360 i += 1;
361 }
362 }
363 _ => {
364 i += 1;
365 }
366 }
367 } else {
368 i += 1;
369 }
370 }
371 "39" => {
372 self.current_fg = None;
374 i += 1;
375 }
376 "49" => {
377 self.current_bg = None;
379 i += 1;
380 }
381 code => {
382 if let Ok(color_code) = code.parse::<u8>() {
384 match color_code {
385 30..=37 => {
386 let color_index = color_code - 30;
387 self.current_fg =
388 Some(color_models::four_bit::ansi_color_to_egui(color_index));
389 }
390 40..=47 => {
391 let color_index = color_code - 40;
392 self.current_bg =
393 Some(color_models::four_bit::ansi_color_to_egui(color_index));
394 }
395 90..=97 => {
396 let color_index = color_code - 90 + 8;
397 self.current_fg =
398 Some(color_models::four_bit::ansi_color_to_egui(color_index));
399 }
400 100..=107 => {
401 let color_index = color_code - 100 + 8;
402 self.current_bg =
403 Some(color_models::four_bit::ansi_color_to_egui(color_index));
404 }
405 _ => {}
406 }
407 }
408 i += 1;
409 }
410 }
411 }
412 }
413}
414
415pub fn convert_to_rich_text(colored_texts: &[ColoredText]) -> Vec<RichText> {
423 colored_texts
424 .iter()
425 .map(|colored_text| {
426 let mut rich_text = RichText::new(&colored_text.text);
427
428 if let Some(fg) = colored_text.foreground_color {
429 rich_text = rich_text.color(fg);
430 }
431
432 if let Some(bg) = colored_text.background_color {
433 rich_text = rich_text.background_color(bg);
434 }
435
436 rich_text
437 })
438 .collect()
439}
440
441pub fn ansi_to_rich_text(input: &str) -> Vec<RichText> {
449 let mut parser = AnsiParser::new();
452 let colored_texts = parser.parse(input);
453 convert_to_rich_text(&colored_texts)
454}
455
456pub fn example_usage() {
458 let example_4bit =
460 "This is \x1b[31mred\x1b[0m, \x1b[34mblue\x1b[0m and \x1b[33myellow\x1b[0m text";
461 let _rich_text_4bit = ansi_to_rich_text(example_4bit);
462
463 let example_8bit = "This is \x1b[38;5;208morange\x1b[0m, \x1b[38;5;51msky blue\x1b[0m and \x1b[38;5;120mgreen\x1b[0m text";
465 let _rich_text_8bit = ansi_to_rich_text(example_8bit);
466
467 let example_24bit = "This is \x1b[38;2;255;105;180mhot pink\x1b[0m and \x1b[38;2;128;0;128mdeep purple\x1b[0m text";
469 let _rich_text_24bit = ansi_to_rich_text(example_24bit);
470
471 let example_mixed = "Normal text \x1b[31mred\x1b[0m normal \x1b[38;5;208morange\x1b[0m normal \x1b[38;2;255;105;180mpink\x1b[0m normal";
473 let _rich_text_mixed = ansi_to_rich_text(example_mixed);
474
475 let example_fg_bg = "Default text \x1b[41;33mYellow on red\x1b[0m default text";
477 let _rich_text_fg_bg = ansi_to_rich_text(example_fg_bg);
478
479 }
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490
491 #[test]
492 fn test_4bit_color_parsing() {
493 let input = "\x1b[31mRed Text\x1b[0m";
494 let result = ansi_to_rich_text(input);
495 assert_eq!(result.len(), 1);
496 assert_eq!(result[0].text(), "Red Text");
498 }
500
501 #[test]
502 fn test_8bit_color_parsing() {
503 let input = "\x1b[38;5;208mOrange Text\x1b[0m";
504 let result = ansi_to_rich_text(input);
505 assert_eq!(result.len(), 1);
506 assert_eq!(result[0].text(), "Orange Text");
507 }
508
509 #[test]
510 fn test_24bit_color_parsing() {
511 let input = "\x1b[38;2;255;105;180mHot Pink Text\x1b[0m";
512 let result = ansi_to_rich_text(input);
513 assert_eq!(result.len(), 1);
514 assert_eq!(result[0].text(), "Hot Pink Text");
515 }
516
517 #[test]
518 fn test_mixed_colors() {
519 let input =
520 "Normal text \x1b[31mred\x1b[0m normal text \x1b[38;5;208morange\x1b[0m normal text";
521 let result = ansi_to_rich_text(input);
522 assert_eq!(result.len(), 5); assert_eq!(result[0].text(), "Normal text ");
524 assert_eq!(result[1].text(), "red");
525 assert_eq!(result[2].text(), " normal text ");
526 assert_eq!(result[3].text(), "orange");
527 assert_eq!(result[4].text(), " normal text");
528 }
529
530 #[test]
531 fn test_background_color() {
532 let input = "\x1b[41mWhite on red\x1b[0m";
533 let result = ansi_to_rich_text(input);
534 assert_eq!(result.len(), 1);
535 assert_eq!(result[0].text(), "White on red");
536 }
537
538 #[test]
539 fn test_foreground_and_background_color() {
540 let input = "\x1b[31;43mYellow on red\x1b[0m";
541 let mut parser = AnsiParser::new();
542 let colored_segments = parser.parse(input);
543
544 assert_eq!(colored_segments.len(), 1);
546 assert_eq!(colored_segments[0].text, "Yellow on red");
547 assert!(colored_segments[0].foreground_color.is_some());
548 assert!(colored_segments[0].background_color.is_some());
549 }
550
551 #[test]
552 fn test_sequential_color_changes() {
553 let input = "Default\x1b[31mRed\x1b[43mRed on yellow\x1b[32mGreen on yellow\x1b[0mDefault";
554 let mut parser = AnsiParser::new();
555 let colored_segments = parser.parse(input);
556
557 assert_eq!(colored_segments.len(), 5);
559 assert_eq!(colored_segments[0].text, "Default");
560 assert_eq!(colored_segments[1].text, "Red");
561 assert_eq!(colored_segments[2].text, "Red on yellow");
562 assert_eq!(colored_segments[3].text, "Green on yellow");
563 assert_eq!(colored_segments[4].text, "Default");
564
565 assert!(colored_segments[0].foreground_color.is_none());
567 assert!(colored_segments[0].background_color.is_none());
568
569 assert!(colored_segments[1].foreground_color.is_some());
570 assert!(colored_segments[1].background_color.is_none());
571
572 assert!(colored_segments[2].foreground_color.is_some());
573 assert!(colored_segments[2].background_color.is_some());
574
575 assert!(colored_segments[3].foreground_color.is_some());
576 assert!(colored_segments[3].background_color.is_some());
577
578 assert!(colored_segments[4].foreground_color.is_none());
579 assert!(colored_segments[4].background_color.is_none());
580 }
581
582 #[test]
583 fn test_escape_sequence_variations() {
584 let inputs = [
587 "\\x1b[31mRed\\x1b[0m",
588 "\\x1B[31mRed\\x1B[0m",
589 "\\X1b[31mRed\\X1b[0m",
590 "\\X1B[31mRed\\X1B[0m",
591 "\\033[31mRed\\033[0m",
592 ];
593
594 for input in inputs {
595 let mut parser = AnsiParser::new();
596 let colored_segments = parser.parse(input);
597
598 assert_eq!(colored_segments.len(), 1);
600 assert_eq!(colored_segments[0].text, input);
601 assert!(colored_segments[0].foreground_color.is_none());
602 assert!(colored_segments[0].background_color.is_none());
603 }
604 }
605
606 #[test]
607 fn test_mixed_escape_sequence_variations() {
608 let input = "\\x1b[31mRed\\033[32mGreen\\X1B[33mYellow\\x1B[0m";
611 let mut parser = AnsiParser::new();
612 let colored_segments = parser.parse(input);
613
614 assert_eq!(colored_segments.len(), 1);
616 assert_eq!(colored_segments[0].text, input);
617
618 assert!(colored_segments[0].foreground_color.is_none());
620 assert!(colored_segments[0].background_color.is_none());
621 }
622
623 #[test]
624 fn test_ansi_to_rich_text_with_escape_variations() {
625 let inputs = [
627 "\\x1b[31mRed\\x1b[0m",
628 "\\x1B[31mRed\\x1B[0m",
629 "\\X1b[31mRed\\X1b[0m",
630 "\\X1B[31mRed\\X1B[0m",
631 "\\033[31mRed\\033[0m",
632 ];
633
634 for input in inputs {
635 let rich_text = ansi_to_rich_text(input);
636 assert_eq!(rich_text.len(), 1);
637 assert_eq!(rich_text[0].text(), input);
638 }
640 }
641
642 #[test]
645 fn test_empty_input() {
646 let result = ansi_to_rich_text("");
647 assert_eq!(result.len(), 1);
648 assert_eq!(result[0].text(), "");
649 }
650
651 #[test]
652 fn test_plain_text_no_escape() {
653 let input = "Hello, World!";
654 let result = ansi_to_rich_text(input);
655 assert_eq!(result.len(), 1);
656 assert_eq!(result[0].text(), "Hello, World!");
657 }
658
659 #[test]
660 fn test_reset_foreground_color() {
661 let input = "\x1b[31mRed\x1b[39mDefault";
662 let mut parser = AnsiParser::new();
663 let colored_segments = parser.parse(input);
664
665 assert_eq!(colored_segments.len(), 2);
666 assert_eq!(colored_segments[0].text, "Red");
667 assert!(colored_segments[0].foreground_color.is_some());
668 assert_eq!(colored_segments[1].text, "Default");
669 assert!(colored_segments[1].foreground_color.is_none());
670 }
671
672 #[test]
673 fn test_reset_background_color() {
674 let input = "\x1b[41mRed BG\x1b[49mDefault BG";
675 let mut parser = AnsiParser::new();
676 let colored_segments = parser.parse(input);
677
678 assert_eq!(colored_segments.len(), 2);
679 assert_eq!(colored_segments[0].text, "Red BG");
680 assert!(colored_segments[0].background_color.is_some());
681 assert_eq!(colored_segments[1].text, "Default BG");
682 assert!(colored_segments[1].background_color.is_none());
683 }
684
685 #[test]
686 fn test_bright_foreground_colors() {
687 let input = "\x1b[90mBright Black\x1b[91mBright Red\x1b[97mBright White\x1b[0m";
688 let mut parser = AnsiParser::new();
689 let colored_segments = parser.parse(input);
690
691 assert_eq!(colored_segments.len(), 3);
692 assert_eq!(colored_segments[0].text, "Bright Black");
693 assert!(colored_segments[0].foreground_color.is_some());
694 assert_eq!(colored_segments[1].text, "Bright Red");
695 assert!(colored_segments[1].foreground_color.is_some());
696 assert_eq!(colored_segments[2].text, "Bright White");
697 assert!(colored_segments[2].foreground_color.is_some());
698 }
699
700 #[test]
701 fn test_bright_background_colors() {
702 let input = "\x1b[100mBright Black BG\x1b[101mBright Red BG\x1b[107mBright White BG\x1b[0m";
703 let mut parser = AnsiParser::new();
704 let colored_segments = parser.parse(input);
705
706 assert_eq!(colored_segments.len(), 3);
707 assert_eq!(colored_segments[0].text, "Bright Black BG");
708 assert!(colored_segments[0].background_color.is_some());
709 assert_eq!(colored_segments[1].text, "Bright Red BG");
710 assert!(colored_segments[1].background_color.is_some());
711 assert_eq!(colored_segments[2].text, "Bright White BG");
712 assert!(colored_segments[2].background_color.is_some());
713 }
714
715 #[test]
716 fn test_8bit_background_color() {
717 let input = "\x1b[48;5;196mRed BG\x1b[0m";
718 let mut parser = AnsiParser::new();
719 let colored_segments = parser.parse(input);
720
721 assert_eq!(colored_segments.len(), 1);
722 assert_eq!(colored_segments[0].text, "Red BG");
723 assert!(colored_segments[0].background_color.is_some());
724 }
725
726 #[test]
727 fn test_24bit_foreground_color_value() {
728 let input = "\x1b[38;2;255;0;0mRed\x1b[0m";
729 let mut parser = AnsiParser::new();
730 let colored_segments = parser.parse(input);
731
732 assert_eq!(colored_segments.len(), 1);
733 assert_eq!(colored_segments[0].text, "Red");
734 assert_eq!(
735 colored_segments[0].foreground_color,
736 Some(Color32::from_rgb(255, 0, 0))
737 );
738 }
739
740 #[test]
741 fn test_24bit_background_color_value() {
742 let input = "\x1b[48;2;0;255;0mGreen BG\x1b[0m";
743 let mut parser = AnsiParser::new();
744 let colored_segments = parser.parse(input);
745
746 assert_eq!(colored_segments.len(), 1);
747 assert_eq!(colored_segments[0].text, "Green BG");
748 assert_eq!(
749 colored_segments[0].background_color,
750 Some(Color32::from_rgb(0, 255, 0))
751 );
752 }
753
754 #[test]
755 fn test_256_color_boundary_values() {
756 let input = "\x1b[38;5;0mColor0\x1b[38;5;15mColor15\x1b[38;5;16mColor16\x1b[38;5;231mColor231\x1b[38;5;232mColor232\x1b[38;5;255mColor255\x1b[0m";
758 let mut parser = AnsiParser::new();
759 let colored_segments = parser.parse(input);
760
761 assert_eq!(colored_segments.len(), 6);
762 for segment in &colored_segments {
763 assert!(segment.foreground_color.is_some());
764 }
765 }
766
767 #[test]
768 fn test_consecutive_resets() {
769 let input = "\x1b[0m\x1b[0m\x1b[0mText\x1b[0m";
770 let mut parser = AnsiParser::new();
771 let colored_segments = parser.parse(input);
772
773 assert_eq!(colored_segments.len(), 1);
774 assert_eq!(colored_segments[0].text, "Text");
775 assert!(colored_segments[0].foreground_color.is_none());
776 assert!(colored_segments[0].background_color.is_none());
777 }
778
779 #[test]
780 fn test_default_parser() {
781 let parser: AnsiParser = Default::default();
782 let mut parser = parser;
783 let result = parser.parse("\x1b[31mRed\x1b[0m");
784 assert_eq!(result.len(), 1);
785 assert_eq!(result[0].text, "Red");
786 }
787
788 #[test]
789 fn test_colored_text_constructors() {
790 let plain = ColoredText::new("Hello");
791 assert_eq!(plain.text, "Hello");
792 assert!(plain.foreground_color.is_none());
793 assert!(plain.background_color.is_none());
794
795 let fg = ColoredText::with_foreground("Hello", Color32::RED);
796 assert_eq!(fg.text, "Hello");
797 assert_eq!(fg.foreground_color, Some(Color32::RED));
798 assert!(fg.background_color.is_none());
799
800 let bg = ColoredText::with_background("Hello", Color32::BLUE);
801 assert_eq!(bg.text, "Hello");
802 assert!(bg.foreground_color.is_none());
803 assert_eq!(bg.background_color, Some(Color32::BLUE));
804
805 let both = ColoredText::with_colors("Hello", Some(Color32::RED), Some(Color32::BLUE));
806 assert_eq!(both.text, "Hello");
807 assert_eq!(both.foreground_color, Some(Color32::RED));
808 assert_eq!(both.background_color, Some(Color32::BLUE));
809 }
810
811 #[test]
812 fn test_colored_text_equality() {
813 let a = ColoredText::new("Hello");
814 let b = ColoredText::new("Hello");
815 assert_eq!(a, b);
816
817 let c = ColoredText::with_foreground("Hello", Color32::RED);
818 let d = ColoredText::with_foreground("Hello", Color32::RED);
819 assert_eq!(c, d);
820
821 let e = ColoredText::with_foreground("Hello", Color32::BLUE);
822 assert_ne!(c, e);
823 }
824
825 #[test]
826 fn test_multiline_text() {
827 let input = "\x1b[31mLine1\nLine2\x1b[0m";
828 let mut parser = AnsiParser::new();
829 let colored_segments = parser.parse(input);
830
831 assert_eq!(colored_segments.len(), 1);
832 assert_eq!(colored_segments[0].text, "Line1\nLine2");
833 assert!(colored_segments[0].foreground_color.is_some());
834 }
835
836 #[test]
837 fn test_unicode_text() {
838 let input = "\x1b[31mä½ å¥½ä¸–ç•ŒðŸŽ‰\x1b[0m";
839 let mut parser = AnsiParser::new();
840 let colored_segments = parser.parse(input);
841
842 assert_eq!(colored_segments.len(), 1);
843 assert_eq!(colored_segments[0].text, "ä½ å¥½ä¸–ç•ŒðŸŽ‰");
844 assert!(colored_segments[0].foreground_color.is_some());
845 }
846
847 #[test]
848 fn test_4bit_color_values() {
849 use color_models::four_bit::ansi_color_to_egui;
850
851 assert_eq!(ansi_color_to_egui(0), Color32::BLACK);
852 assert_eq!(ansi_color_to_egui(1), Color32::RED);
853 assert_eq!(ansi_color_to_egui(2), Color32::GREEN);
854 assert_eq!(ansi_color_to_egui(3), Color32::YELLOW);
855 assert_eq!(ansi_color_to_egui(4), Color32::BLUE);
856 }
857
858 #[test]
859 fn test_8bit_standard_colors() {
860 use color_models::eight_bit::ansi_256_to_egui;
861
862 assert_eq!(ansi_256_to_egui(0), Color32::BLACK);
863 assert_eq!(ansi_256_to_egui(1), Color32::RED);
864 assert_eq!(ansi_256_to_egui(15), Color32::WHITE);
865 }
866
867 #[test]
868 fn test_8bit_rgb_cube() {
869 use color_models::eight_bit::ansi_256_to_egui;
870
871 assert_eq!(ansi_256_to_egui(16), Color32::from_rgb(0, 0, 0));
873 assert_eq!(ansi_256_to_egui(21), Color32::from_rgb(0, 0, 255));
875 assert_eq!(ansi_256_to_egui(196), Color32::from_rgb(255, 0, 0));
877 }
878
879 #[test]
880 fn test_8bit_grayscale() {
881 use color_models::eight_bit::ansi_256_to_egui;
882
883 assert_eq!(ansi_256_to_egui(232), Color32::from_rgb(8, 8, 8));
884 assert_eq!(ansi_256_to_egui(255), Color32::from_rgb(248, 248, 248));
885 }
886
887 #[test]
889 fn test_empty_reset_sequence() {
890 let input = "\x1b[31mRed\x1b[mDefault";
892 let mut parser = AnsiParser::new();
893 let colored_segments = parser.parse(input);
894
895 assert_eq!(colored_segments.len(), 2);
896 assert_eq!(colored_segments[0].text, "Red");
897 assert!(colored_segments[0].foreground_color.is_some());
898 assert_eq!(colored_segments[1].text, "Default");
899 assert!(colored_segments[1].foreground_color.is_none());
900 }
901
902 #[test]
903 fn test_empty_reset_at_start() {
904 let input = "\x1b[mPlain text";
905 let mut parser = AnsiParser::new();
906 let colored_segments = parser.parse(input);
907
908 assert_eq!(colored_segments.len(), 1);
909 assert_eq!(colored_segments[0].text, "Plain text");
910 assert!(colored_segments[0].foreground_color.is_none());
911 }
912
913 #[test]
915 fn test_linux_prompt_format() {
916 let input = "\x1b[1;31mroot\x1b[m@\x1b[1;34mhost\x1b[m:#";
918 let mut parser = AnsiParser::new();
919 let colored_segments = parser.parse(input);
920
921 assert!(!colored_segments.is_empty());
923 let combined: String = colored_segments.iter().map(|s| s.text.as_str()).collect();
925 assert_eq!(combined, "root@host:#");
926 }
927
928 #[test]
929 fn test_bash_prompt_colors() {
930 let input = "\x1b[1;34muser@host\x1b[m:\x1b[1;32m~\x1b[m$ ";
932 let mut parser = AnsiParser::new();
933 let colored_segments = parser.parse(input);
934
935 let combined: String = colored_segments.iter().map(|s| s.text.as_str()).collect();
936 assert_eq!(combined, "user@host:~$ ");
937 }
938
939 #[test]
941 fn test_osc_sequence_stripped() {
942 let input = "Before\x1b]0;Window Title\x07After";
944 let mut parser = AnsiParser::new();
945 let colored_segments = parser.parse(input);
946
947 let combined: String = colored_segments.iter().map(|s| s.text.as_str()).collect();
948 assert_eq!(combined, "BeforeAfter");
949 }
950
951 #[test]
952 fn test_osc_with_bel_terminator() {
953 let input = "\x1b]2;Title\x07Text";
954 let mut parser = AnsiParser::new();
955 let colored_segments = parser.parse(input);
956
957 let combined: String = colored_segments.iter().map(|s| s.text.as_str()).collect();
958 assert_eq!(combined, "Text");
959 }
960
961 #[test]
962 fn test_csi_cursor_movement_ignored() {
963 let input = "\x1b[2J\x1b[H\x1b[31mRed\x1b[0m";
965 let mut parser = AnsiParser::new();
966 let colored_segments = parser.parse(input);
967
968 assert_eq!(colored_segments.len(), 1);
969 assert_eq!(colored_segments[0].text, "Red");
970 }
971
972 #[test]
973 fn test_complex_terminal_output() {
974 let input = "\x1b[1;31mError:\x1b[m \x1b[33mFile not found\x1b[m\n\x1b[32mDone\x1b[m";
976 let mut parser = AnsiParser::new();
977 let colored_segments = parser.parse(input);
978
979 let combined: String = colored_segments.iter().map(|s| s.text.as_str()).collect();
980 assert!(combined.contains("Error:"));
981 assert!(combined.contains("File not found"));
982 assert!(combined.contains("Done"));
983 }
984
985 #[test]
986 fn test_osc_without_terminator() {
987 let input = "Start\x1b]0;No terminatorText";
989 let mut parser = AnsiParser::new();
990 let colored_segments = parser.parse(input);
991
992 let combined: String = colored_segments.iter().map(|s| s.text.as_str()).collect();
993 assert!(!combined.contains("\x1b]"));
995 }
996
997 #[test]
998 fn test_reverse_video() {
999 let input = "\x1b[31;42mRed on Green\x1b[7mSwapped\x1b[0m";
1001 let mut parser = AnsiParser::new();
1002 let colored_segments = parser.parse(input);
1003
1004 assert_eq!(colored_segments.len(), 2);
1005 assert_eq!(colored_segments[0].text, "Red on Green");
1006 assert_eq!(colored_segments[1].text, "Swapped");
1007 assert!(colored_segments[1].foreground_color.is_some());
1009 }
1010
1011 #[test]
1012 fn test_text_attributes() {
1013 let input = "\x1b[1mBold\x1b[22m\x1b[4mUnderline\x1b[24mNormal";
1015 let mut parser = AnsiParser::new();
1016 let colored_segments = parser.parse(input);
1017
1018 let combined: String = colored_segments.iter().map(|s| s.text.as_str()).collect();
1019 assert_eq!(combined, "BoldUnderlineNormal");
1020 }
1021}