git_igitt/util/
ctrl_chars.rs

1use std::fmt;
2
3use muncher::Muncher;
4use tui::style::{Color, Style};
5use tui::text::Text;
6
7#[derive(Clone, Debug, Default)]
8pub struct CtrlChunk {
9    ctrl: Vec<String>,
10    text: String,
11}
12
13impl CtrlChunk {
14    pub fn text(text: String) -> Self {
15        Self {
16            ctrl: Vec::new(),
17            text,
18        }
19    }
20
21    pub fn parse(munch: &mut Muncher) -> Self {
22        munch.reset_peek();
23        if munch.seek(1) == Some("\x1B") {
24            munch.eat();
25        }
26
27        let text_or_ctrl = munch.eat_until(|c| *c == '\x1B').collect::<String>();
28
29        if text_or_ctrl.is_empty() {
30            return Self {
31                ctrl: Vec::new(),
32                text: String::new(),
33            };
34        }
35
36        munch.reset_peek();
37
38        if munch.seek(4) == Some("\x1B[0m") {
39            // eat the reset escape code
40            let _ = munch.eat_until(|c| *c == 'm');
41            munch.eat();
42
43            let mut ctrl_chars = Vec::new();
44            loop {
45                let ctrl_text = text_or_ctrl.splitn(2, 'm').collect::<Vec<_>>();
46
47                let mut ctrl = vec![ctrl_text[0].replace('[', "")];
48                if ctrl[0].contains(';') {
49                    ctrl = ctrl[0].split(';').map(|s| s.to_string()).collect();
50                }
51                ctrl_chars.extend(ctrl);
52                if ctrl_text[1].contains('\x1B') {
53                    continue;
54                } else {
55                    let mut text = ctrl_text[1].to_string();
56
57                    let ws = munch.eat_until(|c| !c.is_whitespace()).collect::<String>();
58                    text.push_str(&ws);
59
60                    return Self {
61                        ctrl: ctrl_chars,
62                        text,
63                    };
64                }
65            }
66        } else {
67            // un control coded text
68            Self {
69                ctrl: Vec::new(),
70                text: text_or_ctrl,
71            }
72        }
73    }
74    pub fn into_text<'a>(self) -> Text<'a> {
75        let mut style = Style::default();
76        if self.ctrl.len() > 2 {
77            match &self.ctrl[2] {
78                ctrl if ctrl == "0" => {
79                    style = style.fg(Color::Black);
80                }
81                ctrl if ctrl == "1" => {
82                    style = style.fg(Color::Red);
83                }
84                ctrl if ctrl == "2" => {
85                    style = style.fg(Color::Green);
86                }
87                ctrl if ctrl == "3" => {
88                    style = style.fg(Color::Yellow);
89                }
90                ctrl if ctrl == "4" => {
91                    style = style.fg(Color::Blue);
92                }
93                ctrl if ctrl == "5" => {
94                    style = style.fg(Color::Magenta);
95                }
96                ctrl if ctrl == "6" => {
97                    style = style.fg(Color::Cyan);
98                }
99                ctrl if ctrl == "7" => {
100                    style = style.fg(Color::White);
101                }
102                // Bright Colors
103                ctrl if ctrl == "8" => {
104                    style = style.fg(Color::DarkGray);
105                }
106                ctrl if ctrl == "9" => {
107                    style = style.fg(Color::LightRed);
108                }
109                ctrl if ctrl == "10" => {
110                    style = style.fg(Color::LightGreen);
111                }
112                ctrl if ctrl == "11" => {
113                    style = style.fg(Color::LightYellow);
114                }
115                ctrl if ctrl == "12" => {
116                    style = style.fg(Color::LightBlue);
117                }
118                ctrl if ctrl == "13" => {
119                    style = style.fg(Color::LightMagenta);
120                }
121                ctrl if ctrl == "14" => {
122                    style = style.fg(Color::LightCyan);
123                }
124                // tui has no "Bright White" color code equivalent
125                // White
126                ctrl if ctrl == "15" => {
127                    style = style.fg(Color::White);
128                }
129                // _ => panic!("control sequence not found"),
130                _ => return Text::raw(self.text),
131            }
132        } else {
133            return Text::raw(self.text);
134        }
135        Text::styled(self.text, style)
136    }
137}
138
139impl fmt::Display for CtrlChunk {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        let ctrl_code = self
142            .ctrl
143            .iter()
144            .map(|c| {
145                if c == "38;5;" {
146                    format!("\x1B]{}", c)
147                } else {
148                    format!("\x1B[{}", c)
149                }
150            })
151            .collect::<String>();
152        if ctrl_code.is_empty() && self.text.is_empty() {
153            Ok(())
154        } else {
155            write!(f, "{}{}", ctrl_code, self.text)
156        }
157    }
158}
159
160#[derive(Clone, Debug, Default)]
161pub struct CtrlChars {
162    parsed: Vec<CtrlChunk>,
163}
164
165impl fmt::Display for CtrlChars {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        let text = self
168            .parsed
169            .iter()
170            .map(CtrlChunk::to_string)
171            .collect::<String>();
172        write!(f, "{}", text)
173    }
174}
175
176impl CtrlChars {
177    pub fn parse(input: &str) -> Self {
178        let mut parsed = Vec::new();
179
180        let mut munch = Muncher::new(input);
181        let pre_ctrl = munch.eat_until(|c| *c == '\x1B').collect::<String>();
182        parsed.push(CtrlChunk::text(pre_ctrl));
183
184        loop {
185            if munch.is_done() {
186                break;
187            } else {
188                parsed.push(CtrlChunk::parse(&mut munch))
189            }
190        }
191        Self { parsed }
192    }
193
194    pub fn into_text<'a>(self) -> Vec<Text<'a>> {
195        self.parsed.into_iter().map(CtrlChunk::into_text).collect()
196    }
197}