worktree

Conrad Irwin created

Change summary

crates/settings/src/settings_content.rs  |  4 
crates/settings/src/settings_store.rs    |  1 
crates/worktree/src/worktree_settings.rs | 98 +++++++++++++++++--------
crates/worktree/src/worktree_tests.rs    | 39 +++++----
4 files changed, 89 insertions(+), 53 deletions(-)

Detailed changes

crates/settings/src/settings_content.rs 🔗

@@ -132,7 +132,7 @@ pub enum BaseKeymapContent {
     None,
 }
 
-#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
+#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
 pub struct ProjectSettingsContent {
     #[serde(flatten)]
     pub all_languages: AllLanguageSettingsContent,
@@ -229,7 +229,7 @@ pub enum GitHostingProviderKind {
     Bitbucket,
 }
 
-#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
 pub struct WorktreeSettingsContent {
     /// The displayed name of this project. If not set, the root directory name
     /// will be displayed.

crates/settings/src/settings_store.rs 🔗

@@ -765,6 +765,7 @@ impl SettingsStore {
         self.extension_settings = Some(SettingsContent {
             project: ProjectSettingsContent {
                 all_languages: content.all_languages,
+                ..Default::default()
             },
             ..Default::default()
         });

crates/worktree/src/worktree_settings.rs 🔗

@@ -2,10 +2,8 @@ use std::path::Path;
 
 use anyhow::Context as _;
 use gpui::App;
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsKey, SettingsUi};
-use util::paths::PathMatcher;
+use settings::{Settings, SettingsContent};
+use util::{ResultExt, paths::PathMatcher};
 
 #[derive(Clone, PartialEq, Eq)]
 pub struct WorktreeSettings {
@@ -34,19 +32,11 @@ impl WorktreeSettings {
 
 impl Settings for WorktreeSettings {
     fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
-        Self {
-            project_name: None,
-            file_scan_exclusions: content.project.worktree.file_scan_exclusions.unwrap(),
-            file_scan_inclusions: PathMatcher::default(),
-        }
-    }
-    fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
-        let result: WorktreeSettingsContent = sources.json_merge()?;
-        let mut file_scan_exclusions = result.file_scan_exclusions.unwrap_or_default();
-        let mut private_files = result.private_files.unwrap_or_default();
-        let mut parsed_file_scan_inclusions: Vec<String> = result
-            .file_scan_inclusions
-            .unwrap_or_default()
+        let worktree = content.project.worktree.clone();
+        let file_scan_exclusions = worktree.file_scan_exclusions.unwrap();
+        let file_scan_inclusions = worktree.file_scan_inclusions.unwrap();
+        let private_files = worktree.private_files.unwrap();
+        let parsed_file_scan_inclusions: Vec<String> = file_scan_inclusions
             .iter()
             .flat_map(|glob| {
                 Path::new(glob)
@@ -55,30 +45,71 @@ impl Settings for WorktreeSettings {
             })
             .filter(|p: &String| !p.is_empty())
             .collect();
-        file_scan_exclusions.sort();
-        private_files.sort();
-        parsed_file_scan_inclusions.sort();
-        Ok(Self {
-            file_scan_exclusions: path_matchers(&file_scan_exclusions, "file_scan_exclusions")?,
-            private_files: path_matchers(&private_files, "private_files")?,
+
+        Self {
+            project_name: None,
+            file_scan_exclusions: path_matchers(file_scan_exclusions, "file_scan_exclusions")
+                .unwrap(),
             file_scan_inclusions: path_matchers(
-                &parsed_file_scan_inclusions,
+                parsed_file_scan_inclusions,
                 "file_scan_inclusions",
-            )?,
-            project_name: result.project_name,
-        })
+            )
+            .unwrap(),
+            private_files: path_matchers(private_files, "private_files").unwrap(),
+        }
+    }
+
+    fn refine(&mut self, content: &SettingsContent, _cx: &mut App) {
+        let worktree = &content.project.worktree;
+
+        if let Some(project_name) = worktree.project_name.clone() {
+            self.project_name = Some(project_name);
+        }
+
+        // todo!() test this. Did it used to extend the arrays, or overwrite them?
+
+        if let Some(private_files) = worktree.private_files.clone() {
+            if let Some(matchers) = path_matchers(private_files, "private_files").log_err() {
+                self.private_files = matchers
+            }
+        }
+
+        if let Some(file_scan_exclusions) = worktree.file_scan_exclusions.clone() {
+            if let Some(matchers) =
+                path_matchers(file_scan_exclusions, "file_scan_exclusions").log_err()
+            {
+                self.file_scan_exclusions = matchers
+            }
+        }
+
+        if let Some(file_scan_inclusions) = worktree.file_scan_inclusions.clone() {
+            let parsed_file_scan_inclusions: Vec<String> = file_scan_inclusions
+                .iter()
+                .flat_map(|glob| {
+                    Path::new(glob)
+                        .ancestors()
+                        .map(|a| a.to_string_lossy().into())
+                })
+                .filter(|p: &String| !p.is_empty())
+                .collect();
+            if let Some(matchers) =
+                path_matchers(parsed_file_scan_inclusions, "file_scan_inclusions").log_err()
+            {
+                self.file_scan_inclusions = matchers
+            }
+        }
     }
 
-    fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
+    fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
         if let Some(inclusions) = vscode
             .read_value("files.watcherInclude")
             .and_then(|v| v.as_array())
             .and_then(|v| v.iter().map(|n| n.as_str().map(str::to_owned)).collect())
         {
-            if let Some(old) = current.file_scan_inclusions.as_mut() {
+            if let Some(old) = current.project.worktree.file_scan_inclusions.as_mut() {
                 old.extend(inclusions)
             } else {
-                current.file_scan_inclusions = Some(inclusions)
+                current.project.worktree.file_scan_inclusions = Some(inclusions)
             }
         }
         if let Some(exclusions) = vscode
@@ -86,15 +117,16 @@ impl Settings for WorktreeSettings {
             .and_then(|v| v.as_array())
             .and_then(|v| v.iter().map(|n| n.as_str().map(str::to_owned)).collect())
         {
-            if let Some(old) = current.file_scan_exclusions.as_mut() {
+            if let Some(old) = current.project.worktree.file_scan_exclusions.as_mut() {
                 old.extend(exclusions)
             } else {
-                current.file_scan_exclusions = Some(exclusions)
+                current.project.worktree.file_scan_exclusions = Some(exclusions)
             }
         }
     }
 }
 
-fn path_matchers(values: &[String], context: &'static str) -> anyhow::Result<PathMatcher> {
+fn path_matchers(mut values: Vec<String>, context: &'static str) -> anyhow::Result<PathMatcher> {
+    values.sort();
     PathMatcher::new(values).with_context(|| format!("Failed to parse globs from {}", context))
 }

crates/worktree/src/worktree_tests.rs 🔗

@@ -770,9 +770,9 @@ async fn test_file_scan_inclusions(cx: &mut TestAppContext) {
     }));
     cx.update(|cx| {
         cx.update_global::<SettingsStore, _>(|store, cx| {
-            store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions = Some(vec![]);
-                project_settings.file_scan_inclusions = Some(vec![
+            store.update_user_settings(cx, |settings| {
+                settings.project.worktree.file_scan_exclusions = Some(vec![]);
+                settings.project.worktree.file_scan_inclusions = Some(vec![
                     "node_modules/**/package.json".to_string(),
                     "**/.DS_Store".to_string(),
                 ]);
@@ -836,9 +836,11 @@ async fn test_file_scan_exclusions_overrules_inclusions(cx: &mut TestAppContext)
 
     cx.update(|cx| {
         cx.update_global::<SettingsStore, _>(|store, cx| {
-            store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions = Some(vec!["**/.DS_Store".to_string()]);
-                project_settings.file_scan_inclusions = Some(vec!["**/.DS_Store".to_string()]);
+            store.update_user_settings(cx, |settings| {
+                settings.project.worktree.file_scan_exclusions =
+                    Some(vec!["**/.DS_Store".to_string()]);
+                settings.project.worktree.file_scan_inclusions =
+                    Some(vec!["**/.DS_Store".to_string()]);
             });
         });
     });
@@ -894,9 +896,10 @@ async fn test_file_scan_inclusions_reindexes_on_setting_change(cx: &mut TestAppC
 
     cx.update(|cx| {
         cx.update_global::<SettingsStore, _>(|store, cx| {
-            store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions = Some(vec![]);
-                project_settings.file_scan_inclusions = Some(vec!["node_modules/**".to_string()]);
+            store.update_user_settings(cx, |settings| {
+                settings.project.worktree.file_scan_exclusions = Some(vec![]);
+                settings.project.worktree.file_scan_inclusions =
+                    Some(vec!["node_modules/**".to_string()]);
             });
         });
     });
@@ -926,9 +929,9 @@ async fn test_file_scan_inclusions_reindexes_on_setting_change(cx: &mut TestAppC
 
     cx.update(|cx| {
         cx.update_global::<SettingsStore, _>(|store, cx| {
-            store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions = Some(vec![]);
-                project_settings.file_scan_inclusions = Some(vec![]);
+            store.update_user_settings(cx, |settings| {
+                settings.project.worktree.file_scan_exclusions = Some(vec![]);
+                settings.project.worktree.file_scan_inclusions = Some(vec![]);
             });
         });
     });
@@ -978,8 +981,8 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
     }));
     cx.update(|cx| {
         cx.update_global::<SettingsStore, _>(|store, cx| {
-            store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions =
+            store.update_user_settings(cx, |settings| {
+                settings.project.worktree.file_scan_exclusions =
                     Some(vec!["**/foo/**".to_string(), "**/.DS_Store".to_string()]);
             });
         });
@@ -1015,8 +1018,8 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
 
     cx.update(|cx| {
         cx.update_global::<SettingsStore, _>(|store, cx| {
-            store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions =
+            store.update_user_settings(cx, |settings| {
+                settings.project.worktree.file_scan_exclusions =
                     Some(vec!["**/node_modules/**".to_string()]);
             });
         });
@@ -1080,8 +1083,8 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
     }));
     cx.update(|cx| {
         cx.update_global::<SettingsStore, _>(|store, cx| {
-            store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions = Some(vec![
+            store.update_user_settings(cx, |settings| {
+                settings.project.worktree.file_scan_exclusions = Some(vec![
                     "**/.git".to_string(),
                     "node_modules/".to_string(),
                     "build_output".to_string(),