1use std::path::Path;
2
3use anyhow::Context;
4use gpui::AppContext;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use settings::{Settings, SettingsSources};
8use util::paths::PathMatcher;
9
10#[derive(Clone, PartialEq, Eq)]
11pub struct WorktreeSettings {
12 pub file_scan_inclusions: PathMatcher,
13 pub file_scan_exclusions: PathMatcher,
14 pub private_files: PathMatcher,
15}
16
17impl WorktreeSettings {
18 pub fn is_path_private(&self, path: &Path) -> bool {
19 path.ancestors()
20 .any(|ancestor| self.private_files.is_match(ancestor))
21 }
22
23 pub fn is_path_excluded(&self, path: &Path) -> bool {
24 path.ancestors()
25 .any(|ancestor| self.file_scan_exclusions.is_match(&ancestor))
26 }
27
28 pub fn is_path_always_included(&self, path: &Path) -> bool {
29 path.ancestors()
30 .any(|ancestor| self.file_scan_inclusions.is_match(&ancestor))
31 }
32}
33
34#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
35pub struct WorktreeSettingsContent {
36 /// Completely ignore files matching globs from `file_scan_exclusions`. Overrides
37 /// `file_scan_inclusions`.
38 ///
39 /// Default: [
40 /// "**/.git",
41 /// "**/.svn",
42 /// "**/.hg",
43 /// "**/.jj",
44 /// "**/CVS",
45 /// "**/.DS_Store",
46 /// "**/Thumbs.db",
47 /// "**/.classpath",
48 /// "**/.settings"
49 /// ]
50 #[serde(default)]
51 pub file_scan_exclusions: Option<Vec<String>>,
52
53 /// Always include files that match these globs when scanning for files, even if they're
54 /// ignored by git. This setting is overridden by `file_scan_exclusions`.
55 /// Default: [
56 /// ".env*",
57 /// "docker-compose.*.yml",
58 /// ]
59 #[serde(default)]
60 pub file_scan_inclusions: Option<Vec<String>>,
61
62 /// Treat the files matching these globs as `.env` files.
63 /// Default: [ "**/.env*" ]
64 pub private_files: Option<Vec<String>>,
65}
66
67impl Settings for WorktreeSettings {
68 const KEY: Option<&'static str> = None;
69
70 type FileContent = WorktreeSettingsContent;
71
72 fn load(
73 sources: SettingsSources<Self::FileContent>,
74 _: &mut AppContext,
75 ) -> anyhow::Result<Self> {
76 let result: WorktreeSettingsContent = sources.json_merge()?;
77 let mut file_scan_exclusions = result.file_scan_exclusions.unwrap_or_default();
78 let mut private_files = result.private_files.unwrap_or_default();
79 let mut parsed_file_scan_inclusions: Vec<String> = result
80 .file_scan_inclusions
81 .unwrap_or_default()
82 .iter()
83 .flat_map(|glob| {
84 Path::new(glob)
85 .ancestors()
86 .map(|a| a.to_string_lossy().into())
87 })
88 .filter(|p| p != "")
89 .collect();
90 file_scan_exclusions.sort();
91 private_files.sort();
92 parsed_file_scan_inclusions.sort();
93 Ok(Self {
94 file_scan_exclusions: path_matchers(&file_scan_exclusions, "file_scan_exclusions")?,
95 private_files: path_matchers(&private_files, "private_files")?,
96 file_scan_inclusions: path_matchers(
97 &parsed_file_scan_inclusions,
98 "file_scan_inclusions",
99 )?,
100 })
101 }
102}
103
104fn path_matchers(values: &[String], context: &'static str) -> anyhow::Result<PathMatcher> {
105 PathMatcher::new(values).with_context(|| format!("Failed to parse globs from {}", context))
106}