caixa_theme/
blackmatter.rs1use crate::palette::{Nord, Rgb};
8use crate::style::Semantic;
9
10#[derive(Debug, Clone, Copy)]
11pub struct Theme {
12 pub name: &'static str,
13 resolver: fn(Semantic) -> Rgb,
14}
15
16impl Theme {
17 #[must_use]
18 pub fn blackmatter_dark() -> Self {
19 Self {
20 name: "blackmatter-dark",
21 resolver: blackmatter_dark_color,
22 }
23 }
24
25 #[must_use]
26 pub fn blackmatter_light() -> Self {
27 Self {
28 name: "blackmatter-light",
29 resolver: blackmatter_light_color,
30 }
31 }
32
33 #[must_use]
34 pub fn color(&self, s: Semantic) -> Rgb {
35 (self.resolver)(s)
36 }
37
38 #[must_use]
39 pub fn ansi(&self, s: Semantic) -> String {
40 self.color(s).fg_ansi()
41 }
42
43 #[must_use]
44 pub fn paint(&self, s: Semantic, text: &str) -> String {
45 format!("{}{}{}", self.ansi(s), text, crate::palette::ANSI_RESET)
46 }
47}
48
49impl Default for Theme {
50 fn default() -> Self {
51 Self::blackmatter_dark()
52 }
53}
54
55fn blackmatter_dark_color(s: Semantic) -> Rgb {
56 match s {
57 Semantic::Keyword => Nord::NORD9,
58 Semantic::Symbol => Nord::NORD4,
59 Semantic::KeywordArg => Nord::NORD8,
60 Semantic::String => Nord::NORD14,
61 Semantic::Number => Nord::NORD15,
62 Semantic::Literal => Nord::NORD13,
63 Semantic::Comment => Nord::NORD3,
64 Semantic::Accent => Nord::NORD8,
65 Semantic::Muted => Nord::NORD3,
66 Semantic::Error => Nord::NORD11,
67 Semantic::Warning => Nord::NORD12,
68 Semantic::Info => Nord::NORD8,
69 Semantic::Hint => Nord::NORD13,
70 Semantic::Added => Nord::NORD14,
71 Semantic::Removed => Nord::NORD11,
72 Semantic::Unchanged => Nord::NORD4,
73 }
74}
75
76fn blackmatter_light_color(s: Semantic) -> Rgb {
77 match s {
79 Semantic::Keyword => Nord::NORD10,
80 Semantic::Symbol => Nord::NORD0,
81 Semantic::KeywordArg => Nord::NORD10,
82 Semantic::String => Nord::NORD14,
83 Semantic::Number => Nord::NORD15,
84 Semantic::Literal => Nord::NORD12,
85 Semantic::Comment => Nord::NORD2,
86 Semantic::Accent => Nord::NORD10,
87 Semantic::Muted => Nord::NORD2,
88 Semantic::Error => Nord::NORD11,
89 Semantic::Warning => Nord::NORD12,
90 Semantic::Info => Nord::NORD10,
91 Semantic::Hint => Nord::NORD13,
92 Semantic::Added => Nord::NORD14,
93 Semantic::Removed => Nord::NORD11,
94 Semantic::Unchanged => Nord::NORD0,
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn default_is_dark() {
104 let t = Theme::default();
105 assert_eq!(t.name, "blackmatter-dark");
106 }
107
108 #[test]
109 fn error_maps_to_red() {
110 let t = Theme::blackmatter_dark();
111 assert_eq!(t.color(Semantic::Error), Nord::NORD11);
112 }
113
114 #[test]
115 fn paint_wraps_with_reset() {
116 let t = Theme::blackmatter_dark();
117 let out = t.paint(Semantic::Error, "boom");
118 assert!(out.starts_with("\x1b["));
119 assert!(out.ends_with(crate::palette::ANSI_RESET));
120 assert!(out.contains("boom"));
121 }
122
123 #[test]
124 fn every_semantic_maps_to_a_nord_palette_color_across_both_themes() {
125 use crate::palette::Nord;
146 let nord_palette: [Rgb; 16] = [
147 Nord::NORD0,
148 Nord::NORD1,
149 Nord::NORD2,
150 Nord::NORD3,
151 Nord::NORD4,
152 Nord::NORD5,
153 Nord::NORD6,
154 Nord::NORD7,
155 Nord::NORD8,
156 Nord::NORD9,
157 Nord::NORD10,
158 Nord::NORD11,
159 Nord::NORD12,
160 Nord::NORD13,
161 Nord::NORD14,
162 Nord::NORD15,
163 ];
164 for theme in [Theme::blackmatter_dark(), Theme::blackmatter_light()] {
165 for &sem in Semantic::ALL {
166 let rgb = theme.color(sem);
167 assert!(
168 nord_palette.contains(&rgb),
169 "Theme::{} maps Semantic::{sem:?} to {rgb:?} — a \
170 color outside the closed Nord palette. Every \
171 theme-overlay resolver must reach for one of the \
172 16 Nord entries; a wildcard arm returning a \
173 color outside the palette (an invisible fallback, \
174 a paint-bleed on a hypothetical extension arm) \
175 is a defect.",
176 theme.name,
177 );
178 }
179 }
180 }
181
182 #[test]
183 fn diagnostic_severity_arms_paint_red_and_orange_across_both_themes() {
184 use crate::palette::Nord;
202 for theme in [Theme::blackmatter_dark(), Theme::blackmatter_light()] {
203 assert_eq!(
204 theme.color(Semantic::Error),
205 Nord::NORD11,
206 "Theme::{} must paint Semantic::Error as NORD11 (red)",
207 theme.name,
208 );
209 assert_eq!(
210 theme.color(Semantic::Warning),
211 Nord::NORD12,
212 "Theme::{} must paint Semantic::Warning as NORD12 (orange)",
213 theme.name,
214 );
215 }
216 }
217}