vt_push_parser/
capture.rs

1//! Raw-input-capturing push parser.
2
3use crate::{VT_PARSER_INTEREST_DEFAULT, VTEvent, VTPushParser};
4
5/// The type of capture mode to use after this event has been emitted.
6///
7/// The data will be emitted as a [`VTInputEvent::Captured`] event.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum VTInputCapture {
10    /// No capture mode. This must also be returned from any
11    /// [`VTInputEvent::Captured`] event.
12    None,
13    /// Capture a fixed number of bytes.
14    Count(usize),
15    /// Capture a fixed number of UTF-8 chars.
16    CountUtf8(usize),
17    /// Capture bytes until a terminator is found.
18    Terminator(&'static [u8]),
19}
20
21#[cfg_attr(feature = "serde", derive(serde::Serialize))]
22#[derive(Debug)]
23pub enum VTCaptureEvent<'a> {
24    VTEvent(VTEvent<'a>),
25    Capture(&'a [u8]),
26    CaptureEnd,
27}
28
29enum VTCaptureInternal {
30    None,
31    Count(usize),
32    CountUtf8(usize),
33    Terminator(&'static [u8], usize),
34}
35
36impl VTCaptureInternal {
37    fn feed<'a>(&mut self, input: &mut &'a [u8]) -> Option<&'a [u8]> {
38        match self {
39            VTCaptureInternal::None => None,
40            VTCaptureInternal::Count(count) => {
41                if input.len() >= *count {
42                    let (capture, rest) = input.split_at(*count);
43                    *input = rest;
44                    *self = VTCaptureInternal::None;
45                    Some(capture)
46                } else {
47                    None
48                }
49            }
50            VTCaptureInternal::CountUtf8(count) => {
51                // Count UTF-8 characters, not bytes
52                let mut chars_found = 0;
53                let mut bytes_consumed = 0;
54
55                for (i, &byte) in input.iter().enumerate() {
56                    // Check if this is the start of a new UTF-8 character
57                    if byte & 0xC0 != 0x80 {
58                        // Not a continuation byte
59                        chars_found += 1;
60                        if chars_found == *count {
61                            // We found the nth character, now we need to find where it ends
62                            // by consuming all its continuation bytes
63                            let mut j = i + 1;
64                            while j < input.len() && input[j] & 0xC0 == 0x80 {
65                                j += 1;
66                            }
67                            bytes_consumed = j;
68                            break;
69                        }
70                    }
71                }
72
73                if chars_found == *count {
74                    let (capture, rest) = input.split_at(bytes_consumed);
75                    *input = rest;
76                    *self = VTCaptureInternal::None;
77                    Some(capture)
78                } else {
79                    None
80                }
81            }
82            VTCaptureInternal::Terminator(terminator, _found) => {
83                // For now, use the simple approach that works
84                // TODO: Implement proper partial matching across calls
85                if let Some(pos) = input
86                    .windows(terminator.len())
87                    .position(|window| window == *terminator)
88                {
89                    let (capture, rest) = input.split_at(pos);
90                    *input = &rest[terminator.len()..]; // Skip the terminator
91                    *self = VTCaptureInternal::None;
92                    Some(capture)
93                } else {
94                    // No complete terminator found, emit all input and keep looking
95                    if !input.is_empty() {
96                        let (capture, rest) = input.split_at(input.len());
97                        *input = rest;
98                        Some(capture)
99                    } else {
100                        None
101                    }
102                }
103            }
104        }
105    }
106}
107
108/// A parser that allows for "capturing" of input data, ie: temporarily
109/// transferring control of the parser to unparsed data events.
110///
111/// This functions in the same way as [`VTPushParser`], but emits
112/// [`VTCaptureEvent`]s instead of [`VTEvent`]s.
113pub struct VTCapturePushParser<const INTEREST: u8 = VT_PARSER_INTEREST_DEFAULT> {
114    parser: VTPushParser<INTEREST>,
115    capture: VTCaptureInternal,
116}
117
118impl Default for VTCapturePushParser {
119    fn default() -> Self {
120        Self::new()
121    }
122}
123
124impl VTCapturePushParser {
125    pub const fn new() -> VTCapturePushParser {
126        VTCapturePushParser::new_with_interest::<VT_PARSER_INTEREST_DEFAULT>()
127    }
128
129    pub const fn new_with_interest<const INTEREST: u8>() -> VTCapturePushParser<INTEREST> {
130        VTCapturePushParser::new_with()
131    }
132}
133
134impl<const INTEREST: u8> VTCapturePushParser<INTEREST> {
135    const fn new_with() -> Self {
136        Self {
137            parser: VTPushParser::new_with(),
138            capture: VTCaptureInternal::None,
139        }
140    }
141
142    pub fn is_ground(&self) -> bool {
143        self.parser.is_ground()
144    }
145
146    pub fn idle(&mut self) -> Option<VTCaptureEvent<'static>> {
147        self.parser.idle().map(VTCaptureEvent::VTEvent)
148    }
149
150    pub fn feed_with<'this, 'input, F: for<'any> FnMut(VTCaptureEvent<'any>) -> VTInputCapture>(
151        &'this mut self,
152        mut input: &'input [u8],
153        cb: &mut F,
154    ) {
155        while !input.is_empty() {
156            match &mut self.capture {
157                VTCaptureInternal::None => {
158                    // Normal parsing mode - feed to the underlying parser
159                    let count = self.parser.feed_with_abortable(input, &mut |event| {
160                        let capture_mode = cb(VTCaptureEvent::VTEvent(event));
161                        match capture_mode {
162                            VTInputCapture::None => {
163                                // Stay in normal mode
164                            }
165                            VTInputCapture::Count(count) => {
166                                self.capture = VTCaptureInternal::Count(count);
167                            }
168                            VTInputCapture::CountUtf8(count) => {
169                                self.capture = VTCaptureInternal::CountUtf8(count);
170                            }
171                            VTInputCapture::Terminator(terminator) => {
172                                self.capture = VTCaptureInternal::Terminator(terminator, 0);
173                            }
174                        }
175                        false // Don't abort parsing
176                    });
177
178                    input = &input[count..];
179                }
180                capture => {
181                    // Capture mode - collect data until capture is complete
182                    if let Some(captured_data) = capture.feed(&mut input) {
183                        cb(VTCaptureEvent::Capture(captured_data));
184                    }
185
186                    // Check if capture is complete
187                    if matches!(self.capture, VTCaptureInternal::None) {
188                        cb(VTCaptureEvent::CaptureEnd);
189                    }
190                }
191            }
192        }
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_capture_paste() {
202        let mut output = String::new();
203        let mut parser = VTCapturePushParser::new();
204        parser.feed_with(b"raw\x1b[200~paste\x1b[201~raw", &mut |event| {
205            output.push_str(&format!("{event:?}\n"));
206            match event {
207                VTCaptureEvent::VTEvent(VTEvent::Csi(csi)) => {
208                    if csi.params.try_parse::<usize>(0).unwrap_or(0) == 200 {
209                        VTInputCapture::Terminator(b"\x1b[201~")
210                    } else {
211                        VTInputCapture::None
212                    }
213                }
214                _ => VTInputCapture::None,
215            }
216        });
217        assert_eq!(
218            output.trim(),
219            r#"
220VTEvent(Raw('raw'))
221VTEvent(Csi(, '200', '', '~'))
222Capture([112, 97, 115, 116, 101])
223CaptureEnd
224VTEvent(Raw('raw'))
225"#
226            .trim()
227        );
228    }
229
230    #[test]
231    fn test_capture_count() {
232        let mut output = String::new();
233        let mut parser = VTCapturePushParser::new();
234        parser.feed_with(b"raw\x1b[Xpaste\x1b[Yraw", &mut |event| {
235            output.push_str(&format!("{event:?}\n"));
236            match event {
237                VTCaptureEvent::VTEvent(VTEvent::Csi(csi)) => {
238                    if csi.final_byte == b'X' {
239                        VTInputCapture::Count(5)
240                    } else {
241                        VTInputCapture::None
242                    }
243                }
244                _ => VTInputCapture::None,
245            }
246        });
247        assert_eq!(
248            output.trim(),
249            r#"
250VTEvent(Raw('raw'))
251VTEvent(Csi(, '', 'X'))
252Capture([112, 97, 115, 116, 101])
253CaptureEnd
254VTEvent(Csi(, '', 'Y'))
255VTEvent(Raw('raw'))
256"#
257            .trim()
258        );
259    }
260
261    #[test]
262    fn test_capture_count_utf8_but_ascii() {
263        let mut output = String::new();
264        let mut parser = VTCapturePushParser::new();
265        parser.feed_with(b"raw\x1b[Xpaste\x1b[Yraw", &mut |event| {
266            output.push_str(&format!("{event:?}\n"));
267            match event {
268                VTCaptureEvent::VTEvent(VTEvent::Csi(csi)) => {
269                    if csi.final_byte == b'X' {
270                        VTInputCapture::CountUtf8(5)
271                    } else {
272                        VTInputCapture::None
273                    }
274                }
275                _ => VTInputCapture::None,
276            }
277        });
278        assert_eq!(
279            output.trim(),
280            r#"
281VTEvent(Raw('raw'))
282VTEvent(Csi(, '', 'X'))
283Capture([112, 97, 115, 116, 101])
284CaptureEnd
285VTEvent(Csi(, '', 'Y'))
286VTEvent(Raw('raw'))
287"#
288            .trim()
289        );
290    }
291
292    #[test]
293    fn test_capture_count_utf8() {
294        let mut output = String::new();
295        let mut parser = VTCapturePushParser::new();
296        let input = "raw\u{001b}[X🤖🦕✅😀🕓\u{001b}[Yraw".as_bytes();
297        parser.feed_with(input, &mut |event| {
298            output.push_str(&format!("{event:?}\n"));
299            match event {
300                VTCaptureEvent::VTEvent(VTEvent::Csi(csi)) => {
301                    if csi.final_byte == b'X' {
302                        VTInputCapture::CountUtf8(5)
303                    } else {
304                        VTInputCapture::None
305                    }
306                }
307                _ => VTInputCapture::None,
308            }
309        });
310        assert_eq!(output.trim(), r#"
311VTEvent(Raw('raw'))
312VTEvent(Csi(, '', 'X'))
313Capture([240, 159, 164, 150, 240, 159, 166, 149, 226, 156, 133, 240, 159, 152, 128, 240, 159, 149, 147])
314CaptureEnd
315VTEvent(Csi(, '', 'Y'))
316VTEvent(Raw('raw'))
317"#.trim());
318    }
319
320    #[test]
321    fn test_capture_terminator_partial_match() {
322        let mut output = String::new();
323        let mut parser = VTCapturePushParser::new();
324
325        parser.feed_with(b"start\x1b[200~part\x1b[201ial\x1b[201~end", &mut |event| {
326            output.push_str(&format!("{event:?}\n"));
327            match event {
328                VTCaptureEvent::VTEvent(VTEvent::Csi(csi)) => {
329                    if csi.final_byte == b'~'
330                        && csi.params.try_parse::<usize>(0).unwrap_or(0) == 200
331                    {
332                        VTInputCapture::Terminator(b"\x1b[201~")
333                    } else {
334                        VTInputCapture::None
335                    }
336                }
337                _ => VTInputCapture::None,
338            }
339        });
340
341        assert_eq!(
342            output.trim(),
343            r#"VTEvent(Raw('start'))
344VTEvent(Csi(, '200', '', '~'))
345Capture([112, 97, 114, 116, 27, 91, 50, 48, 49, 105, 97, 108])
346CaptureEnd
347VTEvent(Raw('end'))"#
348        );
349    }
350}