1use editor::Editor;
2use gpui::{
3 elements::{
4 ChildView, Flex, FlexItem, Label, ParentElement, ScrollTarget, UniformList,
5 UniformListState,
6 },
7 keymap, AppContext, Axis, Element, ElementBox, Entity, MutableAppContext, RenderContext, Task,
8 View, ViewContext, ViewHandle, WeakViewHandle,
9};
10use settings::Settings;
11use std::cmp;
12use workspace::menu::{Cancel, Confirm, SelectFirst, SelectLast, SelectNext, SelectPrev};
13
14pub fn init<D: SelectorModalDelegate>(cx: &mut MutableAppContext) {
15 cx.add_action(SelectorModal::<D>::select_first);
16 cx.add_action(SelectorModal::<D>::select_last);
17 cx.add_action(SelectorModal::<D>::select_next);
18 cx.add_action(SelectorModal::<D>::select_prev);
19 cx.add_action(SelectorModal::<D>::confirm);
20 cx.add_action(SelectorModal::<D>::cancel);
21}
22
23pub struct SelectorModal<D: SelectorModalDelegate> {
24 delegate: WeakViewHandle<D>,
25 query_editor: ViewHandle<Editor>,
26 list_state: UniformListState,
27}
28
29pub trait SelectorModalDelegate: View {
30 fn match_count(&self) -> usize;
31 fn selected_index(&self) -> usize;
32 fn set_selected_index(&mut self, ix: usize);
33 fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()>;
34 fn confirm(&mut self, cx: &mut ViewContext<Self>);
35 fn dismiss(&mut self, cx: &mut ViewContext<Self>);
36 fn render_match(&self, ix: usize, selected: bool, cx: &AppContext) -> ElementBox;
37}
38
39impl<D: SelectorModalDelegate> Entity for SelectorModal<D> {
40 type Event = ();
41}
42
43impl<D: SelectorModalDelegate> View for SelectorModal<D> {
44 fn ui_name() -> &'static str {
45 "SelectorModal"
46 }
47
48 fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
49 let settings = cx.global::<Settings>();
50 Flex::new(Axis::Vertical)
51 .with_child(
52 ChildView::new(&self.query_editor)
53 .contained()
54 .with_style(settings.theme.selector.input_editor.container)
55 .boxed(),
56 )
57 .with_child(
58 FlexItem::new(self.render_matches(cx))
59 .flex(1., false)
60 .boxed(),
61 )
62 .contained()
63 .with_style(settings.theme.selector.container)
64 .constrained()
65 .with_max_width(500.0)
66 .with_max_height(420.0)
67 .aligned()
68 .top()
69 .named("selector")
70 }
71
72 fn keymap_context(&self, _: &AppContext) -> keymap::Context {
73 let mut cx = Self::default_keymap_context();
74 cx.set.insert("menu".into());
75 cx
76 }
77
78 fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
79 cx.focus(&self.query_editor);
80 }
81}
82
83impl<D: SelectorModalDelegate> SelectorModal<D> {
84 pub fn new(delegate: WeakViewHandle<D>, cx: &mut ViewContext<Self>) -> Self {
85 let query_editor = cx.add_view(|cx| {
86 Editor::single_line(Some(|theme| theme.selector.input_editor.clone()), cx)
87 });
88 cx.subscribe(&query_editor, Self::on_query_editor_event)
89 .detach();
90
91 Self {
92 delegate,
93 query_editor,
94 list_state: Default::default(),
95 }
96 }
97
98 fn render_matches(&self, cx: &AppContext) -> ElementBox {
99 let delegate = self.delegate.clone();
100 let match_count = if let Some(delegate) = delegate.upgrade(cx) {
101 delegate.read(cx).match_count()
102 } else {
103 0
104 };
105
106 if match_count == 0 {
107 let settings = cx.global::<Settings>();
108 return Label::new(
109 "No matches".into(),
110 settings.theme.selector.empty.label.clone(),
111 )
112 .contained()
113 .with_style(settings.theme.selector.empty.container)
114 .named("empty matches");
115 }
116
117 UniformList::new(
118 self.list_state.clone(),
119 match_count,
120 move |mut range, items, cx| {
121 let cx = cx.as_ref();
122 let delegate = delegate.upgrade(cx).unwrap();
123 let delegate = delegate.read(cx);
124 let selected_ix = delegate.selected_index();
125 range.end = cmp::min(range.end, delegate.match_count());
126 items.extend(range.map(move |ix| delegate.render_match(ix, ix == selected_ix, cx)));
127 },
128 )
129 .contained()
130 .with_margin_top(6.0)
131 .named("matches")
132 }
133
134 fn on_query_editor_event(
135 &mut self,
136 _: ViewHandle<Editor>,
137 event: &editor::Event,
138 cx: &mut ViewContext<Self>,
139 ) {
140 if let Some(delegate) = self.delegate.upgrade(cx) {
141 match event {
142 editor::Event::BufferEdited { .. } => {
143 let query = self.query_editor.read(cx).text(cx);
144 let update = delegate.update(cx, |d, cx| d.update_matches(query, cx));
145 cx.spawn(|this, mut cx| async move {
146 update.await;
147 this.update(&mut cx, |_, cx| cx.notify());
148 })
149 .detach();
150 }
151 editor::Event::Blurred => delegate.update(cx, |delegate, cx| {
152 delegate.dismiss(cx);
153 }),
154 _ => {}
155 }
156 }
157 }
158
159 fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
160 if let Some(delegate) = self.delegate.upgrade(cx) {
161 let index = 0;
162 delegate.update(cx, |delegate, _| delegate.set_selected_index(0));
163 self.list_state.scroll_to(ScrollTarget::Show(index));
164 cx.notify();
165 }
166 }
167
168 fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
169 if let Some(delegate) = self.delegate.upgrade(cx) {
170 let index = delegate.update(cx, |delegate, _| {
171 let match_count = delegate.match_count();
172 let index = if match_count > 0 { match_count - 1 } else { 0 };
173 delegate.set_selected_index(index);
174 index
175 });
176 self.list_state.scroll_to(ScrollTarget::Show(index));
177 cx.notify();
178 }
179 }
180
181 fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
182 if let Some(delegate) = self.delegate.upgrade(cx) {
183 let index = delegate.update(cx, |delegate, _| {
184 let mut selected_index = delegate.selected_index();
185 if selected_index + 1 < delegate.match_count() {
186 selected_index += 1;
187 delegate.set_selected_index(selected_index);
188 }
189 selected_index
190 });
191 self.list_state.scroll_to(ScrollTarget::Show(index));
192 cx.notify();
193 }
194 }
195
196 fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
197 if let Some(delegate) = self.delegate.upgrade(cx) {
198 let index = delegate.update(cx, |delegate, _| {
199 let mut selected_index = delegate.selected_index();
200 if selected_index > 0 {
201 selected_index -= 1;
202 delegate.set_selected_index(selected_index);
203 }
204 selected_index
205 });
206 self.list_state.scroll_to(ScrollTarget::Show(index));
207 cx.notify();
208 }
209 }
210
211 fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
212 if let Some(delegate) = self.delegate.upgrade(cx) {
213 delegate.update(cx, |delegate, cx| delegate.confirm(cx));
214 }
215 }
216
217 fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
218 if let Some(delegate) = self.delegate.upgrade(cx) {
219 delegate.update(cx, |delegate, cx| delegate.dismiss(cx));
220 }
221 }
222}