Skip to main content

dot/tui/
theme.rs

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}