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