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 VTCapturePushParser {
119    pub const fn new() -> VTCapturePushParser {
120        VTCapturePushParser::new_with_interest::<VT_PARSER_INTEREST_DEFAULT>()
121    }
122
123    pub const fn new_with_interest<const INTEREST: u8>() -> VTCapturePushParser<INTEREST> {
124        VTCapturePushParser::new_with()
125    }
126}
127
128impl<const INTEREST: u8> VTCapturePushParser<INTEREST> {
129    const fn new_with() -> Self {
130        Self {
131            parser: VTPushParser::new_with(),
132            capture: VTCaptureInternal::None,
133        }
134    }
135
136    pub fn is_ground(&self) -> bool {
137        self.parser.is_ground()
138    }
139
140    pub fn idle(&mut self) -> Option<VTCaptureEvent<'static>> {
141        self.parser
142            .idle()
143            .map(|event| VTCaptureEvent::VTEvent(event))
144    }
145
146    pub fn feed_with<'this, 'input, F: for<'any> FnMut(VTCaptureEvent<'any>) -> VTInputCapture>(
147        &'this mut self,
148        mut input: &'input [u8],
149        cb: &mut F,
150    ) {
151        while !input.is_empty() {
152            match &mut self.capture {
153                VTCaptureInternal::None => {
154                    // Normal parsing mode - feed to the underlying parser
155                    let count = self.parser.feed_with_abortable(input, &mut |event| {
156                        let capture_mode = cb(VTCaptureEvent::VTEvent(event));
157                        match capture_mode {
158                            VTInputCapture::None => {
159                                // Stay in normal mode
160                            }
161                            VTInputCapture::Count(count) => {
162                                self.capture = VTCaptureInternal::Count(count);
163                            }
164                            VTInputCapture::CountUtf8(count) => {
165                                self.capture = VTCaptureInternal::CountUtf8(count);
166                            }
167                            VTInputCapture::Terminator(terminator) => {
168                                self.capture = VTCaptureInternal::Terminator(terminator, 0);
169                            }
170                        }
171                        false // Don't abort parsing
172                    });
173
174                    input = &input[count..];
175                }
176                capture => {
177                    // Capture mode - collect data until capture is complete
178                    if let Some(captured_data) = capture.feed(&mut input) {
179                        cb(VTCaptureEvent::Capture(captured_data));
180                    }
181
182                    // Check if capture is complete
183                    if matches!(self.capture, VTCaptureInternal::None) {
184                        cb(VTCaptureEvent::CaptureEnd);
185                    }
186                }
187            }
188        }
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195
196    #[test]
197    fn test_capture_paste() {
198        let mut output = String::new();
199        let mut parser = VTCapturePushParser::new();
200        parser.feed_with(b"raw\x1b[200~paste\x1b[201~raw", &mut |event| {
201            output.push_str(&format!("{:?}\n", event));
202            match event {
203                VTCaptureEvent::VTEvent(VTEvent::Csi(csi)) => {
204                    if csi.params.try_parse::<usize>(0).unwrap_or(0) == 200 {
205                        VTInputCapture::Terminator(b"\x1b[201~")
206                    } else {
207                        VTInputCapture::None
208                    }
209                }
210                _ => VTInputCapture::None,
211            }
212        });
213        assert_eq!(
214            output.trim(),
215            r#"
216VTEvent(Raw('raw'))
217VTEvent(Csi(, '200', '', '~'))
218Capture([112, 97, 115, 116, 101])
219CaptureEnd
220VTEvent(Raw('raw'))
221"#
222            .trim()
223        );
224    }
225
226    #[test]
227    fn test_capture_count() {
228        let mut output = String::new();
229        let mut parser = VTCapturePushParser::new();
230        parser.feed_with(b"raw\x1b[Xpaste\x1b[Yraw", &mut |event| {
231            output.push_str(&format!("{:?}\n", event));
232            match event {
233                VTCaptureEvent::VTEvent(VTEvent::Csi(csi)) => {
234                    if csi.final_byte == b'X' {
235                        VTInputCapture::Count(5)
236                    } else {
237                        VTInputCapture::None
238                    }
239                }
240                _ => VTInputCapture::None,
241            }
242        });
243        assert_eq!(
244            output.trim(),
245            r#"
246VTEvent(Raw('raw'))
247VTEvent(Csi(, '', 'X'))
248Capture([112, 97, 115, 116, 101])
249CaptureEnd
250VTEvent(Csi(, '', 'Y'))
251VTEvent(Raw('raw'))
252"#
253            .trim()
254        );
255    }
256
257    #[test]
258    fn test_capture_count_utf8_but_ascii() {
259        let mut output = String::new();
260        let mut parser = VTCapturePushParser::new();
261        parser.feed_with(b"raw\x1b[Xpaste\x1b[Yraw", &mut |event| {
262            output.push_str(&format!("{:?}\n", event));
263            match event {
264                VTCaptureEvent::VTEvent(VTEvent::Csi(csi)) => {
265                    if csi.final_byte == b'X' {
266                        VTInputCapture::CountUtf8(5)
267                    } else {
268                        VTInputCapture::None
269                    }
270                }
271                _ => VTInputCapture::None,
272            }
273        });
274        assert_eq!(
275            output.trim(),
276            r#"
277VTEvent(Raw('raw'))
278VTEvent(Csi(, '', 'X'))
279Capture([112, 97, 115, 116, 101])
280CaptureEnd
281VTEvent(Csi(, '', 'Y'))
282VTEvent(Raw('raw'))
283"#
284            .trim()
285        );
286    }
287
288    #[test]
289    fn test_capture_count_utf8() {
290        let mut output = String::new();
291        let mut parser = VTCapturePushParser::new();
292        let input = "raw\u{001b}[X🤖🦕✅😀🕓\u{001b}[Yraw".as_bytes();
293        parser.feed_with(input, &mut |event| {
294            output.push_str(&format!("{:?}\n", event));
295            match event {
296                VTCaptureEvent::VTEvent(VTEvent::Csi(csi)) => {
297                    if csi.final_byte == b'X' {
298                        VTInputCapture::CountUtf8(5)
299                    } else {
300                        VTInputCapture::None
301                    }
302                }
303                _ => VTInputCapture::None,
304            }
305        });
306        assert_eq!(output.trim(), r#"
307VTEvent(Raw('raw'))
308VTEvent(Csi(, '', 'X'))
309Capture([240, 159, 164, 150, 240, 159, 166, 149, 226, 156, 133, 240, 159, 152, 128, 240, 159, 149, 147])
310CaptureEnd
311VTEvent(Csi(, '', 'Y'))
312VTEvent(Raw('raw'))
313"#.trim());
314    }
315
316    #[test]
317    fn test_capture_terminator_partial_match() {
318        let mut output = String::new();
319        let mut parser = VTCapturePushParser::new();
320
321        parser.feed_with(b"start\x1b[200~part\x1b[201ial\x1b[201~end", &mut |event| {
322            output.push_str(&format!("{:?}\n", event));
323            match event {
324                VTCaptureEvent::VTEvent(VTEvent::Csi(csi)) => {
325                    if csi.final_byte == b'~'
326                        && csi.params.try_parse::<usize>(0).unwrap_or(0) == 200
327                    {
328                        VTInputCapture::Terminator(b"\x1b[201~")
329                    } else {
330                        VTInputCapture::None
331                    }
332                }
333                _ => VTInputCapture::None,
334            }
335        });
336
337        assert_eq!(
338            output.trim(),
339            r#"VTEvent(Raw('start'))
340VTEvent(Csi(, '200', '', '~'))
341Capture([112, 97, 114, 116, 27, 91, 50, 48, 49, 105, 97, 108])
342CaptureEnd
343VTEvent(Raw('end'))"#
344        );
345    }
346}