Fix project search holding a reference to closed unsaved files (#61677)

# Objective

Fixes #28398

## Solution

This issue was occurring because `ProjectSearch` stored
`Entity<MultiBuffer>` with has reference counted pointers to underlying
buffers
The function `remove_deleted_buffers` is renamed to
`remove_stale_buffers` and it now also checks for buffers which do not
have a file associated with them and are closed. Then, it removes them
from `Entity<MultiBuffer>` stored in ProjectSearch. This function is
also called in again when workspace is upgraded.

## Testing

Bug being fixed was manually verified. 

## Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable


Release Notes:

- Fixed project search retaining results from closed untitled buffers.

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Co-authored-by: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-08-26 17:00:03 +00:00
1 parent 0a10d97e6a
commit 34c48d6579
5 files changed
+331 -55

No files matched your search

Generated
+1
View File
@@ -16059,6 +16059,7 @@ dependencies = [
"picker_preview",
"pretty_assertions",
"project",
"proto",
"regex",
"serde_json",
"settings",
+12
View File
@@ -1666,6 +1666,18 @@ impl BufferStore {
self.shared_buffers.remove(peer_id);
}
pub fn is_shared(&self, buffer_id: BufferId, cx: &App) -> bool {
self.shared_buffers
.values()
.any(|buffers| buffers.contains_key(&buffer_id))
|| self.as_remote().is_some_and(|remote| {
remote
.shared_with_me
.iter()
.any(|buffer| buffer.read(cx).remote_id() == buffer_id)
})
}
pub fn update_peer_id(&mut self, old_peer_id: &proto::PeerId, new_peer_id: proto::PeerId) {
if let Some(buffers) = self.shared_buffers.remove(old_peer_id) {
self.shared_buffers.insert(new_peer_id, buffers);
+1
View File
@@ -60,5 +60,6 @@ gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }
lsp.workspace = true
pretty_assertions.workspace = true
proto.workspace = true
unindent.workspace = true
workspace = { workspace = true, features = ["test-support"] }
+316 -54
View File
@@ -58,7 +58,7 @@ use util::{ResultExt as _, paths::PathMatcher};
use workspace::{
DeploySearch, ItemNavHistory, NewSearch, ToolbarItemEvent, ToolbarItemLocation,
ToolbarItemView, Workspace, WorkspaceId,
item::{Item, ItemEvent, ItemHandle, SaveOptions},
item::{Item, ItemBufferKind, ItemEvent, ItemHandle, SaveOptions},
searchable::{Direction, SearchEvent, SearchToken, SearchableItem, SearchableItemHandle},
};
@@ -253,6 +253,7 @@ const SEARCH_ON_TYPE_DEBOUNCE: Duration = Duration::from_millis(250);
pub struct ProjectSearch {
pub(crate) project: Entity<Project>,
workspace: WeakEntity<Workspace>,
pub excerpts: Entity<MultiBuffer>,
pub pending_search: Option<Task<Option<SearchResults<SearchResult>>>>,
pub match_ranges: Vec<Range<Anchor>>,
@@ -268,6 +269,7 @@ pub struct ProjectSearch {
search_excluded_history_cursor: SearchHistoryCursor,
pub project_search_turning_into_text_finder: Arc<AtomicBool>,
_excerpts_subscription: Subscription,
_workspace_subscription: Option<Subscription>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -393,13 +395,19 @@ pub struct ProjectSearchBar {
}
impl ProjectSearch {
pub fn new(project: Entity<Project>, cx: &mut Context<Self>) -> Self {
pub fn new(
project: Entity<Project>,
workspace: WeakEntity<Workspace>,
cx: &mut Context<Self>,
) -> Self {
let capability = project.read(cx).capability();
let excerpts = cx.new(|_| MultiBuffer::new(capability));
let subscription = Self::subscribe_to_excerpts(&excerpts, cx);
let excerpts_subscription = Self::subscribe_to_excerpts(&excerpts, cx);
let workspace_subscription = Self::subscribe_to_workspace(&workspace, cx);
Self {
project,
workspace,
excerpts,
pending_search: Default::default(),
match_ranges: Default::default(),
@@ -414,7 +422,8 @@ impl ProjectSearch {
search_included_history_cursor: Default::default(),
search_excluded_history_cursor: Default::default(),
project_search_turning_into_text_finder: Arc::new(AtomicBool::new(false)),
_excerpts_subscription: subscription,
_excerpts_subscription: excerpts_subscription,
_workspace_subscription: workspace_subscription,
}
}
@@ -423,10 +432,12 @@ impl ProjectSearch {
let excerpts = self
.excerpts
.update(cx, |excerpts, cx| cx.new(|cx| excerpts.clone(cx)));
let subscription = Self::subscribe_to_excerpts(&excerpts, cx);
let excerpts_subscription = Self::subscribe_to_excerpts(&excerpts, cx);
let workspace_subscription = Self::subscribe_to_workspace(&self.workspace, cx);
Self {
project: self.project.clone(),
workspace: self.workspace.clone(),
excerpts,
pending_search: Default::default(),
match_ranges: self.match_ranges.clone(),
@@ -449,7 +460,8 @@ impl ProjectSearch {
search_included_history_cursor: self.search_included_history_cursor.clone(),
search_excluded_history_cursor: self.search_excluded_history_cursor.clone(),
project_search_turning_into_text_finder: Arc::new(AtomicBool::new(false)),
_excerpts_subscription: subscription,
_excerpts_subscription: excerpts_subscription,
_workspace_subscription: workspace_subscription,
}
})
}
@@ -464,26 +476,46 @@ impl ProjectSearch {
})
}
fn subscribe_to_workspace(
workspace: &WeakEntity<Workspace>,
cx: &mut Context<Self>,
) -> Option<Subscription> {
workspace.upgrade().map(|workspace| {
cx.subscribe(&workspace, |this, _, event, cx| {
if matches!(event, workspace::Event::ItemRemoved { .. }) {
this.remove_closed_untitled_buffers(cx);
}
})
})
}
fn remove_deleted_buffers(&mut self, cx: &mut Context<Self>) {
let deleted_buffer_ids = self
self.remove_stale_buffers(None, cx);
}
fn remove_closed_untitled_buffers(&mut self, cx: &mut Context<Self>) {
self.remove_stale_buffers(self.workspace.upgrade().as_ref(), cx);
}
fn remove_stale_buffers(
&mut self,
workspace: Option<&Entity<Workspace>>,
cx: &mut Context<Self>,
) {
let stale_buffer_ids = self
.excerpts
.read(cx)
.all_buffers_iter()
.filter(|buffer| {
buffer
.read(cx)
.file()
.is_some_and(|file| file.disk_state().is_deleted())
})
.filter(|buffer| is_buffer_stale(&self.project, workspace, buffer, cx))
.map(|buffer| buffer.read(cx).remote_id())
.collect::<Vec<_>>();
if deleted_buffer_ids.is_empty() {
if stale_buffer_ids.is_empty() {
return;
}
let snapshot = self.excerpts.update(cx, |excerpts, cx| {
for buffer_id in deleted_buffer_ids {
for buffer_id in stale_buffer_ids {
excerpts.remove_excerpts_for_buffer(buffer_id, cx);
}
excerpts.snapshot(cx)
@@ -691,25 +723,39 @@ async fn consume_search_stream(
buffers_with_ranges
.into_iter()
.map(|(buffer, ranges)| {
excerpts.set_anchored_excerpts_for_path(
let new_ranges = excerpts.set_anchored_excerpts_for_path(
PathKey::for_buffer(&buffer, cx),
buffer,
buffer.clone(),
ranges,
multibuffer_context_lines(cx),
cx,
)
);
async move { (buffer, new_ranges.await) }
})
.collect::<FuturesOrdered<_>>()
})
})
.ok()?;
while let Some(new_ranges) = new_ranges.next().await {
while let Some((buffer, new_ranges)) = new_ranges.next().await {
// `new_ranges.next().await` likely never gets hit while still pending so `async_task`
// will not reschedule, starving other front end tasks, insert a yield point for that here
smol::future::yield_now().await;
project_search
.update(cx, |project_search, cx| {
project_search.match_ranges.extend(new_ranges);
let workspace = project_search.workspace.upgrade();
if !is_buffer_stale(
&project_search.project,
workspace.as_ref(),
&buffer,
cx,
) {
project_search.match_ranges.extend(new_ranges);
} else {
let buffer_id = buffer.read(cx).remote_id();
project_search.excerpts.update(cx, |excerpts, cx| {
excerpts.remove_excerpts_for_buffer(buffer_id, cx)
});
}
cx.notify();
})
.ok()?;
@@ -881,6 +927,7 @@ async fn apply_reused_chunk(
.collect::<Vec<_>>();
let batch_changed = project_search
.update(cx, |project_search, cx| {
let workspace = project_search.workspace.upgrade();
let batch_changed = project_search.excerpts.update(cx, |excerpts, cx| {
let mut batch_changed = false;
let mut applied = Vec::with_capacity(batch.len());
@@ -908,6 +955,12 @@ async fn apply_reused_chunk(
} else {
None
};
if is_buffer_stale(&project_search.project, workspace.as_ref(), &buffer, cx)
{
excerpts.remove_excerpts_for_paths(vec![path_key], cx);
batch_changed |= previous_span.is_some();
continue;
}
excerpts.set_excerpt_ranges_for_path(
path_key,
buffer,
@@ -1624,7 +1677,8 @@ impl ProjectSearchView {
) {
let weak_workspace = cx.entity().downgrade();
let entity = cx.new(|cx| ProjectSearch::new(workspace.project().clone(), cx));
let entity = cx
.new(|cx| ProjectSearch::new(workspace.project().clone(), weak_workspace.clone(), cx));
let search = cx.new(|cx| ProjectSearchView::new(weak_workspace, entity, window, cx, None));
workspace.add_item_to_active_pane(Box::new(search.clone()), None, true, window, cx);
search.update(cx, |search, cx| {
@@ -1682,12 +1736,13 @@ impl ProjectSearchView {
new_query
});
if let Some(new_query) = new_query {
let weak_workspace = cx.entity().downgrade();
let entity = cx.new(|cx| {
let mut entity = ProjectSearch::new(workspace.project().clone(), cx);
let mut entity =
ProjectSearch::new(workspace.project().clone(), weak_workspace.clone(), cx);
entity.search(new_query, SearchMode::Manual, false, cx);
entity
});
let weak_workspace = cx.entity().downgrade();
workspace.add_item_to_active_pane(
Box::new(cx.new(|cx| {
ProjectSearchView::new(weak_workspace, entity, window, cx, None)
@@ -1756,7 +1811,9 @@ impl ProjectSearchView {
let weak_workspace = cx.entity().downgrade();
let project_search = cx.new(|cx| ProjectSearch::new(workspace.project().clone(), cx));
let project_search = cx.new(|cx| {
ProjectSearch::new(workspace.project().clone(), weak_workspace.clone(), cx)
});
let project_search_view = cx.new(|cx| {
ProjectSearchView::new(weak_workspace, project_search, window, cx, settings)
});
@@ -3380,6 +3437,30 @@ fn register_workspace_action_for_present_search<A: Action>(
});
}
fn is_buffer_stale(
project: &Entity<Project>,
workspace: Option<&Entity<Workspace>>,
buffer: &Entity<Buffer>,
cx: &App,
) -> bool {
let buffer_entity_id = buffer.entity_id();
let buffer = buffer.read(cx);
if let Some(file) = buffer.file() {
file.disk_state().is_deleted()
} else if let Some(workspace) = workspace {
!workspace.read(cx).items(cx).any(|item| {
item.buffer_kind(cx) == ItemBufferKind::Singleton
&& item.project_item_model_ids(cx).contains(&buffer_entity_id)
}) && !project
.read(cx)
.buffer_store()
.read(cx)
.is_shared(buffer.remote_id(), cx)
} else {
false
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn perform_project_search(
search_view: &Entity<ProjectSearchView>,
@@ -3422,7 +3503,7 @@ pub mod tests {
};
use util::{path, paths::PathStyle, rel_path::rel_path};
use util_macros::perf;
use workspace::{DeploySearch, MultiWorkspace};
use workspace::{DeploySearch, MultiWorkspace, SaveIntent};
#[test]
fn test_split_glob_patterns() {
@@ -3474,7 +3555,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project, cx));
let search = cx.new(|cx| ProjectSearch::new(project, workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search, window, cx, None)
});
@@ -3544,7 +3625,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project, cx));
let search = cx.new(|cx| ProjectSearch::new(project, workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search, window, cx, None)
});
@@ -3585,7 +3666,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project, cx));
let search = cx.new(|cx| ProjectSearch::new(project, workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search, window, cx, None)
});
@@ -3735,7 +3816,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search = cx.new(|cx| ProjectSearch::new(project.clone(), workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -3887,6 +3968,184 @@ pub mod tests {
assert!(!results_collapsed);
}
#[gpui::test]
async fn test_search_results_do_not_read_closed_untitled_buffer(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree(
path!("/dir"),
json!({
"one.rs": "const ONE: usize = 1;",
}),
)
.await;
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
let window =
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let untitled_buffer = project.update(cx, |project, cx| {
project.create_local_buffer("const TWO: usize = one::ONE;\n", None, true, cx)
});
let editor = window
.update(cx, |_, window, cx| {
let multibuffer = MultiBuffer::build_from_buffer(untitled_buffer.clone(), cx);
let editor = cx.new(|cx| {
Editor::new(
editor::EditorMode::full(),
multibuffer,
Some(project.clone()),
window,
cx,
)
});
workspace.update(cx, |workspace, cx| {
workspace.add_item_to_center(Box::new(editor.clone()), window, cx);
});
editor
})
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project, workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
perform_search(search_view, "const", cx);
search_view
.update(cx, |search_view, _window, cx| {
let results_text = search_view
.results_editor
.update(cx, |editor, cx| editor.display_text(cx));
assert_eq!(
"\n\nconst TWO: usize = one::ONE;\n\n\n\nconst ONE: usize = 1;",
results_text
);
})
.unwrap();
let pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
let close_task = window
.update(cx, |_, window, cx| {
pane.update(cx, |pane, cx| {
pane.close_item_by_id(editor.entity_id(), SaveIntent::Skip, window, cx)
})
})
.unwrap();
close_task.await.unwrap();
cx.run_until_parked();
search_view
.update(cx, |search_view, _window, cx| {
let results_text = search_view
.results_editor
.update(cx, |editor, cx| editor.display_text(cx));
assert_eq!("\n\nconst ONE: usize = 1;", results_text);
})
.unwrap();
// Re-run the search and verify the closed untitled buffer stays gone
perform_search(search_view, "const", cx);
search_view
.update(cx, |search_view, _window, cx| {
let results_text = search_view
.results_editor
.update(cx, |editor, cx| editor.display_text(cx));
assert_eq!("\n\nconst ONE: usize = 1;", results_text);
})
.unwrap();
}
#[gpui::test]
async fn test_search_results_keep_peer_shared_untitled_buffers(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree(
path!("/dir"),
json!({
"one.rs": "const ONE: usize = 1;",
}),
)
.await;
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
let window =
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let untitled_buffer = project.update(cx, |project, cx| {
project.create_local_buffer("const TWO: usize = one::ONE;\n", None, true, cx)
});
project.update(cx, |project, cx| {
project.buffer_store().update(cx, |buffer_store, cx| {
buffer_store
.create_buffer_for_peer(
&untitled_buffer,
proto::PeerId { owner_id: 0, id: 1 },
cx,
)
.detach_and_log_err(cx);
});
});
let search = cx.new(|cx| ProjectSearch::new(project, workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
perform_search(search_view, "const", cx);
search_view
.update(cx, |search_view, _window, cx| {
let results_text = search_view
.results_editor
.update(cx, |editor, cx| editor.display_text(cx));
assert_eq!(
"\n\nconst TWO: usize = one::ONE;\n\n\n\nconst ONE: usize = 1;",
results_text
);
})
.unwrap();
search.update(cx, |search, cx| search.remove_closed_untitled_buffers(cx));
cx.run_until_parked();
search_view
.update(cx, |search_view, _window, cx| {
let results_text = search_view
.results_editor
.update(cx, |editor, cx| editor.display_text(cx));
assert_eq!(
"\n\nconst TWO: usize = one::ONE;\n\n\n\nconst ONE: usize = 1;",
results_text
);
})
.unwrap();
perform_search(search_view, "const", cx);
search_view
.update(cx, |search_view, _window, cx| {
let results_text = search_view
.results_editor
.update(cx, |editor, cx| editor.display_text(cx));
assert_eq!(
"\n\nconst TWO: usize = one::ONE;\n\n\n\nconst ONE: usize = 1;",
results_text
);
})
.unwrap();
}
#[perf]
#[gpui::test]
async fn test_collapse_state_syncs_after_manual_buffer_fold(cx: &mut TestAppContext) {
@@ -3908,7 +4167,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search = cx.new(|cx| ProjectSearch::new(project.clone(), workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -5632,7 +5891,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project, cx));
let search = cx.new(|cx| ProjectSearch::new(project, workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -5986,7 +6245,7 @@ pub mod tests {
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let cx = &mut VisualTestContext::from_window(window.into(), cx);
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search = cx.new(|cx| ProjectSearch::new(project.clone(), workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -6156,7 +6415,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search = cx.new(|cx| ProjectSearch::new(project.clone(), workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -6420,7 +6679,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search = cx.new(|cx| ProjectSearch::new(project.clone(), workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -6627,7 +6886,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search = cx.new(|cx| ProjectSearch::new(project.clone(), workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -6699,7 +6958,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search = cx.new(|cx| ProjectSearch::new(project.clone(), workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -6906,7 +7165,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search = cx.new(|cx| ProjectSearch::new(project.clone(), workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -7151,7 +7410,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search = cx.new(|cx| ProjectSearch::new(project.clone(), workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -7213,7 +7472,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project, cx));
let search = cx.new(|cx| ProjectSearch::new(project, workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -7286,7 +7545,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project, cx));
let search = cx.new(|cx| ProjectSearch::new(project, workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -7378,7 +7637,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search = cx.new(|cx| ProjectSearch::new(project.clone(), workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -7458,7 +7717,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search = cx.new(|cx| ProjectSearch::new(project.clone(), workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -7838,7 +8097,8 @@ pub mod tests {
let buffer_untitled = project.update(cx, |project, cx| {
project.create_local_buffer("fn needle_untitled() {}", None, true, cx)
});
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search =
cx.new(|cx| ProjectSearch::new(project.clone(), WeakEntity::new_invalid(), cx));
let build_query = || {
SearchOptions::NONE
.build_query(
@@ -7913,7 +8173,8 @@ pub mod tests {
})
.await
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search =
cx.new(|cx| ProjectSearch::new(project.clone(), WeakEntity::new_invalid(), cx));
let query = SearchOptions::NONE
.build_query(
@@ -7964,7 +8225,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project, cx));
let search = cx.new(|cx| ProjectSearch::new(project, workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -8025,7 +8286,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project, cx));
let search = cx.new(|cx| ProjectSearch::new(project, workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -8493,7 +8754,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search = cx.new(|cx| ProjectSearch::new(project.clone(), workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -8577,7 +8838,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search = cx.new(|cx| ProjectSearch::new(project.clone(), workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -8659,7 +8920,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search = cx.new(|cx| ProjectSearch::new(project.clone(), workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -8728,7 +8989,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project, cx));
let search = cx.new(|cx| ProjectSearch::new(project, workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -8769,7 +9030,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search = cx.new(|cx| ProjectSearch::new(project.clone(), workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -8835,7 +9096,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search = cx.new(|cx| ProjectSearch::new(project.clone(), workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -8918,7 +9179,7 @@ pub mod tests {
let workspace = window
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search = cx.new(|cx| ProjectSearch::new(project.clone(), workspace.downgrade(), cx));
let search_view = cx.add_window(|window, cx| {
ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
});
@@ -8985,7 +9246,8 @@ pub mod tests {
)
.await;
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
let search =
cx.new(|cx| ProjectSearch::new(project.clone(), WeakEntity::new_invalid(), cx));
let query = SearchOptions::REGEX
.build_query(
"needle_[a-z]+",
+1 -1
View File
@@ -350,7 +350,7 @@ impl Delegate {
.get(&project.downgrade())
.cloned();
let search = cx.new(|cx| ProjectSearch::new(project, cx));
let search = cx.new(|cx| ProjectSearch::new(project, weak_workspace.clone(), cx));
let project_search =
cx.new(|cx| ProjectSearchView::new(weak_workspace, search, window, cx, settings));
cx.spawn(async move |_, cx| Self::new_from_project_search(project_search, cx).await)