Start work on exposing semantic search via project search view

Co-authored-by: Kyle <kyle@zed.dev>
This commit is contained in:
Max Brunsfeld 2023-07-17 18:10:51 -07:00
parent d83c4ffb07
commit afc4c10ec1
9 changed files with 397 additions and 423 deletions

View file

@ -19,6 +19,7 @@ settings = { path = "../settings" }
theme = { path = "../theme" }
util = { path = "../util" }
workspace = { path = "../workspace" }
semantic_index = { path = "../semantic_index" }
anyhow.workspace = true
futures.workspace = true
log.workspace = true

View file

@ -2,7 +2,7 @@ use crate::{
SearchOption, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex,
ToggleWholeWord,
};
use anyhow::Result;
use anyhow::{Context, Result};
use collections::HashMap;
use editor::{
items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, MultiBuffer,
@ -18,7 +18,9 @@ use gpui::{
Task, View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle,
};
use menu::Confirm;
use postage::stream::Stream;
use project::{search::SearchQuery, Project};
use semantic_index::SemanticIndex;
use smallvec::SmallVec;
use std::{
any::{Any, TypeId},
@ -36,7 +38,10 @@ use workspace::{
ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId,
};
actions!(project_search, [SearchInNew, ToggleFocus, NextField]);
actions!(
project_search,
[SearchInNew, ToggleFocus, NextField, ToggleSemanticSearch]
);
#[derive(Default)]
struct ActiveSearches(HashMap<WeakModelHandle<Project>, WeakViewHandle<ProjectSearchView>>);
@ -92,6 +97,7 @@ pub struct ProjectSearchView {
case_sensitive: bool,
whole_word: bool,
regex: bool,
semantic: Option<SemanticSearchState>,
panels_with_errors: HashSet<InputPanel>,
active_match_index: Option<usize>,
search_id: usize,
@ -100,6 +106,13 @@ pub struct ProjectSearchView {
excluded_files_editor: ViewHandle<Editor>,
}
struct SemanticSearchState {
file_count: usize,
outstanding_file_count: usize,
_progress_task: Task<()>,
search_task: Option<Task<Result<()>>>,
}
pub struct ProjectSearchBar {
active_project_search: Option<ViewHandle<ProjectSearchView>>,
subscription: Option<Subscription>,
@ -198,12 +211,25 @@ impl View for ProjectSearchView {
let theme = theme::current(cx).clone();
let text = if self.query_editor.read(cx).text(cx).is_empty() {
""
Cow::Borrowed("")
} else if let Some(semantic) = &self.semantic {
if semantic.search_task.is_some() {
Cow::Borrowed("Searching...")
} else if semantic.outstanding_file_count > 0 {
Cow::Owned(format!(
"Indexing. {} of {}...",
semantic.file_count - semantic.outstanding_file_count,
semantic.file_count
))
} else {
Cow::Borrowed("Indexing complete")
}
} else if model.pending_search.is_some() {
"Searching..."
Cow::Borrowed("Searching...")
} else {
"No results"
Cow::Borrowed("No results")
};
MouseEventHandler::<Status, _>::new(0, cx, |_, _| {
Label::new(text, theme.search.results_status.clone())
.aligned()
@ -499,6 +525,7 @@ impl ProjectSearchView {
case_sensitive,
whole_word,
regex,
semantic: None,
panels_with_errors: HashSet::new(),
active_match_index: None,
query_editor_was_focused: false,
@ -563,6 +590,35 @@ impl ProjectSearchView {
}
fn search(&mut self, cx: &mut ViewContext<Self>) {
if let Some(semantic) = &mut self.semantic {
if semantic.outstanding_file_count > 0 {
return;
}
let search_phrase = self.query_editor.read(cx).text(cx);
let project = self.model.read(cx).project.clone();
if let Some(semantic_index) = SemanticIndex::global(cx) {
let search_task = semantic_index.update(cx, |semantic_index, cx| {
semantic_index.search_project(project, search_phrase, 10, cx)
});
semantic.search_task = Some(cx.spawn(|this, mut cx| async move {
let results = search_task.await.context("search task")?;
this.update(&mut cx, |this, cx| {
dbg!(&results);
// TODO: Update results
if let Some(semantic) = &mut this.semantic {
semantic.search_task = None;
}
})?;
anyhow::Ok(())
}));
}
return;
}
if let Some(query) = self.build_search_query(cx) {
self.model.update(cx, |model, cx| model.search(query, cx));
}
@ -876,6 +932,59 @@ impl ProjectSearchBar {
}
}
fn toggle_semantic_search(&mut self, cx: &mut ViewContext<Self>) -> bool {
if let Some(search_view) = self.active_project_search.as_ref() {
search_view.update(cx, |search_view, cx| {
if search_view.semantic.is_some() {
search_view.semantic = None;
} else if let Some(semantic_index) = SemanticIndex::global(cx) {
// TODO: confirm that it's ok to send this project
let project = search_view.model.read(cx).project.clone();
let index_task = semantic_index.update(cx, |semantic_index, cx| {
semantic_index.index_project(project, cx)
});
cx.spawn(|search_view, mut cx| async move {
let (files_to_index, mut files_remaining_rx) = index_task.await?;
search_view.update(&mut cx, |search_view, cx| {
search_view.semantic = Some(SemanticSearchState {
file_count: files_to_index,
outstanding_file_count: files_to_index,
search_task: None,
_progress_task: cx.spawn(|search_view, mut cx| async move {
while let Some(count) = files_remaining_rx.recv().await {
search_view
.update(&mut cx, |search_view, cx| {
if let Some(semantic_search_state) =
&mut search_view.semantic
{
semantic_search_state.outstanding_file_count =
count;
cx.notify();
if count == 0 {
return;
}
}
})
.ok();
}
}),
});
})?;
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
});
cx.notify();
true
} else {
false
}
}
fn render_nav_button(
&self,
icon: &'static str,
@ -953,6 +1062,42 @@ impl ProjectSearchBar {
.into_any()
}
fn render_semantic_search_button(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let tooltip_style = theme::current(cx).tooltip.clone();
let is_active = if let Some(search) = self.active_project_search.as_ref() {
let search = search.read(cx);
search.semantic.is_some()
} else {
false
};
let region_id = 3;
MouseEventHandler::<Self, _>::new(region_id, cx, |state, cx| {
let theme = theme::current(cx);
let style = theme
.search
.option_button
.in_state(is_active)
.style_for(state);
Label::new("Semantic", style.text.clone())
.contained()
.with_style(style.container)
})
.on_click(MouseButton::Left, move |_, this, cx| {
this.toggle_semantic_search(cx);
})
.with_cursor_style(CursorStyle::PointingHand)
.with_tooltip::<Self>(
region_id,
format!("Toggle Semantic Search"),
Some(Box::new(ToggleSemanticSearch)),
tooltip_style,
cx,
)
.into_any()
}
fn is_option_enabled(&self, option: SearchOption, cx: &AppContext) -> bool {
if let Some(search) = self.active_project_search.as_ref() {
let search = search.read(cx);
@ -1049,6 +1194,7 @@ impl View for ProjectSearchBar {
)
.with_child(
Flex::row()
.with_child(self.render_semantic_search_button(cx))
.with_child(self.render_option_button(
"Case",
SearchOption::CaseSensitive,