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_exclusions: PathMatcher,
13 pub private_files: PathMatcher,
14}
15
16impl WorktreeSettings {
17 pub fn is_path_private(&self, path: &Path) -> bool {
18 path.ancestors()
19 .any(|ancestor| self.private_files.is_match(ancestor))
20 }
21
22 pub fn is_path_excluded(&self, path: &Path) -> bool {
23 path.ancestors()
24 .any(|ancestor| self.file_scan_exclusions.is_match(ancestor))
25 }
26}
27
28#[derive(Clone, Serialize, Deserialize, JsonSchema)]
29#[serde(default)]
30pub struct WorktreeSettingsContent {
31 /// Completely ignore files matching globs from `file_scan_exclusions`
32 ///
33 /// Default: [
34 /// "**/.git",
35 /// "**/.svn",
36 /// "**/.hg",
37 /// "**/CVS",
38 /// "**/.DS_Store",
39 /// "**/Thumbs.db",
40 /// "**/.classpath",
41 /// "**/.settings"
42 /// ]
43 pub file_scan_exclusions: Vec<String>,
44
45 /// Treat the files matching these globs as `.env` files.
46 /// Default: [ "**/.env*" ]
47 pub private_files: Vec<String>,
48}
49
50impl Default for WorktreeSettingsContent {
51 fn default() -> Self {
52 Self {
53 private_files: [
54 "**/.env*",
55 "**/*.pem",
56 "**/*.key",
57 "**/*.cert",
58 "**/*.crt",
59 "**/secrets.yml",
60 ]
61 .into_iter()
62 .map(str::to_owned)
63 .collect(),
64 file_scan_exclusions: [
65 "**/.git",
66 "**/.svn",
67 "**/.hg",
68 "**/CVS",
69 "**/.DS_Store",
70 "**/Thumbs.db",
71 "**/.classpath",
72 "**/.settings",
73 ]
74 .into_iter()
75 .map(str::to_owned)
76 .collect(),
77 }
78 }
79}
80
81impl Settings for WorktreeSettings {
82 const KEY: Option<&'static str> = None;
83
84 type FileContent = WorktreeSettingsContent;
85
86 fn load(
87 sources: SettingsSources<Self::FileContent>,
88 _: &mut AppContext,
89 ) -> anyhow::Result<Self> {
90 let result: WorktreeSettingsContent = sources.json_merge()?;
91 let mut file_scan_exclusions = result.file_scan_exclusions;
92 let mut private_files = result.private_files;
93 file_scan_exclusions.sort();
94 private_files.sort();
95 Ok(Self {
96 file_scan_exclusions: path_matchers(&file_scan_exclusions, "file_scan_exclusions")?,
97 private_files: path_matchers(&private_files, "private_files")?,
98 })
99 }
100}
101
102fn path_matchers(values: &[String], context: &'static str) -> anyhow::Result<PathMatcher> {
103 PathMatcher::new(values).with_context(|| format!("Failed to parse globs from {}", context))
104}