Skip to main content

frankenterm_core/
modes.rs

1//! Terminal modes (ANSI + DEC private).
2//!
3//! This module models the mode bits that influence how the terminal engine
4//! mutates the grid (origin mode, autowrap, insert mode, etc.).
5//!
6//! The intent is to keep this as pure state with small helpers so that the
7//! VT/ANSI parser can toggle modes deterministically.
8
9use bitflags::bitflags;
10
11bitflags! {
12    /// DEC private mode flags (DECSET/DECRST, `CSI ? Pm h` / `CSI ? Pm l`).
13    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
14    pub struct DecModes: u32 {
15        /// DECCKM (mode 1): Application cursor keys.
16        const APPLICATION_CURSOR = 1 << 0;
17        /// DECOM (mode 6): Origin mode — cursor addressing relative to scroll region.
18        const ORIGIN = 1 << 1;
19        /// DECAWM (mode 7): Auto-wrap at right margin.
20        const AUTOWRAP = 1 << 2;
21        /// DECTCEM (mode 25): Text cursor enable (visible).
22        const CURSOR_VISIBLE = 1 << 3;
23        /// Mode 1000: Mouse button event tracking.
24        const MOUSE_BUTTON = 1 << 4;
25        /// Mode 1002: Mouse cell motion tracking.
26        const MOUSE_CELL_MOTION = 1 << 5;
27        /// Mode 1003: Mouse all motion tracking.
28        const MOUSE_ALL_MOTION = 1 << 6;
29        /// Mode 1004: Focus event reporting.
30        const FOCUS_EVENTS = 1 << 7;
31        /// Mode 1006: SGR extended mouse coordinates.
32        const MOUSE_SGR = 1 << 8;
33        /// Mode 1049: Alternate screen buffer (save cursor + switch + clear).
34        const ALT_SCREEN = 1 << 9;
35        /// Mode 2004: Bracketed paste.
36        const BRACKETED_PASTE = 1 << 10;
37        /// Mode 2026: Synchronized output.
38        const SYNC_OUTPUT = 1 << 11;
39    }
40}
41
42bitflags! {
43    /// ANSI standard mode flags (SM/RM, `CSI Pm h` / `CSI Pm l`).
44    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
45    pub struct AnsiModes: u8 {
46        /// IRM (mode 4): Insert/Replace mode.
47        const INSERT = 1 << 0;
48        /// LNM (mode 20): Linefeed / Newline mode.
49        const LINEFEED_NEWLINE = 1 << 1;
50    }
51}
52
53/// Combined mode state for the terminal.
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
55pub struct Modes {
56    pub dec: DecModes,
57    pub ansi: AnsiModes,
58}
59
60impl Modes {
61    /// Construct default modes (typical xterm defaults).
62    /// DECAWM and DECTCEM are ON by default.
63    #[must_use]
64    pub fn new() -> Self {
65        Self {
66            dec: DecModes::AUTOWRAP | DecModes::CURSOR_VISIBLE,
67            ansi: AnsiModes::empty(),
68        }
69    }
70
71    /// Reset all modes to power-on defaults.
72    pub fn reset(&mut self) {
73        *self = Self::new();
74    }
75
76    // ── DEC mode accessors ──────────────────────────────────────────
77
78    /// Raw access to DEC mode flags.
79    #[must_use]
80    pub fn dec_flags(&self) -> DecModes {
81        self.dec
82    }
83
84    /// Whether origin mode (DECOM) is enabled.
85    #[must_use]
86    pub fn origin_mode(&self) -> bool {
87        self.dec.contains(DecModes::ORIGIN)
88    }
89
90    /// Enable/disable origin mode.
91    pub fn set_origin_mode(&mut self, enabled: bool) {
92        self.dec.set(DecModes::ORIGIN, enabled);
93    }
94
95    /// Whether autowrap (DECAWM) is enabled.
96    #[must_use]
97    pub fn autowrap(&self) -> bool {
98        self.dec.contains(DecModes::AUTOWRAP)
99    }
100
101    /// Enable/disable autowrap.
102    pub fn set_autowrap(&mut self, enabled: bool) {
103        self.dec.set(DecModes::AUTOWRAP, enabled);
104    }
105
106    /// Whether the cursor is visible (DECTCEM).
107    #[must_use]
108    pub fn cursor_visible(&self) -> bool {
109        self.dec.contains(DecModes::CURSOR_VISIBLE)
110    }
111
112    /// Enable/disable cursor visibility.
113    pub fn set_cursor_visible(&mut self, enabled: bool) {
114        self.dec.set(DecModes::CURSOR_VISIBLE, enabled);
115    }
116
117    /// Whether insert mode (IRM) is enabled.
118    #[must_use]
119    pub fn insert_mode(&self) -> bool {
120        self.ansi.contains(AnsiModes::INSERT)
121    }
122
123    /// Enable/disable insert mode.
124    pub fn set_insert_mode(&mut self, enabled: bool) {
125        self.ansi.set(AnsiModes::INSERT, enabled);
126    }
127
128    /// Whether alt screen buffer is active.
129    #[must_use]
130    pub fn alt_screen(&self) -> bool {
131        self.dec.contains(DecModes::ALT_SCREEN)
132    }
133
134    /// Enable/disable alt screen.
135    pub fn set_alt_screen(&mut self, enabled: bool) {
136        self.dec.set(DecModes::ALT_SCREEN, enabled);
137    }
138
139    /// Whether bracketed paste is enabled.
140    #[must_use]
141    pub fn bracketed_paste(&self) -> bool {
142        self.dec.contains(DecModes::BRACKETED_PASTE)
143    }
144
145    /// Enable/disable bracketed paste.
146    pub fn set_bracketed_paste(&mut self, enabled: bool) {
147        self.dec.set(DecModes::BRACKETED_PASTE, enabled);
148    }
149
150    /// Whether focus event reporting is enabled.
151    #[must_use]
152    pub fn focus_events(&self) -> bool {
153        self.dec.contains(DecModes::FOCUS_EVENTS)
154    }
155
156    /// Enable/disable focus events.
157    pub fn set_focus_events(&mut self, enabled: bool) {
158        self.dec.set(DecModes::FOCUS_EVENTS, enabled);
159    }
160
161    /// Whether synchronized output is enabled.
162    #[must_use]
163    pub fn sync_output(&self) -> bool {
164        self.dec.contains(DecModes::SYNC_OUTPUT)
165    }
166
167    /// Enable/disable synchronized output.
168    pub fn set_sync_output(&mut self, enabled: bool) {
169        self.dec.set(DecModes::SYNC_OUTPUT, enabled);
170    }
171
172    // ── DEC mode by number ──────────────────────────────────────────
173
174    /// Set a DEC private mode by its ECMA-48 number.
175    /// Returns `true` if the mode is recognized.
176    pub fn set_dec_mode(&mut self, mode: u16, enabled: bool) -> bool {
177        let Some(flag) = Self::dec_flag_for_mode(mode) else {
178            return false;
179        };
180        self.dec.set(flag, enabled);
181        true
182    }
183
184    /// Query a DEC private mode by number.
185    ///
186    /// Returns:
187    /// - `Some(true)` if the mode is recognized and set,
188    /// - `Some(false)` if the mode is recognized and reset,
189    /// - `None` if the mode number is unknown.
190    #[must_use]
191    pub fn dec_mode(&self, mode: u16) -> Option<bool> {
192        let flag = Self::dec_flag_for_mode(mode)?;
193        Some(self.dec.contains(flag))
194    }
195
196    /// Set an ANSI standard mode by its number.
197    /// Returns `true` if the mode is recognized.
198    pub fn set_ansi_mode(&mut self, mode: u16, enabled: bool) -> bool {
199        let Some(flag) = Self::ansi_flag_for_mode(mode) else {
200            return false;
201        };
202        self.ansi.set(flag, enabled);
203        true
204    }
205
206    fn dec_flag_for_mode(mode: u16) -> Option<DecModes> {
207        let flag = match mode {
208            1 => DecModes::APPLICATION_CURSOR,
209            6 => DecModes::ORIGIN,
210            7 => DecModes::AUTOWRAP,
211            25 => DecModes::CURSOR_VISIBLE,
212            1000 => DecModes::MOUSE_BUTTON,
213            1002 => DecModes::MOUSE_CELL_MOTION,
214            1003 => DecModes::MOUSE_ALL_MOTION,
215            1004 => DecModes::FOCUS_EVENTS,
216            1006 => DecModes::MOUSE_SGR,
217            1049 => DecModes::ALT_SCREEN,
218            2004 => DecModes::BRACKETED_PASTE,
219            2026 => DecModes::SYNC_OUTPUT,
220            _ => return None,
221        };
222        Some(flag)
223    }
224
225    fn ansi_flag_for_mode(mode: u16) -> Option<AnsiModes> {
226        let flag = match mode {
227            4 => AnsiModes::INSERT,
228            20 => AnsiModes::LINEFEED_NEWLINE,
229            _ => return None,
230        };
231        Some(flag)
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    #[test]
240    fn defaults_have_autowrap_and_cursor_visible() {
241        let m = Modes::new();
242        assert!(m.autowrap());
243        assert!(m.cursor_visible());
244        assert!(!m.origin_mode());
245        assert!(!m.insert_mode());
246        assert!(!m.alt_screen());
247        assert!(!m.bracketed_paste());
248    }
249
250    #[test]
251    fn reset_restores_defaults() {
252        let mut m = Modes::new();
253        m.set_alt_screen(true);
254        m.set_insert_mode(true);
255        m.set_origin_mode(true);
256        m.reset();
257        assert!(m.autowrap());
258        assert!(m.cursor_visible());
259        assert!(!m.alt_screen());
260        assert!(!m.insert_mode());
261        assert!(!m.origin_mode());
262    }
263
264    #[test]
265    fn toggle_origin_mode() {
266        let mut m = Modes::new();
267        m.set_origin_mode(true);
268        assert!(m.origin_mode());
269        m.set_origin_mode(false);
270        assert!(!m.origin_mode());
271    }
272
273    #[test]
274    fn set_dec_mode_by_number() {
275        let mut m = Modes::new();
276        assert!(m.set_dec_mode(25, false));
277        assert!(!m.cursor_visible());
278        assert!(m.set_dec_mode(25, true));
279        assert!(m.cursor_visible());
280    }
281
282    #[test]
283    fn set_dec_mode_unknown_returns_false() {
284        let mut m = Modes::new();
285        assert!(!m.set_dec_mode(9999, true));
286    }
287
288    #[test]
289    fn dec_mode_by_number_reports_state() {
290        let mut m = Modes::new();
291        assert_eq!(m.dec_mode(7), Some(true));
292        assert_eq!(m.dec_mode(1004), Some(false));
293        assert_eq!(m.dec_mode(9999), None);
294        assert!(m.set_dec_mode(1004, true));
295        assert_eq!(m.dec_mode(1004), Some(true));
296    }
297
298    #[test]
299    fn set_ansi_mode_by_number() {
300        let mut m = Modes::new();
301        assert!(m.set_ansi_mode(4, true));
302        assert!(m.insert_mode());
303        assert!(m.set_ansi_mode(4, false));
304        assert!(!m.insert_mode());
305    }
306
307    #[test]
308    fn mouse_modes() {
309        let mut m = Modes::new();
310        m.set_dec_mode(1000, true);
311        assert!(m.dec.contains(DecModes::MOUSE_BUTTON));
312        m.set_dec_mode(1006, true);
313        assert!(m.dec.contains(DecModes::MOUSE_SGR));
314    }
315
316    #[test]
317    fn alt_screen_and_bracketed_paste() {
318        let mut m = Modes::new();
319        m.set_alt_screen(true);
320        m.set_bracketed_paste(true);
321        assert!(m.alt_screen());
322        assert!(m.bracketed_paste());
323    }
324
325    #[test]
326    fn sync_output() {
327        let mut m = Modes::new();
328        assert!(!m.sync_output());
329        m.set_sync_output(true);
330        assert!(m.sync_output());
331    }
332
333    // --- Default trait vs new() ---
334
335    #[test]
336    fn default_has_all_modes_off() {
337        let m = Modes::default();
338        assert!(!m.autowrap());
339        assert!(!m.cursor_visible());
340        assert!(!m.origin_mode());
341        assert!(!m.alt_screen());
342        assert!(!m.insert_mode());
343        assert!(!m.bracketed_paste());
344        assert!(!m.focus_events());
345        assert!(!m.sync_output());
346    }
347
348    #[test]
349    fn new_differs_from_default() {
350        let d = Modes::default();
351        let n = Modes::new();
352        assert_ne!(d, n);
353        assert!(n.autowrap());
354        assert!(n.cursor_visible());
355        assert!(!d.autowrap());
356        assert!(!d.cursor_visible());
357    }
358
359    // --- All DEC modes by number ---
360
361    #[test]
362    fn dec_mode_all_recognized_numbers() {
363        let recognized = [1, 6, 7, 25, 1000, 1002, 1003, 1004, 1006, 1049, 2004, 2026];
364        let mut m = Modes::default();
365        for &mode in &recognized {
366            assert!(
367                m.set_dec_mode(mode, true),
368                "mode {mode} should be recognized"
369            );
370            assert_eq!(m.dec_mode(mode), Some(true), "mode {mode} should be set");
371        }
372    }
373
374    #[test]
375    fn dec_mode_unrecognized_numbers() {
376        let mut m = Modes::new();
377        for mode in [
378            0,
379            2,
380            5,
381            8,
382            999,
383            1001,
384            1005,
385            1050,
386            2005,
387            2025,
388            2027,
389            u16::MAX,
390        ] {
391            assert!(!m.set_dec_mode(mode, true), "mode {mode} should be unknown");
392            assert_eq!(m.dec_mode(mode), None, "mode {mode} query should be None");
393        }
394    }
395
396    // --- All ANSI modes by number ---
397
398    #[test]
399    fn ansi_mode_all_recognized() {
400        let mut m = Modes::default();
401        assert!(m.set_ansi_mode(4, true));
402        assert!(m.insert_mode());
403        assert!(m.set_ansi_mode(20, true));
404        assert!(m.ansi.contains(AnsiModes::LINEFEED_NEWLINE));
405    }
406
407    #[test]
408    fn ansi_mode_unrecognized() {
409        let mut m = Modes::new();
410        assert!(!m.set_ansi_mode(0, true));
411        assert!(!m.set_ansi_mode(1, true));
412        assert!(!m.set_ansi_mode(21, true));
413        assert!(!m.set_ansi_mode(u16::MAX, true));
414    }
415
416    // --- Accessor coverage ---
417
418    #[test]
419    fn focus_events_toggle() {
420        let mut m = Modes::new();
421        assert!(!m.focus_events());
422        m.set_focus_events(true);
423        assert!(m.focus_events());
424        m.set_focus_events(false);
425        assert!(!m.focus_events());
426    }
427
428    #[test]
429    fn dec_flags_accessor() {
430        let m = Modes::new();
431        let flags = m.dec_flags();
432        assert!(flags.contains(DecModes::AUTOWRAP));
433        assert!(flags.contains(DecModes::CURSOR_VISIBLE));
434        assert!(!flags.contains(DecModes::ORIGIN));
435    }
436
437    #[test]
438    fn autowrap_toggle() {
439        let mut m = Modes::new();
440        assert!(m.autowrap());
441        m.set_autowrap(false);
442        assert!(!m.autowrap());
443        m.set_autowrap(true);
444        assert!(m.autowrap());
445    }
446
447    #[test]
448    fn cursor_visible_toggle() {
449        let mut m = Modes::new();
450        assert!(m.cursor_visible());
451        m.set_cursor_visible(false);
452        assert!(!m.cursor_visible());
453        m.set_cursor_visible(true);
454        assert!(m.cursor_visible());
455    }
456
457    #[test]
458    fn bracketed_paste_toggle() {
459        let mut m = Modes::new();
460        assert!(!m.bracketed_paste());
461        m.set_bracketed_paste(true);
462        assert!(m.bracketed_paste());
463        m.set_bracketed_paste(false);
464        assert!(!m.bracketed_paste());
465    }
466
467    #[test]
468    fn sync_output_toggle() {
469        let mut m = Modes::new();
470        assert!(!m.sync_output());
471        m.set_sync_output(true);
472        assert!(m.sync_output());
473        m.set_sync_output(false);
474        assert!(!m.sync_output());
475    }
476
477    // --- Idempotency ---
478
479    #[test]
480    fn double_set_is_idempotent() {
481        let mut m = Modes::new();
482        m.set_origin_mode(true);
483        m.set_origin_mode(true);
484        assert!(m.origin_mode());
485    }
486
487    #[test]
488    fn double_clear_is_idempotent() {
489        let mut m = Modes::new();
490        m.set_origin_mode(false);
491        m.set_origin_mode(false);
492        assert!(!m.origin_mode());
493    }
494
495    // --- Orthogonality ---
496
497    #[test]
498    fn setting_one_dec_mode_does_not_affect_others() {
499        let mut m = Modes::new();
500        let before = m.dec;
501        m.set_focus_events(true);
502        // Only FOCUS_EVENTS bit should have changed
503        let diff = m.dec ^ before;
504        assert_eq!(diff, DecModes::FOCUS_EVENTS);
505    }
506
507    #[test]
508    fn setting_ansi_mode_does_not_affect_dec() {
509        let mut m = Modes::new();
510        let dec_before = m.dec;
511        m.set_insert_mode(true);
512        assert_eq!(m.dec, dec_before);
513    }
514
515    #[test]
516    fn setting_dec_mode_does_not_affect_ansi() {
517        let mut m = Modes::new();
518        let ansi_before = m.ansi;
519        m.set_alt_screen(true);
520        assert_eq!(m.ansi, ansi_before);
521    }
522
523    // --- Multiple modes simultaneous ---
524
525    #[test]
526    fn all_dec_modes_enabled_simultaneously() {
527        let mut m = Modes::default();
528        for &mode in &[1, 6, 7, 25, 1000, 1002, 1003, 1004, 1006, 1049, 2004, 2026] {
529            m.set_dec_mode(mode, true);
530        }
531        // Every recognized mode should be set
532        for &mode in &[1, 6, 7, 25, 1000, 1002, 1003, 1004, 1006, 1049, 2004, 2026] {
533            assert_eq!(m.dec_mode(mode), Some(true), "mode {mode} should be on");
534        }
535    }
536
537    #[test]
538    fn all_ansi_modes_enabled() {
539        let mut m = Modes::default();
540        m.set_ansi_mode(4, true);
541        m.set_ansi_mode(20, true);
542        assert!(m.insert_mode());
543        assert!(m.ansi.contains(AnsiModes::LINEFEED_NEWLINE));
544    }
545
546    // --- Mouse mode mutual exclusivity check ---
547
548    #[test]
549    fn mouse_modes_are_independent_bits() {
550        let mut m = Modes::new();
551        m.set_dec_mode(1000, true);
552        m.set_dec_mode(1002, true);
553        m.set_dec_mode(1003, true);
554        assert!(m.dec.contains(DecModes::MOUSE_BUTTON));
555        assert!(m.dec.contains(DecModes::MOUSE_CELL_MOTION));
556        assert!(m.dec.contains(DecModes::MOUSE_ALL_MOTION));
557        // Disabling one doesn't affect others
558        m.set_dec_mode(1002, false);
559        assert!(m.dec.contains(DecModes::MOUSE_BUTTON));
560        assert!(!m.dec.contains(DecModes::MOUSE_CELL_MOTION));
561        assert!(m.dec.contains(DecModes::MOUSE_ALL_MOTION));
562    }
563
564    #[test]
565    fn reset_from_all_modes_enabled() {
566        let mut m = Modes::default();
567        for &mode in &[1, 6, 7, 25, 1000, 1002, 1003, 1004, 1006, 1049, 2004, 2026] {
568            m.set_dec_mode(mode, true);
569        }
570        m.set_ansi_mode(4, true);
571        m.set_ansi_mode(20, true);
572        m.reset();
573        // Should be back to new() defaults
574        assert_eq!(m, Modes::new());
575    }
576
577    // --- Application cursor ---
578
579    #[test]
580    fn application_cursor_via_dec_mode() {
581        let mut m = Modes::new();
582        assert_eq!(m.dec_mode(1), Some(false));
583        m.set_dec_mode(1, true);
584        assert!(m.dec.contains(DecModes::APPLICATION_CURSOR));
585        assert_eq!(m.dec_mode(1), Some(true));
586    }
587}