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}