Skip to main content

uncurses/ansi/
mode.rs

1//! ANSI and DEC private terminal mode management.
2//!
3//! ## Category
4//!
5//! This module encodes SM/RM, DECSET/DECRST, DECRQM, and report responses for
6//! terminal modes. It also names commonly used modes such as alternate screen,
7//! mouse tracking, bracketed paste, synchronized output, and in-band resize.
8//!
9//! ## CSI anatomy
10//!
11//! Standard ANSI modes omit a private prefix; DEC private modes insert `?` after
12//! CSI. Final byte `h` sets a mode, `l` resets it, `$p` requests state, and `$y`
13//! reports state.
14//!
15//! ```text
16//! ESC [  ?  2 0 4 8  h        CSI ? 2048 h  (enable mode 2048)
17//! ──┬── ─┬─ ───┬──── ┬
18//!  CSI  priv  params final
19//! ```
20//!
21//! ## Batching conventions
22//!
23//! [`write_set_mode`] and [`write_reset_mode`] split mixed mode slices into DEC
24//! and ANSI sequences because the prefixes differ. An empty slice emits nothing.
25
26use std::io::{self, Write};
27
28/// A terminal mode addressable by ANSI SM/RM or DECSET/DECRST.
29///
30/// [`Mode::Ansi`] writes ordinary CSI mode numbers such as `ESC [ 4 h`;
31/// [`Mode::Dec`] writes private mode numbers with `?`, such as
32/// `ESC [ ? 1049 h`.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34pub enum Mode {
35    /// Standard ANSI mode number.
36    ///
37    /// Set/reset/request forms use `ESC [ <number> h`, `ESC [ <number> l`, and `ESC [ <number> $ p`.
38    Ansi(u16),
39    /// DEC private mode number.
40    ///
41    /// Set/reset/request forms use `ESC [ ? <number> h`, `ESC [ ? <number> l`, and `ESC [ ? <number> $ p`.
42    Dec(u16),
43}
44
45// Well-known DEC private modes
46impl Mode {
47    /// DEC private mode 1 (DECCKM): cursor keys send application sequences when set and normal cursor sequences when reset.
48    pub const CURSOR_KEYS: Mode = Mode::Dec(1);
49    /// DEC private mode 2 (DECANM): selects ANSI mode when set and VT52 mode when reset on terminals that implement it.
50    pub const ANSI_VT52: Mode = Mode::Dec(2);
51    /// DEC private mode 3 (DECCOLM): 132-column mode when set; commonly resets margins and clears display on supporting terminals.
52    pub const COLUMN_132: Mode = Mode::Dec(3);
53    /// DEC private mode 4 (DECSCLM): smooth scrolling when set, jump scrolling when reset.
54    pub const SMOOTH_SCROLL: Mode = Mode::Dec(4);
55    /// DEC private mode 5 (DECSCNM): reverse-video screen mode.
56    pub const REVERSE_VIDEO: Mode = Mode::Dec(5);
57    /// DEC private mode 6 (DECOM): absolute cursor positions are relative to the scroll region when set.
58    pub const ORIGIN: Mode = Mode::Dec(6);
59    /// DEC private mode 7 (DECAWM): automatically wrap at the right margin when set.
60    pub const AUTO_WRAP: Mode = Mode::Dec(7);
61    /// DEC private mode 8 (DECARM): key auto-repeat enabled when set.
62    pub const AUTO_REPEAT: Mode = Mode::Dec(8);
63    /// DEC private mode 9: X10 mouse reporting.
64    pub const MOUSE_X10: Mode = Mode::Dec(9);
65    /// DEC private mode 18 (DECPFF): print form-feed mode.
66    pub const PRINT_FORM_FEED: Mode = Mode::Dec(18);
67    /// DEC private mode 19 (DECPEX): print extent mode.
68    pub const PRINT_EXTENT: Mode = Mode::Dec(19);
69    /// DEC private mode 25 (DECTCEM): cursor visible when set, hidden when reset.
70    pub const CURSOR_VISIBLE: Mode = Mode::Dec(25);
71    /// DEC private mode 40 (DECNCSM): allow column-mode changes without clearing on supporting terminals.
72    pub const NO_CLEAR_COLUMN: Mode = Mode::Dec(40);
73    /// DEC private mode 66 (DECNKM): keypad application/numeric behavior; see [`crate::ansi::keypad`].
74    pub const NUMERIC_KEYPAD: Mode = Mode::Dec(66);
75    /// DEC private mode 67 (DECBKM): backarrow key sends backspace/delete according to set/reset state.
76    pub const BACKARROW_KEY: Mode = Mode::Dec(67);
77    /// DEC private mode 69 (DECLRMM): enables left/right margin interpretation for DECSLRM.
78    pub const LEFT_RIGHT_MARGIN: Mode = Mode::Dec(69);
79    /// DEC private mode 47: legacy alternate screen buffer.
80    pub const ALT_SCREEN_LEGACY: Mode = Mode::Dec(47);
81    /// DEC private mode 1000: report mouse button press/release events.
82    pub const MOUSE_NORMAL: Mode = Mode::Dec(1000);
83    /// DEC private mode 1001: highlight mouse tracking.
84    pub const MOUSE_HIGHLIGHT: Mode = Mode::Dec(1001);
85    /// DEC private mode 1002: report button-motion mouse events.
86    pub const MOUSE_BUTTON: Mode = Mode::Dec(1002);
87    /// DEC private mode 1003: report any-motion mouse events.
88    pub const MOUSE_ANY: Mode = Mode::Dec(1003);
89    /// DEC private mode 1004: enable focus in/out reports (`ESC [ I` / `ESC [ O`).
90    pub const FOCUS: Mode = Mode::Dec(1004);
91    /// DEC private mode 1005: UTF-8 mouse coordinate encoding.
92    pub const MOUSE_UTF8: Mode = Mode::Dec(1005);
93    /// DEC private mode 1006: SGR mouse coordinate encoding.
94    pub const MOUSE_SGR: Mode = Mode::Dec(1006);
95    /// DEC private mode 1015: alternate mouse coordinate encoding.
96    pub const MOUSE_URXVT: Mode = Mode::Dec(1015);
97    /// DEC private mode 1016: SGR-pixel mouse coordinate encoding.
98    pub const MOUSE_SGR_PIXEL: Mode = Mode::Dec(1016);
99    /// Alternate screen buffer (1047).
100    pub const ALT_SCREEN: Mode = Mode::Dec(1047);
101    /// DEC private mode 1048: save/restore cursor around mode set/reset.
102    pub const SAVE_CURSOR: Mode = Mode::Dec(1048);
103    /// DEC private mode 1049: alternate screen buffer with cursor save/restore and clear semantics.
104    pub const ALT_SCREEN_SAVE_CURSOR: Mode = Mode::Dec(1049);
105    /// DEC private mode 2004: wrap pasted data in bracketed-paste delimiters.
106    pub const BRACKETED_PASTE: Mode = Mode::Dec(2004);
107    /// DEC private mode 2026: synchronized output batching.
108    pub const SYNCHRONIZED_OUTPUT: Mode = Mode::Dec(2026);
109    /// DEC private mode 2027: Unicode core keyboard/input behavior on supporting terminals.
110    pub const UNICODE_CORE: Mode = Mode::Dec(2027);
111    /// DEC private mode 2031: light/dark color-scheme notifications.
112    pub const LIGHT_DARK: Mode = Mode::Dec(2031);
113    /// DEC private mode 2048: in-band resize reports.
114    pub const IN_BAND_RESIZE: Mode = Mode::Dec(2048);
115    /// DEC private mode 9001: Win32-input reporting on supporting terminals.
116    pub const WIN32_INPUT: Mode = Mode::Dec(9001);
117}
118
119// Well-known ANSI modes
120impl Mode {
121    /// ANSI mode 2 (KAM): keyboard action mode.
122    pub const KEYBOARD_ACTION: Mode = Mode::Ansi(2);
123    /// ANSI mode 4 (IRM): insert mode when set, replace mode when reset.
124    pub const INSERT: Mode = Mode::Ansi(4);
125    /// ANSI mode 8 (BDSM): bidirectional-support mode.
126    pub const BIDI_SUPPORT: Mode = Mode::Ansi(8);
127    /// ANSI mode 12 (SRM): send/receive mode, often associated with local echo behavior.
128    pub const SEND_RECEIVE: Mode = Mode::Ansi(12);
129    /// ANSI mode 20 (LNM): line-feed/new-line handling mode.
130    pub const LINE_FEED_NEW_LINE: Mode = Mode::Ansi(20);
131}
132
133/// Decoded state value from a mode report response (`DECRPM` or ANSI report).
134///
135/// The numeric values are the `Ps` status field in `ESC [ ? mode ; Ps $ y` or
136/// `ESC [ mode ; Ps $ y`.
137#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
138pub enum ModeSetting {
139    /// Report value `0`: the terminal does not recognize the requested mode.
140    NotRecognized,
141    /// Report value `1`: the mode is currently set.
142    Set,
143    /// Report value `2`: the mode is currently reset.
144    Reset,
145    /// Report value `3`: the mode is permanently set and cannot be reset.
146    PermanentlySet,
147    /// Report value `4`: the mode is permanently reset and cannot be set.
148    PermanentlyReset,
149}
150
151impl ModeSetting {
152    /// Convert a numeric mode-report status value into [`ModeSetting`].
153    ///
154    /// Values `1..=4` map to their defined states; every other value maps to [`ModeSetting::NotRecognized`].
155    pub fn from_value(v: u16) -> Self {
156        match v {
157            1 => ModeSetting::Set,
158            2 => ModeSetting::Reset,
159            3 => ModeSetting::PermanentlySet,
160            4 => ModeSetting::PermanentlyReset,
161            _ => ModeSetting::NotRecognized,
162        }
163    }
164
165    /// Return the numeric status value used in mode-report responses.
166    pub fn value(self) -> u16 {
167        match self {
168            ModeSetting::NotRecognized => 0,
169            ModeSetting::Set => 1,
170            ModeSetting::Reset => 2,
171            ModeSetting::PermanentlySet => 3,
172            ModeSetting::PermanentlyReset => 4,
173        }
174    }
175
176    /// Return `true` for [`ModeSetting::Set`] and [`ModeSetting::PermanentlySet`].
177    pub fn is_set(self) -> bool {
178        matches!(self, ModeSetting::Set | ModeSetting::PermanentlySet)
179    }
180
181    /// Return `true` for [`ModeSetting::Reset`] and [`ModeSetting::PermanentlyReset`].
182    pub fn is_reset(self) -> bool {
183        matches!(self, ModeSetting::Reset | ModeSetting::PermanentlyReset)
184    }
185
186    /// Return whether the terminal recognized the mode.
187    ///
188    /// Only [`ModeSetting::NotRecognized`] is considered unrecognized.
189    pub fn is_recognized(self) -> bool {
190        !matches!(self, ModeSetting::NotRecognized)
191    }
192
193    /// Return whether the mode is permanently fixed and cannot be toggled.
194    ///
195    /// Both [`ModeSetting::PermanentlySet`] and [`ModeSetting::PermanentlyReset`]
196    /// report a state the host cannot change.
197    pub fn is_permanent(self) -> bool {
198        matches!(
199            self,
200            ModeSetting::PermanentlySet | ModeSetting::PermanentlyReset
201        )
202    }
203
204    /// Return whether the mode can actually be used by the host.
205    ///
206    /// This is `true` for every recognized state except
207    /// [`ModeSetting::PermanentlyReset`]: a permanently reset mode is
208    /// recognized but the terminal will never allow it to be set, so the
209    /// feature it gates is effectively unavailable. Use this (rather than
210    /// [`is_recognized`](Self::is_recognized)) when deciding whether a
211    /// capability can be relied upon.
212    pub fn is_available(self) -> bool {
213        matches!(
214            self,
215            ModeSetting::Set | ModeSetting::Reset | ModeSetting::PermanentlySet
216        )
217    }
218}
219
220impl std::fmt::Display for ModeSetting {
221    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222        let label = match self {
223            ModeSetting::NotRecognized => "not recognized",
224            ModeSetting::Set => "set",
225            ModeSetting::Reset => "reset",
226            ModeSetting::PermanentlySet => "permanently set",
227            ModeSetting::PermanentlyReset => "permanently reset",
228        };
229        f.write_str(label)
230    }
231}
232
233/// Set one or more modes.
234///
235/// DEC private modes emit `ESC [ ? ... h`; ANSI modes emit `ESC [ ... h`. If `modes` contains both kinds, this function writes one DEC sequence and one ANSI sequence. An empty slice emits nothing.
236pub fn write_set_mode<W: Write>(w: &mut W, modes: &[Mode]) -> io::Result<()> {
237    write_mode_seq(w, modes, b'h')
238}
239
240/// Reset one or more modes.
241///
242/// DEC private modes emit `ESC [ ? ... l`; ANSI modes emit `ESC [ ... l`. Mixed slices are split by mode kind. An empty slice emits nothing.
243pub fn write_reset_mode<W: Write>(w: &mut W, modes: &[Mode]) -> io::Result<()> {
244    write_mode_seq(w, modes, b'l')
245}
246
247impl Mode {
248    /// Write the set-mode sequence for this single mode.
249    ///
250    /// This is a convenience wrapper around [`write_set_mode`].
251    pub fn set<W: Write>(self, w: &mut W) -> io::Result<()> {
252        write_set_mode(w, &[self])
253    }
254
255    /// Write the reset-mode sequence for this single mode.
256    ///
257    /// This is a convenience wrapper around [`write_reset_mode`].
258    pub fn reset<W: Write>(self, w: &mut W) -> io::Result<()> {
259        write_reset_mode(w, &[self])
260    }
261
262    /// Request this mode's current state with DECRQM/RQM.
263    ///
264    /// DEC modes emit `ESC [ ? <mode> $ p`; ANSI modes emit `ESC [ <mode> $ p`.
265    pub fn request<W: Write>(self, w: &mut W) -> io::Result<()> {
266        write_request_mode(w, self)
267    }
268}
269
270fn write_mode_seq<W: Write>(w: &mut W, modes: &[Mode], final_byte: u8) -> io::Result<()> {
271    if modes.is_empty() {
272        return Ok(());
273    }
274
275    // Separate ANSI and DEC modes — they use different CSI prefixes
276    let mut ansi_modes = Vec::new();
277    let mut dec_modes = Vec::new();
278
279    for &mode in modes {
280        match mode {
281            Mode::Ansi(n) => ansi_modes.push(n),
282            Mode::Dec(n) => dec_modes.push(n),
283        }
284    }
285
286    if !dec_modes.is_empty() {
287        w.write_all(b"\x1b[?")?;
288        for (i, &n) in dec_modes.iter().enumerate() {
289            if i > 0 {
290                w.write_all(b";")?;
291            }
292            write!(w, "{n}")?;
293        }
294        w.write_all(&[final_byte])?;
295    }
296
297    if !ansi_modes.is_empty() {
298        w.write_all(b"\x1b[")?;
299        for (i, &n) in ansi_modes.iter().enumerate() {
300            if i > 0 {
301                w.write_all(b";")?;
302            }
303            write!(w, "{n}")?;
304        }
305        w.write_all(&[final_byte])?;
306    }
307
308    Ok(())
309}
310
311/// Request mode status.
312///
313/// DEC modes emit `ESC [ ? <mode> $ p`; ANSI modes emit `ESC [ <mode> $ p`. The terminal response can be represented by [`ModeSetting`].
314pub fn write_request_mode<W: Write>(w: &mut W, mode: Mode) -> io::Result<()> {
315    match mode {
316        Mode::Dec(n) => write!(w, "\x1b[?{n}$p"),
317        Mode::Ansi(n) => write!(w, "\x1b[{n}$p"),
318    }
319}
320
321/// Write a mode-report response.
322///
323/// DEC modes emit `ESC [ ? <mode> ; <setting> $ y`; ANSI modes emit `ESC [ <mode> ; <setting> $ y`. Use when synthesizing input reports or testing parsers.
324pub fn write_report_mode<W: Write>(w: &mut W, mode: Mode, setting: ModeSetting) -> io::Result<()> {
325    let v = setting.value();
326    match mode {
327        Mode::Dec(n) => write!(w, "\x1b[?{n};{v}$y"),
328        Mode::Ansi(n) => write!(w, "\x1b[{n};{v}$y"),
329    }
330}
331
332#[cfg(test)]
333mod tests {
334    use super::*;
335
336    #[test]
337    fn test_set_dec_mode() {
338        let mut buf = Vec::new();
339        write_set_mode(&mut buf, &[Mode::ALT_SCREEN_SAVE_CURSOR]).unwrap();
340        assert_eq!(buf, b"\x1b[?1049h");
341    }
342
343    #[test]
344    fn test_reset_dec_mode() {
345        let mut buf = Vec::new();
346        write_reset_mode(&mut buf, &[Mode::ALT_SCREEN_SAVE_CURSOR]).unwrap();
347        assert_eq!(buf, b"\x1b[?1049l");
348    }
349
350    #[test]
351    fn test_set_multiple_dec_modes() {
352        let mut buf = Vec::new();
353        write_set_mode(&mut buf, &[Mode::MOUSE_ANY, Mode::MOUSE_SGR]).unwrap();
354        assert_eq!(buf, b"\x1b[?1003;1006h");
355    }
356
357    #[test]
358    fn test_mode_setting_roundtrip() {
359        for s in [
360            ModeSetting::NotRecognized,
361            ModeSetting::Set,
362            ModeSetting::Reset,
363            ModeSetting::PermanentlySet,
364            ModeSetting::PermanentlyReset,
365        ] {
366            assert_eq!(ModeSetting::from_value(s.value()), s);
367        }
368        assert!(ModeSetting::Set.is_set());
369        assert!(ModeSetting::PermanentlySet.is_set());
370        assert!(!ModeSetting::Reset.is_set());
371        assert!(ModeSetting::Reset.is_reset());
372        assert!(ModeSetting::PermanentlyReset.is_reset());
373        // A recognized mode is anything other than NotRecognized.
374        assert!(!ModeSetting::NotRecognized.is_recognized());
375        for s in [
376            ModeSetting::Set,
377            ModeSetting::Reset,
378            ModeSetting::PermanentlySet,
379            ModeSetting::PermanentlyReset,
380        ] {
381            assert!(s.is_recognized());
382        }
383        // Permanent states are the two that cannot be toggled.
384        assert!(ModeSetting::PermanentlySet.is_permanent());
385        assert!(ModeSetting::PermanentlyReset.is_permanent());
386        assert!(!ModeSetting::Set.is_permanent());
387        assert!(!ModeSetting::Reset.is_permanent());
388        assert!(!ModeSetting::NotRecognized.is_permanent());
389        // A mode is usable unless it is unrecognized or permanently reset.
390        assert!(ModeSetting::Set.is_available());
391        assert!(ModeSetting::Reset.is_available());
392        assert!(ModeSetting::PermanentlySet.is_available());
393        assert!(!ModeSetting::PermanentlyReset.is_available());
394        assert!(!ModeSetting::NotRecognized.is_available());
395    }
396
397    #[test]
398    fn test_mode_setting_display() {
399        assert_eq!(ModeSetting::NotRecognized.to_string(), "not recognized");
400        assert_eq!(ModeSetting::Set.to_string(), "set");
401        assert_eq!(ModeSetting::Reset.to_string(), "reset");
402        assert_eq!(ModeSetting::PermanentlySet.to_string(), "permanently set");
403        assert_eq!(
404            ModeSetting::PermanentlyReset.to_string(),
405            "permanently reset"
406        );
407    }
408
409    #[test]
410    fn test_write_report_mode_dec() {
411        let mut buf = Vec::new();
412        write_report_mode(&mut buf, Mode::ALT_SCREEN_SAVE_CURSOR, ModeSetting::Set).unwrap();
413        assert_eq!(buf, b"\x1b[?1049;1$y");
414    }
415
416    #[test]
417    fn test_write_report_mode_ansi() {
418        let mut buf = Vec::new();
419        write_report_mode(&mut buf, Mode::INSERT, ModeSetting::Reset).unwrap();
420        assert_eq!(buf, b"\x1b[4;2$y");
421    }
422
423    #[test]
424    fn test_request_mode_dec() {
425        let mut buf = Vec::new();
426        write_request_mode(&mut buf, Mode::ALT_SCREEN_SAVE_CURSOR).unwrap();
427        assert_eq!(buf, b"\x1b[?1049$p");
428    }
429
430    #[test]
431    fn test_new_mode_constants() {
432        assert_eq!(Mode::KEYBOARD_ACTION, Mode::Ansi(2));
433        assert_eq!(Mode::BIDI_SUPPORT, Mode::Ansi(8));
434        assert_eq!(Mode::SEND_RECEIVE, Mode::Ansi(12));
435        assert_eq!(Mode::LINE_FEED_NEW_LINE, Mode::Ansi(20));
436        assert_eq!(Mode::MOUSE_UTF8, Mode::Dec(1005));
437        assert_eq!(Mode::MOUSE_URXVT, Mode::Dec(1015));
438        assert_eq!(Mode::WIN32_INPUT, Mode::Dec(9001));
439        assert_eq!(Mode::ALT_SCREEN_LEGACY, Mode::Dec(47));
440        assert_eq!(Mode::LIGHT_DARK, Mode::Dec(2031));
441    }
442}