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(|| {
13 vec![
14 "rust",
15 "python",
16 "javascript",
17 "typescript",
18 "go",
19 "java",
20 "cpp",
21 "c",
22 "php",
23 "html",
24 "css",
25 "sql",
26 "csharp",
27 "bash",
28 "swift",
29 ]
30 .into_iter()
31 .map(String::from)
32 .collect()
33});
34
35static CONFIG_DEFAULTS: Lazy<RwLock<Arc<dyn ConfigDefaultsProvider>>> =
36 Lazy::new(|| RwLock::new(Arc::new(DefaultConfigDefaults)));
37
38pub trait ConfigDefaultsProvider: Send + Sync {
41 fn config_file_name(&self) -> &str {
43 DEFAULT_CONFIG_FILE_NAME
44 }
45
46 fn workspace_paths_for(&self, workspace_root: &Path) -> Box<dyn WorkspacePaths>;
49
50 fn home_config_paths(&self, config_file_name: &str) -> Vec<PathBuf>;
53
54 fn syntax_theme(&self) -> String;
56
57 fn syntax_languages(&self) -> Vec<String>;
59}
60
61#[derive(Debug, Default)]
62struct DefaultConfigDefaults;
63
64impl ConfigDefaultsProvider for DefaultConfigDefaults {
65 fn workspace_paths_for(&self, workspace_root: &Path) -> Box<dyn WorkspacePaths> {
66 Box::new(DefaultWorkspacePaths::new(workspace_root.to_path_buf()))
67 }
68
69 fn home_config_paths(&self, config_file_name: &str) -> Vec<PathBuf> {
70 default_home_paths(config_file_name)
71 }
72
73 fn syntax_theme(&self) -> String {
74 DEFAULT_SYNTAX_THEME.to_string()
75 }
76
77 fn syntax_languages(&self) -> Vec<String> {
78 default_syntax_languages()
79 }
80}
81
82pub fn install_config_defaults_provider(
84 provider: Arc<dyn ConfigDefaultsProvider>,
85) -> Arc<dyn ConfigDefaultsProvider> {
86 let mut guard = CONFIG_DEFAULTS
87 .write()
88 .expect("config defaults provider lock poisoned");
89 std::mem::replace(&mut *guard, provider)
90}
91
92pub fn reset_to_default_config_defaults() {
94 let _ = install_config_defaults_provider(Arc::new(DefaultConfigDefaults));
95}
96
97pub fn with_config_defaults<F, R>(operation: F) -> R
99where
100 F: FnOnce(&dyn ConfigDefaultsProvider) -> R,
101{
102 let guard = CONFIG_DEFAULTS
103 .read()
104 .expect("config defaults provider lock poisoned");
105 operation(guard.as_ref())
106}
107
108pub fn current_config_defaults() -> Arc<dyn ConfigDefaultsProvider> {
110 let guard = CONFIG_DEFAULTS
111 .read()
112 .expect("config defaults provider lock poisoned");
113 Arc::clone(&*guard)
114}
115
116pub fn with_config_defaults_provider_for_test<F, R>(
117 provider: Arc<dyn ConfigDefaultsProvider>,
118 action: F,
119) -> R
120where
121 F: FnOnce() -> R,
122{
123 use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
124
125 let previous = install_config_defaults_provider(provider);
126 let result = catch_unwind(AssertUnwindSafe(action));
127 let _ = install_config_defaults_provider(previous);
128
129 match result {
130 Ok(value) => value,
131 Err(payload) => resume_unwind(payload),
132 }
133}
134
135pub fn get_config_dir() -> Option<PathBuf> {
144 if let Ok(custom_dir) = std::env::var("VTCODE_CONFIG") {
146 return Some(PathBuf::from(custom_dir));
147 }
148
149 if let Some(proj_dirs) = ProjectDirs::from("com", "vinhnx", "vtcode") {
151 return Some(proj_dirs.config_local_dir().to_path_buf());
152 }
153
154 dirs::home_dir().map(|home| home.join(DEFAULT_CONFIG_DIR_NAME))
156}
157
158pub fn get_data_dir() -> Option<PathBuf> {
167 if let Ok(custom_dir) = std::env::var("VTCODE_DATA") {
169 return Some(PathBuf::from(custom_dir));
170 }
171
172 if let Some(proj_dirs) = ProjectDirs::from("com", "vinhnx", "vtcode") {
174 return Some(proj_dirs.data_local_dir().to_path_buf());
175 }
176
177 dirs::home_dir().map(|home| home.join(DEFAULT_CONFIG_DIR_NAME).join("cache"))
179}
180
181fn default_home_paths(config_file_name: &str) -> Vec<PathBuf> {
182 get_config_dir()
183 .map(|config_dir| config_dir.join(config_file_name))
184 .into_iter()
185 .collect()
186}
187
188fn default_syntax_languages() -> Vec<String> {
189 DEFAULT_SYNTAX_LANGUAGES.clone()
190}
191
192#[derive(Debug, Clone)]
193struct DefaultWorkspacePaths {
194 root: PathBuf,
195}
196
197impl DefaultWorkspacePaths {
198 fn new(root: PathBuf) -> Self {
199 Self { root }
200 }
201
202 fn config_dir_path(&self) -> PathBuf {
203 self.root.join(DEFAULT_CONFIG_DIR_NAME)
204 }
205}
206
207impl WorkspacePaths for DefaultWorkspacePaths {
208 fn workspace_root(&self) -> &Path {
209 &self.root
210 }
211
212 fn config_dir(&self) -> PathBuf {
213 self.config_dir_path()
214 }
215
216 fn cache_dir(&self) -> Option<PathBuf> {
217 Some(self.config_dir_path().join("cache"))
218 }
219
220 fn telemetry_dir(&self) -> Option<PathBuf> {
221 Some(self.config_dir_path().join("telemetry"))
222 }
223}
224
225#[derive(Debug, Clone)]
228pub struct WorkspacePathsDefaults<P>
229where
230 P: WorkspacePaths + ?Sized,
231{
232 paths: Arc<P>,
233 config_file_name: String,
234 home_paths: Option<Vec<PathBuf>>,
235 syntax_theme: String,
236 syntax_languages: Vec<String>,
237}
238
239impl<P> WorkspacePathsDefaults<P>
240where
241 P: WorkspacePaths + 'static,
242{
243 pub fn new(paths: Arc<P>) -> Self {
246 Self {
247 paths,
248 config_file_name: DEFAULT_CONFIG_FILE_NAME.to_string(),
249 home_paths: None,
250 syntax_theme: DEFAULT_SYNTAX_THEME.to_string(),
251 syntax_languages: default_syntax_languages(),
252 }
253 }
254
255 pub fn with_config_file_name(mut self, file_name: impl Into<String>) -> Self {
257 self.config_file_name = file_name.into();
258 self
259 }
260
261 pub fn with_home_paths(mut self, home_paths: Vec<PathBuf>) -> Self {
263 self.home_paths = Some(home_paths);
264 self
265 }
266
267 pub fn with_syntax_theme(mut self, theme: impl Into<String>) -> Self {
269 self.syntax_theme = theme.into();
270 self
271 }
272
273 pub fn with_syntax_languages(mut self, languages: Vec<String>) -> Self {
275 self.syntax_languages = languages;
276 self
277 }
278
279 pub fn build(self) -> Box<dyn ConfigDefaultsProvider> {
281 Box::new(self)
282 }
283}
284
285impl<P> ConfigDefaultsProvider for WorkspacePathsDefaults<P>
286where
287 P: WorkspacePaths + 'static,
288{
289 fn config_file_name(&self) -> &str {
290 &self.config_file_name
291 }
292
293 fn workspace_paths_for(&self, _workspace_root: &Path) -> Box<dyn WorkspacePaths> {
294 Box::new(WorkspacePathsWrapper {
295 inner: Arc::clone(&self.paths),
296 })
297 }
298
299 fn home_config_paths(&self, config_file_name: &str) -> Vec<PathBuf> {
300 self.home_paths
301 .clone()
302 .unwrap_or_else(|| default_home_paths(config_file_name))
303 }
304
305 fn syntax_theme(&self) -> String {
306 self.syntax_theme.clone()
307 }
308
309 fn syntax_languages(&self) -> Vec<String> {
310 self.syntax_languages.clone()
311 }
312}
313
314#[derive(Debug, Clone)]
315struct WorkspacePathsWrapper<P>
316where
317 P: WorkspacePaths + ?Sized,
318{
319 inner: Arc<P>,
320}
321
322impl<P> WorkspacePaths for WorkspacePathsWrapper<P>
323where
324 P: WorkspacePaths + ?Sized,
325{
326 fn workspace_root(&self) -> &Path {
327 self.inner.workspace_root()
328 }
329
330 fn config_dir(&self) -> PathBuf {
331 self.inner.config_dir()
332 }
333
334 fn cache_dir(&self) -> Option<PathBuf> {
335 self.inner.cache_dir()
336 }
337
338 fn telemetry_dir(&self) -> Option<PathBuf> {
339 self.inner.telemetry_dir()
340 }
341}