1use std::sync::{PoisonError, RwLock};
12
13use crate::style::Style;
14use crate::trace_printer::TraceTheme;
15
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
22pub struct Theme {
23 red: (u8, u8, u8),
24 peach: (u8, u8, u8),
25 yellow: (u8, u8, u8),
26 teal: (u8, u8, u8),
27 sky: (u8, u8, u8),
28 blue: (u8, u8, u8),
29 mauve: (u8, u8, u8),
30 overlay1: (u8, u8, u8),
31 overlay0: (u8, u8, u8),
32}
33
34const _: () = assert!(
35 std::mem::size_of::<Theme>() <= 32,
36 "Theme is copied on every get_theme(); keep it a small palette",
37);
38
39const fn hex(literal: &str) -> (u8, u8, u8) {
42 assert!(
45 literal.len() == 7 && literal.as_bytes()[0] == b'#',
46 "color literal must be in #rrggbb form",
47 );
48 let (_, digits) = literal.split_at(1);
49 let Ok(value) = u32::from_str_radix(digits, 16) else {
50 panic!("color literal has a non-hex digit")
51 };
52 (
53 ((value >> 16) & 0xFF) as u8,
54 ((value >> 8) & 0xFF) as u8,
55 (value & 0xFF) as u8,
56 )
57}
58
59const fn style((r, g, b): (u8, u8, u8)) -> Style {
60 Style::new().truecolor(r, g, b)
61}
62
63impl Theme {
64 pub const CATPPUCCIN_MOCHA: Self = Self {
66 red: hex("#f38ba8"),
67 peach: hex("#fab387"),
68 yellow: hex("#f9e2af"),
69 teal: hex("#94e2d5"),
70 sky: hex("#89dceb"),
71 blue: hex("#89b4fa"),
72 mauve: hex("#cba6f7"),
73 overlay1: hex("#7f849c"),
74 overlay0: hex("#6c7086"),
75 };
76
77 pub const CATPPUCCIN_MACCHIATO: Self = Self {
79 red: hex("#ed8796"),
80 peach: hex("#f5a97f"),
81 yellow: hex("#eed49f"),
82 teal: hex("#8bd5ca"),
83 sky: hex("#91d7e3"),
84 blue: hex("#8aadf4"),
85 mauve: hex("#c6a0f6"),
86 overlay1: hex("#8087a2"),
87 overlay0: hex("#6e738d"),
88 };
89
90 pub const CATPPUCCIN_FRAPPE: Self = Self {
92 red: hex("#e78284"),
93 peach: hex("#ef9f76"),
94 yellow: hex("#e5c890"),
95 teal: hex("#81c8be"),
96 sky: hex("#99d1db"),
97 blue: hex("#8caaee"),
98 mauve: hex("#ca9ee6"),
99 overlay1: hex("#838ba7"),
100 overlay0: hex("#737994"),
101 };
102
103 pub const CATPPUCCIN_LATTE: Self = Self {
105 red: hex("#d20f39"),
106 peach: hex("#fe640b"),
107 yellow: hex("#df8e1d"),
108 teal: hex("#179299"),
109 sky: hex("#04a5e5"),
110 blue: hex("#1e66f5"),
111 mauve: hex("#8839ef"),
112 overlay1: hex("#8c8fa1"),
113 overlay0: hex("#9ca0b0"),
114 };
115
116 pub const DRACULA: Self = Self {
118 red: hex("#ff5555"),
119 peach: hex("#ffb86c"),
120 yellow: hex("#f1fa8c"),
121 teal: hex("#8be9fd"),
122 sky: hex("#8be9fd"),
123 blue: hex("#bd93f9"),
124 mauve: hex("#bd93f9"),
125 overlay1: hex("#6272a4"),
126 overlay0: hex("#6272a4"),
127 };
128
129 pub const NORD: Self = Self {
131 red: hex("#bf616a"),
132 peach: hex("#d08770"),
133 yellow: hex("#ebcb8b"),
134 teal: hex("#8fbcbb"),
135 sky: hex("#88c0d0"),
136 blue: hex("#81a1c1"),
137 mauve: hex("#b48ead"),
138 overlay1: hex("#4c566a"),
139 overlay0: hex("#4c566a"),
140 };
141
142 pub const DEFAULT: Self = Self::CATPPUCCIN_MOCHA;
144
145 #[must_use]
149 pub const fn frame_number(&self) -> Style {
150 style(self.overlay1)
151 }
152 #[must_use]
154 pub const fn function_name(&self) -> Style {
155 style(self.red)
156 }
157 #[must_use]
159 pub const fn function_hash(&self) -> Style {
160 style(self.overlay0)
161 }
162 #[must_use]
164 pub const fn file_path(&self) -> Style {
165 style(self.mauve)
166 }
167 #[must_use]
169 pub const fn line_number(&self) -> Style {
170 style(self.peach)
171 }
172 #[must_use]
174 pub const fn separator(&self) -> Style {
175 style(self.overlay0)
176 }
177 #[must_use]
179 pub const fn fields(&self) -> Style {
180 style(self.sky)
181 }
182 #[must_use]
184 pub const fn header(&self) -> Style {
185 style(self.mauve)
186 }
187 #[must_use]
189 pub const fn frames_hidden(&self) -> Style {
190 style(self.teal)
191 }
192
193 #[must_use]
197 pub const fn error_title(&self) -> Style {
198 style(self.red).bold()
199 }
200 #[must_use]
202 pub const fn error_code(&self) -> Style {
203 style(self.blue)
204 }
205 #[must_use]
207 pub const fn delimiter(&self) -> Style {
208 style(self.overlay0)
209 }
210 #[must_use]
212 pub const fn cause(&self) -> Style {
213 style(self.yellow)
214 }
215 #[must_use]
217 pub const fn help(&self) -> Style {
218 style(self.teal)
219 }
220 #[must_use]
222 pub const fn panic_message(&self) -> Style {
223 style(self.sky)
224 }
225 #[must_use]
227 pub const fn panic_location(&self) -> Style {
228 style(self.mauve)
229 }
230 #[must_use]
232 pub const fn hint(&self) -> Style {
233 style(self.overlay0)
234 }
235
236 #[must_use]
240 pub const fn trace(&self) -> TraceTheme {
241 TraceTheme {
242 frame_number: self.frame_number(),
243 function_name: self.function_name(),
244 function_hash: self.function_hash(),
245 file_path: self.file_path(),
246 line_number: self.line_number(),
247 separator: self.separator(),
248 fields: self.fields(),
249 header: self.header(),
250 frames_hidden: self.frames_hidden(),
251 }
252 }
253}
254
255static THEME: RwLock<Theme> = RwLock::new(Theme::DEFAULT);
256
257#[inline]
260pub fn set_theme(theme: Theme) {
261 *THEME.write().unwrap_or_else(PoisonError::into_inner) = theme;
262}
263
264#[must_use]
266#[inline]
267pub fn get_theme() -> Theme {
268 *THEME.read().unwrap_or_else(PoisonError::into_inner)
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn set_get_theme_roundtrips() {
277 let original = get_theme();
278 set_theme(Theme::DRACULA);
279 assert_eq!(get_theme(), Theme::DRACULA);
280 set_theme(Theme::NORD);
281 assert_eq!(get_theme(), Theme::NORD);
282 set_theme(original);
283 }
284}