binocular/preview/request/git/
ansi.rs1use ratatui::style::{Color, Modifier, Style};
2use ratatui::text::{Line, Span, Text};
3
4pub(crate) fn parse_ansi_text(text: String) -> Text<'static> {
5 let mut lines = Vec::new();
6 let mut spans = Vec::new();
7 let mut current = String::new();
8 let mut style = Style::default();
9 let bytes = text.as_bytes();
10 let mut idx = 0usize;
11
12 while idx < bytes.len() {
13 if bytes[idx] == 0x1b && idx + 1 < bytes.len() && bytes[idx + 1] == b'[' {
14 if !current.is_empty() {
15 spans.push(Span::styled(std::mem::take(&mut current), style));
16 }
17 idx += 2;
18 let start = idx;
19 while idx < bytes.len() && bytes[idx] != b'm' {
20 idx += 1;
21 }
22 if idx >= bytes.len() {
23 break;
24 }
25 let codes = &text[start..idx];
26 style = apply_ansi_codes(style, codes);
27 idx += 1;
28 continue;
29 }
30
31 let ch = text[idx..].chars().next().unwrap();
32 idx += ch.len_utf8();
33 if ch == '\n' {
34 if !current.is_empty() {
35 spans.push(Span::styled(std::mem::take(&mut current), style));
36 }
37 lines.push(Line::from(std::mem::take(&mut spans)));
38 } else if ch != '\r' {
39 current.push(ch);
40 }
41 }
42
43 if !current.is_empty() || !spans.is_empty() {
44 spans.push(Span::styled(current, style));
45 lines.push(Line::from(spans));
46 }
47
48 Text::from(lines)
49}
50
51fn apply_ansi_codes(mut style: Style, codes: &str) -> Style {
52 if codes.is_empty() {
53 return Style::default();
54 }
55
56 for code in codes.split(';').filter(|part| !part.is_empty()) {
57 match code {
58 "0" => style = Style::default(),
59 "1" => style = style.add_modifier(Modifier::BOLD),
60 "2" => style = style.add_modifier(Modifier::DIM),
61 "22" => style = style.remove_modifier(Modifier::BOLD | Modifier::DIM),
62 "30" => style = style.fg(Color::Black),
63 "31" => style = style.fg(Color::Red),
64 "32" => style = style.fg(Color::Green),
65 "33" => style = style.fg(Color::Yellow),
66 "34" => style = style.fg(Color::Blue),
67 "35" => style = style.fg(Color::Magenta),
68 "36" => style = style.fg(Color::Cyan),
69 "37" => style = style.fg(Color::Gray),
70 "39" => style = style.fg(Color::Reset),
71 "90" => style = style.fg(Color::DarkGray),
72 "91" => style = style.fg(Color::LightRed),
73 "92" => style = style.fg(Color::LightGreen),
74 "93" => style = style.fg(Color::LightYellow),
75 "94" => style = style.fg(Color::LightBlue),
76 "95" => style = style.fg(Color::LightMagenta),
77 "96" => style = style.fg(Color::LightCyan),
78 "97" => style = style.fg(Color::White),
79 _ => {}
80 }
81 }
82 style
83}