mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-08 07:35:01 +03:00
util: Use GlobSet in PathMatcher (#13197)
Previously we were using a single globset::Glob in PathMatcher; higher
up the stack, we were then resorting to using a list of PathMatchers.
globset crate exposes a GlobSet type that's better suited for this use
case. In my benchmarks, using a single PathMatcher with GlobSet instead
of a Vec of PathMatchers with Globs is about 3 times faster with the
default 'file_scan_exclusions' values. This slightly improves our
project load time for projects with large # of files, as showcased in
the following videos of loading a project with 100k source files. This
project is *not* a git repository, so it should measure raw overhead on
our side.
Current nightly: 51404d4ea0
https://github.com/zed-industries/zed/assets/24362066/e0aa9f8c-aae6-4348-8d42-d20bd41fcd76
versus this PR:
https://github.com/zed-industries/zed/assets/24362066/408dcab1-cee2-4c9e-a541-a31d14772dd7
Release Notes:
- Improved performance in large worktrees
This commit is contained in:
parent
64d815a176
commit
5dc26c261d
@ -220,7 +220,7 @@ struct Options {
|
|||||||
const INCLUDE_WARNINGS_ARGUMENT: &str = "--include-warnings";
|
const INCLUDE_WARNINGS_ARGUMENT: &str = "--include-warnings";
|
||||||
|
|
||||||
impl Options {
|
impl Options {
|
||||||
pub fn parse(arguments_line: Option<&str>) -> Self {
|
fn parse(arguments_line: Option<&str>) -> Self {
|
||||||
arguments_line
|
arguments_line
|
||||||
.map(|arguments_line| {
|
.map(|arguments_line| {
|
||||||
let args = arguments_line.split_whitespace().collect::<Vec<_>>();
|
let args = arguments_line.split_whitespace().collect::<Vec<_>>();
|
||||||
@ -230,7 +230,7 @@ impl Options {
|
|||||||
if arg == INCLUDE_WARNINGS_ARGUMENT {
|
if arg == INCLUDE_WARNINGS_ARGUMENT {
|
||||||
include_warnings = true;
|
include_warnings = true;
|
||||||
} else {
|
} else {
|
||||||
path_matcher = PathMatcher::new(arg).log_err();
|
path_matcher = PathMatcher::new(&[arg.to_owned()]).log_err();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self {
|
Self {
|
||||||
@ -255,7 +255,8 @@ fn collect_diagnostics(
|
|||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Task<Result<(String, Vec<(Range<usize>, PlaceholderType)>)>> {
|
) -> Task<Result<(String, Vec<(Range<usize>, PlaceholderType)>)>> {
|
||||||
let error_source = if let Some(path_matcher) = &options.path_matcher {
|
let error_source = if let Some(path_matcher) = &options.path_matcher {
|
||||||
Some(path_matcher.source().to_string())
|
debug_assert_eq!(path_matcher.sources().len(), 1);
|
||||||
|
Some(path_matcher.sources().first().cloned().unwrap_or_default())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -183,7 +183,7 @@ fn collect_files(
|
|||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Task<Result<(String, Vec<(Range<usize>, PathBuf, EntryType)>)>> {
|
) -> Task<Result<(String, Vec<(Range<usize>, PathBuf, EntryType)>)>> {
|
||||||
let Ok(matcher) = PathMatcher::new(glob_input) else {
|
let Ok(matcher) = PathMatcher::new(&[glob_input.to_owned()]) else {
|
||||||
return Task::ready(Err(anyhow!("invalid path")));
|
return Task::ready(Err(anyhow!("invalid path")));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4904,7 +4904,15 @@ async fn test_project_search(
|
|||||||
let mut results = HashMap::default();
|
let mut results = HashMap::default();
|
||||||
let mut search_rx = project_b.update(cx_b, |project, cx| {
|
let mut search_rx = project_b.update(cx_b, |project, cx| {
|
||||||
project.search(
|
project.search(
|
||||||
SearchQuery::text("world", false, false, false, Vec::new(), Vec::new()).unwrap(),
|
SearchQuery::text(
|
||||||
|
"world",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -875,8 +875,15 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||||||
|
|
||||||
let mut search = project.update(cx, |project, cx| {
|
let mut search = project.update(cx, |project, cx| {
|
||||||
project.search(
|
project.search(
|
||||||
SearchQuery::text(query, false, false, false, Vec::new(), Vec::new())
|
SearchQuery::text(
|
||||||
.unwrap(),
|
query,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -25,8 +25,15 @@ use crate::panel_settings::MessageEditorSettings;
|
|||||||
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
|
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref MENTIONS_SEARCH: SearchQuery =
|
static ref MENTIONS_SEARCH: SearchQuery = SearchQuery::regex(
|
||||||
SearchQuery::regex("@[-_\\w]+", false, false, false, Vec::new(), Vec::new()).unwrap();
|
"@[-_\\w]+",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
Default::default(),
|
||||||
|
Default::default()
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MessageEditor {
|
pub struct MessageEditor {
|
||||||
|
@ -113,7 +113,7 @@ impl Prettier {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}).any(|workspace_definition| {
|
}).any(|workspace_definition| {
|
||||||
if let Some(path_matcher) = PathMatcher::new(&workspace_definition).ok() {
|
if let Some(path_matcher) = PathMatcher::new(&[workspace_definition.clone()]).ok() {
|
||||||
path_matcher.is_match(subproject_path)
|
path_matcher.is_match(subproject_path)
|
||||||
} else {
|
} else {
|
||||||
workspace_definition == subproject_path.to_string_lossy()
|
workspace_definition == subproject_path.to_string_lossy()
|
||||||
|
@ -3929,7 +3929,15 @@ async fn test_search(cx: &mut gpui::TestAppContext) {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
search(
|
search(
|
||||||
&project,
|
&project,
|
||||||
SearchQuery::text("TWO", false, true, false, Vec::new(), Vec::new()).unwrap(),
|
SearchQuery::text(
|
||||||
|
"TWO",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
Default::default(),
|
||||||
|
Default::default()
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
cx
|
cx
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@ -3954,7 +3962,15 @@ async fn test_search(cx: &mut gpui::TestAppContext) {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
search(
|
search(
|
||||||
&project,
|
&project,
|
||||||
SearchQuery::text("TWO", false, true, false, Vec::new(), Vec::new()).unwrap(),
|
SearchQuery::text(
|
||||||
|
"TWO",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
Default::default(),
|
||||||
|
Default::default()
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
cx
|
cx
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@ -3994,8 +4010,8 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
vec![PathMatcher::new("*.odd").unwrap()],
|
PathMatcher::new(&["*.odd".to_owned()]).unwrap(),
|
||||||
Vec::new()
|
Default::default()
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
cx
|
cx
|
||||||
@ -4014,8 +4030,8 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
vec![PathMatcher::new("*.rs").unwrap()],
|
PathMatcher::new(&["*.rs".to_owned()]).unwrap(),
|
||||||
Vec::new()
|
Default::default()
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
cx
|
cx
|
||||||
@ -4037,11 +4053,10 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
vec![
|
|
||||||
PathMatcher::new("*.ts").unwrap(),
|
PathMatcher::new(&["*.ts".to_owned(), "*.odd".to_owned()]).unwrap(),
|
||||||
PathMatcher::new("*.odd").unwrap(),
|
|
||||||
],
|
Default::default(),
|
||||||
Vec::new()
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
cx
|
cx
|
||||||
)
|
)
|
||||||
@ -4062,12 +4077,10 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
vec![
|
|
||||||
PathMatcher::new("*.rs").unwrap(),
|
PathMatcher::new(&["*.rs".to_owned(), "*.ts".to_owned(), "*.odd".to_owned()]).unwrap(),
|
||||||
PathMatcher::new("*.ts").unwrap(),
|
|
||||||
PathMatcher::new("*.odd").unwrap(),
|
Default::default(),
|
||||||
],
|
|
||||||
Vec::new()
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
cx
|
cx
|
||||||
)
|
)
|
||||||
@ -4110,8 +4123,8 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
Vec::new(),
|
Default::default(),
|
||||||
vec![PathMatcher::new("*.odd").unwrap()],
|
PathMatcher::new(&["*.odd".to_owned()]).unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
cx
|
cx
|
||||||
@ -4135,8 +4148,8 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
Vec::new(),
|
Default::default(),
|
||||||
vec![PathMatcher::new("*.rs").unwrap()],
|
PathMatcher::new(&["*.rs".to_owned()]).unwrap()
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
cx
|
cx
|
||||||
@ -4158,11 +4171,10 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
Vec::new(),
|
Default::default(),
|
||||||
vec![
|
|
||||||
PathMatcher::new("*.ts").unwrap(),
|
PathMatcher::new(&["*.ts".to_owned(), "*.odd".to_owned()]).unwrap(),
|
||||||
PathMatcher::new("*.odd").unwrap(),
|
|
||||||
],
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
cx
|
cx
|
||||||
)
|
)
|
||||||
@ -4183,12 +4195,10 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
Vec::new(),
|
Default::default(),
|
||||||
vec![
|
|
||||||
PathMatcher::new("*.rs").unwrap(),
|
PathMatcher::new(&["*.rs".to_owned(), "*.ts".to_owned(), "*.odd".to_owned()]).unwrap(),
|
||||||
PathMatcher::new("*.ts").unwrap(),
|
|
||||||
PathMatcher::new("*.odd").unwrap(),
|
|
||||||
],
|
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
cx
|
cx
|
||||||
)
|
)
|
||||||
@ -4225,8 +4235,8 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
vec![PathMatcher::new("*.odd").unwrap()],
|
PathMatcher::new(&["*.odd".to_owned()]).unwrap(),
|
||||||
vec![PathMatcher::new("*.odd").unwrap()],
|
PathMatcher::new(&["*.odd".to_owned()]).unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
cx
|
cx
|
||||||
@ -4245,8 +4255,8 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
vec![PathMatcher::new("*.ts").unwrap()],
|
PathMatcher::new(&["*.ts".to_owned()]).unwrap(),
|
||||||
vec![PathMatcher::new("*.ts").unwrap()],
|
PathMatcher::new(&["*.ts".to_owned()]).unwrap(),
|
||||||
).unwrap(),
|
).unwrap(),
|
||||||
cx
|
cx
|
||||||
)
|
)
|
||||||
@ -4264,14 +4274,8 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
vec![
|
PathMatcher::new(&["*.ts".to_owned(), "*.odd".to_owned()]).unwrap(),
|
||||||
PathMatcher::new("*.ts").unwrap(),
|
PathMatcher::new(&["*.ts".to_owned(), "*.odd".to_owned()]).unwrap(),
|
||||||
PathMatcher::new("*.odd").unwrap()
|
|
||||||
],
|
|
||||||
vec![
|
|
||||||
PathMatcher::new("*.ts").unwrap(),
|
|
||||||
PathMatcher::new("*.odd").unwrap()
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
cx
|
cx
|
||||||
@ -4290,14 +4294,8 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
vec![
|
PathMatcher::new(&["*.ts".to_owned(), "*.odd".to_owned()]).unwrap(),
|
||||||
PathMatcher::new("*.ts").unwrap(),
|
PathMatcher::new(&["*.rs".to_owned(), "*.odd".to_owned()]).unwrap(),
|
||||||
PathMatcher::new("*.odd").unwrap()
|
|
||||||
],
|
|
||||||
vec![
|
|
||||||
PathMatcher::new("*.rs").unwrap(),
|
|
||||||
PathMatcher::new("*.odd").unwrap()
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
cx
|
cx
|
||||||
@ -4349,8 +4347,8 @@ async fn test_search_multiple_worktrees_with_inclusions(cx: &mut gpui::TestAppCo
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
vec![PathMatcher::new("worktree-a/*.rs").unwrap()],
|
PathMatcher::new(&["worktree-a/*.rs".to_owned()]).unwrap(),
|
||||||
Vec::new()
|
Default::default()
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
cx
|
cx
|
||||||
@ -4368,8 +4366,8 @@ async fn test_search_multiple_worktrees_with_inclusions(cx: &mut gpui::TestAppCo
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
vec![PathMatcher::new("worktree-b/*.rs").unwrap()],
|
PathMatcher::new(&["worktree-b/*.rs".to_owned()]).unwrap(),
|
||||||
Vec::new()
|
Default::default()
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
cx
|
cx
|
||||||
@ -4388,8 +4386,8 @@ async fn test_search_multiple_worktrees_with_inclusions(cx: &mut gpui::TestAppCo
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
vec![PathMatcher::new("*.ts").unwrap()],
|
PathMatcher::new(&["*.ts".to_owned()]).unwrap(),
|
||||||
Vec::new()
|
Default::default()
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
cx
|
cx
|
||||||
@ -4437,7 +4435,15 @@ async fn test_search_in_gitignored_dirs(cx: &mut gpui::TestAppContext) {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
search(
|
search(
|
||||||
&project,
|
&project,
|
||||||
SearchQuery::text(query, false, false, false, Vec::new(), Vec::new()).unwrap(),
|
SearchQuery::text(
|
||||||
|
query,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
Default::default(),
|
||||||
|
Default::default()
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
cx
|
cx
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@ -4450,7 +4456,15 @@ async fn test_search_in_gitignored_dirs(cx: &mut gpui::TestAppContext) {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
search(
|
search(
|
||||||
&project,
|
&project,
|
||||||
SearchQuery::text(query, false, false, true, Vec::new(), Vec::new()).unwrap(),
|
SearchQuery::text(
|
||||||
|
query,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
Default::default(),
|
||||||
|
Default::default()
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
cx
|
cx
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@ -4475,8 +4489,8 @@ async fn test_search_in_gitignored_dirs(cx: &mut gpui::TestAppContext) {
|
|||||||
"Unrestricted search with ignored directories should find every file with the query"
|
"Unrestricted search with ignored directories should find every file with the query"
|
||||||
);
|
);
|
||||||
|
|
||||||
let files_to_include = vec![PathMatcher::new("/dir/node_modules/prettier/**").unwrap()];
|
let files_to_include = PathMatcher::new(&["/dir/node_modules/prettier/**".to_owned()]).unwrap();
|
||||||
let files_to_exclude = vec![PathMatcher::new("*.ts").unwrap()];
|
let files_to_exclude = PathMatcher::new(&["*.ts".to_owned()]).unwrap();
|
||||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
search(
|
search(
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use aho_corasick::{AhoCorasick, AhoCorasickBuilder};
|
use aho_corasick::{AhoCorasick, AhoCorasickBuilder};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::Result;
|
||||||
use client::proto;
|
use client::proto;
|
||||||
use itertools::Itertools;
|
|
||||||
use language::{char_kind, BufferSnapshot};
|
use language::{char_kind, BufferSnapshot};
|
||||||
use regex::{Captures, Regex, RegexBuilder};
|
use regex::{Captures, Regex, RegexBuilder};
|
||||||
use smol::future::yield_now;
|
use smol::future::yield_now;
|
||||||
@ -19,18 +18,18 @@ static TEXT_REPLACEMENT_SPECIAL_CHARACTERS_REGEX: OnceLock<Regex> = OnceLock::ne
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SearchInputs {
|
pub struct SearchInputs {
|
||||||
query: Arc<str>,
|
query: Arc<str>,
|
||||||
files_to_include: Vec<PathMatcher>,
|
files_to_include: PathMatcher,
|
||||||
files_to_exclude: Vec<PathMatcher>,
|
files_to_exclude: PathMatcher,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SearchInputs {
|
impl SearchInputs {
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
self.query.as_ref()
|
self.query.as_ref()
|
||||||
}
|
}
|
||||||
pub fn files_to_include(&self) -> &[PathMatcher] {
|
pub fn files_to_include(&self) -> &PathMatcher {
|
||||||
&self.files_to_include
|
&self.files_to_include
|
||||||
}
|
}
|
||||||
pub fn files_to_exclude(&self) -> &[PathMatcher] {
|
pub fn files_to_exclude(&self) -> &PathMatcher {
|
||||||
&self.files_to_exclude
|
&self.files_to_exclude
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,8 +61,8 @@ impl SearchQuery {
|
|||||||
whole_word: bool,
|
whole_word: bool,
|
||||||
case_sensitive: bool,
|
case_sensitive: bool,
|
||||||
include_ignored: bool,
|
include_ignored: bool,
|
||||||
files_to_include: Vec<PathMatcher>,
|
files_to_include: PathMatcher,
|
||||||
files_to_exclude: Vec<PathMatcher>,
|
files_to_exclude: PathMatcher,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let query = query.to_string();
|
let query = query.to_string();
|
||||||
let search = AhoCorasickBuilder::new()
|
let search = AhoCorasickBuilder::new()
|
||||||
@ -89,8 +88,8 @@ impl SearchQuery {
|
|||||||
whole_word: bool,
|
whole_word: bool,
|
||||||
case_sensitive: bool,
|
case_sensitive: bool,
|
||||||
include_ignored: bool,
|
include_ignored: bool,
|
||||||
files_to_include: Vec<PathMatcher>,
|
files_to_include: PathMatcher,
|
||||||
files_to_exclude: Vec<PathMatcher>,
|
files_to_exclude: PathMatcher,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let mut query = query.to_string();
|
let mut query = query.to_string();
|
||||||
let initial_query = Arc::from(query.as_str());
|
let initial_query = Arc::from(query.as_str());
|
||||||
@ -167,16 +166,8 @@ impl SearchQuery {
|
|||||||
whole_word: self.whole_word(),
|
whole_word: self.whole_word(),
|
||||||
case_sensitive: self.case_sensitive(),
|
case_sensitive: self.case_sensitive(),
|
||||||
include_ignored: self.include_ignored(),
|
include_ignored: self.include_ignored(),
|
||||||
files_to_include: self
|
files_to_include: self.files_to_include().sources().join(","),
|
||||||
.files_to_include()
|
files_to_exclude: self.files_to_exclude().sources().join(","),
|
||||||
.iter()
|
|
||||||
.map(|matcher| matcher.to_string())
|
|
||||||
.join(","),
|
|
||||||
files_to_exclude: self
|
|
||||||
.files_to_exclude()
|
|
||||||
.iter()
|
|
||||||
.map(|matcher| matcher.to_string())
|
|
||||||
.join(","),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,11 +368,11 @@ impl SearchQuery {
|
|||||||
matches!(self, Self::Regex { .. })
|
matches!(self, Self::Regex { .. })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn files_to_include(&self) -> &[PathMatcher] {
|
pub fn files_to_include(&self) -> &PathMatcher {
|
||||||
self.as_inner().files_to_include()
|
self.as_inner().files_to_include()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn files_to_exclude(&self) -> &[PathMatcher] {
|
pub fn files_to_exclude(&self) -> &PathMatcher {
|
||||||
self.as_inner().files_to_exclude()
|
self.as_inner().files_to_exclude()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -390,17 +381,10 @@ impl SearchQuery {
|
|||||||
Some(file_path) => {
|
Some(file_path) => {
|
||||||
let mut path = file_path.to_path_buf();
|
let mut path = file_path.to_path_buf();
|
||||||
loop {
|
loop {
|
||||||
if self
|
if self.files_to_exclude().is_match(&path) {
|
||||||
.files_to_exclude()
|
|
||||||
.iter()
|
|
||||||
.any(|exclude_glob| exclude_glob.is_match(&path))
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
} else if self.files_to_include().is_empty()
|
} else if self.files_to_include().sources().is_empty()
|
||||||
|| self
|
|| self.files_to_include().is_match(&path)
|
||||||
.files_to_include()
|
|
||||||
.iter()
|
|
||||||
.any(|include_glob| include_glob.is_match(&path))
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
} else if !path.pop() {
|
} else if !path.pop() {
|
||||||
@ -408,7 +392,7 @@ impl SearchQuery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => self.files_to_include().is_empty(),
|
None => self.files_to_include().sources().is_empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn as_inner(&self) -> &SearchInputs {
|
pub fn as_inner(&self) -> &SearchInputs {
|
||||||
@ -418,16 +402,13 @@ impl SearchQuery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_path_matches(glob_set: &str) -> anyhow::Result<Vec<PathMatcher>> {
|
fn deserialize_path_matches(glob_set: &str) -> anyhow::Result<PathMatcher> {
|
||||||
glob_set
|
let globs = glob_set
|
||||||
.split(',')
|
.split(',')
|
||||||
.map(str::trim)
|
.map(str::trim)
|
||||||
.filter(|glob_str| !glob_str.is_empty())
|
.filter_map(|glob_str| (!glob_str.is_empty()).then(|| glob_str.to_owned()))
|
||||||
.map(|glob_str| {
|
.collect::<Vec<_>>();
|
||||||
PathMatcher::new(glob_str)
|
Ok(PathMatcher::new(&globs)?)
|
||||||
.with_context(|| format!("deserializing path match glob {glob_str}"))
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -445,7 +426,7 @@ mod tests {
|
|||||||
"dir/[a-z].txt",
|
"dir/[a-z].txt",
|
||||||
"../dir/filé",
|
"../dir/filé",
|
||||||
] {
|
] {
|
||||||
let path_matcher = PathMatcher::new(valid_path).unwrap_or_else(|e| {
|
let path_matcher = PathMatcher::new(&[valid_path.to_owned()]).unwrap_or_else(|e| {
|
||||||
panic!("Valid path {valid_path} should be accepted, but got: {e}")
|
panic!("Valid path {valid_path} should be accepted, but got: {e}")
|
||||||
});
|
});
|
||||||
assert!(
|
assert!(
|
||||||
@ -458,7 +439,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn path_matcher_creation_for_globs() {
|
fn path_matcher_creation_for_globs() {
|
||||||
for invalid_glob in ["dir/[].txt", "dir/[a-z.txt", "dir/{file"] {
|
for invalid_glob in ["dir/[].txt", "dir/[a-z.txt", "dir/{file"] {
|
||||||
match PathMatcher::new(invalid_glob) {
|
match PathMatcher::new(&[invalid_glob.to_owned()]) {
|
||||||
Ok(_) => panic!("Invalid glob {invalid_glob} should not be accepted"),
|
Ok(_) => panic!("Invalid glob {invalid_glob} should not be accepted"),
|
||||||
Err(_expected) => {}
|
Err(_expected) => {}
|
||||||
}
|
}
|
||||||
@ -471,9 +452,9 @@ mod tests {
|
|||||||
"dir/[a-z].txt",
|
"dir/[a-z].txt",
|
||||||
"{dir,file}",
|
"{dir,file}",
|
||||||
] {
|
] {
|
||||||
match PathMatcher::new(valid_glob) {
|
match PathMatcher::new(&[valid_glob.to_owned()]) {
|
||||||
Ok(_expected) => {}
|
Ok(_expected) => {}
|
||||||
Err(e) => panic!("Valid glob {valid_glob} should be accepted, but got: {e}"),
|
Err(e) => panic!("Valid glob should be accepted, but got: {e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -927,8 +927,8 @@ impl BufferSearchBar {
|
|||||||
self.search_options.contains(SearchOptions::WHOLE_WORD),
|
self.search_options.contains(SearchOptions::WHOLE_WORD),
|
||||||
self.search_options.contains(SearchOptions::CASE_SENSITIVE),
|
self.search_options.contains(SearchOptions::CASE_SENSITIVE),
|
||||||
false,
|
false,
|
||||||
Vec::new(),
|
Default::default(),
|
||||||
Vec::new(),
|
Default::default(),
|
||||||
) {
|
) {
|
||||||
Ok(query) => query.with_replacement(self.replacement(cx)),
|
Ok(query) => query.with_replacement(self.replacement(cx)),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@ -944,8 +944,8 @@ impl BufferSearchBar {
|
|||||||
self.search_options.contains(SearchOptions::WHOLE_WORD),
|
self.search_options.contains(SearchOptions::WHOLE_WORD),
|
||||||
self.search_options.contains(SearchOptions::CASE_SENSITIVE),
|
self.search_options.contains(SearchOptions::CASE_SENSITIVE),
|
||||||
false,
|
false,
|
||||||
Vec::new(),
|
Default::default(),
|
||||||
Vec::new(),
|
Default::default(),
|
||||||
) {
|
) {
|
||||||
Ok(query) => query.with_replacement(self.replacement(cx)),
|
Ok(query) => query.with_replacement(self.replacement(cx)),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
@ -3,7 +3,6 @@ use crate::{
|
|||||||
SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleIncludeIgnored, ToggleRegex,
|
SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleIncludeIgnored, ToggleRegex,
|
||||||
ToggleReplace, ToggleWholeWord,
|
ToggleReplace, ToggleWholeWord,
|
||||||
};
|
};
|
||||||
use anyhow::Context as _;
|
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use editor::{
|
use editor::{
|
||||||
actions::SelectAll,
|
actions::SelectAll,
|
||||||
@ -876,7 +875,7 @@ impl ProjectSearchView {
|
|||||||
if should_mark_error {
|
if should_mark_error {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
vec![]
|
PathMatcher::default()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let excluded_files =
|
let excluded_files =
|
||||||
@ -894,7 +893,7 @@ impl ProjectSearchView {
|
|||||||
if should_mark_error {
|
if should_mark_error {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
vec![]
|
PathMatcher::default()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -960,15 +959,14 @@ impl ProjectSearchView {
|
|||||||
query
|
query
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_path_matches(text: &str) -> anyhow::Result<Vec<PathMatcher>> {
|
fn parse_path_matches(text: &str) -> anyhow::Result<PathMatcher> {
|
||||||
text.split(',')
|
let queries = text
|
||||||
|
.split(',')
|
||||||
.map(str::trim)
|
.map(str::trim)
|
||||||
.filter(|maybe_glob_str| !maybe_glob_str.is_empty())
|
.filter(|maybe_glob_str| !maybe_glob_str.is_empty())
|
||||||
.map(|maybe_glob_str| {
|
.map(str::to_owned)
|
||||||
PathMatcher::new(maybe_glob_str)
|
.collect::<Vec<_>>();
|
||||||
.with_context(|| format!("parsing {maybe_glob_str} as path matcher"))
|
Ok(PathMatcher::new(&queries)?)
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
|
fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
|
||||||
|
@ -1024,8 +1024,8 @@ impl SearchableItem for TerminalView {
|
|||||||
query.whole_word(),
|
query.whole_word(),
|
||||||
query.case_sensitive(),
|
query.case_sensitive(),
|
||||||
query.include_ignored(),
|
query.include_ignored(),
|
||||||
query.files_to_include().to_vec(),
|
query.files_to_include().clone(),
|
||||||
query.files_to_exclude().to_vec(),
|
query.files_to_exclude().clone(),
|
||||||
)
|
)
|
||||||
.unwrap()),
|
.unwrap()),
|
||||||
),
|
),
|
||||||
|
@ -3,7 +3,7 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use globset::{Glob, GlobMatcher};
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
@ -257,43 +257,51 @@ impl<P> PathLikeWithPosition<P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct PathMatcher {
|
pub struct PathMatcher {
|
||||||
source: String,
|
sources: Vec<String>,
|
||||||
glob: GlobMatcher,
|
glob: GlobSet,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for PathMatcher {
|
// impl std::fmt::Display for PathMatcher {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
self.source.fmt(f)
|
// self.sources.fmt(f)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
impl PartialEq for PathMatcher {
|
impl PartialEq for PathMatcher {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.source.eq(&other.source)
|
self.sources.eq(&other.sources)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for PathMatcher {}
|
impl Eq for PathMatcher {}
|
||||||
|
|
||||||
impl PathMatcher {
|
impl PathMatcher {
|
||||||
pub fn new(source: &str) -> Result<Self, globset::Error> {
|
pub fn new(globs: &[String]) -> Result<Self, globset::Error> {
|
||||||
Ok(PathMatcher {
|
let globs = globs
|
||||||
glob: Glob::new(source)?.compile_matcher(),
|
.into_iter()
|
||||||
source: String::from(source),
|
.map(|glob| Glob::new(&glob))
|
||||||
})
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
let sources = globs.iter().map(|glob| glob.glob().to_owned()).collect();
|
||||||
|
let mut glob_builder = GlobSetBuilder::new();
|
||||||
|
for single_glob in globs {
|
||||||
|
glob_builder.add(single_glob);
|
||||||
|
}
|
||||||
|
let glob = glob_builder.build()?;
|
||||||
|
Ok(PathMatcher { glob, sources })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source(&self) -> &str {
|
pub fn sources(&self) -> &[String] {
|
||||||
&self.source
|
&self.sources
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_match<P: AsRef<Path>>(&self, other: P) -> bool {
|
pub fn is_match<P: AsRef<Path>>(&self, other: P) -> bool {
|
||||||
let other_path = other.as_ref();
|
let other_path = other.as_ref();
|
||||||
other_path.starts_with(Path::new(&self.source))
|
self.sources.iter().any(|source| {
|
||||||
|| other_path.ends_with(Path::new(&self.source))
|
let as_bytes = other_path.as_os_str().as_encoded_bytes();
|
||||||
|| self.glob.is_match(other_path)
|
as_bytes.starts_with(source.as_bytes()) || as_bytes.ends_with(source.as_bytes())
|
||||||
|
}) || self.glob.is_match(other_path)
|
||||||
|| self.check_with_end_separator(other_path)
|
|| self.check_with_end_separator(other_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -534,20 +542,20 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn edge_of_glob() {
|
fn edge_of_glob() {
|
||||||
let path = Path::new("/work/node_modules");
|
let path = Path::new("/work/node_modules");
|
||||||
let path_matcher = PathMatcher::new("**/node_modules/**").unwrap();
|
let path_matcher = PathMatcher::new(&["**/node_modules/**".to_owned()]).unwrap();
|
||||||
assert!(
|
assert!(
|
||||||
path_matcher.is_match(path),
|
path_matcher.is_match(path),
|
||||||
"Path matcher {path_matcher} should match {path:?}"
|
"Path matcher should match {path:?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn project_search() {
|
fn project_search() {
|
||||||
let path = Path::new("/Users/someonetoignore/work/zed/zed.dev/node_modules");
|
let path = Path::new("/Users/someonetoignore/work/zed/zed.dev/node_modules");
|
||||||
let path_matcher = PathMatcher::new("**/node_modules/**").unwrap();
|
let path_matcher = PathMatcher::new(&["**/node_modules/**".to_owned()]).unwrap();
|
||||||
assert!(
|
assert!(
|
||||||
path_matcher.is_match(path),
|
path_matcher.is_match(path),
|
||||||
"Path matcher {path_matcher} should match {path:?}"
|
"Path matcher should match {path:?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::{path::Path, sync::Arc};
|
use std::path::Path;
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
use gpui::AppContext;
|
use gpui::AppContext;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -8,25 +9,19 @@ use util::paths::PathMatcher;
|
|||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
pub struct WorktreeSettings {
|
pub struct WorktreeSettings {
|
||||||
pub file_scan_exclusions: Arc<[PathMatcher]>,
|
pub file_scan_exclusions: PathMatcher,
|
||||||
pub private_files: Arc<[PathMatcher]>,
|
pub private_files: PathMatcher,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorktreeSettings {
|
impl WorktreeSettings {
|
||||||
pub fn is_path_private(&self, path: &Path) -> bool {
|
pub fn is_path_private(&self, path: &Path) -> bool {
|
||||||
path.ancestors().any(|ancestor| {
|
path.ancestors()
|
||||||
self.private_files
|
.any(|ancestor| self.private_files.is_match(&ancestor))
|
||||||
.iter()
|
|
||||||
.any(|matcher| matcher.is_match(&ancestor))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_path_excluded(&self, path: &Path) -> bool {
|
pub fn is_path_excluded(&self, path: &Path) -> bool {
|
||||||
path.ancestors().any(|ancestor| {
|
path.ancestors()
|
||||||
self.file_scan_exclusions
|
.any(|ancestor| self.file_scan_exclusions.is_match(&ancestor))
|
||||||
.iter()
|
|
||||||
.any(|matcher| matcher.is_match(&ancestor))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,25 +62,12 @@ impl Settings for WorktreeSettings {
|
|||||||
file_scan_exclusions.sort();
|
file_scan_exclusions.sort();
|
||||||
private_files.sort();
|
private_files.sort();
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
file_scan_exclusions: path_matchers(&file_scan_exclusions, "file_scan_exclusions"),
|
file_scan_exclusions: path_matchers(&file_scan_exclusions, "file_scan_exclusions")?,
|
||||||
private_files: path_matchers(&private_files, "private_files"),
|
private_files: path_matchers(&private_files, "private_files")?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_matchers(values: &[String], context: &'static str) -> Arc<[PathMatcher]> {
|
fn path_matchers(values: &[String], context: &'static str) -> anyhow::Result<PathMatcher> {
|
||||||
values
|
PathMatcher::new(values).with_context(|| format!("Failed to parse globs from {}", context))
|
||||||
.iter()
|
|
||||||
.filter_map(|pattern| {
|
|
||||||
PathMatcher::new(pattern)
|
|
||||||
.map(Some)
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
log::error!(
|
|
||||||
"Skipping pattern {pattern} in `{}` project settings due to parsing error: {e:#}", context
|
|
||||||
);
|
|
||||||
None
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user