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, PartialOrd, Ord, 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 2033: terminal visibility reports.
114    pub const VISIBILITY_REPORTS: Mode = Mode::Dec(2033);
115    /// DEC private mode 2048: in-band resize reports.
116    pub const IN_BAND_RESIZE: Mode = Mode::Dec(2048);
117    /// DEC private mode 9001: Win32-input reporting on supporting terminals.
118    pub const WIN32_INPUT: Mode = Mode::Dec(9001);
119}
120
121// Well-known ANSI modes
122impl Mode {
123    /// ANSI mode 2 (KAM): keyboard action mode.
124    pub const KEYBOARD_ACTION: Mode = Mode::Ansi(2);
125    /// ANSI mode 4 (IRM): insert mode when set, replace mode when reset.
126    pub const INSERT: Mode = Mode::Ansi(4);
127    /// ANSI mode 8 (BDSM): bidirectional-support mode.
128    pub const BIDI_SUPPORT: Mode = Mode::Ansi(8);
129    /// ANSI mode 12 (SRM): send/receive mode, often associated with local echo behavior.
130    pub const SEND_RECEIVE: Mode = Mode::Ansi(12);
131    /// ANSI mode 20 (LNM): line-feed/new-line handling mode.
132    pub const LINE_FEED_NEW_LINE: Mode = Mode::Ansi(20);
133}
134
135/// Decoded state value from a mode report response (`DECRPM` or ANSI report).
136///
137/// The numeric values are the `Ps` status field in `ESC [ ? mode ; Ps $ y` or
138/// `ESC [ mode ; Ps $ y`.
139#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
140pub enum ModeSetting {
141    /// Report value `0`: the terminal does not recognize the requested mode.
142    NotRecognized,
143    /// Report value `1`: the mode is currently set.
144    Set,
145    /// Report value `2`: the mode is currently reset.
146    Reset,
147    /// Report value `3`: the mode is permanently set and cannot be reset.
148    PermanentlySet,
149    /// Report value `4`: the mode is permanently reset and cannot be set.
150    PermanentlyReset,
151}
152
153impl ModeSetting {
154    /// Convert a numeric mode-report status value into [`ModeSetting`].
155    ///
156    /// Values `1..=4` map to their defined states; every other value maps to [`ModeSetting::NotRecognized`].
157    pub fn from_value(v: u16) -> Self {
158        match v {
159            1 => ModeSetting::Set,
160            2 => ModeSetting::Reset,
161            3 => ModeSetting::PermanentlySet,
162            4 => ModeSetting::PermanentlyReset,
163            _ => ModeSetting::NotRecognized,
164        }
165    }
166
167    /// Return the numeric status value used in mode-report responses.
168    pub fn value(self) -> u16 {
169        match self {
170            ModeSetting::NotRecognized => 0,
171            ModeSetting::Set => 1,
172            ModeSetting::Reset => 2,
173            ModeSetting::PermanentlySet => 3,
174            ModeSetting::PermanentlyReset => 4,
175        }
176    }
177
178    /// Return `true` for [`ModeSetting::Set`] and [`ModeSetting::PermanentlySet`].
179    pub fn is_set(self) -> bool {
180        matches!(self, ModeSetting::Set | ModeSetting::PermanentlySet)
181    }
182
183    /// Return `true` for [`ModeSetting::Reset`] and [`ModeSetting::PermanentlyReset`].
184    pub fn is_reset(self) -> bool {
185        matches!(self, ModeSetting::Reset | ModeSetting::PermanentlyReset)
186    }
187
188    /// Return whether the terminal recognized the mode.
189    ///
190    /// Only [`ModeSetting::NotRecognized`] is considered unrecognized.
191    pub fn is_recognized(self) -> bool {
192        !matches!(self, ModeSetting::NotRecognized)
193    }
194
195    /// Return whether the mode is permanently fixed and cannot be toggled.
196    ///
197    /// Both [`ModeSetting::PermanentlySet`] and [`ModeSetting::PermanentlyReset`]
198    /// report a state the host cannot change.
199    pub fn is_permanent(self) -> bool {
200        matches!(
201            self,
202            ModeSetting::PermanentlySet | ModeSetting::PermanentlyReset
203        )
204    }
205
206    /// Return whether the mode can actually be used by the host.
207    ///
208    /// This is `true` for every recognized state except
209    /// [`ModeSetting::PermanentlyReset`]: a permanently reset mode is
210    /// recognized but the terminal will never allow it to be set, so the
211    /// feature it gates is effectively unavailable. Use this (rather than
212    /// [`is_recognized`](Self::is_recognized)) when deciding whether a
213    /// capability can be relied upon.
214    pub fn is_available(self) -> bool {
215        matches!(
216            self,
217            ModeSetting::Set | ModeSetting::Reset | ModeSetting::PermanentlySet
218        )
219    }
220}
221
222impl std::fmt::Display for ModeSetting {
223    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224        let label = match self {
225            ModeSetting::NotRecognized => "not recognized",
226            ModeSetting::Set => "set",
227            ModeSetting::Reset => "reset",
228            ModeSetting::PermanentlySet => "permanently set",
229            ModeSetting::PermanentlyReset => "permanently reset",
230        };
231        f.write_str(label)
232    }
233}
234
235/// Set one or more modes.
236///
237/// 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.
238pub fn write_set_mode<W: Write>(w: &mut W, modes: &[Mode]) -> io::Result<()> {
239    write_mode_seq(w, modes, b'h')
240}
241
242/// Reset one or more modes.
243///
244/// DEC private modes emit `ESC [ ? ... l`; ANSI modes emit `ESC [ ... l`. Mixed slices are split by mode kind. An empty slice emits nothing.
245pub fn write_reset_mode<W: Write>(w: &mut W, modes: &[Mode]) -> io::Result<()> {
246    write_mode_seq(w, modes, b'l')
247}
248
249impl Mode {
250    /// Write the set-mode sequence for this single mode.
251    ///
252    /// This is a convenience wrapper around [`write_set_mode`].
253    pub fn set<W: Write>(self, w: &mut W) -> io::Result<()> {
254        write_set_mode(w, &[self])
255    }
256
257    /// Write the reset-mode sequence for this single mode.
258    ///
259    /// This is a convenience wrapper around [`write_reset_mode`].
260    pub fn reset<W: Write>(self, w: &mut W) -> io::Result<()> {
261        write_reset_mode(w, &[self])
262    }
263
264    /// Request this mode's current state with DECRQM/RQM.
265    ///
266    /// DEC modes emit `ESC [ ? <mode> $ p`; ANSI modes emit `ESC [ <mode> $ p`.
267    pub fn request<W: Write>(self, w: &mut W) -> io::Result<()> {
268        write_request_mode(w, self)
269    }
270}
271
272fn write_mode_seq<W: Write>(w: &mut W, modes: &[Mode], final_byte: u8) -> io::Result<()> {
273    if modes.is_empty() {
274        return Ok(());
275    }
276
277    // Separate ANSI and DEC modes — they use different CSI prefixes
278    let mut ansi_modes = Vec::new();
279    let mut dec_modes = Vec::new();
280
281    for &mode in modes {
282        match mode {
283            Mode::Ansi(n) => ansi_modes.push(n),
284            Mode::Dec(n) => dec_modes.push(n),
285        }
286    }
287
288    if !dec_modes.is_empty() {
289        w.write_all(b"\x1b[?")?;
290        for (i, &n) in dec_modes.iter().enumerate() {
291            if i > 0 {
292                w.write_all(b";")?;
293            }
294            write!(w, "{n}")?;
295        }
296        w.write_all(&[final_byte])?;
297    }
298
299    if !ansi_modes.is_empty() {
300        w.write_all(b"\x1b[")?;
301        for (i, &n) in ansi_modes.iter().enumerate() {
302            if i > 0 {
303                w.write_all(b";")?;
304            }
305            write!(w, "{n}")?;
306        }
307        w.write_all(&[final_byte])?;
308    }
309
310    Ok(())
311}
312
313/// Request mode status.
314///
315/// DEC modes emit `ESC [ ? <mode> $ p`; ANSI modes emit `ESC [ <mode> $ p`. The terminal response can be represented by [`ModeSetting`].
316pub fn write_request_mode<W: Write>(w: &mut W, mode: Mode) -> io::Result<()> {
317    match mode {
318        Mode::Dec(n) => write!(w, "\x1b[?{n}$p"),
319        Mode::Ansi(n) => write!(w, "\x1b[{n}$p"),
320    }
321}
322
323/// Write a mode-report response.
324///
325/// DEC modes emit `ESC [ ? <mode> ; <setting> $ y`; ANSI modes emit `ESC [ <mode> ; <setting> $ y`. Use when synthesizing input reports or testing parsers.
326pub fn write_report_mode<W: Write>(w: &mut W, mode: Mode, setting: ModeSetting) -> io::Result<()> {
327    let v = setting.value();
328    match mode {
329        Mode::Dec(n) => write!(w, "\x1b[?{n};{v}$y"),
330        Mode::Ansi(n) => write!(w, "\x1b[{n};{v}$y"),
331    }
332}
333
334#[cfg(test)]
335mod tests {
336    use super::*;
337
338    #[test]
339    fn test_set_dec_mode() {
340        let mut buf = Vec::new();
341        write_set_mode(&mut buf, &[Mode::ALT_SCREEN_SAVE_CURSOR]).unwrap();
342        assert_eq!(buf, b"\x1b[?1049h");
343    }
344
345    #[test]
346    fn test_reset_dec_mode() {
347        let mut buf = Vec::new();
348        write_reset_mode(&mut buf, &[Mode::ALT_SCREEN_SAVE_CURSOR]).unwrap();
349        assert_eq!(buf, b"\x1b[?1049l");
350    }
351
352    #[test]
353    fn test_set_multiple_dec_modes() {
354        let mut buf = Vec::new();
355        write_set_mode(&mut buf, &[Mode::MOUSE_ANY, Mode::MOUSE_SGR]).unwrap();
356        assert_eq!(buf, b"\x1b[?1003;1006h");
357    }
358
359    #[test]
360    fn test_mode_setting_roundtrip() {
361        for s in [
362            ModeSetting::NotRecognized,
363            ModeSetting::Set,
364            ModeSetting::Reset,
365            ModeSetting::PermanentlySet,
366            ModeSetting::PermanentlyReset,
367        ] {
368            assert_eq!(ModeSetting::from_value(s.value()), s);
369        }
370        assert!(ModeSetting::Set.is_set());
371        assert!(ModeSetting::PermanentlySet.is_set());
372        assert!(!ModeSetting::Reset.is_set());
373        assert!(ModeSetting::Reset.is_reset());
374        assert!(ModeSetting::PermanentlyReset.is_reset());
375        // A recognized mode is anything other than NotRecognized.
376        assert!(!ModeSetting::NotRecognized.is_recognized());
377        for s in [
378            ModeSetting::Set,
379            ModeSetting::Reset,
380            ModeSetting::PermanentlySet,
381            ModeSetting::PermanentlyReset,
382        ] {
383            assert!(s.is_recognized());
384        }
385        // Permanent states are the two that cannot be toggled.
386        assert!(ModeSetting::PermanentlySet.is_permanent());
387        assert!(ModeSetting::PermanentlyReset.is_permanent());
388        assert!(!ModeSetting::Set.is_permanent());
389        assert!(!ModeSetting::Reset.is_permanent());
390        assert!(!ModeSetting::NotRecognized.is_permanent());
391        // A mode is usable unless it is unrecognized or permanently reset.
392        assert!(ModeSetting::Set.is_available());
393        assert!(ModeSetting::Reset.is_available());
394        assert!(ModeSetting::PermanentlySet.is_available());
395        assert!(!ModeSetting::PermanentlyReset.is_available());
396        assert!(!ModeSetting::NotRecognized.is_available());
397    }
398
399    #[test]
400    fn test_mode_setting_display() {
401        assert_eq!(ModeSetting::NotRecognized.to_string(), "not recognized");
402        assert_eq!(ModeSetting::Set.to_string(), "set");
403        assert_eq!(ModeSetting::Reset.to_string(), "reset");
404        assert_eq!(ModeSetting::PermanentlySet.to_string(), "permanently set");
405        assert_eq!(
406            ModeSetting::PermanentlyReset.to_string(),
407            "permanently reset"
408        );
409    }
410
411    #[test]
412    fn test_write_report_mode_dec() {
413        let mut buf = Vec::new();
414        write_report_mode(&mut buf, Mode::ALT_SCREEN_SAVE_CURSOR, ModeSetting::Set).unwrap();
415        assert_eq!(buf, b"\x1b[?1049;1$y");
416    }
417
418    #[test]
419    fn test_write_report_mode_ansi() {
420        let mut buf = Vec::new();
421        write_report_mode(&mut buf, Mode::INSERT, ModeSetting::Reset).unwrap();
422        assert_eq!(buf, b"\x1b[4;2$y");
423    }
424
425    #[test]
426    fn test_request_mode_dec() {
427        let mut buf = Vec::new();
428        write_request_mode(&mut buf, Mode::ALT_SCREEN_SAVE_CURSOR).unwrap();
429        assert_eq!(buf, b"\x1b[?1049$p");
430    }
431
432    #[test]
433    fn test_new_mode_constants() {
434        assert_eq!(Mode::KEYBOARD_ACTION, Mode::Ansi(2));
435        assert_eq!(Mode::BIDI_SUPPORT, Mode::Ansi(8));
436        assert_eq!(Mode::SEND_RECEIVE, Mode::Ansi(12));
437        assert_eq!(Mode::LINE_FEED_NEW_LINE, Mode::Ansi(20));
438        assert_eq!(Mode::MOUSE_UTF8, Mode::Dec(1005));
439        assert_eq!(Mode::MOUSE_URXVT, Mode::Dec(1015));
440        assert_eq!(Mode::WIN32_INPUT, Mode::Dec(9001));
441        assert_eq!(Mode::ALT_SCREEN_LEGACY, Mode::Dec(47));
442        assert_eq!(Mode::LIGHT_DARK, Mode::Dec(2031));
443        assert_eq!(Mode::VISIBILITY_REPORTS, Mode::Dec(2033));
444    }
445}