Skip to main content

gpui_base/input/editor/lsp/
overlay.rs

1use super::*;
2use crate::input::EditorMode;
3use std::ops::Range;
4
5use lsp_types::{CompletionItem, Hover};
6
7#[derive(Clone, Debug, Default)]
8pub struct CompletionMenuState {
9    pub open: bool,
10    pub trigger_start_offset: Option<usize>,
11    pub query: String,
12    pub items: Vec<CompletionItem>,
13    revision: u64,
14}
15
16impl CompletionMenuState {
17    /// Bumped whenever the content changes.
18    ///
19    /// A renderer that mirrors this menu compares revisions to decide whether
20    /// to rebuild, so it never has to compare the item list itself.
21    pub fn revision(&self) -> u64 {
22        self.revision
23    }
24
25    pub(super) fn bump(&mut self) {
26        self.revision = self.revision.wrapping_add(1);
27    }
28}
29
30#[derive(Clone, Debug, Default)]
31pub struct CodeActionMenuState {
32    pub open: bool,
33    pub items: Vec<CodeActionItem>,
34    revision: u64,
35}
36
37impl CodeActionMenuState {
38    /// Bumped whenever the content changes. See [`CompletionMenuState::revision`].
39    pub fn revision(&self) -> u64 {
40        self.revision
41    }
42
43    pub(super) fn bump(&mut self) {
44        self.revision = self.revision.wrapping_add(1);
45    }
46}
47
48#[derive(Clone, Debug)]
49pub struct HoverPopoverState {
50    pub symbol_range: Range<usize>,
51    pub hover: Hover,
52}
53
54#[derive(Clone, Debug, Default)]
55pub(crate) struct ContextMenuContent {
56    pub(crate) completion: CompletionMenuState,
57    pub(crate) code_action: CodeActionMenuState,
58}
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61pub enum InputOverlayKind {
62    Completion,
63    CodeAction,
64}
65
66impl InputBaseState<EditorMode> {
67    pub fn present_completion_items(
68        &mut self,
69        trigger_start_offset: usize,
70        query: impl Into<String>,
71        items: Vec<CompletionItem>,
72        cx: &mut Context<Self>,
73    ) {
74        self.extras
75            .context_menu_content
76            .completion
77            .trigger_start_offset = Some(trigger_start_offset);
78        self.extras.context_menu_content.completion.query = query.into();
79        self.extras.context_menu_content.completion.items = items;
80        self.extras.context_menu_content.completion.open =
81            !self.extras.context_menu_content.completion.items.is_empty();
82        self.extras.context_menu_content.completion.bump();
83        cx.notify();
84    }
85
86    pub fn present_code_actions(&mut self, items: Vec<CodeActionItem>, cx: &mut Context<Self>) {
87        self.extras.context_menu_content.code_action.items = items;
88        self.extras.context_menu_content.code_action.open = !self
89            .extras
90            .context_menu_content
91            .code_action
92            .items
93            .is_empty();
94        self.extras.context_menu_content.code_action.bump();
95        cx.notify();
96    }
97
98    pub fn present_hover(
99        &mut self,
100        symbol_range: Range<usize>,
101        hover: Hover,
102        cx: &mut Context<Self>,
103    ) {
104        self.extras.hover_popover = Some(HoverPopoverState {
105            symbol_range,
106            hover,
107        });
108        cx.notify();
109    }
110
111    pub fn present_diagnostic(
112        &mut self,
113        diagnostic: crate::input::DiagnosticEntry,
114        cx: &mut Context<Self>,
115    ) {
116        self.diagnostic_popover = Some(Rc::new(diagnostic));
117        cx.notify();
118    }
119
120    pub fn clear_diagnostic_popover(&mut self, cx: &mut Context<Self>) {
121        if self.diagnostic_popover.take().is_some() {
122            cx.notify();
123        }
124    }
125
126    pub fn route_overlay_action(
127        &mut self,
128        action: Box<dyn gpui::Action>,
129        window: &mut Window,
130        cx: &mut Context<Self>,
131    ) -> bool {
132        self.handle_action_for_context_menu(action, window, cx)
133    }
134
135    pub fn set_overlay_action_handler(
136        &mut self,
137        handler: impl Fn(
138            InputOverlayKind,
139            Box<dyn gpui::Action>,
140            &mut Window,
141            &mut Context<InputBaseState<EditorMode>>,
142        ) -> bool
143        + 'static,
144    ) {
145        self.overlay_action_handler = Some(Rc::new(handler));
146    }
147
148    pub fn has_overlay_action_handler(&self) -> bool {
149        self.overlay_action_handler.is_some()
150    }
151
152    pub fn dismiss_completion_overlay(&mut self, cx: &mut Context<Self>) {
153        if self.extras.context_menu_content.completion.open {
154            self.extras.context_menu_content.completion.open = false;
155            cx.notify();
156        }
157    }
158
159    pub fn dismiss_code_action_overlay(&mut self, cx: &mut Context<Self>) {
160        if self.extras.context_menu_content.code_action.open {
161            self.extras.context_menu_content.code_action.open = false;
162            cx.notify();
163        }
164    }
165
166    pub fn insert_completion(
167        &mut self,
168        item: &CompletionItem,
169        fallback_range: Range<usize>,
170        window: &mut Window,
171        cx: &mut Context<Self>,
172    ) {
173        let mut range = fallback_range;
174        let mut new_text = item.label.clone();
175        if let Some(edit) = item.text_edit.as_ref() {
176            match edit {
177                lsp_types::CompletionTextEdit::Edit(edit) => {
178                    new_text.clone_from(&edit.new_text);
179                    range = self.text.position_to_offset(&edit.range.start)
180                        ..self.text.position_to_offset(&edit.range.end);
181                }
182                lsp_types::CompletionTextEdit::InsertAndReplace(edit) => {
183                    new_text.clone_from(&edit.new_text);
184                    range = self.text.position_to_offset(&edit.replace.start)
185                        ..self.text.position_to_offset(&edit.replace.end);
186                }
187            }
188        } else if let Some(insert_text) = item.insert_text.as_ref() {
189            new_text.clone_from(insert_text);
190            range = range.end..range.end;
191        }
192        self.completion_inserting = true;
193        let range = self.range_to_utf16(&range);
194        self.replace_text_in_range_silent(Some(range), &new_text, window, cx);
195        self.completion_inserting = false;
196        self.focus(window, cx);
197    }
198
199    #[doc(hidden)]
200    pub fn completion_menu_state(&self) -> &CompletionMenuState {
201        &self.extras.context_menu_content.completion
202    }
203
204    #[doc(hidden)]
205    pub fn code_action_menu_state(&self) -> &CodeActionMenuState {
206        &self.extras.context_menu_content.code_action
207    }
208
209    pub fn hover_popover(&self) -> Option<&HoverPopoverState> {
210        self.extras.hover_popover.as_ref()
211    }
212
213    pub fn dismiss_lsp_overlays(&mut self, cx: &mut Context<Self>) {
214        self.hide_context_menu(cx);
215        self.clear_hover_state(cx);
216    }
217}