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}