1use std::path::Path;
2
3use anyhow::Context as _;
4use gpui::App;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use settings::{Settings, SettingsKey, SettingsUi};
8use util::paths::PathMatcher;
9
10#[derive(Clone, PartialEq, Eq)]
11pub struct WorktreeSettings {
12 pub project_name: Option<String>,
13 pub file_scan_inclusions: PathMatcher,
14 pub file_scan_exclusions: PathMatcher,
15 pub private_files: PathMatcher,
16}
17
18impl WorktreeSettings {
19 pub fn is_path_private(&self, path: &Path) -> bool {
20 path.ancestors()
21 .any(|ancestor| self.private_files.is_match(ancestor))
22 }
23
24 pub fn is_path_excluded(&self, path: &Path) -> bool {
25 path.ancestors()
26 .any(|ancestor| self.file_scan_exclusions.is_match(&ancestor))
27 }
28
29 pub fn is_path_always_included(&self, path: &Path) -> bool {
30 path.ancestors()
31 .any(|ancestor| self.file_scan_inclusions.is_match(&ancestor))
32 }
33}
34
35impl Settings for WorktreeSettings {
36 fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
37 Self {
38 project_name: None,
39 file_scan_exclusions: content.project.worktree.file_scan_exclusions.unwrap(),
40 file_scan_inclusions: PathMatcher::default(),
41 }
42 }
43 fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
44 let result: WorktreeSettingsContent = sources.json_merge()?;
45 let mut file_scan_exclusions = result.file_scan_exclusions.unwrap_or_default();
46 let mut private_files = result.private_files.unwrap_or_default();
47 let mut parsed_file_scan_inclusions: Vec<String> = result
48 .file_scan_inclusions
49 .unwrap_or_default()
50 .iter()
51 .flat_map(|glob| {
52 Path::new(glob)
53 .ancestors()
54 .map(|a| a.to_string_lossy().into())
55 })
56 .filter(|p: &String| !p.is_empty())
57 .collect();
58 file_scan_exclusions.sort();
59 private_files.sort();
60 parsed_file_scan_inclusions.sort();
61 Ok(Self {
62 file_scan_exclusions: path_matchers(&file_scan_exclusions, "file_scan_exclusions")?,
63 private_files: path_matchers(&private_files, "private_files")?,
64 file_scan_inclusions: path_matchers(
65 &parsed_file_scan_inclusions,
66 "file_scan_inclusions",
67 )?,
68 project_name: result.project_name,
69 })
70 }
71
72 fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
73 if let Some(inclusions) = vscode
74 .read_value("files.watcherInclude")
75 .and_then(|v| v.as_array())
76 .and_then(|v| v.iter().map(|n| n.as_str().map(str::to_owned)).collect())
77 {
78 if let Some(old) = current.file_scan_inclusions.as_mut() {
79 old.extend(inclusions)
80 } else {
81 current.file_scan_inclusions = Some(inclusions)
82 }
83 }
84 if let Some(exclusions) = vscode
85 .read_value("files.watcherExclude")
86 .and_then(|v| v.as_array())
87 .and_then(|v| v.iter().map(|n| n.as_str().map(str::to_owned)).collect())
88 {
89 if let Some(old) = current.file_scan_exclusions.as_mut() {
90 old.extend(exclusions)
91 } else {
92 current.file_scan_exclusions = Some(exclusions)
93 }
94 }
95 }
96}
97
98fn path_matchers(values: &[String], context: &'static str) -> anyhow::Result<PathMatcher> {
99 PathMatcher::new(values).with_context(|| format!("Failed to parse globs from {}", context))
100}