vtcode_config/defaults/
provider.rs1use std::path::{Path, PathBuf};
2use std::sync::{Arc, RwLock};
3
4use directories::ProjectDirs;
5use once_cell::sync::Lazy;
6use vtcode_commons::paths::WorkspacePaths;
7
8const DEFAULT_CONFIG_FILE_NAME: &str = "vtcode.toml";
9const DEFAULT_CONFIG_DIR_NAME: &str = ".vtcode";
10const DEFAULT_SYNTAX_THEME: &str = "base16-ocean.dark";
11
12static DEFAULT_SYNTAX_LANGUAGES: Lazy<Vec<String>> = Lazy::new(Vec::new);
15
16static CONFIG_DEFAULTS: Lazy<RwLock<Arc<dyn ConfigDefaultsProvider>>> =
17 Lazy::new(|| RwLock::new(Arc::new(DefaultConfigDefaults)));
18
19pub trait ConfigDefaultsProvider: Send + Sync {
22 fn config_file_name(&self) -> &str {
24 DEFAULT_CONFIG_FILE_NAME
25 }
26
27 fn workspace_paths_for(&self, workspace_root: &Path) -> Box<dyn WorkspacePaths>;
30
31 fn home_config_paths(&self, config_file_name: &str) -> Vec<PathBuf>;
34
35 fn syntax_theme(&self) -> String;
37
38 fn syntax_languages(&self) -> Vec<String>;
40}
41
42#[derive(Debug, Default)]
43struct DefaultConfigDefaults;
44
45impl ConfigDefaultsProvider for DefaultConfigDefaults {
46 fn workspace_paths_for(&self, workspace_root: &Path) -> Box<dyn WorkspacePaths> {
47 Box::new(DefaultWorkspacePaths::new(workspace_root.to_path_buf()))
48 }
49
50 fn home_config_paths(&self, config_file_name: &str) -> Vec<PathBuf> {
51 default_home_paths(config_file_name)
52 }
53
54 fn syntax_theme(&self) -> String {
55 DEFAULT_SYNTAX_THEME.to_string()
56 }
57
58 fn syntax_languages(&self) -> Vec<String> {
59 default_syntax_languages()
60 }
61}
62
63pub fn install_config_defaults_provider(
65 provider: Arc<dyn ConfigDefaultsProvider>,
66) -> Arc<dyn ConfigDefaultsProvider> {
67 let mut guard = CONFIG_DEFAULTS.write().unwrap_or_else(|poisoned| {
68 tracing::warn!(
69 "config defaults provider lock poisoned while installing provider; recovering"
70 );
71 poisoned.into_inner()
72 });
73 std::mem::replace(&mut *guard, provider)
74}
75
76pub fn reset_to_default_config_defaults() {
78 let _ = install_config_defaults_provider(Arc::new(DefaultConfigDefaults));
79}
80
81pub fn with_config_defaults<F, R>(operation: F) -> R
83where
84 F: FnOnce(&dyn ConfigDefaultsProvider) -> R,
85{
86 let guard = CONFIG_DEFAULTS.read().unwrap_or_else(|poisoned| {
87 tracing::warn!("config defaults provider lock poisoned while reading provider; recovering");
88 poisoned.into_inner()
89 });
90 operation(guard.as_ref())
91}
92
93pub fn current_config_defaults() -> Arc<dyn ConfigDefaultsProvider> {
95 let guard = CONFIG_DEFAULTS.read().unwrap_or_else(|poisoned| {
96 tracing::warn!("config defaults provider lock poisoned while cloning provider; recovering");
97 poisoned.into_inner()
98 });
99 Arc::clone(&*guard)
100}
101
102pub fn with_config_defaults_provider_for_test<F, R>(
103 provider: Arc<dyn ConfigDefaultsProvider>,
104 action: F,
105) -> R
106where
107 F: FnOnce() -> R,
108{
109 use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
110
111 let previous = install_config_defaults_provider(provider);
112 let result = catch_unwind(AssertUnwindSafe(action));
113 let _ = install_config_defaults_provider(previous);
114
115 match result {
116 Ok(value) => value,
117 Err(payload) => resume_unwind(payload),
118 }
119}
120
121pub fn get_config_dir() -> Option<PathBuf> {
130 if let Ok(custom_dir) = std::env::var("VTCODE_CONFIG") {
132 return Some(PathBuf::from(custom_dir));
133 }
134
135 if let Some(proj_dirs) = ProjectDirs::from("com", "vinhnx", "vtcode") {
137 return Some(proj_dirs.config_local_dir().to_path_buf());
138 }
139
140 dirs::home_dir().map(|home| home.join(DEFAULT_CONFIG_DIR_NAME))
142}
143
144pub fn get_data_dir() -> Option<PathBuf> {
153 if let Ok(custom_dir) = std::env::var("VTCODE_DATA") {
155 return Some(PathBuf::from(custom_dir));
156 }
157
158 if let Some(proj_dirs) = ProjectDirs::from("com", "vinhnx", "vtcode") {
160 return Some(proj_dirs.data_local_dir().to_path_buf());
161 }
162
163 dirs::home_dir().map(|home| home.join(DEFAULT_CONFIG_DIR_NAME).join("cache"))
165}
166
167fn default_home_paths(config_file_name: &str) -> Vec<PathBuf> {
168 get_config_dir()
169 .map(|config_dir| config_dir.join(config_file_name))
170 .into_iter()
171 .collect()
172}
173
174fn default_syntax_languages() -> Vec<String> {
175 DEFAULT_SYNTAX_LANGUAGES.clone()
176}
177
178#[derive(Debug, Clone)]
179struct DefaultWorkspacePaths {
180 root: PathBuf,
181}
182
183impl DefaultWorkspacePaths {
184 fn new(root: PathBuf) -> Self {
185 Self { root }
186 }
187
188 fn config_dir_path(&self) -> PathBuf {
189 self.root.join(DEFAULT_CONFIG_DIR_NAME)
190 }
191}
192
193impl WorkspacePaths for DefaultWorkspacePaths {
194 fn workspace_root(&self) -> &Path {
195 &self.root
196 }
197
198 fn config_dir(&self) -> PathBuf {
199 self.config_dir_path()
200 }
201
202 fn cache_dir(&self) -> Option<PathBuf> {
203 Some(self.config_dir_path().join("cache"))
204 }
205
206 fn telemetry_dir(&self) -> Option<PathBuf> {
207 Some(self.config_dir_path().join("telemetry"))
208 }
209}
210
211#[derive(Debug, Clone)]
214pub struct WorkspacePathsDefaults<P>
215where
216 P: WorkspacePaths + ?Sized,
217{
218 paths: Arc<P>,
219 config_file_name: String,
220 home_paths: Option<Vec<PathBuf>>,
221 syntax_theme: String,
222 syntax_languages: Vec<String>,
223}
224
225impl<P> WorkspacePathsDefaults<P>
226where
227 P: WorkspacePaths + 'static,
228{
229 pub fn new(paths: Arc<P>) -> Self {
232 Self {
233 paths,
234 config_file_name: DEFAULT_CONFIG_FILE_NAME.to_string(),
235 home_paths: None,
236 syntax_theme: DEFAULT_SYNTAX_THEME.to_string(),
237 syntax_languages: default_syntax_languages(),
238 }
239 }
240
241 pub fn with_config_file_name(mut self, file_name: impl Into<String>) -> Self {
243 self.config_file_name = file_name.into();
244 self
245 }
246
247 pub fn with_home_paths(mut self, home_paths: Vec<PathBuf>) -> Self {
249 self.home_paths = Some(home_paths);
250 self
251 }
252
253 pub fn with_syntax_theme(mut self, theme: impl Into<String>) -> Self {
255 self.syntax_theme = theme.into();
256 self
257 }
258
259 pub fn with_syntax_languages(mut self, languages: Vec<String>) -> Self {
261 self.syntax_languages = languages;
262 self
263 }
264
265 pub fn build(self) -> Box<dyn ConfigDefaultsProvider> {
267 Box::new(self)
268 }
269}
270
271impl<P> ConfigDefaultsProvider for WorkspacePathsDefaults<P>
272where
273 P: WorkspacePaths + 'static,
274{
275 fn config_file_name(&self) -> &str {
276 &self.config_file_name
277 }
278
279 fn workspace_paths_for(&self, _workspace_root: &Path) -> Box<dyn WorkspacePaths> {
280 Box::new(WorkspacePathsWrapper {
281 inner: Arc::clone(&self.paths),
282 })
283 }
284
285 fn home_config_paths(&self, config_file_name: &str) -> Vec<PathBuf> {
286 self.home_paths
287 .clone()
288 .unwrap_or_else(|| default_home_paths(config_file_name))
289 }
290
291 fn syntax_theme(&self) -> String {
292 self.syntax_theme.clone()
293 }
294
295 fn syntax_languages(&self) -> Vec<String> {
296 self.syntax_languages.clone()
297 }
298}
299
300#[derive(Debug, Clone)]
301struct WorkspacePathsWrapper<P>
302where
303 P: WorkspacePaths + ?Sized,
304{
305 inner: Arc<P>,
306}
307
308impl<P> WorkspacePaths for WorkspacePathsWrapper<P>
309where
310 P: WorkspacePaths + ?Sized,
311{
312 fn workspace_root(&self) -> &Path {
313 self.inner.workspace_root()
314 }
315
316 fn config_dir(&self) -> PathBuf {
317 self.inner.config_dir()
318 }
319
320 fn cache_dir(&self) -> Option<PathBuf> {
321 self.inner.cache_dir()
322 }
323
324 fn telemetry_dir(&self) -> Option<PathBuf> {
325 self.inner.telemetry_dir()
326 }
327}