1use ratatui::style::{Color, Modifier, Style};
2
3#[derive(Debug, Clone, Copy, PartialEq)]
4pub enum TerminalBackground {
5 Dark,
6 Light,
7}
8
9pub fn detect_terminal_background() -> TerminalBackground {
10 if let Ok(val) = std::env::var("TERM_BACKGROUND") {
11 match val.to_lowercase().as_str() {
12 "light" => return TerminalBackground::Light,
13 "dark" => return TerminalBackground::Dark,
14 _ => {}
15 }
16 }
17
18 if let Ok(val) = std::env::var("COLORFGBG")
19 && let Some(bg_str) = val.rsplit(';').next()
20 && let Ok(bg) = bg_str.trim().parse::<u8>()
21 {
22 return if bg < 8 || (232..232 + 12).contains(&bg) {
23 TerminalBackground::Dark
24 } else if (8..=15).contains(&bg) {
25 TerminalBackground::Light
26 } else {
27 TerminalBackground::Dark
28 };
29 }
30
31 if let Ok(term_program) = std::env::var("TERM_PROGRAM")
32 && term_program.to_lowercase().contains("apple_terminal")
33 {
34 return TerminalBackground::Light;
35 }
36
37 TerminalBackground::Dark
38}
39
40pub struct Theme {
41 pub bg: Color,
42 pub fg: Color,
43 pub dim: Style,
44 pub accent: Color,
45 pub user_label: Style,
46 pub assistant_label: Style,
47 pub border: Style,
48 pub input_prompt: Style,
49 pub status_bar: Style,
50 pub code_bg: Color,
51 pub inline_code: Style,
52 pub error: Style,
53 pub tool_name: Style,
54 pub tool_output: Style,
55 pub heading: Style,
56 pub bold: Style,
57 pub italic: Style,
58 pub blockquote: Style,
59 pub link: Style,
60 pub list_bullet: Style,
61 pub scrollbar_track: Style,
62 pub scrollbar_thumb: Style,
63 pub tool_success: Style,
64 pub highlight: Style,
65 pub muted_fg: Color,
66 pub tool_file_read: Style,
67 pub tool_file_write: Style,
68 pub tool_directory: Style,
69 pub tool_search: Style,
70 pub tool_command: Style,
71 pub tool_mcp: Style,
72 pub tool_skill: Style,
73 pub tool_badge_bg: Color,
74 pub tool_path: Style,
75 pub thinking: Style,
76 pub mode_normal_fg: Color,
77 pub mode_normal_bg: Color,
78 pub mode_insert_fg: Color,
79 pub mode_insert_bg: Color,
80 pub cost: Style,
81 pub user_text: Style,
82 pub tool_action: Style,
83 pub separator: Style,
84 pub tool_exit_ok: Style,
85 pub tool_exit_err: Style,
86 pub syntect_theme: Option<&'static str>,
87}
88
89impl Theme {
90 pub fn from_config(name: &str) -> Self {
91 match name {
92 "light" => Self::light(),
93 "terminal" => Self::terminal(),
94 "auto" => match detect_terminal_background() {
95 TerminalBackground::Light => Self::light(),
96 TerminalBackground::Dark => Self::dark(),
97 },
98 _ => Self::dark(),
99 }
100 }
101
102 pub fn dark() -> Self {
103 let muted = Color::Rgb(88, 91, 112);
104 let surface = Color::Rgb(49, 50, 68);
105 let accent = Color::Rgb(137, 180, 250);
106 let green = Color::Rgb(166, 227, 161);
107 let peach = Color::Rgb(250, 179, 135);
108 let red = Color::Rgb(243, 139, 168);
109 let mauve = Color::Rgb(203, 166, 247);
110 let yellow = Color::Rgb(249, 226, 175);
111 let teal = Color::Rgb(148, 226, 213);
112 let sapphire = Color::Rgb(116, 199, 236);
113 let base = Color::Rgb(30, 30, 46);
114
115 Self {
116 bg: Color::Reset,
117 fg: Color::White,
118 dim: Style::default().fg(muted),
119 accent,
120 muted_fg: muted,
121 user_label: Style::default().fg(mauve).add_modifier(Modifier::BOLD),
122 assistant_label: Style::default().fg(accent).add_modifier(Modifier::BOLD),
123 border: Style::default().fg(surface),
124 input_prompt: Style::default().fg(accent),
125 status_bar: Style::default().fg(muted),
126 code_bg: base,
127 inline_code: Style::default().fg(peach),
128 error: Style::default().fg(red),
129 tool_name: Style::default().fg(yellow).add_modifier(Modifier::BOLD),
130 tool_output: Style::default().fg(muted),
131 tool_success: Style::default().fg(green),
132 heading: Style::default().fg(accent).add_modifier(Modifier::BOLD),
133 bold: Style::default().add_modifier(Modifier::BOLD),
134 italic: Style::default().add_modifier(Modifier::ITALIC),
135 blockquote: Style::default().fg(muted),
136 link: Style::default()
137 .fg(accent)
138 .add_modifier(Modifier::UNDERLINED),
139 list_bullet: Style::default().fg(muted),
140 scrollbar_track: Style::default().fg(surface),
141 scrollbar_thumb: Style::default().fg(muted),
142 highlight: Style::default().fg(base).bg(accent),
143 tool_file_read: Style::default().fg(sapphire),
144 tool_file_write: Style::default().fg(peach),
145 tool_directory: Style::default().fg(accent),
146 tool_search: Style::default().fg(mauve),
147 tool_command: Style::default().fg(green),
148 tool_mcp: Style::default().fg(teal),
149 tool_skill: Style::default().fg(mauve),
150 tool_badge_bg: surface,
151 tool_path: Style::default()
152 .fg(Color::White)
153 .add_modifier(Modifier::UNDERLINED),
154 thinking: Style::default().fg(muted),
155 mode_normal_fg: base,
156 mode_normal_bg: muted,
157 mode_insert_fg: base,
158 mode_insert_bg: accent,
159 cost: Style::default().fg(Color::Rgb(180, 150, 90)),
160 user_text: Style::default().fg(Color::Rgb(205, 214, 244)),
161 tool_action: Style::default().fg(muted),
162 separator: Style::default().fg(Color::Rgb(60, 62, 80)),
163 tool_exit_ok: Style::default().fg(green),
164 tool_exit_err: Style::default().fg(red),
165 syntect_theme: Some("base16-ocean.dark"),
166 }
167 }
168
169 pub fn light() -> Self {
170 let muted = Color::Rgb(140, 143, 161);
171 let surface = Color::Rgb(204, 208, 218);
172 let accent = Color::Rgb(30, 102, 245);
173 let green = Color::Rgb(64, 160, 43);
174 let peach = Color::Rgb(254, 100, 11);
175 let red = Color::Rgb(210, 15, 57);
176 let mauve = Color::Rgb(136, 57, 239);
177 let yellow = Color::Rgb(223, 142, 29);
178 let teal = Color::Rgb(23, 146, 153);
179 let sapphire = Color::Rgb(32, 159, 181);
180 let base = Color::Rgb(239, 241, 245);
181 let text = Color::Rgb(76, 79, 105);
182
183 Self {
184 bg: Color::Reset,
185 fg: text,
186 dim: Style::default().fg(muted),
187 accent,
188 muted_fg: muted,
189 user_label: Style::default().fg(mauve).add_modifier(Modifier::BOLD),
190 assistant_label: Style::default().fg(accent).add_modifier(Modifier::BOLD),
191 border: Style::default().fg(surface),
192 input_prompt: Style::default().fg(accent),
193 status_bar: Style::default().fg(muted),
194 code_bg: base,
195 inline_code: Style::default().fg(peach),
196 error: Style::default().fg(red),
197 tool_name: Style::default().fg(yellow).add_modifier(Modifier::BOLD),
198 tool_output: Style::default().fg(muted),
199 tool_success: Style::default().fg(green),
200 heading: Style::default().fg(accent).add_modifier(Modifier::BOLD),
201 bold: Style::default().add_modifier(Modifier::BOLD),
202 italic: Style::default().add_modifier(Modifier::ITALIC),
203 blockquote: Style::default().fg(muted),
204 link: Style::default()
205 .fg(accent)
206 .add_modifier(Modifier::UNDERLINED),
207 list_bullet: Style::default().fg(muted),
208 scrollbar_track: Style::default().fg(surface),
209 scrollbar_thumb: Style::default().fg(muted),
210 highlight: Style::default().fg(Color::White).bg(accent),
211 tool_file_read: Style::default().fg(sapphire),
212 tool_file_write: Style::default().fg(peach),
213 tool_directory: Style::default().fg(accent),
214 tool_search: Style::default().fg(mauve),
215 tool_command: Style::default().fg(green),
216 tool_mcp: Style::default().fg(teal),
217 tool_skill: Style::default().fg(mauve),
218 tool_badge_bg: surface,
219 tool_path: Style::default().fg(text).add_modifier(Modifier::UNDERLINED),
220 thinking: Style::default().fg(muted),
221 mode_normal_fg: Color::White,
222 mode_normal_bg: muted,
223 mode_insert_fg: Color::White,
224 mode_insert_bg: accent,
225 cost: Style::default().fg(Color::Rgb(160, 120, 40)),
226 user_text: Style::default().fg(text),
227 tool_action: Style::default().fg(muted),
228 separator: Style::default().fg(surface),
229 tool_exit_ok: Style::default().fg(green),
230 tool_exit_err: Style::default().fg(red),
231 syntect_theme: Some("base16-ocean.light"),
232 }
233 }
234
235 pub fn terminal() -> Self {
236 let red = Color::Indexed(1);
237 let green = Color::Indexed(2);
238 let yellow = Color::Indexed(3);
239 let magenta = Color::Indexed(5);
240 let cyan = Color::Indexed(6);
241 let bright_black = Color::Indexed(8);
242
243 let fg = Color::Reset;
244 let dim = Style::default().fg(bright_black);
245 let bold = Style::default().add_modifier(Modifier::BOLD);
246
247 Self {
248 bg: Color::Reset,
249 fg,
250 dim,
251 accent: fg,
252 muted_fg: bright_black,
253 user_label: Style::default().fg(magenta).add_modifier(Modifier::BOLD),
254 assistant_label: bold,
255 border: dim,
256 input_prompt: bold,
257 status_bar: dim,
258 code_bg: Color::Reset,
259 inline_code: Style::default().fg(yellow),
260 error: Style::default().fg(red),
261 tool_name: Style::default().add_modifier(Modifier::BOLD),
262 tool_output: dim,
263 tool_success: Style::default().fg(green),
264 heading: Style::default().add_modifier(Modifier::BOLD | Modifier::UNDERLINED),
265 bold,
266 italic: Style::default().add_modifier(Modifier::ITALIC),
267 blockquote: dim,
268 link: Style::default().fg(cyan).add_modifier(Modifier::UNDERLINED),
269 list_bullet: dim,
270 scrollbar_track: dim,
271 scrollbar_thumb: Style::default().fg(fg),
272 highlight: Style::default().add_modifier(Modifier::REVERSED),
273 tool_file_read: Style::default().fg(cyan),
274 tool_file_write: Style::default().fg(yellow),
275 tool_directory: bold,
276 tool_search: Style::default().fg(magenta),
277 tool_command: Style::default().fg(green),
278 tool_mcp: Style::default().fg(cyan),
279 tool_skill: Style::default().fg(magenta),
280 tool_badge_bg: bright_black,
281 tool_path: Style::default().add_modifier(Modifier::UNDERLINED),
282 thinking: dim,
283 mode_normal_fg: Color::Reset,
284 mode_normal_bg: bright_black,
285 mode_insert_fg: Color::Indexed(0),
286 mode_insert_bg: Color::Reset,
287 cost: dim,
288 user_text: Style::default(),
289 tool_action: dim,
290 separator: dim,
291 tool_exit_ok: Style::default().fg(green),
292 tool_exit_err: Style::default().fg(red),
293 syntect_theme: None,
294 }
295 }
296}
297
298impl Default for Theme {
299 fn default() -> Self {
300 Self::terminal()
301 }
302}