worktree_settings.rs

  1use std::path::Path;
  2
  3use anyhow::Context as _;
  4use gpui::App;
  5use settings::{Settings, SettingsContent};
  6use util::{ResultExt, paths::PathMatcher};
  7
  8#[derive(Clone, PartialEq, Eq)]
  9pub struct WorktreeSettings {
 10    pub project_name: Option<String>,
 11    pub file_scan_inclusions: PathMatcher,
 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    pub fn is_path_always_included(&self, path: &Path) -> bool {
 28        path.ancestors()
 29            .any(|ancestor| self.file_scan_inclusions.is_match(&ancestor))
 30    }
 31}
 32
 33impl Settings for WorktreeSettings {
 34    fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
 35        let worktree = content.project.worktree.clone();
 36        let file_scan_exclusions = worktree.file_scan_exclusions.unwrap();
 37        let file_scan_inclusions = worktree.file_scan_inclusions.unwrap();
 38        let private_files = worktree.private_files.unwrap();
 39        let parsed_file_scan_inclusions: Vec<String> = file_scan_inclusions
 40            .iter()
 41            .flat_map(|glob| {
 42                Path::new(glob)
 43                    .ancestors()
 44                    .map(|a| a.to_string_lossy().into())
 45            })
 46            .filter(|p: &String| !p.is_empty())
 47            .collect();
 48
 49        Self {
 50            project_name: None,
 51            file_scan_exclusions: path_matchers(file_scan_exclusions, "file_scan_exclusions")
 52                .unwrap(),
 53            file_scan_inclusions: path_matchers(
 54                parsed_file_scan_inclusions,
 55                "file_scan_inclusions",
 56            )
 57            .unwrap(),
 58            private_files: path_matchers(private_files, "private_files").unwrap(),
 59        }
 60    }
 61
 62    fn refine(&mut self, content: &SettingsContent, _cx: &mut App) {
 63        let worktree = &content.project.worktree;
 64
 65        if let Some(project_name) = worktree.project_name.clone() {
 66            self.project_name = Some(project_name);
 67        }
 68
 69        if let Some(mut private_files) = worktree.private_files.clone() {
 70            let sources = self.private_files.sources();
 71            private_files.extend_from_slice(sources);
 72            if let Some(matchers) = path_matchers(private_files, "private_files").log_err() {
 73                self.private_files = matchers;
 74            }
 75        }
 76
 77        if let Some(file_scan_exclusions) = worktree.file_scan_exclusions.clone() {
 78            if let Some(matchers) =
 79                path_matchers(file_scan_exclusions, "file_scan_exclusions").log_err()
 80            {
 81                self.file_scan_exclusions = matchers
 82            }
 83        }
 84
 85        if let Some(file_scan_inclusions) = worktree.file_scan_inclusions.clone() {
 86            let parsed_file_scan_inclusions: Vec<String> = file_scan_inclusions
 87                .iter()
 88                .flat_map(|glob| {
 89                    Path::new(glob)
 90                        .ancestors()
 91                        .map(|a| a.to_string_lossy().into())
 92                })
 93                .filter(|p: &String| !p.is_empty())
 94                .collect();
 95            if let Some(matchers) =
 96                path_matchers(parsed_file_scan_inclusions, "file_scan_inclusions").log_err()
 97            {
 98                self.file_scan_inclusions = matchers
 99            }
100        }
101    }
102
103    fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
104        if let Some(inclusions) = vscode
105            .read_value("files.watcherInclude")
106            .and_then(|v| v.as_array())
107            .and_then(|v| v.iter().map(|n| n.as_str().map(str::to_owned)).collect())
108        {
109            if let Some(old) = current.project.worktree.file_scan_inclusions.as_mut() {
110                old.extend(inclusions)
111            } else {
112                current.project.worktree.file_scan_inclusions = Some(inclusions)
113            }
114        }
115        if let Some(exclusions) = vscode
116            .read_value("files.watcherExclude")
117            .and_then(|v| v.as_array())
118            .and_then(|v| v.iter().map(|n| n.as_str().map(str::to_owned)).collect())
119        {
120            if let Some(old) = current.project.worktree.file_scan_exclusions.as_mut() {
121                old.extend(exclusions)
122            } else {
123                current.project.worktree.file_scan_exclusions = Some(exclusions)
124            }
125        }
126    }
127}
128
129fn path_matchers(mut values: Vec<String>, context: &'static str) -> anyhow::Result<PathMatcher> {
130    values.sort();
131    PathMatcher::new(values).with_context(|| format!("Failed to parse globs from {}", context))
132}