uncurses/ansi/status.rs
1//! Device Status Reports and cursor-position reports.
2//!
3//! ## Category
4//!
5//! This module emits DSR requests and report responses: cursor position, extended
6//! cursor position, and light/dark preference reporting.
7//!
8//! ## CSI conventions
9//!
10//! ANSI DSR uses `ESC [ Ps n`; DEC-private DSR inserts `?`. Cursor reports use
11//! final byte `R`, while light/dark reports use private DSR numbers.
12//!
13//! ## Mode interaction
14//!
15//! The light/dark notification request is related to
16//! [`Mode::LIGHT_DARK`](crate::ansi::mode::Mode::LIGHT_DARK), DEC private mode
17//! 2031. Cursor-position reports are independent of modes but may be interpreted
18//! relative to terminal origin behavior.
19
20use std::io::{self, Write};
21
22/// Request standard cursor position: exact bytes `ESC [ 6 n` (`b"\x1b[6n"`).
23///
24/// The terminal replies with CPR, `ESC [ <line> ; <column> R`, using one-based coordinates.
25pub const REQUEST_CURSOR_POSITION: &[u8] = b"\x1b[6n";
26
27/// Request extended cursor position: exact bytes `ESC [ ? 6 n` (`b"\x1b[?6n"`).
28///
29/// The terminal replies with a private cursor-position report, optionally including page.
30pub const REQUEST_EXTENDED_CURSOR_POSITION: &[u8] = b"\x1b[?6n";
31
32/// Request light/dark preference report: exact bytes `ESC [ ? 996 n` (`b"\x1b[?996n"`).
33pub const REQUEST_LIGHT_DARK_REPORT: &[u8] = b"\x1b[?996n";
34
35/// Write [`REQUEST_CURSOR_POSITION`], the standard DSR 6 cursor-position request.
36pub fn write_request_cursor_position<W: Write>(w: &mut W) -> io::Result<()> {
37 w.write_all(REQUEST_CURSOR_POSITION)
38}
39
40/// Write [`REQUEST_EXTENDED_CURSOR_POSITION`], the DEC private extended cursor-position request.
41pub fn write_request_extended_cursor_position<W: Write>(w: &mut W) -> io::Result<()> {
42 w.write_all(REQUEST_EXTENDED_CURSOR_POSITION)
43}
44
45/// Write [`REQUEST_LIGHT_DARK_REPORT`], the light/dark preference query.
46pub fn write_request_light_dark_report<W: Write>(w: &mut W) -> io::Result<()> {
47 w.write_all(REQUEST_LIGHT_DARK_REPORT)
48}
49
50/// Encode a Device Status Report request.
51///
52/// When `dec` is `false`, the format is `ESC [ <ps> n`; when `dec` is `true`, the format is `ESC [ ? <ps> n`.
53pub fn write_dsr_request<W: Write>(w: &mut W, dec: bool, ps: u16) -> io::Result<()> {
54 if dec {
55 write!(w, "\x1b[?{ps}n")
56 } else {
57 write!(w, "\x1b[{ps}n")
58 }
59}
60
61/// Encode a standard Cursor Position Report response, `ESC [ <line> ; <column> R`.
62///
63/// `line` and `column` are one-based terminal coordinates; values less than `1` are clamped to `1`.
64pub fn write_cpr<W: Write>(w: &mut W, line: u16, column: u16) -> io::Result<()> {
65 let l = line.max(1);
66 let c = column.max(1);
67 write!(w, "\x1b[{l};{c}R")
68}
69
70/// Encode an extended Cursor Position Report response.
71///
72/// With `page == 0`, emits `ESC [ ? <line> ; <column> R`; otherwise emits `ESC [ ? <line> ; <column> ; <page> R`. `line` and `column` are clamped to at least `1`.
73pub fn write_decxcpr<W: Write>(w: &mut W, line: u16, column: u16, page: u16) -> io::Result<()> {
74 let l = line.max(1);
75 let c = column.max(1);
76 if page == 0 {
77 write!(w, "\x1b[?{l};{c}R")
78 } else {
79 write!(w, "\x1b[?{l};{c};{page}R")
80 }
81}
82
83/// Encode a light/dark report response.
84///
85/// `dark == true` emits `ESC [ ? 997 ; 1 n`; `false` emits `ESC [ ? 997 ; 2 n`.
86pub fn write_light_dark_report<W: Write>(w: &mut W, dark: bool) -> io::Result<()> {
87 if dark {
88 w.write_all(b"\x1b[?997;1n")
89 } else {
90 w.write_all(b"\x1b[?997;2n")
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn test_cpr() {
100 let mut buf = Vec::new();
101 write_cpr(&mut buf, 10, 20).unwrap();
102 assert_eq!(buf, b"\x1b[10;20R");
103 }
104
105 #[test]
106 fn test_decxcpr() {
107 let mut buf = Vec::new();
108 write_decxcpr(&mut buf, 5, 6, 0).unwrap();
109 write_decxcpr(&mut buf, 5, 6, 2).unwrap();
110 assert_eq!(buf, b"\x1b[?5;6R\x1b[?5;6;2R");
111 }
112
113 #[test]
114 fn test_dsr_request() {
115 let mut buf = Vec::new();
116 write_dsr_request(&mut buf, false, 5).unwrap();
117 write_dsr_request(&mut buf, true, 996).unwrap();
118 assert_eq!(buf, b"\x1b[5n\x1b[?996n");
119 }
120}