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}