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        if let Some(search_result) = self.matches.get(self.selected_match_index) {
 52            // search_result.file_path
 53        }
 54    }
 55
 56    fn dismissed(&mut self, _cx: &mut ViewContext<SemanticSearch>) {}
 57
 58    fn match_count(&self) -> usize {
 59        self.matches.len()
 60    }
 61
 62    fn selected_index(&self) -> usize {
 63        self.selected_match_index
 64    }
 65
 66    fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<SemanticSearch>) {
 67        self.selected_match_index = ix;
 68    }
 69
 70    fn update_matches(&mut self, query: String, cx: &mut ViewContext<SemanticSearch>) -> Task<()> {
 71        let task = self.vector_store.update(cx, |store, cx| {
 72            store.search(&self.project, query.to_string(), 10, cx)
 73        });
 74
 75        cx.spawn(|this, mut cx| async move {
 76            let results = task.await.log_err();
 77            this.update(&mut cx, |this, cx| {
 78                if let Some(results) = results {
 79                    let delegate = this.delegate_mut();
 80                    delegate.matches = results;
 81                }
 82            });
 83        })
 84    }
 85
 86    fn render_match(
 87        &self,
 88        ix: usize,
 89        mouse_state: &mut MouseState,
 90        selected: bool,
 91        cx: &AppContext,
 92    ) -> AnyElement<Picker<Self>> {
 93        let theme = theme::current(cx);
 94        let style = &theme.picker.item;
 95        let current_style = style.in_state(selected).style_for(mouse_state);
 96
 97        let search_result = &self.matches[ix];
 98
 99        let mut path = search_result.file_path.to_string_lossy();
100        let name = search_result.name.clone();
101
102        Flex::column()
103            .with_child(Text::new(name, current_style.label.text.clone()).with_soft_wrap(false))
104            .with_child(Label::new(
105                path.to_string(),
106                style.inactive_state().default.label.clone(),
107            ))
108            .contained()
109            .with_style(current_style.container)
110            .into_any()
111    }
112}