Skip to main content

uncurses/ansi/
color.rs

1//! Default foreground, background, cursor, and indexed palette colors.
2//!
3//! ## Category
4//!
5//! This module emits OSC color-control sequences: OSC 10/11/12 for the default
6//! text and cursor colors, OSC 4 for indexed palette entries, and OSC 104/110
7//! through 112 for resets.
8//!
9//! ## OSC framing
10//!
11//! All writers and byte constants in this module use the 7-bit OSC introducer
12//! and BEL terminator:
13//!
14//! ```text
15//! ESC ]  10 ; rgb:ffff/0000/8080  BEL
16//! ──┬──  ┬─   ─────────┬─────────  ─┬─
17//!  OSC  code        payload       terminator
18//! ```
19//!
20//! The `color` payload is passed through unchanged. Use [`xparse_rgb`] when a
21//! caller needs the `rgb:RRRR/GGGG/BBBB` form accepted by these controls.
22//!
23//! ## Mode interaction
24//!
25//! These sequences do not require a DEC or ANSI mode to be enabled. Requests
26//! cause compatible terminals to report colors asynchronously as OSC replies.
27
28use std::io::{self, Write};
29
30/// Set the default text foreground color with `ESC ] 10 ; <color> BEL`.
31///
32/// The `color` string is emitted verbatim. Use values accepted by the terminal, such as `#rrggbb` or [`xparse_rgb`] output. This changes the default color, not the current SGR foreground attribute.
33pub fn write_set_foreground_color<W: Write>(w: &mut W, color: &str) -> io::Result<()> {
34    write!(w, "\x1b]10;{color}\x07")
35}
36
37/// Set the default text background color with `ESC ] 11 ; <color> BEL`.
38///
39/// The `color` payload is emitted verbatim and typically uses an XParseColor-compatible color string. This changes the terminal default background rather than emitting SGR.
40pub fn write_set_background_color<W: Write>(w: &mut W, color: &str) -> io::Result<()> {
41    write!(w, "\x1b]11;{color}\x07")
42}
43
44/// Set the cursor color with `ESC ] 12 ; <color> BEL`.
45///
46/// The `color` payload is emitted verbatim. Use this when the cursor color should differ from the terminal theme default.
47pub fn write_set_cursor_color<W: Write>(w: &mut W, color: &str) -> io::Result<()> {
48    write!(w, "\x1b]12;{color}\x07")
49}
50
51/// Set one indexed palette entry with `ESC ] 4 ; <index> ; <color> BEL`.
52///
53/// `index` is written as a decimal palette index and `color` is emitted verbatim. Use [`xparse_rgb`] to build an `rgb:RRRR/GGGG/BBBB` payload from 8-bit channels.
54pub fn write_set_palette_color<W: Write>(w: &mut W, index: u8, color: &str) -> io::Result<()> {
55    write!(w, "\x1b]4;{index};{color}\x07")
56}
57
58/// Request the default foreground color: exact bytes `ESC ] 10 ; ? BEL` (`b"\x1b]10;?\x07"`).
59///
60/// A compatible terminal replies asynchronously with an OSC 10 color report.
61pub const REQUEST_FOREGROUND_COLOR: &[u8] = b"\x1b]10;?\x07";
62
63/// Request the default background color: exact bytes `ESC ] 11 ; ? BEL` (`b"\x1b]11;?\x07"`).
64///
65/// A compatible terminal replies asynchronously with an OSC 11 color report.
66pub const REQUEST_BACKGROUND_COLOR: &[u8] = b"\x1b]11;?\x07";
67
68/// Request the cursor color: exact bytes `ESC ] 12 ; ? BEL` (`b"\x1b]12;?\x07"`).
69///
70/// A compatible terminal replies asynchronously with an OSC 12 color report.
71pub const REQUEST_CURSOR_COLOR: &[u8] = b"\x1b]12;?\x07";
72
73/// Request one indexed palette entry with `ESC ] 4 ; <index> ; ? BEL`.
74///
75/// The terminal reply, when supported, uses OSC 4 with the same index and a color payload such as `rgb:RRRR/GGGG/BBBB`.
76pub fn write_request_palette_color<W: Write>(w: &mut W, index: u8) -> io::Result<()> {
77    write!(w, "\x1b]4;{index};?\x07")
78}
79
80/// Reset one indexed palette entry with `ESC ] 104 ; <index> BEL`.
81///
82/// `index` is written as a decimal palette index and the terminal restores that entry to its configured default.
83pub fn write_reset_palette_color<W: Write>(w: &mut W, index: u8) -> io::Result<()> {
84    write!(w, "\x1b]104;{index}\x07")
85}
86
87/// Reset all indexed palette colors: exact bytes `ESC ] 104 BEL` (`b"\x1b]104\x07"`).
88pub const RESET_PALETTE_COLORS: &[u8] = b"\x1b]104\x07";
89
90/// Reset the default foreground color: exact bytes `ESC ] 110 BEL` (`b"\x1b]110\x07"`).
91pub const RESET_FOREGROUND_COLOR: &[u8] = b"\x1b]110\x07";
92
93/// Reset the default background color: exact bytes `ESC ] 111 BEL` (`b"\x1b]111\x07"`).
94pub const RESET_BACKGROUND_COLOR: &[u8] = b"\x1b]111\x07";
95
96/// Reset the cursor color: exact bytes `ESC ] 112 BEL` (`b"\x1b]112\x07"`).
97pub const RESET_CURSOR_COLOR: &[u8] = b"\x1b]112\x07";
98
99/// Format 8-bit RGB channels as an XParseColor `rgb:RRRR/GGGG/BBBB` string.
100///
101/// Each channel is duplicated into 16-bit form (`0x80` becomes `8080`). The returned string is suitable for the color payloads accepted by this module.
102pub fn xparse_rgb(r: u8, g: u8, b: u8) -> String {
103    // Match the 16-bit channel convention (low byte == high byte).
104    let r = (r as u16) << 8 | r as u16;
105    let g = (g as u16) << 8 | g as u16;
106    let b = (b as u16) << 8 | b as u16;
107    format!("rgb:{r:04x}/{g:04x}/{b:04x}")
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn test_set_fg() {
116        let mut buf = Vec::new();
117        write_set_foreground_color(&mut buf, "#ffffff").unwrap();
118        assert_eq!(buf, b"\x1b]10;#ffffff\x07");
119    }
120
121    #[test]
122    fn test_xparse_rgb() {
123        assert_eq!(xparse_rgb(0xff, 0x00, 0x80), "rgb:ffff/0000/8080");
124    }
125
126    #[test]
127    fn test_set_palette_color() {
128        let mut buf = Vec::new();
129        write_set_palette_color(&mut buf, 1, "rgb:ffff/0000/8080").unwrap();
130        assert_eq!(buf, b"\x1b]4;1;rgb:ffff/0000/8080\x07");
131    }
132
133    #[test]
134    fn test_request_palette_color() {
135        let mut buf = Vec::new();
136        write_request_palette_color(&mut buf, 5).unwrap();
137        assert_eq!(buf, b"\x1b]4;5;?\x07");
138    }
139
140    #[test]
141    fn test_reset_palette_color_one() {
142        let mut buf = Vec::new();
143        write_reset_palette_color(&mut buf, 5).unwrap();
144        assert_eq!(buf, b"\x1b]104;5\x07");
145    }
146
147    #[test]
148    fn test_reset_palette_color_all() {
149        assert_eq!(RESET_PALETTE_COLORS, b"\x1b]104\x07");
150    }
151}