rush_sync_server/commands/theme/
themes.rs1use crate::core::prelude::*;
2use std::collections::HashMap;
3
4#[derive(Debug, Clone)]
5pub struct ThemeDefinition {
6 pub input_text: String,
7 pub input_bg: String,
8 pub cursor: String,
9 pub output_text: String,
10 pub output_bg: String,
11}
12
13pub struct TomlThemeLoader;
15
16impl TomlThemeLoader {
17 pub async fn load_all_themes() -> Result<HashMap<String, ThemeDefinition>> {
19 let config_paths = crate::setup::setup_toml::get_config_paths();
20
21 for path in config_paths {
22 if path.exists() {
23 match tokio::fs::read_to_string(&path).await {
24 Ok(content) => {
25 if let Ok(themes) = Self::parse_themes_from_toml(&content) {
26 log::debug!(
27 "Loaded {} themes from TOML: {}",
28 themes.len(),
29 path.display()
30 );
31 return Ok(themes);
32 }
33 }
34 Err(e) => {
35 log::debug!("Could not read config file '{}': {}", path.display(), e);
36 continue;
37 }
38 }
39 }
40 }
41
42 log::debug!("No TOML themes found, using fallback themes");
44 Ok(Self::get_fallback_themes())
45 }
46
47 pub async fn load_theme_by_name(theme_name: &str) -> Result<Option<ThemeDefinition>> {
49 let all_themes = Self::load_all_themes().await?;
50 Ok(all_themes.get(&theme_name.to_lowercase()).cloned())
51 }
52
53 pub fn load_theme_by_name_sync(theme_name: &str) -> Option<ThemeDefinition> {
55 let config_paths = crate::setup::setup_toml::get_config_paths();
56
57 for path in config_paths {
58 if path.exists() {
59 if let Ok(content) = std::fs::read_to_string(&path) {
60 if let Ok(themes) = Self::parse_themes_from_toml(&content) {
61 return themes.get(&theme_name.to_lowercase()).cloned();
62 }
63 }
64 }
65 }
66
67 Self::get_fallback_themes()
69 .get(&theme_name.to_lowercase())
70 .cloned()
71 }
72
73 pub fn parse_themes_from_toml(content: &str) -> Result<HashMap<String, ThemeDefinition>> {
75 let mut themes = HashMap::new();
76 let mut current_theme_name: Option<String> = None;
77 let mut current_theme_data: HashMap<String, String> = HashMap::new();
78
79 for line in content.lines() {
80 let trimmed = line.trim();
81
82 if trimmed.is_empty() || trimmed.starts_with('#') {
84 continue;
85 }
86
87 if trimmed.starts_with("[theme.") && trimmed.ends_with(']') {
89 if let Some(theme_name) = current_theme_name.take() {
91 if let Some(theme_def) = Self::build_theme_from_data(¤t_theme_data) {
92 themes.insert(theme_name, theme_def);
93 }
94 current_theme_data.clear();
95 }
96
97 if let Some(name) = trimmed
99 .strip_prefix("[theme.")
100 .and_then(|s| s.strip_suffix(']'))
101 {
102 current_theme_name = Some(name.to_lowercase());
103 }
104 }
105 else if trimmed.starts_with('[')
107 && trimmed.ends_with(']')
108 && !trimmed.starts_with("[theme.")
109 {
110 if let Some(theme_name) = current_theme_name.take() {
111 if let Some(theme_def) = Self::build_theme_from_data(¤t_theme_data) {
112 themes.insert(theme_name, theme_def);
113 }
114 current_theme_data.clear();
115 }
116 }
117 else if current_theme_name.is_some() && trimmed.contains('=') {
119 if let Some((key, value)) = trimmed.split_once('=') {
120 let clean_key = key.trim().to_string();
121 let clean_value = value
122 .trim()
123 .trim_matches('"')
124 .trim_matches('\'')
125 .trim()
126 .to_string();
127
128 if !clean_value.is_empty() {
129 current_theme_data.insert(clean_key, clean_value);
130 }
131 }
132 }
133 }
134
135 if let Some(theme_name) = current_theme_name {
137 if let Some(theme_def) = Self::build_theme_from_data(¤t_theme_data) {
138 themes.insert(theme_name, theme_def);
139 }
140 }
141
142 log::debug!("Parsed {} themes from TOML", themes.len());
143 Ok(themes)
144 }
145
146 pub fn build_theme_from_data(data: &HashMap<String, String>) -> Option<ThemeDefinition> {
148 let input_text = data.get("input_text")?.clone();
150 let input_bg = data.get("input_bg")?.clone();
151 let cursor = data.get("cursor")?.clone();
152 let output_text = data.get("output_text")?.clone();
153 let output_bg = data.get("output_bg")?.clone();
154
155 Some(ThemeDefinition {
156 input_text,
157 input_bg,
158 cursor,
159 output_text,
160 output_bg,
161 })
162 }
163
164 pub fn get_fallback_themes() -> HashMap<String, ThemeDefinition> {
166 let mut themes = HashMap::new();
167
168 themes.insert(
169 "dark".to_string(),
170 ThemeDefinition {
171 input_text: "White".to_string(),
172 input_bg: "Black".to_string(),
173 cursor: "White".to_string(),
174 output_text: "White".to_string(),
175 output_bg: "Black".to_string(),
176 },
177 );
178
179 themes.insert(
180 "light".to_string(),
181 ThemeDefinition {
182 input_text: "Black".to_string(),
183 input_bg: "White".to_string(),
184 cursor: "Black".to_string(),
185 output_text: "Black".to_string(),
186 output_bg: "White".to_string(),
187 },
188 );
189
190 themes.insert(
191 "matrix".to_string(),
192 ThemeDefinition {
193 input_text: "LightGreen".to_string(),
194 input_bg: "Black".to_string(),
195 cursor: "LightGreen".to_string(),
196 output_text: "Green".to_string(),
197 output_bg: "Black".to_string(),
198 },
199 );
200
201 themes.insert(
202 "blue".to_string(),
203 ThemeDefinition {
204 input_text: "LightBlue".to_string(),
205 input_bg: "Black".to_string(),
206 cursor: "LightBlue".to_string(),
207 output_text: "LightBlue".to_string(),
208 output_bg: "Black".to_string(),
209 },
210 );
211
212 themes
213 }
214
215 pub async fn theme_exists(theme_name: &str) -> bool {
217 if let Ok(themes) = Self::load_all_themes().await {
218 themes.contains_key(&theme_name.to_lowercase())
219 } else {
220 false
221 }
222 }
223
224 pub fn theme_exists_sync(theme_name: &str) -> bool {
226 Self::load_theme_by_name_sync(theme_name).is_some()
227 }
228
229 pub async fn get_available_names() -> Vec<String> {
231 if let Ok(themes) = Self::load_all_themes().await {
232 let mut names: Vec<String> = themes.keys().cloned().collect();
233 names.sort();
234 names
235 } else {
236 vec![
237 "dark".to_string(),
238 "light".to_string(),
239 "matrix".to_string(),
240 "blue".to_string(),
241 ]
242 }
243 }
244}
245
246pub struct PredefinedThemes;
248
249impl PredefinedThemes {
250 pub fn get_by_name(name: &str) -> Option<ThemeDefinition> {
252 TomlThemeLoader::load_theme_by_name_sync(name)
253 }
254
255 pub fn exists(name: &str) -> bool {
257 TomlThemeLoader::theme_exists_sync(name)
258 }
259
260 pub fn get_all() -> HashMap<String, ThemeDefinition> {
262 let config_paths = crate::setup::setup_toml::get_config_paths();
264
265 for path in config_paths {
266 if path.exists() {
267 if let Ok(content) = std::fs::read_to_string(&path) {
268 if let Ok(themes) = TomlThemeLoader::parse_themes_from_toml(&content) {
269 return themes;
270 }
271 }
272 }
273 }
274
275 TomlThemeLoader::get_fallback_themes()
277 }
278
279 pub fn get_available_names() -> Vec<String> {
281 Self::get_all().keys().cloned().collect()
282 }
283}