gpui_base/input/editor/lsp/
overlay.rs1use 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 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 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}