1pub enum ContextMenu {
2 Completions(CompletionsMenu),
3 CodeActions(CodeActionsMenu),
4}
5
6impl ContextMenu {
7 pub fn select_prev(&mut self, cx: &mut ViewContext<Editor>) -> bool {
8 if self.visible() {
9 match self {
10 ContextMenu::Completions(menu) => menu.select_prev(cx),
11 ContextMenu::CodeActions(menu) => menu.select_prev(cx),
12 }
13 true
14 } else {
15 false
16 }
17 }
18
19 pub fn select_next(&mut self, cx: &mut ViewContext<Editor>) -> bool {
20 if self.visible() {
21 match self {
22 ContextMenu::Completions(menu) => menu.select_next(cx),
23 ContextMenu::CodeActions(menu) => menu.select_next(cx),
24 }
25 true
26 } else {
27 false
28 }
29 }
30
31 pub fn visible(&self) -> bool {
32 match self {
33 ContextMenu::Completions(menu) => menu.visible(),
34 ContextMenu::CodeActions(menu) => menu.visible(),
35 }
36 }
37
38 pub fn render(
39 &self,
40 cursor_position: DisplayPoint,
41 style: EditorStyle,
42 cx: &AppContext,
43 ) -> (DisplayPoint, ElementBox) {
44 match self {
45 ContextMenu::Completions(menu) => (cursor_position, menu.render(style, cx)),
46 ContextMenu::CodeActions(menu) => menu.render(cursor_position, style),
47 }
48 }
49}
50
51struct CompletionsMenu {
52 id: CompletionId,
53 initial_position: Anchor,
54 buffer: ModelHandle<Buffer>,
55 completions: Arc<[Completion]>,
56 match_candidates: Vec<StringMatchCandidate>,
57 matches: Arc<[StringMatch]>,
58 selected_item: usize,
59 list: UniformListState,
60}
61
62impl CompletionsMenu {
63 fn select_prev(&mut self, cx: &mut ViewContext<Editor>) {
64 if self.selected_item > 0 {
65 self.selected_item -= 1;
66 self.list.scroll_to(ScrollTarget::Show(self.selected_item));
67 }
68 cx.notify();
69 }
70
71 fn select_next(&mut self, cx: &mut ViewContext<Editor>) {
72 if self.selected_item + 1 < self.matches.len() {
73 self.selected_item += 1;
74 self.list.scroll_to(ScrollTarget::Show(self.selected_item));
75 }
76 cx.notify();
77 }
78
79 fn visible(&self) -> bool {
80 !self.matches.is_empty()
81 }
82
83 fn render(&self, style: EditorStyle, _: &AppContext) -> ElementBox {
84 enum CompletionTag {}
85
86 let completions = self.completions.clone();
87 let matches = self.matches.clone();
88 let selected_item = self.selected_item;
89 let container_style = style.autocomplete.container;
90 UniformList::new(self.list.clone(), matches.len(), move |range, items, cx| {
91 let start_ix = range.start;
92 for (ix, mat) in matches[range].iter().enumerate() {
93 let completion = &completions[mat.candidate_id];
94 let item_ix = start_ix + ix;
95 items.push(
96 MouseEventHandler::new::<CompletionTag, _, _>(
97 mat.candidate_id,
98 cx,
99 |state, _| {
100 let item_style = if item_ix == selected_item {
101 style.autocomplete.selected_item
102 } else if state.hovered {
103 style.autocomplete.hovered_item
104 } else {
105 style.autocomplete.item
106 };
107
108 Text::new(completion.label.text.clone(), style.text.clone())
109 .with_soft_wrap(false)
110 .with_highlights(combine_syntax_and_fuzzy_match_highlights(
111 &completion.label.text,
112 style.text.color.into(),
113 styled_runs_for_code_label(&completion.label, &style.syntax),
114 &mat.positions,
115 ))
116 .contained()
117 .with_style(item_style)
118 .boxed()
119 },
120 )
121 .with_cursor_style(CursorStyle::PointingHand)
122 .on_mouse_down(move |cx| {
123 cx.dispatch_action(ConfirmCompletion(Some(item_ix)));
124 })
125 .boxed(),
126 );
127 }
128 })
129 .with_width_from_item(
130 self.matches
131 .iter()
132 .enumerate()
133 .max_by_key(|(_, mat)| {
134 self.completions[mat.candidate_id]
135 .label
136 .text
137 .chars()
138 .count()
139 })
140 .map(|(ix, _)| ix),
141 )
142 .contained()
143 .with_style(container_style)
144 .boxed()
145 }
146
147 pub async fn filter(&mut self, query: Option<&str>, executor: Arc<executor::Background>) {
148 let mut matches = if let Some(query) = query {
149 fuzzy::match_strings(
150 &self.match_candidates,
151 query,
152 false,
153 100,
154 &Default::default(),
155 executor,
156 )
157 .await
158 } else {
159 self.match_candidates
160 .iter()
161 .enumerate()
162 .map(|(candidate_id, candidate)| StringMatch {
163 candidate_id,
164 score: Default::default(),
165 positions: Default::default(),
166 string: candidate.string.clone(),
167 })
168 .collect()
169 };
170 matches.sort_unstable_by_key(|mat| {
171 (
172 Reverse(OrderedFloat(mat.score)),
173 self.completions[mat.candidate_id].sort_key(),
174 )
175 });
176
177 for mat in &mut matches {
178 let filter_start = self.completions[mat.candidate_id].label.filter_range.start;
179 for position in &mut mat.positions {
180 *position += filter_start;
181 }
182 }
183
184 self.matches = matches.into();
185 }
186}
187
188#[derive(Clone)]
189struct CodeActionsMenu {
190 actions: Arc<[CodeAction]>,
191 buffer: ModelHandle<Buffer>,
192 selected_item: usize,
193 list: UniformListState,
194 deployed_from_indicator: bool,
195}
196
197impl CodeActionsMenu {
198 fn select_prev(&mut self, cx: &mut ViewContext<Editor>) {
199 if self.selected_item > 0 {
200 self.selected_item -= 1;
201 cx.notify()
202 }
203 }
204
205 fn select_next(&mut self, cx: &mut ViewContext<Editor>) {
206 if self.selected_item + 1 < self.actions.len() {
207 self.selected_item += 1;
208 cx.notify()
209 }
210 }
211
212 fn visible(&self) -> bool {
213 !self.actions.is_empty()
214 }
215
216 fn render(
217 &self,
218 mut cursor_position: DisplayPoint,
219 style: EditorStyle,
220 ) -> (DisplayPoint, ElementBox) {
221 enum ActionTag {}
222
223 let container_style = style.autocomplete.container;
224 let actions = self.actions.clone();
225 let selected_item = self.selected_item;
226 let element =
227 UniformList::new(self.list.clone(), actions.len(), move |range, items, cx| {
228 let start_ix = range.start;
229 for (ix, action) in actions[range].iter().enumerate() {
230 let item_ix = start_ix + ix;
231 items.push(
232 MouseEventHandler::new::<ActionTag, _, _>(item_ix, cx, |state, _| {
233 let item_style = if item_ix == selected_item {
234 style.autocomplete.selected_item
235 } else if state.hovered {
236 style.autocomplete.hovered_item
237 } else {
238 style.autocomplete.item
239 };
240
241 Text::new(action.lsp_action.title.clone(), style.text.clone())
242 .with_soft_wrap(false)
243 .contained()
244 .with_style(item_style)
245 .boxed()
246 })
247 .with_cursor_style(CursorStyle::PointingHand)
248 .on_mouse_down(move |cx| {
249 cx.dispatch_action(ConfirmCodeAction(Some(item_ix)));
250 })
251 .boxed(),
252 );
253 }
254 })
255 .with_width_from_item(
256 self.actions
257 .iter()
258 .enumerate()
259 .max_by_key(|(_, action)| action.lsp_action.title.chars().count())
260 .map(|(ix, _)| ix),
261 )
262 .contained()
263 .with_style(container_style)
264 .boxed();
265
266 if self.deployed_from_indicator {
267 *cursor_position.column_mut() = 0;
268 }
269
270 (cursor_position, element)
271 }
272}