1use std::{path::Path, sync::Arc};
2
3use gpui::AppContext;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6use settings::{Settings, SettingsSources};
7use util::paths::PathMatcher;
8
9#[derive(Clone, PartialEq, Eq)]
10pub struct WorktreeSettings {
11 pub file_scan_exclusions: Arc<[PathMatcher]>,
12 pub private_files: Arc<[PathMatcher]>,
13}
14
15impl WorktreeSettings {
16 pub fn is_path_private(&self, path: &Path) -> bool {
17 path.ancestors().any(|ancestor| {
18 self.private_files
19 .iter()
20 .any(|matcher| matcher.is_match(&ancestor))
21 })
22 }
23
24 pub fn is_path_excluded(&self, path: &Path) -> bool {
25 path.ancestors().any(|ancestor| {
26 self.file_scan_exclusions
27 .iter()
28 .any(|matcher| matcher.is_match(&ancestor))
29 })
30 }
31}
32
33#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
34pub struct WorktreeSettingsContent {
35 /// Completely ignore files matching globs from `file_scan_exclusions`
36 ///
37 /// Default: [
38 /// "**/.git",
39 /// "**/.svn",
40 /// "**/.hg",
41 /// "**/CVS",
42 /// "**/.DS_Store",
43 /// "**/Thumbs.db",
44 /// "**/.classpath",
45 /// "**/.settings"
46 /// ]
47 #[serde(default)]
48 pub file_scan_exclusions: Option<Vec<String>>,
49
50 /// Treat the files matching these globs as `.env` files.
51 /// Default: [ "**/.env*" ]
52 pub private_files: Option<Vec<String>>,
53}
54
55impl Settings for WorktreeSettings {
56 const KEY: Option<&'static str> = None;
57
58 type FileContent = WorktreeSettingsContent;
59
60 fn load(
61 sources: SettingsSources<Self::FileContent>,
62 _: &mut AppContext,
63 ) -> anyhow::Result<Self> {
64 let result: WorktreeSettingsContent = sources.json_merge()?;
65 let mut file_scan_exclusions = result.file_scan_exclusions.unwrap_or_default();
66 let mut private_files = result.private_files.unwrap_or_default();
67 file_scan_exclusions.sort();
68 private_files.sort();
69 Ok(Self {
70 file_scan_exclusions: path_matchers(&file_scan_exclusions, "file_scan_exclusions"),
71 private_files: path_matchers(&private_files, "private_files"),
72 })
73 }
74}
75
76fn path_matchers(values: &[String], context: &'static str) -> Arc<[PathMatcher]> {
77 values
78 .iter()
79 .filter_map(|pattern| {
80 PathMatcher::new(pattern)
81 .map(Some)
82 .unwrap_or_else(|e| {
83 log::error!(
84 "Skipping pattern {pattern} in `{}` project settings due to parsing error: {e:#}", context
85 );
86 None
87 })
88 })
89 .collect::<Vec<_>>()
90 .into()
91}