gpui_base/input/editor/lsp/
definitions.rs1use anyhow::Result;
2use gpui::{
3 App, Context, HighlightStyle, Hitbox, MouseDownEvent, Task, UnderlineStyle, Window, px,
4};
5use ropey::Rope;
6use std::{ops::Range, rc::Rc};
7
8use crate::input::{EditorMode, GoToDefinition, InputBaseState, RopeExt};
9
10pub trait DefinitionProvider {
14 fn definitions(
18 &self,
19 _text: &Rope,
20 _offset: usize,
21 _window: &mut Window,
22 _cx: &mut App,
23 ) -> Task<Result<Vec<lsp_types::LocationLink>>>;
24}
25
26#[derive(Clone, Default)]
27pub(crate) struct HoverDefinition {
28 symbol_range: Range<usize>,
30 pub(crate) locations: Rc<Vec<lsp_types::LocationLink>>,
31}
32
33impl HoverDefinition {
34 pub(crate) fn update(
35 &mut self,
36 symbol_range: Range<usize>,
37 locations: Vec<lsp_types::LocationLink>,
38 ) {
39 self.clear();
40 self.symbol_range = symbol_range;
41 self.locations = Rc::new(locations);
42 }
43
44 pub(crate) fn is_empty(&self) -> bool {
45 self.locations.is_empty()
46 }
47
48 pub(crate) fn clear(&mut self) {
49 self.symbol_range = 0..0;
50 self.locations = Rc::new(vec![]);
51 }
52
53 pub(crate) fn is_same(&self, offset: usize) -> bool {
54 self.symbol_range.contains(&offset)
55 }
56}
57
58impl InputBaseState<EditorMode> {
59 pub(crate) fn handle_hover_definition(
60 &mut self,
61 offset: usize,
62 window: &mut Window,
63 cx: &mut Context<Self>,
64 ) {
65 let Some(provider) = self.extras.lsp.definition_provider.clone() else {
66 return;
67 };
68
69 if self.extras.hover_definition.is_same(offset) {
70 return;
71 }
72
73 let task = provider.definitions(&self.text, offset, window, cx);
75 let mut symbol_range = self.text.word_range(offset).unwrap_or(offset..offset);
76 let editor = cx.entity();
77 self.extras.lsp._hover_task = cx.spawn_in(window, async move |_, cx| {
78 let locations = task.await?;
79
80 _ = editor.update(cx, |editor, cx| {
81 if locations.is_empty() {
82 editor.extras.hover_definition.clear();
83 } else {
84 if let Some(location) = locations.first() {
85 if let Some(range) = location.origin_selection_range {
86 let start = editor.text.position_to_offset(&range.start);
87 let end = editor.text.position_to_offset(&range.end);
88 symbol_range = start..end;
89 }
90 }
91
92 editor
93 .extras
94 .hover_definition
95 .update(symbol_range.clone(), locations.clone());
96 }
97 cx.notify();
98 });
99
100 Ok(())
101 });
102 }
103
104 pub(crate) fn on_action_go_to_definition(
105 &mut self,
106 _: &GoToDefinition,
107 window: &mut Window,
108 cx: &mut Context<Self>,
109 ) {
110 let offset = self.cursor();
111 let Some(provider) = self.extras.lsp.definition_provider.clone() else {
113 return;
114 };
115 let text = self.text.clone();
116 let response = provider.definitions(&text, offset, window, cx);
117 let blur_subscription = cx.on_blur(&self.focus_handle, window, |editor, _, _| {
118 editor.extras.lsp._definition_task = Task::ready(Ok(()));
119 });
120 self.extras.lsp._definition_task = cx.spawn_in(window, async move |editor, cx| {
121 let _blur_subscription = blur_subscription;
122 let locations = response.await?;
123 editor.update_in(cx, |editor, window, cx| {
124 if editor.cursor() != offset
125 || editor.text != text
126 || !editor.focus_handle.is_focused(window)
127 {
128 return;
129 }
130 if let Some(location) = locations.first() {
131 editor.go_to_definition(location, window, cx);
132 }
133 })?;
134 Ok(())
135 });
136 }
137
138 pub(crate) fn handle_click_hover_definition(
140 &mut self,
141 event: &MouseDownEvent,
142 offset: usize,
143 window: &mut Window,
144 cx: &mut Context<InputBaseState<EditorMode>>,
145 ) -> bool {
146 if !event.modifiers.secondary() || event.modifiers.alt {
147 return false;
148 }
149
150 if self.extras.hover_definition.is_empty() {
151 return false;
152 };
153 if !self.extras.hover_definition.is_same(offset) {
154 return false;
155 }
156
157 let Some(location) = self.extras.hover_definition.locations.first().cloned() else {
158 return false;
159 };
160
161 self.go_to_definition(&location, window, cx);
162
163 true
164 }
165
166 pub(crate) fn go_to_definition(
167 &mut self,
168 location: &lsp_types::LocationLink,
169 window: &mut Window,
170 cx: &mut Context<Self>,
171 ) {
172 let external = location
173 .target_uri
174 .scheme()
175 .map(|s| s.as_str() == "https" || s.as_str() == "http")
176 == Some(true);
177
178 if let Some(handler) = self.extras.lsp.show_document.clone() {
181 let params = lsp_types::ShowDocumentParams {
182 uri: location.target_uri.clone(),
183 external: Some(external),
184 take_focus: Some(true),
185 selection: Some(location.target_selection_range),
186 };
187 if handler(¶ms, window, cx) {
188 return;
189 }
190 }
191
192 if external {
193 cx.open_url(&location.target_uri.to_string());
194 } else {
195 let target_range = location.target_selection_range;
197 let start = self.text.position_to_offset(&target_range.start);
198 let end = self.text.position_to_offset(&target_range.end);
199
200 self.move_to(start, None, cx);
201 self.select_to(end, cx);
202 }
203 }
204}
205
206impl InputBaseState<EditorMode> {
209 pub(crate) fn hover_definition_style(&self) -> Option<(Range<usize>, HighlightStyle)> {
211 let editor = self;
212 if editor.extras.hover_definition.is_empty() {
213 return None;
214 };
215
216 let mut highlight_style = editor.editor_style.highlight_styles.style("link_text")?;
217
218 highlight_style.underline = Some(UnderlineStyle {
219 thickness: px(1.),
220 ..UnderlineStyle::default()
221 });
222
223 Some((
224 editor.extras.hover_definition.symbol_range.clone(),
225 highlight_style,
226 ))
227 }
228
229 pub(crate) fn hover_definition_hitbox(&self, window: &mut Window) -> Option<Hitbox> {
231 let editor = self;
232 if editor.extras.hover_definition.is_empty() {
233 return None;
234 };
235
236 let Some(bounds) = editor.range_to_bounds(&editor.extras.hover_definition.symbol_range)
237 else {
238 return None;
239 };
240
241 Some(window.insert_hitbox(bounds, gpui::HitboxBehavior::Normal))
242 }
243}