context_menu.rs

  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}