1use super::{
2 cursor::CharCursor,
3 graphic_rendition::{self, Sgr},
4};
5
6fn cursor_skip_space(cursor: &mut CharCursor) {
7 cursor.read_while(|c| matches!(c, '\u{0020}'..='\u{002f}'));
9}
10
11#[derive(Clone, Debug, Eq, PartialEq)]
13#[non_exhaustive]
14pub enum Escape {
15 Csi(Csi),
16}
17impl Escape {
18 const ESC: char = '\u{001b}';
19
20 fn parse(cursor: &mut CharCursor) -> Option<Self> {
21 cursor.read_char(Self::ESC)?;
22 cursor_skip_space(cursor);
23 if Csi::peek(cursor) {
24 Csi::parse(cursor).map(Self::Csi)
25 } else {
26 None
27 }
28 }
29}
30
31#[derive(Clone, Debug, Eq, PartialEq)]
33#[non_exhaustive]
34pub enum Csi {
35 Sgr(Vec<Sgr>),
36}
37impl Csi {
38 const START: char = '[';
39
40 fn peek(cursor: &mut CharCursor) -> bool {
41 cursor.peek_char(Self::START)
42 }
43
44 fn read_params<'a>(cursor: &mut CharCursor<'a>) -> Option<(char, Vec<&'a str>)> {
45 let mut start = cursor.position();
46 let mut end = start;
47 let mut params = Vec::new();
48
49 cursor.read_while(|c| {
50 match c {
51 ';' => {
52 params.push(start..end);
53 start = end + c.len_utf8();
54 end = start;
55 true
56 }
57 '\u{0030}'..='\u{003f}' => {
59 end += c.len_utf8();
60 true
61 }
62 _ => false,
63 }
64 });
65
66 if start != end {
67 params.push(start..end);
68 }
69
70 cursor_skip_space(cursor);
71
72 match cursor.read()? {
74 c @ '\u{0040}'..='\u{007e}' => {
75 let params = params.drain(..).map(|r| cursor.get(r).unwrap()).collect();
76 Some((c, params))
77 }
78 _ => None,
79 }
80 }
81
82 fn parse(cursor: &mut CharCursor) -> Option<Self> {
83 cursor.read_char(Self::START)?;
84 let (method, params) = Self::read_params(cursor)?;
85 match method {
86 'm' => {
87 let params: Vec<usize> = params
88 .iter()
89 .map(|p| p.parse())
90 .collect::<Result<_, _>>()
91 .ok()?;
92 let sgrs = graphic_rendition::parse_sgrs(params.iter().copied());
93 Some(Self::Sgr(sgrs))
94 }
95 _ => None,
96 }
97 }
98}
99
100pub fn read_next_sequence(s: &str) -> (&str, Option<Escape>, &str) {
118 s.find(Escape::ESC).map_or((s, None, ""), |index| {
119 let (pre, post) = s.split_at(index);
120
121 let mut cursor = CharCursor::new(post);
122 let esc = Escape::parse(&mut cursor);
123
124 (pre, esc, cursor.remainder())
125 })
126}
127
128#[derive(Clone, Debug, Eq, PartialEq)]
130pub enum Marker<'a> {
131 Text(&'a str),
133 Sequence(Escape),
135}
136
137#[must_use = "iterators are lazy and do nothing unless consumed"]
143#[derive(Clone, Debug)]
144pub struct MarkerIter<'a> {
145 remaining: &'a str,
146 buf: Option<Marker<'a>>,
147}
148impl<'a> MarkerIter<'a> {
149 fn new(s: &'a str) -> Self {
150 Self {
151 remaining: s,
152 buf: None,
153 }
154 }
155}
156impl<'a> Iterator for MarkerIter<'a> {
157 type Item = Marker<'a>;
158
159 fn next(&mut self) -> Option<Self::Item> {
160 if let Some(marker) = self.buf.take() {
162 return Some(marker);
163 }
164
165 while !self.remaining.is_empty() {
166 let (pre, esc, post) = read_next_sequence(&self.remaining);
167 self.remaining = post;
168
169 let esc_marker = esc.map(Marker::Sequence);
170
171 if pre.is_empty() {
172 if let Some(marker) = esc_marker {
173 return Some(marker);
174 }
175
176 continue;
179 } else {
180 self.buf = esc_marker;
182 return Some(Marker::Text(pre));
183 }
184 }
185
186 None
187 }
188}
189
190pub fn get_markers(s: &str) -> MarkerIter {
212 MarkerIter::new(s)
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use crate::graphic_rendition::ColorName;
219
220 fn parse(s: &str) -> Option<Escape> {
221 let s = s.replace("CSI ", "\u{001b} [");
222 Escape::parse(&mut CharCursor::new(&s))
223 }
224
225 fn parse_sgr(s: &str) -> Vec<Sgr> {
226 match parse(s) {
227 Some(Escape::Csi(Csi::Sgr(sgr))) => sgr,
228 _ => panic!("expected sgr"),
229 }
230 }
231
232 #[test]
233 fn parsing() {
234 assert_eq!(
235 parse_sgr("CSI 32 m"),
236 vec![Sgr::ColorFgName(ColorName::Green)]
237 );
238 assert_eq!(
239 parse_sgr("CSI 32;1m"),
240 vec![Sgr::ColorFgName(ColorName::Green), Sgr::Bold]
241 );
242 }
243
244 #[test]
245 fn marking() {
246 let markers = get_markers("Hello \u{001b} [33mWorld").collect::<Vec<_>>();
247 assert_eq!(
248 markers,
249 vec![
250 Marker::Text("Hello "),
251 Marker::Sequence(Escape::Csi(Csi::Sgr(vec![Sgr::ColorFgName(
252 ColorName::Yellow
253 )]))),
254 Marker::Text("World"),
255 ]
256 )
257 }
258}