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 "diff",
30 "patch",
31 "udiff",
32 "git",
33 "json",
34 "yaml",
35 "toml",
36 "markdown",
37 ]
38 .into_iter()
39 .map(String::from)
40 .collect()
41});
42
43static CONFIG_DEFAULTS: Lazy<RwLock<Arc<dyn ConfigDefaultsProvider>>> =
44 Lazy::new(|| RwLock::new(Arc::new(DefaultConfigDefaults)));
45
46pub trait ConfigDefaultsProvider: Send + Sync {
49 fn config_file_name(&self) -> &str {
51 DEFAULT_CONFIG_FILE_NAME
52 }
53
54 fn workspace_paths_for(&self, workspace_root: &Path) -> Box<dyn WorkspacePaths>;
57
58 fn home_config_paths(&self, config_file_name: &str) -> Vec<PathBuf>;
61
62 fn syntax_theme(&self) -> String;
64
65 fn syntax_languages(&self) -> Vec<String>;
67}
68
69#[derive(Debug, Default)]
70struct DefaultConfigDefaults;
71
72impl ConfigDefaultsProvider for DefaultConfigDefaults {
73 fn workspace_paths_for(&self, workspace_root: &Path) -> Box<dyn WorkspacePaths> {
74 Box::new(DefaultWorkspacePaths::new(workspace_root.to_path_buf()))
75 }
76
77 fn home_config_paths(&self, config_file_name: &str) -> Vec<PathBuf> {
78 default_home_paths(config_file_name)
79 }
80
81 fn syntax_theme(&self) -> String {
82 DEFAULT_SYNTAX_THEME.to_string()
83 }
84
85 fn syntax_languages(&self) -> Vec<String> {
86 default_syntax_languages()
87 }
88}
89
90pub fn install_config_defaults_provider(
92 provider: Arc<dyn ConfigDefaultsProvider>,
93) -> Arc<dyn ConfigDefaultsProvider> {
94 let mut guard = CONFIG_DEFAULTS
95 .write()
96 .expect("config defaults provider lock poisoned");
97 std::mem::replace(&mut *guard, provider)
98}
99
100pub fn reset_to_default_config_defaults() {
102 let _ = install_config_defaults_provider(Arc::new(DefaultConfigDefaults));
103}
104
105pub fn with_config_defaults<F, R>(operation: F) -> R
107where
108 F: FnOnce(&dyn ConfigDefaultsProvider) -> R,
109{
110 let guard = CONFIG_DEFAULTS
111 .read()
112 .expect("config defaults provider lock poisoned");
113 operation(guard.as_ref())
114}
115
116pub fn current_config_defaults() -> Arc<dyn ConfigDefaultsProvider> {
118 let guard = CONFIG_DEFAULTS
119 .read()
120 .expect("config defaults provider lock poisoned");
121 Arc::clone(&*guard)
122}
123
124pub fn with_config_defaults_provider_for_test<F, R>(
125 provider: Arc<dyn ConfigDefaultsProvider>,
126 action: F,
127) -> R
128where
129 F: FnOnce() -> R,
130{
131 use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
132
133 let previous = install_config_defaults_provider(provider);
134 let result = catch_unwind(AssertUnwindSafe(action));
135 let _ = install_config_defaults_provider(previous);
136
137 match result {
138 Ok(value) => value,
139 Err(payload) => resume_unwind(payload),
140 }
141}
142
143pub fn get_config_dir() -> Option<PathBuf> {
152 if let Ok(custom_dir) = std::env::var("VTCODE_CONFIG") {
154 return Some(PathBuf::from(custom_dir));
155 }
156
157 if let Some(proj_dirs) = ProjectDirs::from("com", "vinhnx", "vtcode") {
159 return Some(proj_dirs.config_local_dir().to_path_buf());
160 }
161
162 dirs::home_dir().map(|home| home.join(DEFAULT_CONFIG_DIR_NAME))
164}
165
166pub fn get_data_dir() -> Option<PathBuf> {
175 if let Ok(custom_dir) = std::env::var("VTCODE_DATA") {
177 return Some(PathBuf::from(custom_dir));
178 }
179
180 if let Some(proj_dirs) = ProjectDirs::from("com", "vinhnx", "vtcode") {
182 return Some(proj_dirs.data_local_dir().to_path_buf());
183 }
184
185 dirs::home_dir().map(|home| home.join(DEFAULT_CONFIG_DIR_NAME).join("cache"))
187}
188
189fn default_home_paths(config_file_name: &str) -> Vec<PathBuf> {
190 get_config_dir()
191 .map(|config_dir| config_dir.join(config_file_name))
192 .into_iter()
193 .collect()
194}
195
196fn default_syntax_languages() -> Vec<String> {
197 DEFAULT_SYNTAX_LANGUAGES.clone()
198}
199
200#[derive(Debug, Clone)]
201struct DefaultWorkspacePaths {
202 root: PathBuf,
203}
204
205impl DefaultWorkspacePaths {
206 fn new(root: PathBuf) -> Self {
207 Self { root }
208 }
209
210 fn config_dir_path(&self) -> PathBuf {
211 self.root.join(DEFAULT_CONFIG_DIR_NAME)
212 }
213}
214
215impl WorkspacePaths for DefaultWorkspacePaths {
216 fn workspace_root(&self) -> &Path {
217 &self.root
218 }
219
220 fn config_dir(&self) -> PathBuf {
221 self.config_dir_path()
222 }
223
224 fn cache_dir(&self) -> Option<PathBuf> {
225 Some(self.config_dir_path().join("cache"))
226 }
227
228 fn telemetry_dir(&self) -> Option<PathBuf> {
229 Some(self.config_dir_path().join("telemetry"))
230 }
231}
232
233#[derive(Debug, Clone)]
236pub struct WorkspacePathsDefaults<P>
237where
238 P: WorkspacePaths + ?Sized,
239{
240 paths: Arc<P>,
241 config_file_name: String,
242 home_paths: Option<Vec<PathBuf>>,
243 syntax_theme: String,
244 syntax_languages: Vec<String>,
245}
246
247impl<P> WorkspacePathsDefaults<P>
248where
249 P: WorkspacePaths + 'static,
250{
251 pub fn new(paths: Arc<P>) -> Self {
254 Self {
255 paths,
256 config_file_name: DEFAULT_CONFIG_FILE_NAME.to_string(),
257 home_paths: None,
258 syntax_theme: DEFAULT_SYNTAX_THEME.to_string(),
259 syntax_languages: default_syntax_languages(),
260 }
261 }
262
263 pub fn with_config_file_name(mut self, file_name: impl Into<String>) -> Self {
265 self.config_file_name = file_name.into();
266 self
267 }
268
269 pub fn with_home_paths(mut self, home_paths: Vec<PathBuf>) -> Self {
271 self.home_paths = Some(home_paths);
272 self
273 }
274
275 pub fn with_syntax_theme(mut self, theme: impl Into<String>) -> Self {
277 self.syntax_theme = theme.into();
278 self
279 }
280
281 pub fn with_syntax_languages(mut self, languages: Vec<String>) -> Self {
283 self.syntax_languages = languages;
284 self
285 }
286
287 pub fn build(self) -> Box<dyn ConfigDefaultsProvider> {
289 Box::new(self)
290 }
291}
292
293impl<P> ConfigDefaultsProvider for WorkspacePathsDefaults<P>
294where
295 P: WorkspacePaths + 'static,
296{
297 fn config_file_name(&self) -> &str {
298 &self.config_file_name
299 }
300
301 fn workspace_paths_for(&self, _workspace_root: &Path) -> Box<dyn WorkspacePaths> {
302 Box::new(WorkspacePathsWrapper {
303 inner: Arc::clone(&self.paths),
304 })
305 }
306
307 fn home_config_paths(&self, config_file_name: &str) -> Vec<PathBuf> {
308 self.home_paths
309 .clone()
310 .unwrap_or_else(|| default_home_paths(config_file_name))
311 }
312
313 fn syntax_theme(&self) -> String {
314 self.syntax_theme.clone()
315 }
316
317 fn syntax_languages(&self) -> Vec<String> {
318 self.syntax_languages.clone()
319 }
320}
321
322#[derive(Debug, Clone)]
323struct WorkspacePathsWrapper<P>
324where
325 P: WorkspacePaths + ?Sized,
326{
327 inner: Arc<P>,
328}
329
330impl<P> WorkspacePaths for WorkspacePathsWrapper<P>
331where
332 P: WorkspacePaths + ?Sized,
333{
334 fn workspace_root(&self) -> &Path {
335 self.inner.workspace_root()
336 }
337
338 fn config_dir(&self) -> PathBuf {
339 self.inner.config_dir()
340 }
341
342 fn cache_dir(&self) -> Option<PathBuf> {
343 self.inner.cache_dir()
344 }
345
346 fn telemetry_dir(&self) -> Option<PathBuf> {
347 self.inner.telemetry_dir()
348 }
349}