vtcode_commons/
color256_theme.rs1use std::sync::atomic::{AtomicU8, Ordering};
8
9const HARMONIOUS_FALSE: u8 = 0;
10const HARMONIOUS_TRUE: u8 = 1;
11const HARMONIOUS_UNSET: u8 = 2;
12
13static RUNTIME_HARMONIOUS_HINT: AtomicU8 = AtomicU8::new(HARMONIOUS_UNSET);
14
15fn resolve_harmony(is_light_theme: bool, env_override: Option<bool>, runtime_hint: Option<bool>) -> bool {
16 env_override.or(runtime_hint).unwrap_or(!is_light_theme)
17}
18
19fn is_harmonious_for_theme(is_light_theme: bool) -> bool {
26 resolve_harmony(is_light_theme, harmonious_override(), harmonious_runtime_hint())
27}
28
29fn harmonious_runtime_hint() -> Option<bool> {
30 match RUNTIME_HARMONIOUS_HINT.load(Ordering::Relaxed) {
31 HARMONIOUS_TRUE => Some(true),
32 HARMONIOUS_FALSE => Some(false),
33 _ => None,
34 }
35}
36
37pub fn set_harmonious_runtime_hint(value: Option<bool>) {
42 let encoded = match value {
43 Some(true) => HARMONIOUS_TRUE,
44 Some(false) => HARMONIOUS_FALSE,
45 None => HARMONIOUS_UNSET,
46 };
47 RUNTIME_HARMONIOUS_HINT.store(encoded, Ordering::Relaxed);
48}
49
50fn gray_index(level: u8) -> u8 {
52 232 + (23 - level.min(23))
53}
54
55#[allow(clippy::cast_sign_loss)]
57fn cube_index(r: u8, g: u8, b: u8) -> u8 {
58 let r = r.min(5);
59 let g = g.min(5);
60 let b = b.min(5);
61
62 let max = r.max(g).max(b) as i16;
63 let min = r.min(g).min(b) as i16;
64 let offset = 5 - max - min;
65
66 let r = ((r as i16 + offset).clamp(0, 5)) as u8;
67 let g = ((g as i16 + offset).clamp(0, 5)) as u8;
68 let b = ((b as i16 + offset).clamp(0, 5)) as u8;
69
70 16 + 36 * r + 6 * g + b
71}
72
73fn adjust_index(index: u8, is_harmonious: bool) -> u8 {
79 if is_harmonious {
80 return index;
81 }
82
83 match index {
84 16..=231 => {
85 let adjusted = index - 16;
86 let r = adjusted / 36;
87 let g = (adjusted % 36) / 6;
88 let b = adjusted % 6;
89 cube_index(r, g, b)
90 }
91 232..=255 => gray_index(index - 232),
92 _ => index,
93 }
94}
95
96pub fn adjust_index_for_theme(index: u8, is_light_theme: bool) -> u8 {
98 adjust_index(index, is_harmonious_for_theme(is_light_theme))
99}
100
101pub fn rgb_to_ansi256_for_theme(r: u8, g: u8, b: u8, is_light_theme: bool) -> u8 {
103 let base_index = if r == g && g == b {
104 if r < 8 {
105 16
106 } else if r > 248 {
107 231
108 } else {
109 ((r as u16 - 8) / 10) as u8 + 232
110 }
111 } else {
112 let r_index = ((r as u16 * 5) / 255) as u8;
113 let g_index = ((g as u16 * 5) / 255) as u8;
114 let b_index = ((b as u16 * 5) / 255) as u8;
115 16 + 36 * r_index + 6 * g_index + b_index
116 };
117
118 adjust_index_for_theme(base_index, is_light_theme)
119}
120
121fn parse_bool(value: &str) -> Option<bool> {
122 match value.trim().to_ascii_lowercase().as_str() {
123 "1" | "true" | "yes" | "on" => Some(true),
124 "0" | "false" | "no" | "off" => Some(false),
125 _ => None,
126 }
127}
128
129fn harmonious_override() -> Option<bool> {
130 std::env::var("VTCODE_256_HARMONIOUS").ok().and_then(|value| parse_bool(&value))
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn harmonious_indices_are_identity() {
139 assert_eq!(adjust_index(16, true), 16);
140 assert_eq!(adjust_index(231, true), 231);
141 assert_eq!(adjust_index(232, true), 232);
142 assert_eq!(adjust_index(255, true), 255);
143 assert_eq!(adjust_index(194, true), 194);
144 }
145
146 #[test]
147 fn non_harmonious_gray_and_cube_reflect() {
148 assert_eq!(gray_index(0), 255);
149 assert_eq!(gray_index(23), 232);
150 assert_eq!(cube_index(0, 0, 0), 231);
151 assert_eq!(cube_index(5, 5, 5), 16);
152 }
153
154 #[test]
155 fn non_harmonious_adjusts_existing_indices() {
156 assert_eq!(adjust_index(194, false), 22);
157 assert_eq!(adjust_index(224, false), 52);
158 assert_eq!(adjust_index(233, false), 254);
159 assert_eq!(adjust_index(14, false), 14);
160 }
161
162 #[test]
163 fn rgb_to_ansi256_applies_theme_adjustment() {
164 assert_eq!(rgb_to_ansi256_for_theme(0, 0, 0, false), 16);
165 assert_eq!(rgb_to_ansi256_for_theme(0, 0, 0, true), 231);
166 assert_eq!(rgb_to_ansi256_for_theme(255, 255, 255, false), 231);
167 assert_eq!(rgb_to_ansi256_for_theme(255, 255, 255, true), 16);
168 }
169
170 #[test]
171 fn resolve_harmony_precedence_is_env_then_runtime_then_default() {
172 assert!(resolve_harmony(true, Some(true), Some(false)));
173 assert!(!resolve_harmony(false, Some(false), Some(true)));
174 assert!(resolve_harmony(true, None, Some(true)));
175 assert!(!resolve_harmony(true, None, None));
176 assert!(resolve_harmony(false, None, None));
177 }
178}