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