From f5c93f2f1a37b08541e6c2cf58dace4a5c887d7f Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 16 Sep 2025 00:05:07 -0600 Subject: [PATCH] worktree --- 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(-) diff --git a/crates/settings/src/settings_content.rs b/crates/settings/src/settings_content.rs index 30b762af2e06be684b3153855a930b3c3eab7596..54c25198c4f0d6f56e963cd1670caf4ee40815e4 100644 --- a/crates/settings/src/settings_content.rs +++ b/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. diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 93ec6cc995a7e14f8eaf3aafad65dbdd7b35e7ef..7f6d332c4f6e5084c759dd475886fe454f2375a6 100644 --- a/crates/settings/src/settings_store.rs +++ b/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() }); diff --git a/crates/worktree/src/worktree_settings.rs b/crates/worktree/src/worktree_settings.rs index 18aa9c0ae41eca95a77eeff2a8b57ab943f1f2b0..92f1d7220c3984bea98f439dfca51890c2d2e473 100644 --- a/crates/worktree/src/worktree_settings.rs +++ b/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, _: &mut App) -> anyhow::Result { - 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 = 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 = 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 = 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 { +fn path_matchers(mut values: Vec, context: &'static str) -> anyhow::Result { + values.sort(); PathMatcher::new(values).with_context(|| format!("Failed to parse globs from {}", context)) } diff --git a/crates/worktree/src/worktree_tests.rs b/crates/worktree/src/worktree_tests.rs index 796840367a0dca717fda7f3973eb8bcc17dc5f57..87db17347e59a07f9c4a9456e9e7c7a74af4853f 100644 --- a/crates/worktree/src/worktree_tests.rs +++ b/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::(|store, cx| { - store.update_user_settings::(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::(|store, cx| { - store.update_user_settings::(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::(|store, cx| { - store.update_user_settings::(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::(|store, cx| { - store.update_user_settings::(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::(|store, cx| { - store.update_user_settings::(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::(|store, cx| { - store.update_user_settings::(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::(|store, cx| { - store.update_user_settings::(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(),