Fix release notes appearing in project search (#32898)

Closes #28829

Release Notes:

- Fixed an issue where release notes would appear in project search
results when opened locally
This commit is contained in:
Ben Kunkle 2025-06-17 15:56:41 -05:00 committed by GitHub
parent a422345224
commit 0cda28f786
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 149 additions and 5 deletions

View file

@ -82,7 +82,10 @@ fn view_release_notes_locally(
.update_in(cx, |workspace, window, cx| { .update_in(cx, |workspace, window, cx| {
let project = workspace.project().clone(); let project = workspace.project().clone();
let buffer = project.update(cx, |project, cx| { let buffer = project.update(cx, |project, cx| {
project.create_local_buffer("", markdown, cx) let buffer = project.create_local_buffer("", markdown, cx);
project
.mark_buffer_as_non_searchable(buffer.read(cx).remote_id(), cx);
buffer
}); });
buffer.update(cx, |buffer, cx| { buffer.update(cx, |buffer, cx| {
buffer.edit([(0..0, body.release_notes)], None, cx) buffer.edit([(0..0, body.release_notes)], None, cx)

View file

@ -39,6 +39,7 @@ pub struct BufferStore {
path_to_buffer_id: HashMap<ProjectPath, BufferId>, path_to_buffer_id: HashMap<ProjectPath, BufferId>,
downstream_client: Option<(AnyProtoClient, u64)>, downstream_client: Option<(AnyProtoClient, u64)>,
shared_buffers: HashMap<proto::PeerId, HashMap<BufferId, SharedBuffer>>, shared_buffers: HashMap<proto::PeerId, HashMap<BufferId, SharedBuffer>>,
non_searchable_buffers: HashSet<BufferId>,
} }
#[derive(Hash, Eq, PartialEq, Clone)] #[derive(Hash, Eq, PartialEq, Clone)]
@ -726,6 +727,7 @@ impl BufferStore {
path_to_buffer_id: Default::default(), path_to_buffer_id: Default::default(),
shared_buffers: Default::default(), shared_buffers: Default::default(),
loading_buffers: Default::default(), loading_buffers: Default::default(),
non_searchable_buffers: Default::default(),
worktree_store, worktree_store,
} }
} }
@ -750,6 +752,7 @@ impl BufferStore {
path_to_buffer_id: Default::default(), path_to_buffer_id: Default::default(),
loading_buffers: Default::default(), loading_buffers: Default::default(),
shared_buffers: Default::default(), shared_buffers: Default::default(),
non_searchable_buffers: Default::default(),
worktree_store, worktree_store,
} }
} }
@ -1059,7 +1062,9 @@ impl BufferStore {
let mut unnamed_buffers = Vec::new(); let mut unnamed_buffers = Vec::new();
for handle in self.buffers() { for handle in self.buffers() {
let buffer = handle.read(cx); let buffer = handle.read(cx);
if let Some(entry_id) = buffer.entry_id(cx) { if self.non_searchable_buffers.contains(&buffer.remote_id()) {
continue;
} else if let Some(entry_id) = buffer.entry_id(cx) {
open_buffers.insert(entry_id); open_buffers.insert(entry_id);
} else { } else {
limit = limit.saturating_sub(1); limit = limit.saturating_sub(1);
@ -1665,6 +1670,10 @@ impl BufferStore {
} }
serialized_transaction serialized_transaction
} }
pub(crate) fn mark_buffer_as_non_searchable(&mut self, buffer_id: BufferId) {
self.non_searchable_buffers.insert(buffer_id);
}
} }
impl OpenBuffer { impl OpenBuffer {

View file

@ -5068,6 +5068,12 @@ impl Project {
pub fn agent_location(&self) -> Option<AgentLocation> { pub fn agent_location(&self) -> Option<AgentLocation> {
self.agent_location.clone() self.agent_location.clone()
} }
pub fn mark_buffer_as_non_searchable(&self, buffer_id: BufferId, cx: &mut Context<Project>) {
self.buffer_store.update(cx, |buffer_store, _| {
buffer_store.mark_buffer_as_non_searchable(buffer_id)
});
}
} }
pub struct PathMatchCandidateSet { pub struct PathMatchCandidateSet {

View file

@ -5336,6 +5336,132 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
); );
} }
#[gpui::test]
async fn test_search_with_buffer_exclusions(cx: &mut gpui::TestAppContext) {
init_test(cx);
let search_query = "file";
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
path!("/dir"),
json!({
"one.rs": r#"// Rust file one"#,
"one.ts": r#"// TypeScript file one"#,
"two.rs": r#"// Rust file two"#,
"two.ts": r#"// TypeScript file two"#,
}),
)
.await;
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
let _buffer = project.update(cx, |project, cx| {
let buffer = project.create_local_buffer("file", None, cx);
project.mark_buffer_as_non_searchable(buffer.read(cx).remote_id(), cx);
buffer
});
assert_eq!(
search(
&project,
SearchQuery::text(
search_query,
false,
true,
false,
Default::default(),
PathMatcher::new(&["*.odd".to_owned()]).unwrap(),
false,
None,
)
.unwrap(),
cx
)
.await
.unwrap(),
HashMap::from_iter([
(path!("dir/one.rs").to_string(), vec![8..12]),
(path!("dir/one.ts").to_string(), vec![14..18]),
(path!("dir/two.rs").to_string(), vec![8..12]),
(path!("dir/two.ts").to_string(), vec![14..18]),
]),
"If no exclusions match, all files should be returned"
);
assert_eq!(
search(
&project,
SearchQuery::text(
search_query,
false,
true,
false,
Default::default(),
PathMatcher::new(&["*.rs".to_owned()]).unwrap(),
false,
None,
)
.unwrap(),
cx
)
.await
.unwrap(),
HashMap::from_iter([
(path!("dir/one.ts").to_string(), vec![14..18]),
(path!("dir/two.ts").to_string(), vec![14..18]),
]),
"Rust exclusion search should give only TypeScript files"
);
assert_eq!(
search(
&project,
SearchQuery::text(
search_query,
false,
true,
false,
Default::default(),
PathMatcher::new(&["*.ts".to_owned(), "*.odd".to_owned()]).unwrap(),
false,
None,
)
.unwrap(),
cx
)
.await
.unwrap(),
HashMap::from_iter([
(path!("dir/one.rs").to_string(), vec![8..12]),
(path!("dir/two.rs").to_string(), vec![8..12]),
]),
"TypeScript exclusion search should give only Rust files, even if other exclusions don't match anything"
);
assert!(
search(
&project,
SearchQuery::text(
search_query,
false,
true,
false,
Default::default(),
PathMatcher::new(&["*.rs".to_owned(), "*.ts".to_owned(), "*.odd".to_owned()])
.unwrap(),
false,
None,
)
.unwrap(),
cx
)
.await
.unwrap()
.is_empty(),
"Rust and typescript exclusion should give no files, even if other exclusions don't match anything"
);
}
#[gpui::test] #[gpui::test]
async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContext) { async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContext) {
init_test(cx); init_test(cx);

View file

@ -164,7 +164,7 @@ pub fn init(cx: &mut App) {
.detach(); .detach();
} }
fn is_contains_uppercase(str: &str) -> bool { fn contains_uppercase(str: &str) -> bool {
str.chars().any(|c| c.is_uppercase()) str.chars().any(|c| c.is_uppercase())
} }
@ -767,7 +767,7 @@ impl ProjectSearchView {
let query = this.search_query_text(cx); let query = this.search_query_text(cx);
if !query.is_empty() if !query.is_empty()
&& this.search_options.contains(SearchOptions::CASE_SENSITIVE) && this.search_options.contains(SearchOptions::CASE_SENSITIVE)
!= is_contains_uppercase(&query) != contains_uppercase(&query)
{ {
this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx); this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
} }
@ -1323,7 +1323,7 @@ impl ProjectSearchView {
if EditorSettings::get_global(cx).use_smartcase_search if EditorSettings::get_global(cx).use_smartcase_search
&& !query.is_empty() && !query.is_empty()
&& self.search_options.contains(SearchOptions::CASE_SENSITIVE) && self.search_options.contains(SearchOptions::CASE_SENSITIVE)
!= is_contains_uppercase(query) != contains_uppercase(query)
{ {
self.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx) self.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx)
} }