vtcode_config/defaults/
provider.rs

1use 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
36/// Provides access to filesystem and syntax defaults used by the configuration
37/// loader.
38pub trait ConfigDefaultsProvider: Send + Sync {
39    /// Returns the primary configuration file name expected in a workspace.
40    fn config_file_name(&self) -> &str {
41        DEFAULT_CONFIG_FILE_NAME
42    }
43
44    /// Creates a [`WorkspacePaths`] implementation for the provided workspace
45    /// root.
46    fn workspace_paths_for(&self, workspace_root: &Path) -> Box<dyn WorkspacePaths>;
47
48    /// Returns the fallback configuration locations searched outside the
49    /// workspace.
50    fn home_config_paths(&self, config_file_name: &str) -> Vec<PathBuf>;
51
52    /// Returns the default syntax highlighting theme identifier.
53    fn syntax_theme(&self) -> String;
54
55    /// Returns the default list of syntax highlighting languages.
56    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
80/// Installs a new [`ConfigDefaultsProvider`], returning the previous provider.
81pub 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
90/// Restores the built-in defaults provider.
91pub fn reset_to_default_config_defaults() {
92    let _ = install_config_defaults_provider(Arc::new(DefaultConfigDefaults));
93}
94
95/// Executes the provided function with the currently installed provider.
96pub 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
106/// Returns the currently installed provider as an [`Arc`].
107pub 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/// Adapter that maps an existing [`WorkspacePaths`] implementation into a
178/// [`ConfigDefaultsProvider`].
179#[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    /// Creates a defaults provider that delegates to the supplied
196    /// [`WorkspacePaths`] implementation.
197    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    /// Overrides the configuration file name returned by the provider.
208    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    /// Overrides the fallback configuration search paths returned by the provider.
214    pub fn with_home_paths(mut self, home_paths: Vec<PathBuf>) -> Self {
215        self.home_paths = Some(home_paths);
216        self
217    }
218
219    /// Overrides the default syntax theme returned by the provider.
220    pub fn with_syntax_theme(mut self, theme: impl Into<String>) -> Self {
221        self.syntax_theme = theme.into();
222        self
223    }
224
225    /// Overrides the default syntax languages returned by the provider.
226    pub fn with_syntax_languages(mut self, languages: Vec<String>) -> Self {
227        self.syntax_languages = languages;
228        self
229    }
230
231    /// Consumes the builder, returning a boxed provider implementation.
232    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}