1use ratatui::style::Color;
2
3#[derive(Debug, Clone, Copy, PartialEq)]
4pub enum ThemeMode {
5 Dark,
6 Light,
7 Auto,
8}
9
10#[derive(Debug, Clone)]
11pub struct Theme {
12 pub selection_bg: Color,
14 pub file_header_bg: Color,
15 pub added_line_bg: Color,
16 pub removed_line_bg: Color,
17 pub added_emphasis_bg: Color, pub removed_emphasis_bg: Color, pub file_header_fg: Color,
20 pub context_fg: Color,
21 pub context_bg: Color,
22
23 pub gutter_fg: Color,
25
26 pub help_text_fg: Color,
28 pub help_section_fg: Color, pub help_key_fg: Color, pub help_dismiss_fg: Color, pub help_overlay_bg: Color, pub tree_highlight_fg: Color,
35 pub tree_highlight_bg: Color,
36 pub tree_group_fg: Color,
37
38 pub search_match_fg: Color,
40 pub search_match_bg: Color,
41
42 pub md_inline_code_fg: Color,
44 pub md_heading_h1_fg: Color,
45 pub md_heading_h2_fg: Color,
46 pub md_heading_h3_fg: Color,
47 pub md_heading_h4_fg: Color,
48 pub md_heading_h5_fg: Color,
49 pub md_heading_h6_fg: Color,
50 pub md_list_bullet_fg: Color,
51 pub md_code_block_fg: Color,
52 pub md_code_block_delim_fg: Color,
53 pub md_blockquote_fg: Color,
54 pub md_link_fg: Color,
55 pub md_rule_fg: Color,
56
57 pub syntect_theme: &'static str,
59}
60
61impl Theme {
62 pub fn dark() -> Self {
63 Self {
64 selection_bg: Color::Rgb(40, 40, 60),
65 file_header_bg: Color::Rgb(30, 30, 40),
66 added_line_bg: Color::Rgb(0, 40, 0),
67 removed_line_bg: Color::Rgb(40, 0, 0),
68 added_emphasis_bg: Color::Rgb(0, 80, 0),
69 removed_emphasis_bg: Color::Rgb(80, 0, 0),
70 file_header_fg: Color::White,
71 context_fg: Color::Reset,
72 context_bg: Color::Reset,
73 gutter_fg: Color::DarkGray,
74 help_text_fg: Color::White,
75 help_section_fg: Color::Cyan,
76 help_key_fg: Color::Yellow,
77 help_dismiss_fg: Color::DarkGray,
78 help_overlay_bg: Color::Black,
79 tree_highlight_fg: Color::Black,
80 tree_highlight_bg: Color::Cyan,
81 tree_group_fg: Color::Cyan,
82 search_match_fg: Color::Black,
83 search_match_bg: Color::Yellow,
84 md_inline_code_fg: Color::Yellow,
85 md_heading_h1_fg: Color::Magenta,
86 md_heading_h2_fg: Color::Cyan,
87 md_heading_h3_fg: Color::Green,
88 md_heading_h4_fg: Color::Yellow,
89 md_heading_h5_fg: Color::Blue,
90 md_heading_h6_fg: Color::Red,
91 md_list_bullet_fg: Color::Cyan,
92 md_code_block_fg: Color::Green,
93 md_code_block_delim_fg: Color::DarkGray,
94 md_blockquote_fg: Color::DarkGray,
95 md_link_fg: Color::Blue,
96 md_rule_fg: Color::DarkGray,
97 syntect_theme: "base16-ocean.dark",
98 }
99 }
100
101 pub fn light() -> Self {
102 Self {
103 selection_bg: Color::Rgb(210, 210, 230),
104 file_header_bg: Color::Rgb(220, 220, 235),
105 added_line_bg: Color::Rgb(210, 255, 210),
106 removed_line_bg: Color::Rgb(255, 210, 210),
107 added_emphasis_bg: Color::Rgb(170, 240, 170),
108 removed_emphasis_bg: Color::Rgb(240, 170, 170),
109 file_header_fg: Color::Black,
110 context_fg: Color::Reset,
111 context_bg: Color::Reset,
112 gutter_fg: Color::Gray,
113 help_text_fg: Color::Black,
114 help_section_fg: Color::Blue,
115 help_key_fg: Color::Red,
116 help_dismiss_fg: Color::Gray,
117 help_overlay_bg: Color::White,
118 tree_highlight_fg: Color::White,
119 tree_highlight_bg: Color::Blue,
120 tree_group_fg: Color::Blue,
121 search_match_fg: Color::Black,
122 search_match_bg: Color::Yellow,
123 md_inline_code_fg: Color::Rgb(180, 80, 0),
124 md_heading_h1_fg: Color::Rgb(160, 0, 160),
125 md_heading_h2_fg: Color::Rgb(0, 130, 150),
126 md_heading_h3_fg: Color::Rgb(0, 130, 0),
127 md_heading_h4_fg: Color::Rgb(180, 80, 0),
128 md_heading_h5_fg: Color::Blue,
129 md_heading_h6_fg: Color::Red,
130 md_list_bullet_fg: Color::Rgb(0, 130, 150),
131 md_code_block_fg: Color::Rgb(0, 130, 0),
132 md_code_block_delim_fg: Color::Gray,
133 md_blockquote_fg: Color::Gray,
134 md_link_fg: Color::Blue,
135 md_rule_fg: Color::Gray,
136 syntect_theme: "base16-ocean.light",
137 }
138 }
139
140 pub fn from_mode(mode: ThemeMode) -> Self {
141 match mode {
142 ThemeMode::Dark => Self::dark(),
143 ThemeMode::Light => Self::light(),
144 ThemeMode::Auto => {
145 if detect_light_background() {
146 Self::light()
147 } else {
148 Self::dark()
149 }
150 }
151 }
152 }
153}
154
155fn detect_light_background() -> bool {
168 use std::io::IsTerminal;
169
170 if !std::io::stdin().is_terminal() || !std::io::stdout().is_terminal() {
172 return false;
173 }
174 if std::env::var("CI").is_ok() || std::env::var("TERM").as_deref() == Ok("dumb") {
175 return false;
176 }
177
178 if let Ok(luma) = terminal_light::luma() {
180 return luma > 0.6;
181 }
182
183 if let Ok(val) = std::env::var("COLORFGBG") {
185 if let Some(bg) = val.rsplit(';').next().and_then(|s| s.parse::<u8>().ok()) {
186 return bg >= 7;
188 }
189 }
190
191 false
192}