vtcode_core/ui/
theme_manager.rs1use crate::ui::{FileColorizer, GitColorConfig, ThemeConfig};
7use anyhow::Result;
8use std::path::Path;
9
10#[derive(Debug, Clone)]
12pub struct ThemeManager {
13 pub custom_config: Option<ThemeConfig>,
15
16 pub git_config: Option<GitColorConfig>,
18
19 pub file_colorizer: FileColorizer,
21}
22
23impl ThemeManager {
24 pub fn new(workspace_root: Option<&Path>) -> Self {
26 let custom_config = Self::load_custom_config(workspace_root);
27 let git_config = Self::load_git_config(workspace_root);
28 let file_colorizer = FileColorizer::new();
29
30 Self { custom_config, git_config, file_colorizer }
31 }
32
33 fn load_custom_config(workspace_root: Option<&Path>) -> Option<ThemeConfig> {
35 if let Some(workspace) = workspace_root {
36 let theme_path = workspace.join(".vtcode").join("theme.toml");
37 match ThemeConfig::load_from_file(&theme_path) {
38 Ok(config) => {
39 tracing::info!("Loaded custom theme configuration from: {}", theme_path.display());
40 Some(config)
41 }
42 Err(e) => {
43 if theme_path.exists() {
44 tracing::warn!("Failed to load theme config from {}: {}", theme_path.display(), e);
45 }
46 None
47 }
48 }
49 } else {
50 None
51 }
52 }
53
54 fn load_git_config(workspace_root: Option<&Path>) -> Option<GitColorConfig> {
56 if let Some(workspace) = workspace_root {
57 let git_config_path = workspace.join(".git").join("config");
58 if git_config_path.exists() {
59 match GitColorConfig::from_git_config(&git_config_path) {
60 Ok(config) => {
61 tracing::info!("Loaded Git color configuration from: {}", git_config_path.display());
62 Some(config)
63 }
64 Err(e) => {
65 tracing::warn!("Failed to load Git config from {}: {}", git_config_path.display(), e);
66 GitColorConfig::default().into()
67 }
68 }
69 } else {
70 None
71 }
72 } else {
73 None
74 }
75 }
76
77 pub fn active_theme_config(&self) -> ThemeConfig {
79 self.custom_config.clone().unwrap_or_default()
80 }
81
82 pub fn load_theme_from_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
84 let config = ThemeConfig::load_from_file(path)?;
85 self.custom_config = Some(config);
86 Ok(())
87 }
88
89 pub fn reset_to_default(&mut self) {
91 self.custom_config = None;
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use std::fs;
99 use tempfile::tempdir;
100
101 #[test]
102 fn test_theme_manager_default() {
103 let manager = ThemeManager::new(None);
104 assert!(manager.custom_config.is_none());
105 assert!(manager.git_config.is_none());
106 assert_eq!(manager.file_colorizer.style_for_path(Path::new("/tmp/test.rs")), None);
108 }
109
110 #[test]
111 fn test_load_custom_config() {
112 let temp_dir = tempdir().unwrap();
113 let vtcode_dir = temp_dir.path().join(".vtcode");
114 fs::create_dir_all(&vtcode_dir).unwrap();
115
116 let theme_content = r#"
117[cli]
118success = "bold green"
119error = "bold red"
120
121[diff]
122new = "green"
123old = "red"
124
125[status]
126added = "green"
127
128[files]
129directory = "bold cyan"
130"#;
131
132 let theme_path = vtcode_dir.join("theme.toml");
133 fs::write(&theme_path, theme_content).unwrap();
134
135 let manager = ThemeManager::new(Some(temp_dir.path()));
136 assert!(manager.custom_config.is_some());
137
138 let config = manager.active_theme_config();
139 assert_eq!(config.cli.success, "bold green");
140 assert_eq!(config.diff.new, "green");
141 assert_eq!(config.status.added, "green");
142 }
143
144 #[test]
145 fn test_load_theme_from_file() {
146 let temp_dir = tempdir().unwrap();
147 let theme_content = r#"
148[cli]
149success = "cyan"
150"#;
151
152 let theme_path = temp_dir.path().join("custom_theme.toml");
153 fs::write(&theme_path, theme_content).unwrap();
154
155 let mut manager = ThemeManager::new(None);
156 assert!(manager.custom_config.is_none());
157
158 manager.load_theme_from_file(&theme_path).unwrap();
159 assert!(manager.custom_config.is_some());
160
161 let config = manager.active_theme_config();
162 assert_eq!(config.cli.success, "cyan");
163 }
164
165 #[test]
166 fn test_reset_to_default() {
167 let temp_dir = tempdir().unwrap();
168 let theme_content = r#"
169[cli]
170success = "red"
171"#;
172
173 let theme_path = temp_dir.path().join("custom_theme.toml");
174 fs::write(&theme_path, theme_content).unwrap();
175
176 let mut manager = ThemeManager::new(None);
177 manager.load_theme_from_file(&theme_path).unwrap();
178 assert!(manager.custom_config.is_some());
179
180 manager.reset_to_default();
181 assert!(manager.custom_config.is_none());
182 }
183}