diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 3c2bfea77aa1f0f409ce78a3a69e612aa30715e2..7c85077c488371e611fdadf0baf7ee94f49fe511 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -71,6 +71,32 @@ actions!( ] ); +fn split_glob_patterns(text: &str) -> Vec<&str> { + let mut patterns = Vec::new(); + let mut pattern_start = 0; + let mut brace_depth: usize = 0; + let mut escaped = false; + + for (index, character) in text.char_indices() { + if escaped { + escaped = false; + continue; + } + match character { + '\\' => escaped = true, + '{' => brace_depth += 1, + '}' => brace_depth = brace_depth.saturating_sub(1), + ',' if brace_depth == 0 => { + patterns.push(&text[pattern_start..index]); + pattern_start = index + 1; + } + _ => {} + } + } + patterns.push(&text[pattern_start..]); + patterns +} + #[derive(Default)] struct ActiveSettings(HashMap, ProjectSearchSettings>); @@ -1381,8 +1407,8 @@ impl ProjectSearchView { fn parse_path_matches(&self, text: String, cx: &App) -> anyhow::Result { let path_style = self.entity.read(cx).project.read(cx).path_style(cx); - let queries = text - .split(',') + let queries = split_glob_patterns(&text) + .into_iter() .map(str::trim) .filter(|maybe_glob_str| !maybe_glob_str.is_empty()) .map(str::to_owned) @@ -2517,6 +2543,29 @@ pub mod tests { use util_macros::perf; use workspace::{DeploySearch, MultiWorkspace}; + #[test] + fn test_split_glob_patterns() { + assert_eq!(split_glob_patterns("a,b,c"), vec!["a", "b", "c"]); + assert_eq!(split_glob_patterns("a, b, c"), vec!["a", " b", " c"]); + assert_eq!( + split_glob_patterns("src/{a,b}/**/*.rs"), + vec!["src/{a,b}/**/*.rs"] + ); + assert_eq!( + split_glob_patterns("src/{a,b}/*.rs, tests/**/*.rs"), + vec!["src/{a,b}/*.rs", " tests/**/*.rs"] + ); + assert_eq!(split_glob_patterns("{a,b},{c,d}"), vec!["{a,b}", "{c,d}"]); + assert_eq!(split_glob_patterns("{{a,b},{c,d}}"), vec!["{{a,b},{c,d}}"]); + assert_eq!(split_glob_patterns(""), vec![""]); + assert_eq!(split_glob_patterns("a"), vec!["a"]); + // Escaped characters should not be treated as special + assert_eq!(split_glob_patterns(r"a\,b,c"), vec![r"a\,b", "c"]); + assert_eq!(split_glob_patterns(r"\{a,b\}"), vec![r"\{a", r"b\}"]); + assert_eq!(split_glob_patterns(r"a\\,b"), vec![r"a\\", "b"]); + assert_eq!(split_glob_patterns(r"a\\\,b"), vec![r"a\\\,b"]); + } + #[perf] #[gpui::test] async fn test_project_search(cx: &mut TestAppContext) {