git: Fix searching in the split diff (#48894)

- Fix panics caused by reusing cached matches for the wrong side
- Highlight matches on the side that was searched only
- Clear matches in non-searched editor when initiating a new search

Release Notes:

- N/A

---------

Co-authored-by: Eric <eric@zed.dev>
Co-authored-by: Jakub <jakub@zed.dev>
This commit is contained in:
Cole Miller 2026-02-10 18:29:51 -05:00 committed by GitHub
parent d2c922b815
commit c8054cacbd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 336 additions and 100 deletions

View file

@ -61,7 +61,7 @@ use ui::{
use util::{ResultExt, maybe};
use workspace::{
CollaboratorId,
searchable::{Direction, SearchableItemHandle},
searchable::{Direction, SearchToken, SearchableItemHandle},
};
use workspace::{
@ -2799,11 +2799,12 @@ impl SearchableItem for TextThreadEditor {
&mut self,
matches: &[Self::Match],
active_match_index: Option<usize>,
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.editor.update(cx, |editor, cx| {
editor.update_matches(matches, active_match_index, window, cx)
editor.update_matches(matches, active_match_index, token, window, cx)
});
}
@ -2816,33 +2817,37 @@ impl SearchableItem for TextThreadEditor {
&mut self,
index: usize,
matches: &[Self::Match],
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.editor.update(cx, |editor, cx| {
editor.activate_match(index, matches, window, cx);
editor.activate_match(index, matches, token, window, cx);
});
}
fn select_matches(
&mut self,
matches: &[Self::Match],
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.editor
.update(cx, |editor, cx| editor.select_matches(matches, window, cx));
self.editor.update(cx, |editor, cx| {
editor.select_matches(matches, token, window, cx)
});
}
fn replace(
&mut self,
identifier: &Self::Match,
query: &project::search::SearchQuery,
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.editor.update(cx, |editor, cx| {
editor.replace(identifier, query, window, cx)
editor.replace(identifier, query, token, window, cx)
});
}
@ -2860,11 +2865,12 @@ impl SearchableItem for TextThreadEditor {
&mut self,
direction: Direction,
matches: &[Self::Match],
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<usize> {
self.editor.update(cx, |editor, cx| {
editor.active_match_index(direction, matches, window, cx)
editor.active_match_index(direction, matches, token, window, cx)
})
}
}

View file

@ -28,7 +28,7 @@ use util::maybe;
use workspace::{
ToolbarItemEvent, ToolbarItemView, Workspace,
item::Item,
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
searchable::{Direction, SearchEvent, SearchToken, SearchableItem, SearchableItemHandle},
ui::{Button, Clickable, ContextMenu, Label, LabelCommon, PopoverMenu, h_flex},
};
@ -1018,11 +1018,12 @@ impl SearchableItem for DapLogView {
&mut self,
matches: &[Self::Match],
active_match_index: Option<usize>,
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.editor.update(cx, |e, cx| {
e.update_matches(matches, active_match_index, window, cx)
e.update_matches(matches, active_match_index, token, window, cx)
})
}
@ -1035,21 +1036,24 @@ impl SearchableItem for DapLogView {
&mut self,
index: usize,
matches: &[Self::Match],
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.editor
.update(cx, |e, cx| e.activate_match(index, matches, window, cx))
self.editor.update(cx, |e, cx| {
e.activate_match(index, matches, token, window, cx)
})
}
fn select_matches(
&mut self,
matches: &[Self::Match],
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.editor
.update(cx, |e, cx| e.select_matches(matches, window, cx))
.update(cx, |e, cx| e.select_matches(matches, token, window, cx))
}
fn find_matches(
@ -1066,6 +1070,7 @@ impl SearchableItem for DapLogView {
&mut self,
_: &Self::Match,
_: &SearchQuery,
_token: SearchToken,
_window: &mut Window,
_: &mut Context<Self>,
) {
@ -1087,11 +1092,12 @@ impl SearchableItem for DapLogView {
&mut self,
direction: Direction,
matches: &[Self::Match],
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<usize> {
self.editor.update(cx, |e, cx| {
e.active_match_index(direction, matches, window, cx)
e.active_match_index(direction, matches, token, window, cx)
})
}
}

View file

@ -46,7 +46,8 @@ use workspace::{
invalid_item_view::InvalidItemView,
item::{FollowableItem, Item, ItemBufferKind, ItemEvent, ProjectItem, SaveOptions},
searchable::{
Direction, FilteredSearchRange, SearchEvent, SearchableItem, SearchableItemHandle,
Direction, FilteredSearchRange, SearchEvent, SearchToken, SearchableItem,
SearchableItemHandle,
},
};
use workspace::{
@ -1496,12 +1497,15 @@ impl Editor {
impl SearchableItem for Editor {
type Match = Range<Anchor>;
fn get_matches(&self, _window: &mut Window, _: &mut App) -> Vec<Range<Anchor>> {
self.background_highlights
.get(&HighlightKey::BufferSearchHighlights)
.map_or(Vec::new(), |(_color, ranges)| {
ranges.iter().cloned().collect()
})
fn get_matches(&self, _window: &mut Window, _: &mut App) -> (Vec<Range<Anchor>>, SearchToken) {
(
self.background_highlights
.get(&HighlightKey::BufferSearchHighlights)
.map_or(Vec::new(), |(_color, ranges)| {
ranges.iter().cloned().collect()
}),
SearchToken::default(),
)
}
fn clear_matches(&mut self, _: &mut Window, cx: &mut Context<Self>) {
@ -1517,6 +1521,7 @@ impl SearchableItem for Editor {
&mut self,
matches: &[Range<Anchor>],
active_match_index: Option<usize>,
_token: SearchToken,
_: &mut Window,
cx: &mut Context<Self>,
) {
@ -1630,6 +1635,7 @@ impl SearchableItem for Editor {
&mut self,
index: usize,
matches: &[Range<Anchor>],
_token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) {
@ -1648,6 +1654,7 @@ impl SearchableItem for Editor {
fn select_matches(
&mut self,
matches: &[Self::Match],
_token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) {
@ -1660,6 +1667,7 @@ impl SearchableItem for Editor {
&mut self,
identifier: &Self::Match,
query: &SearchQuery,
_token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) {
@ -1683,6 +1691,7 @@ impl SearchableItem for Editor {
&mut self,
matches: &mut dyn Iterator<Item = &Self::Match>,
query: &SearchQuery,
_token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) {
@ -1725,6 +1734,7 @@ impl SearchableItem for Editor {
current_index: usize,
direction: Direction,
count: usize,
_token: SearchToken,
_: &mut Window,
cx: &mut Context<Self>,
) -> usize {
@ -1832,6 +1842,7 @@ impl SearchableItem for Editor {
&mut self,
direction: Direction,
matches: &[Range<Anchor>],
_token: SearchToken,
_: &mut Window,
cx: &mut Context<Self>,
) -> Option<usize> {

View file

@ -24,12 +24,13 @@ use ui::{
use crate::{
display_map::CompanionExcerptPatch,
element::SplitSide,
split_editor_view::{SplitEditorState, SplitEditorView},
};
use workspace::{
ActivatePaneLeft, ActivatePaneRight, Item, ToolbarItemLocation, Workspace,
item::{BreadcrumbText, ItemBufferKind, ItemEvent, SaveOptions, TabContentParams},
searchable::{SearchEvent, SearchableItem, SearchableItemHandle},
searchable::{SearchEvent, SearchToken, SearchableItem, SearchableItemHandle},
};
use crate::{
@ -381,6 +382,7 @@ pub struct SplittableEditor {
lhs: Option<LhsEditor>,
workspace: WeakEntity<Workspace>,
split_state: Entity<SplitEditorState>,
searched_side: Option<SplitSide>,
_subscriptions: Vec<Subscription>,
}
@ -420,7 +422,17 @@ impl SplittableEditor {
}
}
pub fn last_selected_editor(&self) -> &Entity<Editor> {
fn focused_side(&self) -> SplitSide {
if let Some(lhs) = &self.lhs
&& lhs.was_last_focused
{
SplitSide::Left
} else {
SplitSide::Right
}
}
pub fn focused_editor(&self) -> &Entity<Editor> {
if let Some(lhs) = &self.lhs
&& lhs.was_last_focused
{
@ -459,8 +471,10 @@ impl SplittableEditor {
_ => cx.emit(event.clone()),
},
),
cx.subscribe(&rhs_editor, |_, _, event: &SearchEvent, cx| {
cx.emit(event.clone());
cx.subscribe(&rhs_editor, |this, _, event: &SearchEvent, cx| {
if this.searched_side.is_none() || this.searched_side == Some(SplitSide::Right) {
cx.emit(event.clone());
}
}),
];
@ -493,6 +507,7 @@ impl SplittableEditor {
lhs: None,
workspace: workspace.downgrade(),
split_state,
searched_side: None,
_subscriptions: subscriptions,
}
}
@ -596,13 +611,20 @@ impl SplittableEditor {
},
)];
subscriptions.push(
cx.subscribe(&lhs_editor, |this, _, event: &SearchEvent, cx| {
if this.searched_side == Some(SplitSide::Left) {
cx.emit(event.clone());
}
}),
);
let lhs_focus_handle = lhs_editor.read(cx).focus_handle(cx);
subscriptions.push(
cx.on_focus_in(&lhs_focus_handle, window, |this, _window, cx| {
if let Some(lhs) = &mut this.lhs {
if !lhs.was_last_focused {
lhs.was_last_focused = true;
cx.emit(SearchEvent::MatchesInvalidated);
cx.notify();
}
}
@ -615,7 +637,6 @@ impl SplittableEditor {
if let Some(lhs) = &mut this.lhs {
if lhs.was_last_focused {
lhs.was_last_focused = false;
cx.emit(SearchEvent::MatchesInvalidated);
cx.notify();
}
}
@ -1085,6 +1106,19 @@ impl SplittableEditor {
});
}
}
fn search_token(&self) -> SearchToken {
SearchToken::new(self.focused_side() as u64)
}
fn editor_for_token(&self, token: SearchToken) -> &Entity<Editor> {
if token.value() == SplitSide::Left as u64 {
if let Some(lhs) = &self.lhs {
return &lhs.editor;
}
}
&self.rhs_editor
}
}
#[cfg(test)]
@ -1665,12 +1699,12 @@ impl Item for SplittableEditor {
window: &mut Window,
cx: &mut Context<Self>,
) -> bool {
self.last_selected_editor()
self.focused_editor()
.update(cx, |editor, cx| editor.navigate(data, window, cx))
}
fn deactivated(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.last_selected_editor().update(cx, |editor, cx| {
self.focused_editor().update(cx, |editor, cx| {
editor.deactivated(window, cx);
});
}
@ -1709,9 +1743,7 @@ impl Item for SplittableEditor {
}
fn pixel_position_of_cursor(&self, cx: &App) -> Option<gpui::Point<gpui::Pixels>> {
self.last_selected_editor()
.read(cx)
.pixel_position_of_cursor(cx)
self.focused_editor().read(cx).pixel_position_of_cursor(cx)
}
}
@ -1719,25 +1751,59 @@ impl SearchableItem for SplittableEditor {
type Match = Range<Anchor>;
fn clear_matches(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.last_selected_editor().update(cx, |editor, cx| {
self.rhs_editor.update(cx, |editor, cx| {
editor.clear_matches(window, cx);
});
if let Some(lhs_editor) = self.lhs_editor() {
lhs_editor.update(cx, |editor, cx| {
editor.clear_matches(window, cx);
})
}
}
fn update_matches(
&mut self,
matches: &[Self::Match],
active_match_index: Option<usize>,
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.last_selected_editor().update(cx, |editor, cx| {
editor.update_matches(matches, active_match_index, window, cx);
self.editor_for_token(token).update(cx, |editor, cx| {
editor.update_matches(matches, active_match_index, token, window, cx);
});
}
fn search_bar_visibility_changed(
&mut self,
visible: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
if visible {
let side = self.focused_side();
self.searched_side = Some(side);
match side {
SplitSide::Left => {
self.rhs_editor.update(cx, |editor, cx| {
editor.clear_matches(window, cx);
});
}
SplitSide::Right => {
if let Some(lhs) = &self.lhs {
lhs.editor.update(cx, |editor, cx| {
editor.clear_matches(window, cx);
});
}
}
}
} else {
self.searched_side = None;
}
}
fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
self.last_selected_editor()
self.focused_editor()
.update(cx, |editor, cx| editor.query_suggestion(window, cx))
}
@ -1745,22 +1811,24 @@ impl SearchableItem for SplittableEditor {
&mut self,
index: usize,
matches: &[Self::Match],
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.last_selected_editor().update(cx, |editor, cx| {
editor.activate_match(index, matches, window, cx);
self.editor_for_token(token).update(cx, |editor, cx| {
editor.activate_match(index, matches, token, window, cx);
});
}
fn select_matches(
&mut self,
matches: &[Self::Match],
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.last_selected_editor().update(cx, |editor, cx| {
editor.select_matches(matches, window, cx);
self.editor_for_token(token).update(cx, |editor, cx| {
editor.select_matches(matches, token, window, cx);
});
}
@ -1768,11 +1836,12 @@ impl SearchableItem for SplittableEditor {
&mut self,
identifier: &Self::Match,
query: &project::search::SearchQuery,
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.last_selected_editor().update(cx, |editor, cx| {
editor.replace(identifier, query, window, cx);
self.editor_for_token(token).update(cx, |editor, cx| {
editor.replace(identifier, query, token, window, cx);
});
}
@ -1782,19 +1851,41 @@ impl SearchableItem for SplittableEditor {
window: &mut Window,
cx: &mut Context<Self>,
) -> gpui::Task<Vec<Self::Match>> {
self.last_selected_editor()
self.focused_editor()
.update(cx, |editor, cx| editor.find_matches(query, window, cx))
}
fn find_matches_with_token(
&mut self,
query: Arc<project::search::SearchQuery>,
window: &mut Window,
cx: &mut Context<Self>,
) -> gpui::Task<(Vec<Self::Match>, SearchToken)> {
let token = self.search_token();
let editor = self.focused_editor().downgrade();
cx.spawn_in(window, async move |_, cx| {
let Some(matches) = editor
.update_in(cx, |editor, window, cx| {
editor.find_matches(query, window, cx)
})
.ok()
else {
return (Vec::new(), token);
};
(matches.await, token)
})
}
fn active_match_index(
&mut self,
direction: workspace::searchable::Direction,
matches: &[Self::Match],
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<usize> {
self.last_selected_editor().update(cx, |editor, cx| {
editor.active_match_index(direction, matches, window, cx)
self.editor_for_token(token).update(cx, |editor, cx| {
editor.active_match_index(direction, matches, token, window, cx)
})
}
}
@ -1803,7 +1894,7 @@ impl EventEmitter<EditorEvent> for SplittableEditor {}
impl EventEmitter<SearchEvent> for SplittableEditor {}
impl Focusable for SplittableEditor {
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
self.last_selected_editor().read(cx).focus_handle(cx)
self.focused_editor().read(cx).focus_handle(cx)
}
}

View file

@ -450,7 +450,7 @@ impl ProjectDiff {
}
pub fn active_path(&self, cx: &App) -> Option<ProjectPath> {
let editor = self.editor.read(cx).last_selected_editor().read(cx);
let editor = self.editor.read(cx).focused_editor().read(cx);
let position = editor.selections.newest_anchor().head();
let multi_buffer = editor.buffer().read(cx);
let (_, buffer, _) = multi_buffer.excerpt_containing(position, cx)?;

View file

@ -23,7 +23,7 @@ use util::ResultExt as _;
use workspace::{
SplitDirection, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId,
item::{Item, ItemHandle},
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
searchable::{Direction, SearchEvent, SearchToken, SearchableItem, SearchableItemHandle},
};
use crate::get_or_create_tool;
@ -813,11 +813,12 @@ impl SearchableItem for LspLogView {
&mut self,
matches: &[Self::Match],
active_match_index: Option<usize>,
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.editor.update(cx, |e, cx| {
e.update_matches(matches, active_match_index, window, cx)
e.update_matches(matches, active_match_index, token, window, cx)
})
}
@ -830,21 +831,24 @@ impl SearchableItem for LspLogView {
&mut self,
index: usize,
matches: &[Self::Match],
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.editor
.update(cx, |e, cx| e.activate_match(index, matches, window, cx))
self.editor.update(cx, |e, cx| {
e.activate_match(index, matches, token, window, cx)
})
}
fn select_matches(
&mut self,
matches: &[Self::Match],
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.editor
.update(cx, |e, cx| e.select_matches(matches, window, cx))
.update(cx, |e, cx| e.select_matches(matches, token, window, cx))
}
fn find_matches(
@ -861,6 +865,7 @@ impl SearchableItem for LspLogView {
&mut self,
_: &Self::Match,
_: &SearchQuery,
_token: SearchToken,
_window: &mut Window,
_: &mut Context<Self>,
) {
@ -881,11 +886,12 @@ impl SearchableItem for LspLogView {
&mut self,
direction: Direction,
matches: &[Self::Match],
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<usize> {
self.editor.update(cx, |e, cx| {
e.active_match_index(direction, matches, window, cx)
e.active_match_index(direction, matches, token, window, cx)
})
}
}

View file

@ -4245,7 +4245,7 @@ impl OutlinePanel {
let buffer_search_matches = self
.active_editor()
.map(|active_editor| {
active_editor.update(cx, |editor, cx| editor.get_matches(window, cx))
active_editor.update(cx, |editor, cx| editor.get_matches(window, cx).0)
})
.unwrap_or_default();

View file

@ -40,8 +40,8 @@ use workspace::{
ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
item::{ItemBufferKind, ItemHandle},
searchable::{
CollapseDirection, Direction, FilteredSearchRange, SearchEvent, SearchableItemHandle,
WeakSearchableItemHandle,
CollapseDirection, Direction, FilteredSearchRange, SearchEvent, SearchToken,
SearchableItemHandle, WeakSearchableItemHandle,
},
};
@ -76,7 +76,8 @@ pub struct BufferSearchBar {
#[cfg(target_os = "macos")]
pending_external_query: Option<(String, SearchOptions)>,
active_search: Option<Arc<SearchQuery>>,
searchable_items_with_matches: HashMap<Box<dyn WeakSearchableItemHandle>, AnyVec<dyn Send>>,
searchable_items_with_matches:
HashMap<Box<dyn WeakSearchableItemHandle>, (AnyVec<dyn Send>, SearchToken)>,
pending_search: Option<Task<()>>,
search_options: SearchOptions,
default_options: SearchOptions,
@ -233,7 +234,7 @@ impl Render for BufferSearchBar {
let matches_count = self
.searchable_items_with_matches
.get(&searchable_item.downgrade())
.map(AnyVec::len)
.map(|(matches, _)| matches.len())
.unwrap_or(0);
if let Some(match_ix) = self.active_match_index {
Some(format!("{}/{}", match_ix + 1, matches_count))
@ -1041,11 +1042,11 @@ impl BufferSearchBar {
pub fn activate_current_match(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if let Some(match_ix) = self.active_match_index
&& let Some(active_searchable_item) = self.active_searchable_item.as_ref()
&& let Some(matches) = self
&& let Some((matches, token)) = self
.searchable_items_with_matches
.get(&active_searchable_item.downgrade())
{
active_searchable_item.activate_match(match_ix, matches, window, cx)
active_searchable_item.activate_match(match_ix, matches, *token, window, cx)
}
}
@ -1227,11 +1228,11 @@ impl BufferSearchBar {
if !self.dismissed
&& self.active_match_index.is_some()
&& let Some(searchable_item) = self.active_searchable_item.as_ref()
&& let Some(matches) = self
&& let Some((matches, token)) = self
.searchable_items_with_matches
.get(&searchable_item.downgrade())
{
searchable_item.select_matches(matches, window, cx);
searchable_item.select_matches(matches, *token, window, cx);
self.focus_editor(&FocusEditor, window, cx);
}
}
@ -1261,10 +1262,10 @@ impl BufferSearchBar {
if let Some(index) = self.active_match_index
&& let Some(searchable_item) = self.active_searchable_item.as_ref()
&& let Some(matches) = self
&& let Some((matches, token)) = self
.searchable_items_with_matches
.get(&searchable_item.downgrade())
.filter(|matches| !matches.is_empty())
.filter(|(matches, _)| !matches.is_empty())
{
// If 'wrapscan' is disabled, searches do not wrap around the end of the file.
if !EditorSettings::get_global(cx).search_wrap
@ -1275,30 +1276,30 @@ impl BufferSearchBar {
return;
}
let new_match_index = searchable_item
.match_index_for_direction(matches, index, direction, count, window, cx);
.match_index_for_direction(matches, index, direction, count, *token, window, cx);
searchable_item.update_matches(matches, Some(new_match_index), window, cx);
searchable_item.activate_match(new_match_index, matches, window, cx);
searchable_item.update_matches(matches, Some(new_match_index), *token, window, cx);
searchable_item.activate_match(new_match_index, matches, *token, window, cx);
}
}
pub fn select_first_match(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if let Some(searchable_item) = self.active_searchable_item.as_ref()
&& let Some(matches) = self
&& let Some((matches, token)) = self
.searchable_items_with_matches
.get(&searchable_item.downgrade())
{
if matches.is_empty() {
return;
}
searchable_item.update_matches(matches, Some(0), window, cx);
searchable_item.activate_match(0, matches, window, cx);
searchable_item.update_matches(matches, Some(0), *token, window, cx);
searchable_item.activate_match(0, matches, *token, window, cx);
}
}
pub fn select_last_match(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if let Some(searchable_item) = self.active_searchable_item.as_ref()
&& let Some(matches) = self
&& let Some((matches, token)) = self
.searchable_items_with_matches
.get(&searchable_item.downgrade())
{
@ -1306,8 +1307,8 @@ impl BufferSearchBar {
return;
}
let new_match_index = matches.len() - 1;
searchable_item.update_matches(matches, Some(new_match_index), window, cx);
searchable_item.activate_match(new_match_index, matches, window, cx);
searchable_item.update_matches(matches, Some(new_match_index), *token, window, cx);
searchable_item.activate_match(new_match_index, matches, *token, window, cx);
}
}
@ -1532,18 +1533,19 @@ impl BufferSearchBar {
self.active_search = Some(query.clone());
let query_text = query.as_str().to_string();
let matches = active_searchable_item.find_matches(query, window, cx);
let matches_with_token =
active_searchable_item.find_matches_with_token(query, window, cx);
let active_searchable_item = active_searchable_item.downgrade();
self.pending_search = Some(cx.spawn_in(window, async move |this, cx| {
let matches = matches.await;
let (matches, token) = matches_with_token.await;
this.update_in(cx, |this, window, cx| {
if let Some(active_searchable_item) =
WeakSearchableItemHandle::upgrade(active_searchable_item.as_ref(), cx)
{
this.searchable_items_with_matches
.insert(active_searchable_item.downgrade(), matches);
.insert(active_searchable_item.downgrade(), (matches, token));
this.update_match_index(window, cx);
@ -1552,7 +1554,7 @@ impl BufferSearchBar {
.add(&mut this.search_history_cursor, query_text);
}
if !this.dismissed {
let matches = this
let (matches, token) = this
.searchable_items_with_matches
.get(&active_searchable_item.downgrade())
.unwrap();
@ -1562,6 +1564,7 @@ impl BufferSearchBar {
active_searchable_item.update_matches(
matches,
this.active_match_index,
*token,
window,
cx,
);
@ -1592,21 +1595,21 @@ impl BufferSearchBar {
.active_searchable_item
.as_ref()
.and_then(|searchable_item| {
let matches = self
let (matches, token) = self
.searchable_items_with_matches
.get(&searchable_item.downgrade())?;
searchable_item.active_match_index(direction, matches, window, cx)
searchable_item.active_match_index(direction, matches, *token, window, cx)
});
if new_index != self.active_match_index {
self.active_match_index = new_index;
if !self.dismissed {
if let Some(searchable_item) = self.active_searchable_item.as_ref() {
if let Some(matches) = self
if let Some((matches, token)) = self
.searchable_items_with_matches
.get(&searchable_item.downgrade())
{
if !matches.is_empty() {
searchable_item.update_matches(matches, new_index, window, cx);
searchable_item.update_matches(matches, new_index, *token, window, cx);
}
}
}
@ -1712,7 +1715,7 @@ impl BufferSearchBar {
&& self.active_search.is_some()
&& let Some(searchable_item) = self.active_searchable_item.as_ref()
&& let Some(query) = self.active_search.as_ref()
&& let Some(matches) = self
&& let Some((matches, token)) = self
.searchable_items_with_matches
.get(&searchable_item.downgrade())
{
@ -1721,7 +1724,7 @@ impl BufferSearchBar {
.as_ref()
.clone()
.with_replacement(self.replacement(cx));
searchable_item.replace(matches.at(active_index), &query, window, cx);
searchable_item.replace(matches.at(active_index), &query, *token, window, cx);
self.select_next_match(&SelectNextMatch, window, cx);
}
should_propagate = false;
@ -1736,7 +1739,7 @@ impl BufferSearchBar {
&& self.active_search.is_some()
&& let Some(searchable_item) = self.active_searchable_item.as_ref()
&& let Some(query) = self.active_search.as_ref()
&& let Some(matches) = self
&& let Some((matches, token)) = self
.searchable_items_with_matches
.get(&searchable_item.downgrade())
{
@ -1744,7 +1747,7 @@ impl BufferSearchBar {
.as_ref()
.clone()
.with_replacement(self.replacement(cx));
searchable_item.replace_all(&mut matches.iter(), &query, window, cx);
searchable_item.replace_all(&mut matches.iter(), &query, *token, window, cx);
}
}

View file

@ -49,7 +49,10 @@ use workspace::{
DeploySearch, ItemNavHistory, NewSearch, ToolbarItemEvent, ToolbarItemLocation,
ToolbarItemView, Workspace, WorkspaceId,
item::{Item, ItemEvent, ItemHandle, SaveOptions},
searchable::{CollapseDirection, Direction, SearchEvent, SearchableItem, SearchableItemHandle},
searchable::{
CollapseDirection, Direction, SearchEvent, SearchToken, SearchableItem,
SearchableItemHandle,
},
};
actions!(
@ -731,7 +734,7 @@ impl ProjectSearchView {
let mat = self.entity.read(cx).match_ranges.get(active_index).cloned();
self.results_editor.update(cx, |editor, cx| {
if let Some(mat) = mat.as_ref() {
editor.replace(mat, &query, window, cx);
editor.replace(mat, &query, SearchToken::default(), window, cx);
}
});
self.select_match(Direction::Next, window, cx)
@ -761,7 +764,13 @@ impl ProjectSearchView {
}
self.results_editor.update(cx, |editor, cx| {
editor.replace_all(&mut match_ranges.iter(), &query, window, cx);
editor.replace_all(
&mut match_ranges.iter(),
&query,
SearchToken::default(),
window,
cx,
);
});
self.entity.update(cx, |model, _cx| {
@ -1394,7 +1403,15 @@ impl ProjectSearchView {
}
let new_index = self.results_editor.update(cx, |editor, cx| {
editor.match_index_for_direction(&match_ranges, index, direction, 1, window, cx)
editor.match_index_for_direction(
&match_ranges,
index,
direction,
1,
SearchToken::default(),
window,
cx,
)
});
let range_to_select = match_ranges[new_index].clone();

View file

@ -56,7 +56,9 @@ use workspace::{
BreadcrumbText, Item, ItemEvent, SerializableItem, TabContentParams, TabTooltipContent,
},
register_serializable_item,
searchable::{Direction, SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle},
searchable::{
Direction, SearchEvent, SearchOptions, SearchToken, SearchableItem, SearchableItemHandle,
},
};
use zed_actions::{agent::AddSelectionToThread, assistant::InlineAssist};
@ -1664,6 +1666,7 @@ impl SearchableItem for TerminalView {
&mut self,
matches: &[Self::Match],
_active_match_index: Option<usize>,
_token: SearchToken,
_window: &mut Window,
cx: &mut Context<Self>,
) {
@ -1686,6 +1689,7 @@ impl SearchableItem for TerminalView {
&mut self,
index: usize,
_: &[Self::Match],
_token: SearchToken,
_window: &mut Window,
cx: &mut Context<Self>,
) {
@ -1695,7 +1699,13 @@ impl SearchableItem for TerminalView {
}
/// Add selections for all matches given.
fn select_matches(&mut self, matches: &[Self::Match], _: &mut Window, cx: &mut Context<Self>) {
fn select_matches(
&mut self,
matches: &[Self::Match],
_token: SearchToken,
_: &mut Window,
cx: &mut Context<Self>,
) {
self.terminal()
.update(cx, |term, _| term.select_matches(matches));
cx.notify();
@ -1721,6 +1731,7 @@ impl SearchableItem for TerminalView {
&mut self,
direction: Direction,
matches: &[Self::Match],
_token: SearchToken,
_: &mut Window,
cx: &mut Context<Self>,
) -> Option<usize> {
@ -1774,6 +1785,7 @@ impl SearchableItem for TerminalView {
&mut self,
_: &Self::Match,
_: &SearchQuery,
_token: SearchToken,
_window: &mut Window,
_: &mut Context<Self>,
) {

View file

@ -12,6 +12,19 @@ use crate::{
item::{Item, WeakItemHandle},
};
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct SearchToken(u64);
impl SearchToken {
pub fn new(value: u64) -> Self {
Self(value)
}
pub fn value(&self) -> u64 {
self.0
}
}
#[derive(Clone, Debug)]
pub enum CollapseDirection {
Collapsed,
@ -96,14 +109,15 @@ pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
) {
}
fn get_matches(&self, _window: &mut Window, _: &mut App) -> Vec<Self::Match> {
Vec::new()
fn get_matches(&self, _window: &mut Window, _: &mut App) -> (Vec<Self::Match>, SearchToken) {
(Vec::new(), SearchToken::default())
}
fn clear_matches(&mut self, window: &mut Window, cx: &mut Context<Self>);
fn update_matches(
&mut self,
matches: &[Self::Match],
active_match_index: Option<usize>,
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
);
@ -112,12 +126,14 @@ pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
&mut self,
index: usize,
matches: &[Self::Match],
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
);
fn select_matches(
&mut self,
matches: &[Self::Match],
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
);
@ -125,6 +141,7 @@ pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
&mut self,
_: &Self::Match,
_: &SearchQuery,
_token: SearchToken,
_window: &mut Window,
_: &mut Context<Self>,
);
@ -132,11 +149,12 @@ pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
&mut self,
matches: &mut dyn Iterator<Item = &Self::Match>,
query: &SearchQuery,
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) {
for item in matches {
self.replace(item, query, window, cx);
self.replace(item, query, token, window, cx);
}
}
fn match_index_for_direction(
@ -145,6 +163,7 @@ pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
current_index: usize,
direction: Direction,
count: usize,
_token: SearchToken,
_window: &mut Window,
_: &mut Context<Self>,
) -> usize {
@ -166,10 +185,22 @@ pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
window: &mut Window,
cx: &mut Context<Self>,
) -> Task<Vec<Self::Match>>;
fn find_matches_with_token(
&mut self,
query: Arc<SearchQuery>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Task<(Vec<Self::Match>, SearchToken)> {
let matches = self.find_matches(query, window, cx);
cx.spawn(async move |_, _| (matches.await, SearchToken::default()))
}
fn active_match_index(
&mut self,
direction: Direction,
matches: &[Self::Match],
token: SearchToken,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<usize>;
@ -191,6 +222,7 @@ pub trait SearchableItemHandle: ItemHandle {
&self,
matches: &AnyVec<dyn Send>,
active_match_index: Option<usize>,
token: SearchToken,
window: &mut Window,
cx: &mut App,
);
@ -199,14 +231,22 @@ pub trait SearchableItemHandle: ItemHandle {
&self,
index: usize,
matches: &AnyVec<dyn Send>,
token: SearchToken,
window: &mut Window,
cx: &mut App,
);
fn select_matches(
&self,
matches: &AnyVec<dyn Send>,
token: SearchToken,
window: &mut Window,
cx: &mut App,
);
fn select_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App);
fn replace(
&self,
_: any_vec::element::ElementRef<'_, dyn Send>,
_: &SearchQuery,
token: SearchToken,
_window: &mut Window,
_: &mut App,
);
@ -214,6 +254,7 @@ pub trait SearchableItemHandle: ItemHandle {
&self,
matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
query: &SearchQuery,
token: SearchToken,
window: &mut Window,
cx: &mut App,
);
@ -223,6 +264,7 @@ pub trait SearchableItemHandle: ItemHandle {
current_index: usize,
direction: Direction,
count: usize,
token: SearchToken,
window: &mut Window,
cx: &mut App,
) -> usize;
@ -232,10 +274,17 @@ pub trait SearchableItemHandle: ItemHandle {
window: &mut Window,
cx: &mut App,
) -> Task<AnyVec<dyn Send>>;
fn find_matches_with_token(
&self,
query: Arc<SearchQuery>,
window: &mut Window,
cx: &mut App,
) -> Task<(AnyVec<dyn Send>, SearchToken)>;
fn active_match_index(
&self,
direction: Direction,
matches: &AnyVec<dyn Send>,
token: SearchToken,
window: &mut Window,
cx: &mut App,
) -> Option<usize>;
@ -282,12 +331,13 @@ impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
&self,
matches: &AnyVec<dyn Send>,
active_match_index: Option<usize>,
token: SearchToken,
window: &mut Window,
cx: &mut App,
) {
let matches = matches.downcast_ref().unwrap();
self.update(cx, |this, cx| {
this.update_matches(matches.as_slice(), active_match_index, window, cx)
this.update_matches(matches.as_slice(), active_match_index, token, window, cx)
});
}
fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String {
@ -297,19 +347,26 @@ impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
&self,
index: usize,
matches: &AnyVec<dyn Send>,
token: SearchToken,
window: &mut Window,
cx: &mut App,
) {
let matches = matches.downcast_ref().unwrap();
self.update(cx, |this, cx| {
this.activate_match(index, matches.as_slice(), window, cx)
this.activate_match(index, matches.as_slice(), token, window, cx)
});
}
fn select_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App) {
fn select_matches(
&self,
matches: &AnyVec<dyn Send>,
token: SearchToken,
window: &mut Window,
cx: &mut App,
) {
let matches = matches.downcast_ref().unwrap();
self.update(cx, |this, cx| {
this.select_matches(matches.as_slice(), window, cx)
this.select_matches(matches.as_slice(), token, window, cx)
});
}
@ -319,6 +376,7 @@ impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
current_index: usize,
direction: Direction,
count: usize,
token: SearchToken,
window: &mut Window,
cx: &mut App,
) -> usize {
@ -329,6 +387,7 @@ impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
current_index,
direction,
count,
token,
window,
cx,
)
@ -353,16 +412,38 @@ impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
any_matches
})
}
fn find_matches_with_token(
&self,
query: Arc<SearchQuery>,
window: &mut Window,
cx: &mut App,
) -> Task<(AnyVec<dyn Send>, SearchToken)> {
let matches_with_token = self.update(cx, |this, cx| {
this.find_matches_with_token(query, window, cx)
});
window.spawn(cx, async |_| {
let (matches, token) = matches_with_token.await;
let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
{
let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
for mat in matches {
any_matches.push(mat);
}
}
(any_matches, token)
})
}
fn active_match_index(
&self,
direction: Direction,
matches: &AnyVec<dyn Send>,
token: SearchToken,
window: &mut Window,
cx: &mut App,
) -> Option<usize> {
let matches = matches.downcast_ref()?;
self.update(cx, |this, cx| {
this.active_match_index(direction, matches.as_slice(), window, cx)
this.active_match_index(direction, matches.as_slice(), token, window, cx)
})
}
@ -370,17 +451,19 @@ impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
&self,
mat: any_vec::element::ElementRef<'_, dyn Send>,
query: &SearchQuery,
token: SearchToken,
window: &mut Window,
cx: &mut App,
) {
let mat = mat.downcast_ref().unwrap();
self.update(cx, |this, cx| this.replace(mat, query, window, cx))
self.update(cx, |this, cx| this.replace(mat, query, token, window, cx))
}
fn replace_all(
&self,
matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
query: &SearchQuery,
token: SearchToken,
window: &mut Window,
cx: &mut App,
) {
@ -388,6 +471,7 @@ impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
this.replace_all(
&mut matches.map(|m| m.downcast_ref().unwrap()),
query,
token,
window,
cx,
);