modal.rs

  1use std::sync::Arc;
  2
  3use gpui::{
  4    actions, elements::*, AnyElement, AppContext, ModelHandle, MouseState, Task, ViewContext,
  5    WeakViewHandle,
  6};
  7use picker::{Picker, PickerDelegate, PickerEvent};
  8use project::Project;
  9use util::ResultExt;
 10use workspace::Workspace;
 11
 12use crate::{SearchResult, VectorStore};
 13
 14actions!(semantic_search, [Toggle]);
 15
 16pub type SemanticSearch = Picker<SemanticSearchDelegate>;
 17
 18pub struct SemanticSearchDelegate {
 19    workspace: WeakViewHandle<Workspace>,
 20    project: ModelHandle<Project>,
 21    vector_store: ModelHandle<VectorStore>,
 22    selected_match_index: usize,
 23    matches: Vec<SearchResult>,
 24}
 25
 26impl SemanticSearchDelegate {
 27    // This is currently searching on every keystroke,
 28    // This is wildly overkill, and has the potential to get expensive
 29    // We will need to update this to throttle searching
 30    pub fn new(
 31        workspace: WeakViewHandle<Workspace>,
 32        project: ModelHandle<Project>,
 33        vector_store: ModelHandle<VectorStore>,
 34    ) -> Self {
 35        Self {
 36            workspace,
 37            project,
 38            vector_store,
 39            selected_match_index: 0,
 40            matches: vec![],
 41        }
 42    }
 43}
 44
 45impl PickerDelegate for SemanticSearchDelegate {
 46    fn placeholder_text(&self) -> Arc<str> {
 47        "Search repository in natural language...".into()
 48    }
 49
 50    fn confirm(&mut self, cx: &mut ViewContext<SemanticSearch>) {
 51        todo!()
 52    }
 53
 54    fn dismissed(&mut self, _cx: &mut ViewContext<SemanticSearch>) {}
 55
 56    fn match_count(&self) -> usize {
 57        self.matches.len()
 58    }
 59
 60    fn selected_index(&self) -> usize {
 61        self.selected_match_index
 62    }
 63
 64    fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<SemanticSearch>) {
 65        self.selected_match_index = ix;
 66    }
 67
 68    fn update_matches(&mut self, query: String, cx: &mut ViewContext<SemanticSearch>) -> Task<()> {
 69        let task = self
 70            .vector_store
 71            .update(cx, |store, cx| store.search(query.to_string(), 10, cx));
 72
 73        cx.spawn(|this, mut cx| async move {
 74            let results = task.await.log_err();
 75            this.update(&mut cx, |this, cx| {
 76                if let Some(results) = results {
 77                    let delegate = this.delegate_mut();
 78                    delegate.matches = results;
 79                }
 80            });
 81        })
 82    }
 83
 84    fn render_match(
 85        &self,
 86        ix: usize,
 87        mouse_state: &mut MouseState,
 88        selected: bool,
 89        cx: &AppContext,
 90    ) -> AnyElement<Picker<Self>> {
 91        let theme = theme::current(cx);
 92        let style = &theme.picker.item;
 93        let current_style = style.style_for(mouse_state, selected);
 94
 95        let search_result = &self.matches[ix];
 96
 97        let mut path = search_result.file_path.to_string_lossy();
 98        let name = search_result.name.clone();
 99
100        Flex::column()
101            .with_child(Text::new(name, current_style.label.text.clone()).with_soft_wrap(false))
102            .with_child(Label::new(path.to_string(), style.default.label.clone()))
103            .contained()
104            .with_style(current_style.container)
105            .into_any()
106    }
107}