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}