1use std::path::Path;
2
3use anyhow::Context as _;
4use settings::{RegisterSetting, Settings};
5use util::{
6 ResultExt,
7 paths::{PathMatcher, PathStyle},
8 rel_path::RelPath,
9};
10
11#[derive(Clone, PartialEq, Eq, RegisterSetting)]
12pub struct WorktreeSettings {
13 pub project_name: Option<String>,
14 /// Whether to prevent this project from being shared in public channels.
15 pub prevent_sharing_in_public_channels: bool,
16 pub file_scan_exclusions: PathMatcher,
17 pub file_scan_inclusions: PathMatcher,
18 /// This field contains all ancestors of the `file_scan_inclusions`. It's used to
19 /// determine whether to terminate worktree scanning for a given dir.
20 pub parent_dir_scan_inclusions: PathMatcher,
21 pub private_files: PathMatcher,
22 pub hidden_files: PathMatcher,
23}
24
25impl WorktreeSettings {
26 pub fn is_path_private(&self, path: &RelPath) -> bool {
27 path.ancestors()
28 .any(|ancestor| self.private_files.is_match(ancestor.as_std_path()))
29 }
30
31 pub fn is_path_excluded(&self, path: &RelPath) -> bool {
32 path.ancestors()
33 .any(|ancestor| self.file_scan_exclusions.is_match(ancestor.as_std_path()))
34 }
35
36 pub fn is_path_always_included(&self, path: &RelPath, is_dir: bool) -> bool {
37 if is_dir {
38 self.parent_dir_scan_inclusions.is_match(path.as_std_path())
39 } else {
40 self.file_scan_inclusions.is_match(path.as_std_path())
41 }
42 }
43
44 pub fn is_path_hidden(&self, path: &RelPath) -> bool {
45 path.ancestors()
46 .any(|ancestor| self.hidden_files.is_match(ancestor.as_std_path()))
47 }
48}
49
50impl Settings for WorktreeSettings {
51 fn from_settings(content: &settings::SettingsContent) -> Self {
52 let worktree = content.project.worktree.clone();
53 let file_scan_exclusions = worktree.file_scan_exclusions.unwrap();
54 let file_scan_inclusions = worktree.file_scan_inclusions.unwrap();
55 let private_files = worktree.private_files.unwrap().0;
56 let hidden_files = worktree.hidden_files.unwrap();
57 let parsed_file_scan_inclusions: Vec<String> = file_scan_inclusions
58 .iter()
59 .flat_map(|glob| {
60 Path::new(glob)
61 .ancestors()
62 .skip(1)
63 .map(|a| a.to_string_lossy().into())
64 })
65 .filter(|p: &String| !p.is_empty())
66 .collect();
67
68 Self {
69 project_name: worktree.project_name.into_inner(),
70 prevent_sharing_in_public_channels: worktree.prevent_sharing_in_public_channels,
71 file_scan_exclusions: path_matchers(file_scan_exclusions, "file_scan_exclusions")
72 .log_err()
73 .unwrap_or_default(),
74 parent_dir_scan_inclusions: path_matchers(
75 parsed_file_scan_inclusions,
76 "file_scan_inclusions",
77 )
78 .unwrap(),
79 file_scan_inclusions: path_matchers(file_scan_inclusions, "file_scan_inclusions")
80 .unwrap(),
81 private_files: path_matchers(private_files, "private_files")
82 .log_err()
83 .unwrap_or_default(),
84 hidden_files: path_matchers(hidden_files, "hidden_files")
85 .log_err()
86 .unwrap_or_default(),
87 }
88 }
89}
90
91fn path_matchers(mut values: Vec<String>, context: &'static str) -> anyhow::Result<PathMatcher> {
92 values.sort();
93 PathMatcher::new(values, PathStyle::local())
94 .with_context(|| format!("Failed to parse globs from {}", context))
95}