Detailed changes
@@ -4208,16 +4208,21 @@ impl Project {
if matching_paths_tx.is_closed() {
break;
}
-
- abs_path.clear();
- abs_path.push(&snapshot.abs_path());
- abs_path.push(&entry.path);
- let matches = if let Some(file) =
- fs.open_sync(&abs_path).await.log_err()
+ let matches = if !query
+ .file_matches(Some(&entry.path))
{
- query.detect(file).unwrap_or(false)
- } else {
false
+ } else {
+ abs_path.clear();
+ abs_path.push(&snapshot.abs_path());
+ abs_path.push(&entry.path);
+ if let Some(file) =
+ fs.open_sync(&abs_path).await.log_err()
+ {
+ query.detect(file).unwrap_or(false)
+ } else {
+ false
+ }
};
if matches {
@@ -4299,15 +4304,21 @@ impl Project {
let mut buffers_rx = buffers_rx.clone();
scope.spawn(async move {
while let Some((buffer, snapshot)) = buffers_rx.next().await {
- let buffer_matches = query
- .search(snapshot.as_rope())
- .await
- .iter()
- .map(|range| {
- snapshot.anchor_before(range.start)
- ..snapshot.anchor_after(range.end)
- })
- .collect::<Vec<_>>();
+ let buffer_matches = if query.file_matches(
+ snapshot.file().map(|file| file.path().as_ref()),
+ ) {
+ query
+ .search(snapshot.as_rope())
+ .await
+ .iter()
+ .map(|range| {
+ snapshot.anchor_before(range.start)
+ ..snapshot.anchor_after(range.end)
+ })
+ .collect()
+ } else {
+ Vec::new()
+ };
if !buffer_matches.is_empty() {
worker_matched_buffers
.insert(buffer.clone(), buffer_matches);
@@ -8,10 +8,11 @@ use smol::future::yield_now;
use std::{
io::{BufRead, BufReader, Read},
ops::Range,
+ path::Path,
sync::Arc,
};
-#[derive(Clone)]
+#[derive(Clone, Debug)]
pub enum SearchQuery {
Text {
search: Arc<AhoCorasick<usize>>,
@@ -97,11 +98,13 @@ impl SearchQuery {
message
.files_to_include
.split(',')
+ .filter(|glob_str| !glob_str.trim().is_empty())
.map(|glob_str| glob::Pattern::new(glob_str))
.collect::<Result<_, _>>()?,
message
.files_to_exclude
.split(',')
+ .filter(|glob_str| !glob_str.trim().is_empty())
.map(|glob_str| glob::Pattern::new(glob_str))
.collect::<Result<_, _>>()?,
)
@@ -113,11 +116,13 @@ impl SearchQuery {
message
.files_to_include
.split(',')
+ .filter(|glob_str| !glob_str.trim().is_empty())
.map(|glob_str| glob::Pattern::new(glob_str))
.collect::<Result<_, _>>()?,
message
.files_to_exclude
.split(',')
+ .filter(|glob_str| !glob_str.trim().is_empty())
.map(|glob_str| glob::Pattern::new(glob_str))
.collect::<Result<_, _>>()?,
))
@@ -290,6 +295,7 @@ impl SearchQuery {
} => files_to_include,
}
}
+
pub fn files_to_exclude(&self) -> &[glob::Pattern] {
match self {
Self::Text {
@@ -300,4 +306,21 @@ impl SearchQuery {
} => files_to_exclude,
}
}
+
+ pub fn file_matches(&self, file_path: Option<&Path>) -> bool {
+ match file_path {
+ Some(file_path) => {
+ !self
+ .files_to_exclude()
+ .iter()
+ .any(|exclude_glob| exclude_glob.matches_path(file_path))
+ && (self.files_to_include().is_empty()
+ || self
+ .files_to_include()
+ .iter()
+ .any(|include_glob| include_glob.matches_path(file_path)))
+ }
+ None => self.files_to_include().is_empty(),
+ }
+ }
}
@@ -555,23 +555,30 @@ impl ProjectSearchView {
fn build_search_query(&mut self, cx: &mut ViewContext<Self>) -> Option<SearchQuery> {
let text = self.query_editor.read(cx).text(cx);
- let included_files = self
+ let Ok(included_files) = self
.included_files_editor
.read(cx)
.text(cx)
.split(',')
+ .filter(|glob_str| !glob_str.trim().is_empty())
.map(|glob_str| glob::Pattern::new(glob_str))
- .collect::<Result<_, _>>()
- // TODO kb validation
- .unwrap_or_default();
- let excluded_files = self
+ .collect::<Result<_, _>>() else {
+ self.query_contains_error = true;
+ cx.notify();
+ return None
+ };
+ let Ok(excluded_files) = self
.excluded_files_editor
.read(cx)
.text(cx)
.split(',')
+ .filter(|glob_str| !glob_str.trim().is_empty())
.map(|glob_str| glob::Pattern::new(glob_str))
- .collect::<Result<_, _>>()
- .unwrap_or_default();
+ .collect::<Result<_, _>>() else {
+ self.query_contains_error = true;
+ cx.notify();
+ return None
+ };
if self.regex {
match SearchQuery::regex(
text,
@@ -928,11 +935,11 @@ impl View for ProjectSearchBar {
let included_files_view = ChildView::new(&search.included_files_editor, cx)
.aligned()
.left()
- .flex(1., true);
+ .flex(0.5, true);
let excluded_files_view = ChildView::new(&search.excluded_files_editor, cx)
.aligned()
- .left()
- .flex(1., true);
+ .right()
+ .flex(0.5, true);
Flex::row()
.with_child(
@@ -983,25 +990,41 @@ impl View for ProjectSearchBar {
.with_style(theme.search.option_button_group)
.aligned(),
)
+ // TODO kb better layout
.with_child(
- // TODO kb better layout
Flex::row()
.with_child(
- Label::new("Include files:", theme.search.match_index.text.clone())
- .contained()
- .with_style(theme.search.match_index.container)
- .aligned(),
+ Label::new(
+ "Include:",
+ theme.search.include_exclude_inputs.text.clone(),
+ )
+ .contained()
+ .with_style(theme.search.include_exclude_inputs.container)
+ .aligned(),
)
.with_child(included_files_view)
+ .contained()
+ .with_style(theme.search.editor.input.container)
+ .aligned()
+ .constrained()
+ .with_min_width(theme.search.editor.min_width)
+ .with_max_width(theme.search.editor.max_width)
+ .flex(1., false),
+ )
+ .with_child(
+ Flex::row()
.with_child(
- Label::new("Exclude files:", theme.search.match_index.text.clone())
- .contained()
- .with_style(theme.search.match_index.container)
- .aligned(),
+ Label::new(
+ "Exclude:",
+ theme.search.include_exclude_inputs.text.clone(),
+ )
+ .contained()
+ .with_style(theme.search.include_exclude_inputs.container)
+ .aligned(),
)
.with_child(excluded_files_view)
.contained()
- .with_style(editor_container)
+ .with_style(theme.search.editor.input.container)
.aligned()
.constrained()
.with_min_width(theme.search.editor.min_width)
@@ -319,6 +319,7 @@ pub struct Search {
pub editor: FindEditor,
pub invalid_editor: ContainerStyle,
pub option_button_group: ContainerStyle,
+ pub include_exclude_inputs: ContainedText,
pub option_button: Interactive<ContainedText>,
pub match_background: Color,
pub match_index: ContainedText,
@@ -74,6 +74,10 @@ export default function search(colorScheme: ColorScheme) {
right: 12,
},
},
+ includeExcludeInputs: {
+ ...text(layer, "mono", "variant"),
+ padding: 6,
+ },
resultsStatus: {
...text(layer, "mono", "on"),
size: 18,