Skip to main content

rmux_core/input/
states.rs

1//! Parser states and transition tables matching tmux `input.c:390–507`.
2
3use std::sync::LazyLock;
4
5/// The 17 parser states matching tmux exactly.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub enum InputState {
8    /// Ground state — normal printable character processing.
9    Ground,
10    /// ESC received, waiting for sequence identifier.
11    EscEnter,
12    /// ESC intermediate character(s) collected.
13    EscIntermediate,
14    /// CSI (`ESC [`) received, waiting for parameters.
15    CsiEnter,
16    /// CSI parameter bytes being collected.
17    CsiParameter,
18    /// CSI intermediate character(s) collected.
19    CsiIntermediate,
20    /// CSI with invalid parameter — absorb until final byte.
21    CsiIgnore,
22    /// DCS (`ESC P`) received, waiting for parameters.
23    DcsEnter,
24    /// DCS parameter bytes being collected.
25    DcsParameter,
26    /// DCS intermediate character(s) collected.
27    DcsIntermediate,
28    /// DCS passthrough data handler.
29    DcsHandler,
30    /// ESC received inside DCS passthrough.
31    DcsEscape,
32    /// DCS with invalid parameter — absorb until ST.
33    DcsIgnore,
34    /// OSC string being collected.
35    OscString,
36    /// APC string being collected.
37    ApcString,
38    /// Screen rename (`ESC k`) string being collected.
39    RenameString,
40    /// Unknown ESC-initiated sequence — absorb until ST.
41    ConsumeSt,
42}
43
44/// Handler action to execute for a transition.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub(crate) enum Handler {
47    None,
48    Print,
49    C0Dispatch,
50    EscDispatch,
51    CsiDispatch,
52    DcsDispatch,
53    Intermediate,
54    Parameter,
55    Input,
56    TopBitSet,
57    EndBel,
58}
59
60/// A single entry in a state transition table.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub(crate) struct TransitionEntry {
63    pub first: u8,
64    pub last: u8,
65    pub handler: Handler,
66    pub next_state: Option<InputState>,
67}
68
69/// Resolved transition from a table lookup.
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub(crate) struct Transition {
72    pub handler: Handler,
73    pub next_state: Option<InputState>,
74}
75
76impl InputState {
77    pub(crate) const COUNT: usize = 17;
78
79    pub(crate) const ALL: [Self; Self::COUNT] = [
80        Self::Ground,
81        Self::EscEnter,
82        Self::EscIntermediate,
83        Self::CsiEnter,
84        Self::CsiParameter,
85        Self::CsiIntermediate,
86        Self::CsiIgnore,
87        Self::DcsEnter,
88        Self::DcsParameter,
89        Self::DcsIntermediate,
90        Self::DcsHandler,
91        Self::DcsEscape,
92        Self::DcsIgnore,
93        Self::OscString,
94        Self::ApcString,
95        Self::RenameString,
96        Self::ConsumeSt,
97    ];
98
99    /// Returns the transition table for this state.
100    pub(crate) fn transition_table(self) -> &'static [TransitionEntry] {
101        match self {
102            Self::Ground => GROUND_TABLE,
103            Self::EscEnter => ESC_ENTER_TABLE,
104            Self::EscIntermediate => ESC_INTERMEDIATE_TABLE,
105            Self::CsiEnter => CSI_ENTER_TABLE,
106            Self::CsiParameter => CSI_PARAMETER_TABLE,
107            Self::CsiIntermediate => CSI_INTERMEDIATE_TABLE,
108            Self::CsiIgnore => CSI_IGNORE_TABLE,
109            Self::DcsEnter => DCS_ENTER_TABLE,
110            Self::DcsParameter => DCS_PARAMETER_TABLE,
111            Self::DcsIntermediate => DCS_INTERMEDIATE_TABLE,
112            Self::DcsHandler => DCS_HANDLER_TABLE,
113            Self::DcsEscape => DCS_ESCAPE_TABLE,
114            Self::DcsIgnore => DCS_IGNORE_TABLE,
115            Self::OscString => OSC_STRING_TABLE,
116            Self::ApcString => APC_STRING_TABLE,
117            Self::RenameString => RENAME_STRING_TABLE,
118            Self::ConsumeSt => CONSUME_ST_TABLE,
119        }
120    }
121
122    pub(crate) fn transition_for_byte(self, byte: u8) -> Transition {
123        TRANSITION_LUT[self.index()][byte as usize]
124    }
125
126    const fn index(self) -> usize {
127        match self {
128            Self::Ground => 0,
129            Self::EscEnter => 1,
130            Self::EscIntermediate => 2,
131            Self::CsiEnter => 3,
132            Self::CsiParameter => 4,
133            Self::CsiIntermediate => 5,
134            Self::CsiIgnore => 6,
135            Self::DcsEnter => 7,
136            Self::DcsParameter => 8,
137            Self::DcsIntermediate => 9,
138            Self::DcsHandler => 10,
139            Self::DcsEscape => 11,
140            Self::DcsIgnore => 12,
141            Self::OscString => 13,
142            Self::ApcString => 14,
143            Self::RenameString => 15,
144            Self::ConsumeSt => 16,
145        }
146    }
147}
148
149static TRANSITION_LUT: LazyLock<[[Transition; 256]; InputState::COUNT]> =
150    LazyLock::new(build_transition_lut);
151
152fn build_transition_lut() -> [[Transition; 256]; InputState::COUNT] {
153    let fallback = Transition {
154        handler: Handler::None,
155        next_state: None,
156    };
157    let mut lut = [[fallback; 256]; InputState::COUNT];
158
159    for state in InputState::ALL {
160        let row = &mut lut[state.index()];
161        for entry in state.transition_table() {
162            for byte in entry.first..=entry.last {
163                row[byte as usize] = Transition {
164                    handler: entry.handler,
165                    next_state: entry.next_state,
166                };
167            }
168        }
169    }
170
171    lut
172}
173
174// Shorthand.
175use Handler as H;
176use InputState as S;
177
178const fn e(
179    first: u8,
180    last: u8,
181    handler: Handler,
182    next_state: Option<InputState>,
183) -> TransitionEntry {
184    TransitionEntry {
185        first,
186        last,
187        handler,
188        next_state,
189    }
190}
191
192// ─── ground ────────────────────────────────────────────────────────
193static GROUND_TABLE: &[TransitionEntry] = &[
194    e(0x18, 0x18, H::C0Dispatch, Some(S::Ground)),
195    e(0x1a, 0x1a, H::C0Dispatch, Some(S::Ground)),
196    e(0x1b, 0x1b, H::None, Some(S::EscEnter)),
197    e(0x00, 0x17, H::C0Dispatch, None),
198    e(0x19, 0x19, H::C0Dispatch, None),
199    e(0x1c, 0x1f, H::C0Dispatch, None),
200    e(0x20, 0x7e, H::Print, None),
201    e(0x7f, 0x7f, H::None, None),
202    e(0x80, 0xff, H::TopBitSet, None),
203];
204
205// ─── esc_enter ─────────────────────────────────────────────────────
206static ESC_ENTER_TABLE: &[TransitionEntry] = &[
207    e(0x18, 0x18, H::C0Dispatch, Some(S::Ground)),
208    e(0x1a, 0x1a, H::C0Dispatch, Some(S::Ground)),
209    e(0x1b, 0x1b, H::None, Some(S::EscEnter)),
210    e(0x00, 0x17, H::C0Dispatch, None),
211    e(0x19, 0x19, H::C0Dispatch, None),
212    e(0x1c, 0x1f, H::C0Dispatch, None),
213    e(0x20, 0x2f, H::Intermediate, Some(S::EscIntermediate)),
214    e(0x30, 0x4f, H::EscDispatch, Some(S::Ground)),
215    e(0x50, 0x50, H::None, Some(S::DcsEnter)),
216    e(0x51, 0x57, H::EscDispatch, Some(S::Ground)),
217    e(0x58, 0x58, H::None, Some(S::ConsumeSt)),
218    e(0x59, 0x5a, H::EscDispatch, Some(S::Ground)),
219    e(0x5b, 0x5b, H::None, Some(S::CsiEnter)),
220    e(0x5c, 0x5c, H::EscDispatch, Some(S::Ground)),
221    e(0x5d, 0x5d, H::None, Some(S::OscString)),
222    e(0x5e, 0x5e, H::None, Some(S::ConsumeSt)),
223    e(0x5f, 0x5f, H::None, Some(S::ApcString)),
224    e(0x60, 0x6a, H::EscDispatch, Some(S::Ground)),
225    e(0x6b, 0x6b, H::None, Some(S::RenameString)),
226    e(0x6c, 0x7e, H::EscDispatch, Some(S::Ground)),
227    e(0x7f, 0xff, H::None, None),
228];
229
230// ─── esc_intermediate ──────────────────────────────────────────────
231static ESC_INTERMEDIATE_TABLE: &[TransitionEntry] = &[
232    e(0x18, 0x18, H::C0Dispatch, Some(S::Ground)),
233    e(0x1a, 0x1a, H::C0Dispatch, Some(S::Ground)),
234    e(0x1b, 0x1b, H::None, Some(S::EscEnter)),
235    e(0x00, 0x17, H::C0Dispatch, None),
236    e(0x19, 0x19, H::C0Dispatch, None),
237    e(0x1c, 0x1f, H::C0Dispatch, None),
238    e(0x20, 0x2f, H::Intermediate, None),
239    e(0x30, 0x7e, H::EscDispatch, Some(S::Ground)),
240    e(0x7f, 0xff, H::None, None),
241];
242
243// ─── csi_enter ─────────────────────────────────────────────────────
244static CSI_ENTER_TABLE: &[TransitionEntry] = &[
245    e(0x18, 0x18, H::C0Dispatch, Some(S::Ground)),
246    e(0x1a, 0x1a, H::C0Dispatch, Some(S::Ground)),
247    e(0x1b, 0x1b, H::None, Some(S::EscEnter)),
248    e(0x00, 0x17, H::C0Dispatch, None),
249    e(0x19, 0x19, H::C0Dispatch, None),
250    e(0x1c, 0x1f, H::C0Dispatch, None),
251    e(0x20, 0x2f, H::Intermediate, Some(S::CsiIntermediate)),
252    e(0x30, 0x39, H::Parameter, Some(S::CsiParameter)),
253    e(0x3a, 0x3a, H::Parameter, Some(S::CsiParameter)),
254    e(0x3b, 0x3b, H::Parameter, Some(S::CsiParameter)),
255    e(0x3c, 0x3f, H::Intermediate, Some(S::CsiParameter)),
256    e(0x40, 0x7e, H::CsiDispatch, Some(S::Ground)),
257    e(0x7f, 0xff, H::None, None),
258];
259
260// ─── csi_parameter ─────────────────────────────────────────────────
261static CSI_PARAMETER_TABLE: &[TransitionEntry] = &[
262    e(0x18, 0x18, H::C0Dispatch, Some(S::Ground)),
263    e(0x1a, 0x1a, H::C0Dispatch, Some(S::Ground)),
264    e(0x1b, 0x1b, H::None, Some(S::EscEnter)),
265    e(0x00, 0x17, H::C0Dispatch, None),
266    e(0x19, 0x19, H::C0Dispatch, None),
267    e(0x1c, 0x1f, H::C0Dispatch, None),
268    e(0x20, 0x2f, H::Intermediate, Some(S::CsiIntermediate)),
269    e(0x30, 0x39, H::Parameter, None),
270    e(0x3a, 0x3a, H::Parameter, None),
271    e(0x3b, 0x3b, H::Parameter, None),
272    e(0x3c, 0x3f, H::None, Some(S::CsiIgnore)),
273    e(0x40, 0x7e, H::CsiDispatch, Some(S::Ground)),
274    e(0x7f, 0xff, H::None, None),
275];
276
277// ─── csi_intermediate ──────────────────────────────────────────────
278static CSI_INTERMEDIATE_TABLE: &[TransitionEntry] = &[
279    e(0x18, 0x18, H::C0Dispatch, Some(S::Ground)),
280    e(0x1a, 0x1a, H::C0Dispatch, Some(S::Ground)),
281    e(0x1b, 0x1b, H::None, Some(S::EscEnter)),
282    e(0x00, 0x17, H::C0Dispatch, None),
283    e(0x19, 0x19, H::C0Dispatch, None),
284    e(0x1c, 0x1f, H::C0Dispatch, None),
285    e(0x20, 0x2f, H::Intermediate, None),
286    e(0x30, 0x3f, H::None, Some(S::CsiIgnore)),
287    e(0x40, 0x7e, H::CsiDispatch, Some(S::Ground)),
288    e(0x7f, 0xff, H::None, None),
289];
290
291// ─── csi_ignore ────────────────────────────────────────────────────
292static CSI_IGNORE_TABLE: &[TransitionEntry] = &[
293    e(0x18, 0x18, H::C0Dispatch, Some(S::Ground)),
294    e(0x1a, 0x1a, H::C0Dispatch, Some(S::Ground)),
295    e(0x1b, 0x1b, H::None, Some(S::EscEnter)),
296    e(0x00, 0x17, H::C0Dispatch, None),
297    e(0x19, 0x19, H::C0Dispatch, None),
298    e(0x1c, 0x1f, H::C0Dispatch, None),
299    e(0x20, 0x3f, H::None, None),
300    e(0x40, 0x7e, H::None, Some(S::Ground)),
301    e(0x7f, 0xff, H::None, None),
302];
303
304// ─── dcs_enter ─────────────────────────────────────────────────────
305static DCS_ENTER_TABLE: &[TransitionEntry] = &[
306    e(0x18, 0x18, H::C0Dispatch, Some(S::Ground)),
307    e(0x1a, 0x1a, H::C0Dispatch, Some(S::Ground)),
308    e(0x1b, 0x1b, H::None, Some(S::EscEnter)),
309    e(0x00, 0x17, H::None, None),
310    e(0x19, 0x19, H::None, None),
311    e(0x1c, 0x1f, H::None, None),
312    e(0x20, 0x2f, H::Intermediate, Some(S::DcsIntermediate)),
313    e(0x30, 0x39, H::Parameter, Some(S::DcsParameter)),
314    e(0x3a, 0x3a, H::None, Some(S::DcsIgnore)),
315    e(0x3b, 0x3b, H::Parameter, Some(S::DcsParameter)),
316    e(0x3c, 0x3f, H::Intermediate, Some(S::DcsParameter)),
317    e(0x40, 0x7e, H::Input, Some(S::DcsHandler)),
318    e(0x7f, 0xff, H::None, None),
319];
320
321// ─── dcs_parameter ─────────────────────────────────────────────────
322static DCS_PARAMETER_TABLE: &[TransitionEntry] = &[
323    e(0x18, 0x18, H::C0Dispatch, Some(S::Ground)),
324    e(0x1a, 0x1a, H::C0Dispatch, Some(S::Ground)),
325    e(0x1b, 0x1b, H::None, Some(S::EscEnter)),
326    e(0x00, 0x17, H::None, None),
327    e(0x19, 0x19, H::None, None),
328    e(0x1c, 0x1f, H::None, None),
329    e(0x20, 0x2f, H::Intermediate, Some(S::DcsIntermediate)),
330    e(0x30, 0x39, H::Parameter, None),
331    e(0x3a, 0x3a, H::None, Some(S::DcsIgnore)),
332    e(0x3b, 0x3b, H::Parameter, None),
333    e(0x3c, 0x3f, H::None, Some(S::DcsIgnore)),
334    e(0x40, 0x7e, H::Input, Some(S::DcsHandler)),
335    e(0x7f, 0xff, H::None, None),
336];
337
338// ─── dcs_intermediate ──────────────────────────────────────────────
339static DCS_INTERMEDIATE_TABLE: &[TransitionEntry] = &[
340    e(0x18, 0x18, H::C0Dispatch, Some(S::Ground)),
341    e(0x1a, 0x1a, H::C0Dispatch, Some(S::Ground)),
342    e(0x1b, 0x1b, H::None, Some(S::EscEnter)),
343    e(0x00, 0x17, H::None, None),
344    e(0x19, 0x19, H::None, None),
345    e(0x1c, 0x1f, H::None, None),
346    e(0x20, 0x2f, H::Intermediate, None),
347    e(0x30, 0x3f, H::None, Some(S::DcsIgnore)),
348    e(0x40, 0x7e, H::Input, Some(S::DcsHandler)),
349    e(0x7f, 0xff, H::None, None),
350];
351
352// ─── dcs_handler ───────────────────────────────────────────────────
353// NOTE: No INPUT_STATE_ANYWHERE — this is a deliberate tmux deviation.
354static DCS_HANDLER_TABLE: &[TransitionEntry] = &[
355    e(0x00, 0x1a, H::Input, None),
356    e(0x1b, 0x1b, H::None, Some(S::DcsEscape)),
357    e(0x1c, 0xff, H::Input, None),
358];
359
360// ─── dcs_escape ────────────────────────────────────────────────────
361// NOTE: No INPUT_STATE_ANYWHERE — deliberate tmux deviation.
362static DCS_ESCAPE_TABLE: &[TransitionEntry] = &[
363    e(0x00, 0x5b, H::Input, Some(S::DcsHandler)),
364    e(0x5c, 0x5c, H::DcsDispatch, Some(S::Ground)),
365    e(0x5d, 0xff, H::Input, Some(S::DcsHandler)),
366];
367
368// ─── dcs_ignore ────────────────────────────────────────────────────
369static DCS_IGNORE_TABLE: &[TransitionEntry] = &[
370    e(0x18, 0x18, H::C0Dispatch, Some(S::Ground)),
371    e(0x1a, 0x1a, H::C0Dispatch, Some(S::Ground)),
372    e(0x1b, 0x1b, H::None, Some(S::EscEnter)),
373    e(0x00, 0x17, H::None, None),
374    e(0x19, 0x19, H::None, None),
375    e(0x1c, 0x1f, H::None, None),
376    e(0x20, 0xff, H::None, None),
377];
378
379// ─── osc_string ────────────────────────────────────────────────────
380static OSC_STRING_TABLE: &[TransitionEntry] = &[
381    e(0x18, 0x18, H::C0Dispatch, Some(S::Ground)),
382    e(0x1a, 0x1a, H::C0Dispatch, Some(S::Ground)),
383    e(0x1b, 0x1b, H::None, Some(S::EscEnter)),
384    e(0x00, 0x06, H::None, None),
385    e(0x07, 0x07, H::EndBel, Some(S::Ground)),
386    e(0x08, 0x17, H::None, None),
387    e(0x19, 0x19, H::None, None),
388    e(0x1c, 0x1f, H::None, None),
389    e(0x20, 0xff, H::Input, None),
390];
391
392// ─── apc_string ────────────────────────────────────────────────────
393static APC_STRING_TABLE: &[TransitionEntry] = &[
394    e(0x18, 0x18, H::C0Dispatch, Some(S::Ground)),
395    e(0x1a, 0x1a, H::C0Dispatch, Some(S::Ground)),
396    e(0x1b, 0x1b, H::None, Some(S::EscEnter)),
397    e(0x00, 0x17, H::None, None),
398    e(0x19, 0x19, H::None, None),
399    e(0x1c, 0x1f, H::None, None),
400    e(0x20, 0xff, H::Input, None),
401];
402
403// ─── rename_string ─────────────────────────────────────────────────
404static RENAME_STRING_TABLE: &[TransitionEntry] = &[
405    e(0x18, 0x18, H::C0Dispatch, Some(S::Ground)),
406    e(0x1a, 0x1a, H::C0Dispatch, Some(S::Ground)),
407    e(0x1b, 0x1b, H::None, Some(S::EscEnter)),
408    e(0x00, 0x17, H::None, None),
409    e(0x19, 0x19, H::None, None),
410    e(0x1c, 0x1f, H::None, None),
411    e(0x20, 0xff, H::Input, None),
412];
413
414// ─── consume_st ────────────────────────────────────────────────────
415static CONSUME_ST_TABLE: &[TransitionEntry] = &[
416    e(0x18, 0x18, H::C0Dispatch, Some(S::Ground)),
417    e(0x1a, 0x1a, H::C0Dispatch, Some(S::Ground)),
418    e(0x1b, 0x1b, H::None, Some(S::EscEnter)),
419    e(0x00, 0x17, H::None, None),
420    e(0x19, 0x19, H::None, None),
421    e(0x1c, 0x1f, H::None, None),
422    e(0x20, 0xff, H::None, None),
423];