vt_push_parser/
signature.rs

1//! Escape sequence signature matching.
2
3use std::ops::Range;
4
5use crate::event::{VTEvent, VTIntermediate};
6
7const CSI: u8 = b'[';
8const SS3: u8 = b'O';
9const DCS: u8 = b'P';
10const OSC: u8 = b']';
11
12/// A signature for an escape sequence.
13pub struct VTEscapeSignature {
14    pub prefix: u8,
15    pub private: Option<u8>,
16    pub intermediates: VTIntermediate,
17    pub final_byte: u8,
18    pub param_count: Range<u8>,
19}
20
21impl VTEscapeSignature {
22    pub const fn with_private(self, private: u8) -> Self {
23        Self {
24            private: Some(private),
25            ..self
26        }
27    }
28
29    pub const fn with_intermediate(self, intermediate: u8) -> Self {
30        Self {
31            intermediates: VTIntermediate::one(intermediate),
32            ..self
33        }
34    }
35
36    pub const fn with_params_exact(self, param_count: u8) -> Self {
37        Self {
38            param_count: param_count..param_count + 1,
39            ..self
40        }
41    }
42
43    pub const fn with_params_count(self, param_count: Range<u8>) -> Self {
44        Self {
45            param_count,
46            ..self
47        }
48    }
49
50    pub const fn csi(final_byte: u8) -> Self {
51        Self {
52            prefix: CSI,
53            final_byte,
54            param_count: 0..1,
55            intermediates: VTIntermediate::empty(),
56            private: None,
57        }
58    }
59
60    pub const fn ss3(final_byte: u8) -> Self {
61        Self {
62            prefix: SS3,
63            private: None,
64            intermediates: VTIntermediate::empty(),
65            final_byte,
66            param_count: 0..1,
67        }
68    }
69
70    pub const fn dcs(final_byte: u8) -> Self {
71        Self {
72            prefix: DCS,
73            private: None,
74            intermediates: VTIntermediate::empty(),
75            final_byte,
76            param_count: 0..1,
77        }
78    }
79
80    pub const fn osc(final_byte: u8) -> Self {
81        Self {
82            prefix: OSC,
83            private: None,
84            intermediates: VTIntermediate::empty(),
85            final_byte,
86            param_count: 0..1,
87        }
88    }
89
90    pub fn matches(&self, entry: &VTEvent) -> bool {
91        // TODO: const
92        match entry {
93            VTEvent::Esc(esc) => {
94                self.prefix == 27
95                    && self.final_byte == esc.final_byte
96                    && self.intermediates.const_eq(&esc.intermediates)
97            }
98            VTEvent::Csi(csi) => {
99                self.prefix == CSI
100                    && self.final_byte == csi.final_byte
101                    && self.intermediates.const_eq(&csi.intermediates)
102                    && self.const_private_eq(&csi.private)
103                    && self.const_contains(csi.params.len())
104            }
105            VTEvent::DcsStart(dcs_start) => {
106                self.prefix == DCS
107                    && self.final_byte == dcs_start.final_byte
108                    && self.intermediates.const_eq(&dcs_start.intermediates)
109                    && self.private == dcs_start.private
110                    && self.const_contains(dcs_start.params.len())
111            }
112            _ => false,
113        }
114    }
115
116    const fn const_private_eq(&self, other: &Option<u8>) -> bool {
117        match (self.private, other) {
118            (Some(a), Some(b)) => a == *b,
119            (None, None) => true,
120            _ => false,
121        }
122    }
123
124    fn const_contains(&self, len: usize) -> bool {
125        // TODO: const
126        self.param_count.contains(&(len as u8))
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133    use crate::VTPushParser;
134
135    const CURSOR_POSITION_REPORT: VTEscapeSignature =
136        VTEscapeSignature::csi(b'n').with_params_exact(2);
137
138    #[test]
139    fn test_matches() {
140        let input = b"\x1b[1;2n";
141        let mut found = false;
142        VTPushParser::decode_buffer(input, |event| {
143            assert!(!found);
144            found = true;
145            assert!(CURSOR_POSITION_REPORT.matches(&event));
146        });
147        assert!(found);
148    }
149}