vt_push_parser/
capture.rs

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