uncurses/ansi/sgr.rs
1//! Select Graphic Rendition (SGR) writer.
2//!
3//! ## Category
4//!
5//! SGR is the CSI `m` family used for rendition attributes such as reset,
6//! intensity, underline, foreground color, and background color. Higher-level
7//! style construction lives outside this module; this module writes the raw
8//! parameter grammar.
9//!
10//! ## Parameter conventions
11//!
12//! The outer slice in [`write_sgr`] becomes semicolon-separated primary
13//! parameters. Each inner slice becomes colon-separated subparameters, allowing
14//! both classic SGR and colon-form color parameters.
15//!
16//! ## Mode interaction
17//!
18//! SGR attributes are not enabled by a separate mode. They change the terminal's
19//! active rendition state until another SGR sequence resets or modifies it.
20
21use std::io::{self, Write};
22
23/// Write an SGR sequence using semicolon-separated groups and colon-separated subparameters.
24///
25/// The emitted format is `ESC [ <params> m`. The outer slice separates primary parameters with `;`; each non-empty inner slice is joined with `:`. An empty outer slice emits `ESC [ m` (SGR reset), and empty inner slices are skipped.
26///
27/// # Examples
28///
29/// ```rust,ignore
30/// // ESC [ 1 ; 31 ; 4 m — bold, red, underline
31/// write_sgr(w, &[&[1], &[31], &[4]])?;
32///
33/// // ESC [ 38 : 2 : 0 : 10 : 20 : 30 m — colon-form truecolor foreground
34/// write_sgr(w, &[&[38, 2, 0, 10, 20, 30]])?;
35///
36/// // ESC [ 0 ; 38 : 2 : 10 : 20 : 30 ; 1 m — mixed groups
37/// write_sgr(w, &[&[0], &[38, 2, 10, 20, 30], &[1]])?;
38/// ```
39pub fn write_sgr<W: Write>(w: &mut W, params: &[&[u16]]) -> io::Result<()> {
40 w.write_all(b"\x1b[")?;
41 let mut first = true;
42 for group in params {
43 if group.is_empty() {
44 continue;
45 }
46 if !first {
47 w.write_all(b";")?;
48 }
49 first = false;
50 for (i, p) in group.iter().enumerate() {
51 if i > 0 {
52 w.write_all(b":")?;
53 }
54 write!(w, "{p}")?;
55 }
56 }
57 w.write_all(b"m")
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn test_empty_is_reset() {
66 let mut buf = Vec::new();
67 write_sgr(&mut buf, &[]).unwrap();
68 assert_eq!(buf, b"\x1b[m");
69 }
70
71 #[test]
72 fn test_semicolon_only() {
73 let mut buf = Vec::new();
74 write_sgr(&mut buf, &[&[1], &[31], &[4]]).unwrap();
75 assert_eq!(buf, b"\x1b[1;31;4m");
76 }
77
78 #[test]
79 fn test_colon_subparams() {
80 let mut buf = Vec::new();
81 write_sgr(&mut buf, &[&[38, 2, 0, 10, 20, 30]]).unwrap();
82 assert_eq!(buf, b"\x1b[38:2:0:10:20:30m");
83 }
84
85 #[test]
86 fn test_mixed() {
87 let mut buf = Vec::new();
88 write_sgr(&mut buf, &[&[0], &[38, 2, 10, 20, 30], &[1]]).unwrap();
89 assert_eq!(buf, b"\x1b[0;38:2:10:20:30;1m");
90 }
91
92 #[test]
93 fn test_skip_empty_groups() {
94 let mut buf = Vec::new();
95 write_sgr(&mut buf, &[&[1], &[], &[4]]).unwrap();
96 assert_eq!(buf, b"\x1b[1;4m");
97 }
98}