diff --git a/Cargo.lock b/Cargo.lock index 1ea1d1a1b4..2eff8630cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7960,6 +7960,7 @@ dependencies = [ "log", "matrixmultiply", "ndarray", + "picker", "project", "rand 0.8.5", "rusqlite", @@ -7968,6 +7969,7 @@ dependencies = [ "sha-1 0.10.1", "smol", "tempdir", + "theme", "tree-sitter", "tree-sitter-rust", "unindent", diff --git a/crates/vector_store/Cargo.toml b/crates/vector_store/Cargo.toml index edc06bb295..ddfef6927b 100644 --- a/crates/vector_store/Cargo.toml +++ b/crates/vector_store/Cargo.toml @@ -14,6 +14,8 @@ language = { path = "../language" } project = { path = "../project" } workspace = { path = "../workspace" } util = { path = "../util" } +picker = { path = "../picker" } +theme = { path = "../theme" } anyhow.workspace = true futures.workspace = true smol.workspace = true diff --git a/crates/vector_store/src/modal.rs b/crates/vector_store/src/modal.rs new file mode 100644 index 0000000000..48429150cd --- /dev/null +++ b/crates/vector_store/src/modal.rs @@ -0,0 +1,107 @@ +use std::sync::Arc; + +use gpui::{ + actions, elements::*, AnyElement, AppContext, ModelHandle, MouseState, Task, ViewContext, + WeakViewHandle, +}; +use picker::{Picker, PickerDelegate, PickerEvent}; +use project::Project; +use util::ResultExt; +use workspace::Workspace; + +use crate::{SearchResult, VectorStore}; + +actions!(semantic_search, [Toggle]); + +pub type SemanticSearch = Picker; + +pub struct SemanticSearchDelegate { + workspace: WeakViewHandle, + project: ModelHandle, + vector_store: ModelHandle, + selected_match_index: usize, + matches: Vec, +} + +impl SemanticSearchDelegate { + // This is currently searching on every keystroke, + // This is wildly overkill, and has the potential to get expensive + // We will need to update this to throttle searching + pub fn new( + workspace: WeakViewHandle, + project: ModelHandle, + vector_store: ModelHandle, + ) -> Self { + Self { + workspace, + project, + vector_store, + selected_match_index: 0, + matches: vec![], + } + } +} + +impl PickerDelegate for SemanticSearchDelegate { + fn placeholder_text(&self) -> Arc { + "Search repository in natural language...".into() + } + + fn confirm(&mut self, cx: &mut ViewContext) { + todo!() + } + + fn dismissed(&mut self, _cx: &mut ViewContext) {} + + fn match_count(&self) -> usize { + self.matches.len() + } + + fn selected_index(&self) -> usize { + self.selected_match_index + } + + fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext) { + self.selected_match_index = ix; + } + + fn update_matches(&mut self, query: String, cx: &mut ViewContext) -> Task<()> { + let task = self + .vector_store + .update(cx, |store, cx| store.search(query.to_string(), 10, cx)); + + cx.spawn(|this, mut cx| async move { + let results = task.await.log_err(); + this.update(&mut cx, |this, cx| { + if let Some(results) = results { + let delegate = this.delegate_mut(); + delegate.matches = results; + } + }); + }) + } + + fn render_match( + &self, + ix: usize, + mouse_state: &mut MouseState, + selected: bool, + cx: &AppContext, + ) -> AnyElement> { + let theme = theme::current(cx); + let style = &theme.picker.item; + let current_style = style.style_for(mouse_state, selected); + + let search_result = &self.matches[ix]; + + let mut path = search_result.file_path.to_string_lossy(); + let name = search_result.name.clone(); + + Flex::column() + .with_child(Text::new(name, current_style.label.text.clone()).with_soft_wrap(false)) + .with_child(Label::new(path.to_string(), style.default.label.clone())) + .contained() + .with_style(current_style.container) + .into_any() + } +} diff --git a/crates/vector_store/src/vector_store.rs b/crates/vector_store/src/vector_store.rs index d7fd59466f..2dc479045f 100644 --- a/crates/vector_store/src/vector_store.rs +++ b/crates/vector_store/src/vector_store.rs @@ -1,5 +1,6 @@ mod db; mod embedding; +mod modal; mod search; #[cfg(test)] @@ -10,6 +11,7 @@ use db::{FileSha1, VectorDatabase, VECTOR_DB_URL}; use embedding::{EmbeddingProvider, OpenAIEmbeddings}; use gpui::{actions, AppContext, Entity, ModelContext, ModelHandle, Task, ViewContext}; use language::{Language, LanguageRegistry}; +use modal::{SemanticSearch, SemanticSearchDelegate, Toggle}; use project::{Fs, Project}; use smol::channel; use std::{cmp::Ordering, collections::HashMap, path::PathBuf, sync::Arc}; @@ -17,8 +19,6 @@ use tree_sitter::{Parser, QueryCursor}; use util::{http::HttpClient, ResultExt, TryFutureExt}; use workspace::{Workspace, WorkspaceCreated}; -actions!(semantic_search, [TestSearch]); - #[derive(Debug)] pub struct Document { pub offset: usize, @@ -60,24 +60,40 @@ pub fn init( .detach(); cx.add_action({ - let vector_store = vector_store.clone(); - move |workspace: &mut Workspace, _: &TestSearch, cx: &mut ViewContext| { - let t0 = std::time::Instant::now(); - let task = vector_store.update(cx, |store, cx| { - store.search("compute embeddings for all of the symbols in the codebase and write them to a database".to_string(), 10, cx) - }); - - cx.spawn(|this, cx| async move { - let results = task.await?; - let duration = t0.elapsed(); - - println!("search took {:?}", duration); - println!("results {:?}", results); - - anyhow::Ok(()) - }).detach() + move |workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext| { + let vector_store = vector_store.clone(); + workspace.toggle_modal(cx, |workspace, cx| { + let project = workspace.project().clone(); + let workspace = cx.weak_handle(); + cx.add_view(|cx| { + SemanticSearch::new( + SemanticSearchDelegate::new(workspace, project, vector_store), + cx, + ) + }) + }) } }); + SemanticSearch::init(cx); + // cx.add_action({ + // let vector_store = vector_store.clone(); + // move |workspace: &mut Workspace, _: &TestSearch, cx: &mut ViewContext| { + // let t0 = std::time::Instant::now(); + // let task = vector_store.update(cx, |store, cx| { + // store.search("compute embeddings for all of the symbols in the codebase and write them to a database".to_string(), 10, cx) + // }); + + // cx.spawn(|this, cx| async move { + // let results = task.await?; + // let duration = t0.elapsed(); + + // println!("search took {:?}", duration); + // println!("results {:?}", results); + + // anyhow::Ok(()) + // }).detach() + // } + // }); } #[derive(Debug)] @@ -87,7 +103,7 @@ pub struct IndexedFile { documents: Vec, } -struct VectorStore { +pub struct VectorStore { fs: Arc, database_url: Arc, embedding_provider: Arc,