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}