vtcode_config/defaults/
provider.rs1use std::path::{Path, PathBuf};
2use std::sync::{Arc, RwLock};
3
4use once_cell::sync::Lazy;
5use vtcode_commons::paths::WorkspacePaths;
6
7const DEFAULT_CONFIG_FILE_NAME: &str = "vtcode.toml";
8const DEFAULT_CONFIG_DIR_NAME: &str = ".vtcode";
9const DEFAULT_SYNTAX_THEME: &str = "base16-ocean.dark";
10
11static DEFAULT_SYNTAX_LANGUAGES: Lazy<Vec<String>> = Lazy::new(|| {
12 vec![
13 "rust",
14 "python",
15 "javascript",
16 "typescript",
17 "go",
18 "java",
19 "cpp",
20 "c",
21 "php",
22 "html",
23 "css",
24 "sql",
25 "csharp",
26 "bash",
27 ]
28 .into_iter()
29 .map(String::from)
30 .collect()
31});
32
33static CONFIG_DEFAULTS: Lazy<RwLock<Arc<dyn ConfigDefaultsProvider>>> =
34 Lazy::new(|| RwLock::new(Arc::new(DefaultConfigDefaults)));
35
36pub trait ConfigDefaultsProvider: Send + Sync {
39 fn config_file_name(&self) -> &str {
41 DEFAULT_CONFIG_FILE_NAME
42 }
43
44 fn workspace_paths_for(&self, workspace_root: &Path) -> Box<dyn WorkspacePaths>;
47
48 fn home_config_paths(&self, config_file_name: &str) -> Vec<PathBuf>;
51
52 fn syntax_theme(&self) -> String;
54
55 fn syntax_languages(&self) -> Vec<String>;
57}
58
59#[derive(Debug, Default)]
60struct DefaultConfigDefaults;
61
62impl ConfigDefaultsProvider for DefaultConfigDefaults {
63 fn workspace_paths_for(&self, workspace_root: &Path) -> Box<dyn WorkspacePaths> {
64 Box::new(DefaultWorkspacePaths::new(workspace_root.to_path_buf()))
65 }
66
67 fn home_config_paths(&self, config_file_name: &str) -> Vec<PathBuf> {
68 default_home_paths(config_file_name)
69 }
70
71 fn syntax_theme(&self) -> String {
72 DEFAULT_SYNTAX_THEME.to_string()
73 }
74
75 fn syntax_languages(&self) -> Vec<String> {
76 default_syntax_languages()
77 }
78}
79
80pub fn install_config_defaults_provider(
82 provider: Arc<dyn ConfigDefaultsProvider>,
83) -> Arc<dyn ConfigDefaultsProvider> {
84 let mut guard = CONFIG_DEFAULTS
85 .write()
86 .expect("config defaults provider lock poisoned");
87 std::mem::replace(&mut *guard, provider)
88}
89
90pub fn reset_to_default_config_defaults() {
92 let _ = install_config_defaults_provider(Arc::new(DefaultConfigDefaults));
93}
94
95pub fn with_config_defaults<F, R>(operation: F) -> R
97where
98 F: FnOnce(&dyn ConfigDefaultsProvider) -> R,
99{
100 let guard = CONFIG_DEFAULTS
101 .read()
102 .expect("config defaults provider lock poisoned");
103 operation(guard.as_ref())
104}
105
106pub fn current_config_defaults() -> Arc<dyn ConfigDefaultsProvider> {
108 let guard = CONFIG_DEFAULTS
109 .read()
110 .expect("config defaults provider lock poisoned");
111 Arc::clone(&*guard)
112}
113
114pub fn with_config_defaults_provider_for_test<F, R>(
115 provider: Arc<dyn ConfigDefaultsProvider>,
116 action: F,
117) -> R
118where
119 F: FnOnce() -> R,
120{
121 use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
122
123 let previous = install_config_defaults_provider(provider);
124 let result = catch_unwind(AssertUnwindSafe(action));
125 let _ = install_config_defaults_provider(previous);
126
127 match result {
128 Ok(value) => value,
129 Err(payload) => resume_unwind(payload),
130 }
131}
132
133fn default_home_paths(config_file_name: &str) -> Vec<PathBuf> {
134 dirs::home_dir()
135 .map(|home| home.join(DEFAULT_CONFIG_DIR_NAME).join(config_file_name))
136 .into_iter()
137 .collect()
138}
139
140fn default_syntax_languages() -> Vec<String> {
141 DEFAULT_SYNTAX_LANGUAGES.clone()
142}
143
144#[derive(Debug, Clone)]
145struct DefaultWorkspacePaths {
146 root: PathBuf,
147}
148
149impl DefaultWorkspacePaths {
150 fn new(root: PathBuf) -> Self {
151 Self { root }
152 }
153
154 fn config_dir_path(&self) -> PathBuf {
155 self.root.join(DEFAULT_CONFIG_DIR_NAME)
156 }
157}
158
159impl WorkspacePaths for DefaultWorkspacePaths {
160 fn workspace_root(&self) -> &Path {
161 &self.root
162 }
163
164 fn config_dir(&self) -> PathBuf {
165 self.config_dir_path()
166 }
167
168 fn cache_dir(&self) -> Option<PathBuf> {
169 Some(self.config_dir_path().join("cache"))
170 }
171
172 fn telemetry_dir(&self) -> Option<PathBuf> {
173 Some(self.config_dir_path().join("telemetry"))
174 }
175}
176
177#[derive(Debug, Clone)]
180pub struct WorkspacePathsDefaults<P>
181where
182 P: WorkspacePaths + ?Sized,
183{
184 paths: Arc<P>,
185 config_file_name: String,
186 home_paths: Option<Vec<PathBuf>>,
187 syntax_theme: String,
188 syntax_languages: Vec<String>,
189}
190
191impl<P> WorkspacePathsDefaults<P>
192where
193 P: WorkspacePaths + 'static,
194{
195 pub fn new(paths: Arc<P>) -> Self {
198 Self {
199 paths,
200 config_file_name: DEFAULT_CONFIG_FILE_NAME.to_string(),
201 home_paths: None,
202 syntax_theme: DEFAULT_SYNTAX_THEME.to_string(),
203 syntax_languages: default_syntax_languages(),
204 }
205 }
206
207 pub fn with_config_file_name(mut self, file_name: impl Into<String>) -> Self {
209 self.config_file_name = file_name.into();
210 self
211 }
212
213 pub fn with_home_paths(mut self, home_paths: Vec<PathBuf>) -> Self {
215 self.home_paths = Some(home_paths);
216 self
217 }
218
219 pub fn with_syntax_theme(mut self, theme: impl Into<String>) -> Self {
221 self.syntax_theme = theme.into();
222 self
223 }
224
225 pub fn with_syntax_languages(mut self, languages: Vec<String>) -> Self {
227 self.syntax_languages = languages;
228 self
229 }
230
231 pub fn build(self) -> Box<dyn ConfigDefaultsProvider> {
233 Box::new(self)
234 }
235}
236
237impl<P> ConfigDefaultsProvider for WorkspacePathsDefaults<P>
238where
239 P: WorkspacePaths + 'static,
240{
241 fn config_file_name(&self) -> &str {
242 &self.config_file_name
243 }
244
245 fn workspace_paths_for(&self, _workspace_root: &Path) -> Box<dyn WorkspacePaths> {
246 Box::new(WorkspacePathsWrapper {
247 inner: Arc::clone(&self.paths),
248 })
249 }
250
251 fn home_config_paths(&self, config_file_name: &str) -> Vec<PathBuf> {
252 self.home_paths
253 .clone()
254 .unwrap_or_else(|| default_home_paths(config_file_name))
255 }
256
257 fn syntax_theme(&self) -> String {
258 self.syntax_theme.clone()
259 }
260
261 fn syntax_languages(&self) -> Vec<String> {
262 self.syntax_languages.clone()
263 }
264}
265
266#[derive(Debug, Clone)]
267struct WorkspacePathsWrapper<P>
268where
269 P: WorkspacePaths + ?Sized,
270{
271 inner: Arc<P>,
272}
273
274impl<P> WorkspacePaths for WorkspacePathsWrapper<P>
275where
276 P: WorkspacePaths + ?Sized,
277{
278 fn workspace_root(&self) -> &Path {
279 self.inner.workspace_root()
280 }
281
282 fn config_dir(&self) -> PathBuf {
283 self.inner.config_dir()
284 }
285
286 fn cache_dir(&self) -> Option<PathBuf> {
287 self.inner.cache_dir()
288 }
289
290 fn telemetry_dir(&self) -> Option<PathBuf> {
291 self.inner.telemetry_dir()
292 }
293}