Skip to main content

vtty/
recording.rs

1// Copyright © Fangdun Tsai <fundon@pindash.io>
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! The owned, trait-free event API: `Event` + `Recording`.
5
6use alloc::vec::Vec;
7use smallvec::SmallVec;
8
9use crate::perform::Perform;
10
11/// A parsed escape-sequence event — the dispatch as data instead of a
12/// [`crate::Perform`] callback. Mirrors `vtparse`'s `VTAction`; useful for
13/// differential testing. Params/intermediates use inline `SmallVec`s (≤6
14/// params, ≤2 intermediates allocates nothing); only OSC bodies are heap
15/// `Vec`s.
16#[derive(Debug, Clone, PartialEq, Eq)]
17#[non_exhaustive]
18pub enum Event {
19    /// A printable character (UTF-8 decoded).
20    Print(char),
21    /// A C0/C1 control byte.
22    Execute(u8),
23    /// A CSI sequence: `ESC [ <params> <intermediates> <final>`.
24    CsiDispatch {
25        /// Collected numeric parameters (inline up to 6).
26        params: SmallVec<i32, 6>,
27        /// Intermediate bytes: private markers + `0x20..0x2f`.
28        intermediates: SmallVec<u8, 2>,
29        /// The final byte selecting the command.
30        final_byte: u8,
31    },
32    /// An escape sequence: `ESC <intermediates> <byte>`.
33    EscDispatch {
34        /// Intermediate bytes (`0x20..0x2f`).
35        intermediates: SmallVec<u8, 2>,
36        /// The final byte selecting the command.
37        byte: u8,
38    },
39    /// A DCS header: `ESC P <params> <intermediates> <final>`.
40    DcsHook {
41        /// Collected numeric parameters (inline up to 6).
42        params: SmallVec<i32, 6>,
43        /// Intermediate bytes: private markers + `0x20..0x2f`.
44        intermediates: SmallVec<u8, 2>,
45        /// The final byte selecting the command.
46        final_byte: u8,
47    },
48    /// A DCS payload byte.
49    DcsPut(u8),
50    /// The DCS string ended.
51    DcsUnhook,
52    /// An OSC command ended: `ESC ] <params split on ';'> (BEL | ST)`. The
53    /// outer list is inline up to 8 params; each body is a heap `Vec<u8>`
54    /// (variable-length byte strings have to be owned).
55    OscDispatch {
56        /// Parameters split on `;`, each an owned byte string.
57        params: SmallVec<Vec<u8>, 8>,
58        /// Whether the sequence was terminated by BEL (vs ST).
59        bell_terminated: bool,
60    },
61}
62
63/// Records parser events into a `Vec<Event>` (the enum-based, trait-free API).
64///
65/// The [`crate::Perform`] callback path is the zero-allocation hot path; this
66/// recording allocates one `Event` per dispatch, for tests and tooling.
67#[derive(Debug, Default)]
68pub struct Recording {
69    /// Decoded events in dispatch order.
70    pub events: Vec<Event>,
71}
72
73impl Recording {
74    /// Compile-time default: an empty recording.
75    pub const DEFAULT: Self = Self { events: Vec::new() };
76
77    /// An empty recording (no events).
78    #[inline]
79    pub const fn new() -> Self {
80        Self::DEFAULT
81    }
82}
83
84impl Perform for Recording {
85    fn print(&mut self, c: char) {
86        self.events.push(Event::Print(c));
87    }
88    fn print_bytes(&mut self, s: &[u8]) {
89        for &b in s {
90            self.events.push(Event::Print(b as char));
91        }
92    }
93    fn execute(&mut self, byte: u8) {
94        self.events.push(Event::Execute(byte));
95    }
96    fn csi_dispatch(&mut self, params: &[i32], _sep: u16, intermediates: &[u8], final_byte: u8) {
97        self.events.push(Event::CsiDispatch {
98            params: params.iter().copied().collect(),
99            intermediates: intermediates.iter().copied().collect(),
100            final_byte,
101        });
102    }
103    fn esc_dispatch(&mut self, intermediates: &[u8], byte: u8) {
104        self.events.push(Event::EscDispatch {
105            intermediates: intermediates.iter().copied().collect(),
106            byte,
107        });
108    }
109    fn hook(&mut self, params: &[i32], intermediates: &[u8], final_byte: u8) {
110        self.events.push(Event::DcsHook {
111            params: params.iter().copied().collect(),
112            intermediates: intermediates.iter().copied().collect(),
113            final_byte,
114        });
115    }
116    fn put(&mut self, byte: u8) {
117        self.events.push(Event::DcsPut(byte));
118    }
119    fn unhook(&mut self) {
120        self.events.push(Event::DcsUnhook);
121    }
122    fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
123        self.events.push(Event::OscDispatch {
124            params: params.iter().map(|p| p.to_vec()).collect(),
125            bell_terminated,
126        });
127    }
128}