1use crate::{
2 display_map::DisplaySnapshot,
3 element::PointForPosition,
4 hover_popover::{self, InlayHover},
5 Anchor, DisplayPoint, Editor, EditorSnapshot, InlayId, SelectPhase,
6};
7use gpui::{Task, ViewContext};
8use language::{Bias, ToOffset};
9use lsp::LanguageServerId;
10use project::{
11 HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink,
12 ResolveState,
13};
14use std::ops::Range;
15use util::TryFutureExt;
16
17#[derive(Debug, Default)]
18pub struct LinkGoToDefinitionState {
19 pub last_trigger_point: Option<TriggerPoint>,
20 pub symbol_range: Option<RangeInEditor>,
21 pub kind: Option<LinkDefinitionKind>,
22 pub definitions: Vec<GoToDefinitionLink>,
23 pub task: Option<Task<Option<()>>>,
24}
25
26#[derive(Debug, Eq, PartialEq, Clone)]
27pub enum RangeInEditor {
28 Text(Range<Anchor>),
29 Inlay(InlayHighlight),
30}
31
32impl RangeInEditor {
33 pub fn as_text_range(&self) -> Option<Range<Anchor>> {
34 match self {
35 Self::Text(range) => Some(range.clone()),
36 Self::Inlay(_) => None,
37 }
38 }
39
40 fn point_within_range(&self, trigger_point: &TriggerPoint, snapshot: &EditorSnapshot) -> bool {
41 match (self, trigger_point) {
42 (Self::Text(range), TriggerPoint::Text(point)) => {
43 let point_after_start = range.start.cmp(point, &snapshot.buffer_snapshot).is_le();
44 point_after_start && range.end.cmp(point, &snapshot.buffer_snapshot).is_ge()
45 }
46 (Self::Inlay(highlight), TriggerPoint::InlayHint(point, _, _)) => {
47 highlight.inlay == point.inlay
48 && highlight.range.contains(&point.range.start)
49 && highlight.range.contains(&point.range.end)
50 }
51 (Self::Inlay(_), TriggerPoint::Text(_))
52 | (Self::Text(_), TriggerPoint::InlayHint(_, _, _)) => false,
53 }
54 }
55}
56
57#[derive(Debug)]
58pub enum GoToDefinitionTrigger {
59 Text(DisplayPoint),
60 InlayHint(InlayHighlight, lsp::Location, LanguageServerId),
61}
62
63#[derive(Debug, Clone)]
64pub enum GoToDefinitionLink {
65 Text(LocationLink),
66 InlayHint(lsp::Location, LanguageServerId),
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct InlayHighlight {
71 pub inlay: InlayId,
72 pub inlay_position: Anchor,
73 pub range: Range<usize>,
74}
75
76#[derive(Debug, Clone)]
77pub enum TriggerPoint {
78 Text(Anchor),
79 InlayHint(InlayHighlight, lsp::Location, LanguageServerId),
80}
81
82impl TriggerPoint {
83 pub fn definition_kind(&self, shift: bool) -> LinkDefinitionKind {
84 match self {
85 TriggerPoint::Text(_) => {
86 if shift {
87 LinkDefinitionKind::Type
88 } else {
89 LinkDefinitionKind::Symbol
90 }
91 }
92 TriggerPoint::InlayHint(_, _, _) => LinkDefinitionKind::Type,
93 }
94 }
95
96 fn anchor(&self) -> &Anchor {
97 match self {
98 TriggerPoint::Text(anchor) => anchor,
99 TriggerPoint::InlayHint(inlay_range, _, _) => &inlay_range.inlay_position,
100 }
101 }
102}
103
104pub fn update_go_to_definition_link(
105 editor: &mut Editor,
106 origin: Option<GoToDefinitionTrigger>,
107 cmd_held: bool,
108 shift_held: bool,
109 cx: &mut ViewContext<Editor>,
110) {
111 let pending_nonempty_selection = editor.has_pending_nonempty_selection();
112
113 // Store new mouse point as an anchor
114 let snapshot = editor.snapshot(cx);
115 let trigger_point = match origin {
116 Some(GoToDefinitionTrigger::Text(p)) => {
117 Some(TriggerPoint::Text(snapshot.buffer_snapshot.anchor_before(
118 p.to_offset(&snapshot.display_snapshot, Bias::Left),
119 )))
120 }
121 Some(GoToDefinitionTrigger::InlayHint(p, lsp_location, language_server_id)) => {
122 Some(TriggerPoint::InlayHint(p, lsp_location, language_server_id))
123 }
124 None => None,
125 };
126
127 // If the new point is the same as the previously stored one, return early
128 if let (Some(a), Some(b)) = (
129 &trigger_point,
130 &editor.link_go_to_definition_state.last_trigger_point,
131 ) {
132 match (a, b) {
133 (TriggerPoint::Text(anchor_a), TriggerPoint::Text(anchor_b)) => {
134 if anchor_a.cmp(anchor_b, &snapshot.buffer_snapshot).is_eq() {
135 return;
136 }
137 }
138 (TriggerPoint::InlayHint(range_a, _, _), TriggerPoint::InlayHint(range_b, _, _)) => {
139 if range_a == range_b {
140 return;
141 }
142 }
143 _ => {}
144 }
145 }
146
147 editor.link_go_to_definition_state.last_trigger_point = trigger_point.clone();
148
149 if pending_nonempty_selection {
150 hide_link_definition(editor, cx);
151 return;
152 }
153
154 if cmd_held {
155 if let Some(trigger_point) = trigger_point {
156 let kind = trigger_point.definition_kind(shift_held);
157 show_link_definition(kind, editor, trigger_point, snapshot, cx);
158 return;
159 }
160 }
161
162 hide_link_definition(editor, cx);
163}
164
165pub fn update_inlay_link_and_hover_points(
166 snapshot: &DisplaySnapshot,
167 point_for_position: PointForPosition,
168 editor: &mut Editor,
169 cmd_held: bool,
170 shift_held: bool,
171 cx: &mut ViewContext<'_, '_, Editor>,
172) {
173 let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 {
174 Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left))
175 } else {
176 None
177 };
178 let mut go_to_definition_updated = false;
179 let mut hover_updated = false;
180 if let Some(hovered_offset) = hovered_offset {
181 let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
182 let previous_valid_anchor = buffer_snapshot.anchor_at(
183 point_for_position.previous_valid.to_point(snapshot),
184 Bias::Left,
185 );
186 let next_valid_anchor = buffer_snapshot.anchor_at(
187 point_for_position.next_valid.to_point(snapshot),
188 Bias::Right,
189 );
190 if let Some(hovered_hint) = editor
191 .visible_inlay_hints(cx)
192 .into_iter()
193 .skip_while(|hint| {
194 hint.position
195 .cmp(&previous_valid_anchor, &buffer_snapshot)
196 .is_lt()
197 })
198 .take_while(|hint| {
199 hint.position
200 .cmp(&next_valid_anchor, &buffer_snapshot)
201 .is_le()
202 })
203 .max_by_key(|hint| hint.id)
204 {
205 let inlay_hint_cache = editor.inlay_hint_cache();
206 let excerpt_id = previous_valid_anchor.excerpt_id;
207 if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id) {
208 match cached_hint.resolve_state {
209 ResolveState::CanResolve(_, _) => {
210 if let Some(buffer_id) = previous_valid_anchor.buffer_id {
211 inlay_hint_cache.spawn_hint_resolve(
212 buffer_id,
213 excerpt_id,
214 hovered_hint.id,
215 cx,
216 );
217 }
218 }
219 ResolveState::Resolved => {
220 let mut extra_shift_left = 0;
221 let mut extra_shift_right = 0;
222 if cached_hint.padding_left {
223 extra_shift_left += 1;
224 extra_shift_right += 1;
225 }
226 if cached_hint.padding_right {
227 extra_shift_right += 1;
228 }
229 match cached_hint.label {
230 project::InlayHintLabel::String(_) => {
231 if let Some(tooltip) = cached_hint.tooltip {
232 hover_popover::hover_at_inlay(
233 editor,
234 InlayHover {
235 excerpt: excerpt_id,
236 tooltip: match tooltip {
237 InlayHintTooltip::String(text) => HoverBlock {
238 text,
239 kind: HoverBlockKind::PlainText,
240 },
241 InlayHintTooltip::MarkupContent(content) => {
242 HoverBlock {
243 text: content.value,
244 kind: content.kind,
245 }
246 }
247 },
248 range: InlayHighlight {
249 inlay: hovered_hint.id,
250 inlay_position: hovered_hint.position,
251 range: extra_shift_left
252 ..hovered_hint.text.len() + extra_shift_right,
253 },
254 },
255 cx,
256 );
257 hover_updated = true;
258 }
259 }
260 project::InlayHintLabel::LabelParts(label_parts) => {
261 let hint_start =
262 snapshot.anchor_to_inlay_offset(hovered_hint.position);
263 if let Some((hovered_hint_part, part_range)) =
264 hover_popover::find_hovered_hint_part(
265 label_parts,
266 hint_start,
267 hovered_offset,
268 )
269 {
270 let highlight_start =
271 (part_range.start - hint_start).0 + extra_shift_left;
272 let highlight_end =
273 (part_range.end - hint_start).0 + extra_shift_right;
274 let highlight = InlayHighlight {
275 inlay: hovered_hint.id,
276 inlay_position: hovered_hint.position,
277 range: highlight_start..highlight_end,
278 };
279 if let Some(tooltip) = hovered_hint_part.tooltip {
280 hover_popover::hover_at_inlay(
281 editor,
282 InlayHover {
283 excerpt: excerpt_id,
284 tooltip: match tooltip {
285 InlayHintLabelPartTooltip::String(text) => {
286 HoverBlock {
287 text,
288 kind: HoverBlockKind::PlainText,
289 }
290 }
291 InlayHintLabelPartTooltip::MarkupContent(
292 content,
293 ) => HoverBlock {
294 text: content.value,
295 kind: content.kind,
296 },
297 },
298 range: highlight.clone(),
299 },
300 cx,
301 );
302 hover_updated = true;
303 }
304 if let Some((language_server_id, location)) =
305 hovered_hint_part.location
306 {
307 go_to_definition_updated = true;
308 update_go_to_definition_link(
309 editor,
310 Some(GoToDefinitionTrigger::InlayHint(
311 highlight,
312 location,
313 language_server_id,
314 )),
315 cmd_held,
316 shift_held,
317 cx,
318 );
319 }
320 }
321 }
322 };
323 }
324 ResolveState::Resolving => {}
325 }
326 }
327 }
328 }
329
330 if !go_to_definition_updated {
331 update_go_to_definition_link(editor, None, cmd_held, shift_held, cx);
332 }
333 if !hover_updated {
334 hover_popover::hover_at(editor, None, cx);
335 }
336}
337
338#[derive(Debug, Clone, Copy, PartialEq)]
339pub enum LinkDefinitionKind {
340 Symbol,
341 Type,
342}
343
344pub fn show_link_definition(
345 definition_kind: LinkDefinitionKind,
346 editor: &mut Editor,
347 trigger_point: TriggerPoint,
348 snapshot: EditorSnapshot,
349 cx: &mut ViewContext<Editor>,
350) {
351 let same_kind = editor.link_go_to_definition_state.kind == Some(definition_kind);
352 if !same_kind {
353 hide_link_definition(editor, cx);
354 }
355
356 if editor.pending_rename.is_some() {
357 return;
358 }
359
360 let trigger_anchor = trigger_point.anchor();
361 let (buffer, buffer_position) = if let Some(output) = editor
362 .buffer
363 .read(cx)
364 .text_anchor_for_position(trigger_anchor.clone(), cx)
365 {
366 output
367 } else {
368 return;
369 };
370
371 let excerpt_id = if let Some((excerpt_id, _, _)) = editor
372 .buffer()
373 .read(cx)
374 .excerpt_containing(trigger_anchor.clone(), cx)
375 {
376 excerpt_id
377 } else {
378 return;
379 };
380
381 let project = if let Some(project) = editor.project.clone() {
382 project
383 } else {
384 return;
385 };
386
387 // Don't request again if the location is within the symbol region of a previous request with the same kind
388 if let Some(symbol_range) = &editor.link_go_to_definition_state.symbol_range {
389 if same_kind && symbol_range.point_within_range(&trigger_point, &snapshot) {
390 return;
391 }
392 }
393
394 let task = cx.spawn(|this, mut cx| {
395 async move {
396 let result = match &trigger_point {
397 TriggerPoint::Text(_) => {
398 // query the LSP for definition info
399 cx.update(|cx| {
400 project.update(cx, |project, cx| match definition_kind {
401 LinkDefinitionKind::Symbol => {
402 project.definition(&buffer, buffer_position, cx)
403 }
404
405 LinkDefinitionKind::Type => {
406 project.type_definition(&buffer, buffer_position, cx)
407 }
408 })
409 })
410 .await
411 .ok()
412 .map(|definition_result| {
413 (
414 definition_result.iter().find_map(|link| {
415 link.origin.as_ref().map(|origin| {
416 let start = snapshot
417 .buffer_snapshot
418 .anchor_in_excerpt(excerpt_id.clone(), origin.range.start);
419 let end = snapshot
420 .buffer_snapshot
421 .anchor_in_excerpt(excerpt_id.clone(), origin.range.end);
422 RangeInEditor::Text(start..end)
423 })
424 }),
425 definition_result
426 .into_iter()
427 .map(GoToDefinitionLink::Text)
428 .collect(),
429 )
430 })
431 }
432 TriggerPoint::InlayHint(highlight, lsp_location, server_id) => Some((
433 Some(RangeInEditor::Inlay(highlight.clone())),
434 vec![GoToDefinitionLink::InlayHint(
435 lsp_location.clone(),
436 *server_id,
437 )],
438 )),
439 };
440
441 this.update(&mut cx, |this, cx| {
442 // Clear any existing highlights
443 this.clear_highlights::<LinkGoToDefinitionState>(cx);
444 this.link_go_to_definition_state.kind = Some(definition_kind);
445 this.link_go_to_definition_state.symbol_range = result
446 .as_ref()
447 .and_then(|(symbol_range, _)| symbol_range.clone());
448
449 if let Some((symbol_range, definitions)) = result {
450 this.link_go_to_definition_state.definitions = definitions.clone();
451
452 let buffer_snapshot = buffer.read(cx).snapshot();
453
454 // Only show highlight if there exists a definition to jump to that doesn't contain
455 // the current location.
456 let any_definition_does_not_contain_current_location =
457 definitions.iter().any(|definition| {
458 match &definition {
459 GoToDefinitionLink::Text(link) => {
460 if link.target.buffer == buffer {
461 let range = &link.target.range;
462 // Expand range by one character as lsp definition ranges include positions adjacent
463 // but not contained by the symbol range
464 let start = buffer_snapshot.clip_offset(
465 range
466 .start
467 .to_offset(&buffer_snapshot)
468 .saturating_sub(1),
469 Bias::Left,
470 );
471 let end = buffer_snapshot.clip_offset(
472 range.end.to_offset(&buffer_snapshot) + 1,
473 Bias::Right,
474 );
475 let offset = buffer_position.to_offset(&buffer_snapshot);
476 !(start <= offset && end >= offset)
477 } else {
478 true
479 }
480 }
481 GoToDefinitionLink::InlayHint(_, _) => true,
482 }
483 });
484
485 if any_definition_does_not_contain_current_location {
486 // todo!()
487 // // Highlight symbol using theme link definition highlight style
488 // let style = theme::current(cx).editor.link_definition;
489 // let highlight_range =
490 // symbol_range.unwrap_or_else(|| match &trigger_point {
491 // TriggerPoint::Text(trigger_anchor) => {
492 // let snapshot = &snapshot.buffer_snapshot;
493 // // If no symbol range returned from language server, use the surrounding word.
494 // let (offset_range, _) =
495 // snapshot.surrounding_word(*trigger_anchor);
496 // RangeInEditor::Text(
497 // snapshot.anchor_before(offset_range.start)
498 // ..snapshot.anchor_after(offset_range.end),
499 // )
500 // }
501 // TriggerPoint::InlayHint(highlight, _, _) => {
502 // RangeInEditor::Inlay(highlight.clone())
503 // }
504 // });
505
506 // match highlight_range {
507 // RangeInEditor::Text(text_range) => this
508 // .highlight_text::<LinkGoToDefinitionState>(
509 // vec![text_range],
510 // style,
511 // cx,
512 // ),
513 // RangeInEditor::Inlay(highlight) => this
514 // .highlight_inlays::<LinkGoToDefinitionState>(
515 // vec![highlight],
516 // style,
517 // cx,
518 // ),
519 // }
520 } else {
521 hide_link_definition(this, cx);
522 }
523 }
524 })?;
525
526 Ok::<_, anyhow::Error>(())
527 }
528 .log_err()
529 });
530
531 editor.link_go_to_definition_state.task = Some(task);
532}
533
534pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
535 if editor.link_go_to_definition_state.symbol_range.is_some()
536 || !editor.link_go_to_definition_state.definitions.is_empty()
537 {
538 editor.link_go_to_definition_state.symbol_range.take();
539 editor.link_go_to_definition_state.definitions.clear();
540 cx.notify();
541 }
542
543 editor.link_go_to_definition_state.task = None;
544
545 editor.clear_highlights::<LinkGoToDefinitionState>(cx);
546}
547
548pub fn go_to_fetched_definition(
549 editor: &mut Editor,
550 point: PointForPosition,
551 split: bool,
552 cx: &mut ViewContext<Editor>,
553) {
554 go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, editor, point, split, cx);
555}
556
557pub fn go_to_fetched_type_definition(
558 editor: &mut Editor,
559 point: PointForPosition,
560 split: bool,
561 cx: &mut ViewContext<Editor>,
562) {
563 go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, editor, point, split, cx);
564}
565
566fn go_to_fetched_definition_of_kind(
567 kind: LinkDefinitionKind,
568 editor: &mut Editor,
569 point: PointForPosition,
570 split: bool,
571 cx: &mut ViewContext<Editor>,
572) {
573 let cached_definitions = editor.link_go_to_definition_state.definitions.clone();
574 hide_link_definition(editor, cx);
575 let cached_definitions_kind = editor.link_go_to_definition_state.kind;
576
577 let is_correct_kind = cached_definitions_kind == Some(kind);
578 if !cached_definitions.is_empty() && is_correct_kind {
579 if !editor.focused {
580 cx.focus_self();
581 }
582
583 editor.navigate_to_definitions(cached_definitions, split, cx);
584 } else {
585 editor.select(
586 SelectPhase::Begin {
587 position: point.next_valid,
588 add: false,
589 click_count: 1,
590 },
591 cx,
592 );
593
594 if point.as_valid().is_some() {
595 match kind {
596 LinkDefinitionKind::Symbol => editor.go_to_definition(&Default::default(), cx),
597 LinkDefinitionKind::Type => editor.go_to_type_definition(&Default::default(), cx),
598 }
599 }
600 }
601}
602
603// #[cfg(test)]
604// mod tests {
605// use super::*;
606// use crate::{
607// display_map::ToDisplayPoint,
608// editor_tests::init_test,
609// inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels},
610// test::editor_lsp_test_context::EditorLspTestContext,
611// };
612// use futures::StreamExt;
613// use gpui::{
614// platform::{self, Modifiers, ModifiersChangedEvent},
615// View,
616// };
617// use indoc::indoc;
618// use language::language_settings::InlayHintSettings;
619// use lsp::request::{GotoDefinition, GotoTypeDefinition};
620// use util::assert_set_eq;
621
622// #[gpui::test]
623// async fn test_link_go_to_type_definition(cx: &mut gpui::TestAppContext) {
624// init_test(cx, |_| {});
625
626// let mut cx = EditorLspTestContext::new_rust(
627// lsp::ServerCapabilities {
628// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
629// type_definition_provider: Some(lsp::TypeDefinitionProviderCapability::Simple(true)),
630// ..Default::default()
631// },
632// cx,
633// )
634// .await;
635
636// cx.set_state(indoc! {"
637// struct A;
638// let vˇariable = A;
639// "});
640
641// // Basic hold cmd+shift, expect highlight in region if response contains type definition
642// let hover_point = cx.display_point(indoc! {"
643// struct A;
644// let vˇariable = A;
645// "});
646// let symbol_range = cx.lsp_range(indoc! {"
647// struct A;
648// let «variable» = A;
649// "});
650// let target_range = cx.lsp_range(indoc! {"
651// struct «A»;
652// let variable = A;
653// "});
654
655// let mut requests =
656// cx.handle_request::<GotoTypeDefinition, _, _>(move |url, _, _| async move {
657// Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
658// lsp::LocationLink {
659// origin_selection_range: Some(symbol_range),
660// target_uri: url.clone(),
661// target_range,
662// target_selection_range: target_range,
663// },
664// ])))
665// });
666
667// // Press cmd+shift to trigger highlight
668// cx.update_editor(|editor, cx| {
669// update_go_to_definition_link(
670// editor,
671// Some(GoToDefinitionTrigger::Text(hover_point)),
672// true,
673// true,
674// cx,
675// );
676// });
677// requests.next().await;
678// cx.foreground().run_until_parked();
679// cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
680// struct A;
681// let «variable» = A;
682// "});
683
684// // Unpress shift causes highlight to go away (normal goto-definition is not valid here)
685// cx.update_editor(|editor, cx| {
686// editor.modifiers_changed(
687// &platform::ModifiersChangedEvent {
688// modifiers: Modifiers {
689// cmd: true,
690// ..Default::default()
691// },
692// ..Default::default()
693// },
694// cx,
695// );
696// });
697// // Assert no link highlights
698// cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
699// struct A;
700// let variable = A;
701// "});
702
703// // Cmd+shift click without existing definition requests and jumps
704// let hover_point = cx.display_point(indoc! {"
705// struct A;
706// let vˇariable = A;
707// "});
708// let target_range = cx.lsp_range(indoc! {"
709// struct «A»;
710// let variable = A;
711// "});
712
713// let mut requests =
714// cx.handle_request::<GotoTypeDefinition, _, _>(move |url, _, _| async move {
715// Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
716// lsp::LocationLink {
717// origin_selection_range: None,
718// target_uri: url,
719// target_range,
720// target_selection_range: target_range,
721// },
722// ])))
723// });
724
725// cx.update_editor(|editor, cx| {
726// go_to_fetched_type_definition(editor, PointForPosition::valid(hover_point), false, cx);
727// });
728// requests.next().await;
729// cx.foreground().run_until_parked();
730
731// cx.assert_editor_state(indoc! {"
732// struct «Aˇ»;
733// let variable = A;
734// "});
735// }
736
737// #[gpui::test]
738// async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) {
739// init_test(cx, |_| {});
740
741// let mut cx = EditorLspTestContext::new_rust(
742// lsp::ServerCapabilities {
743// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
744// ..Default::default()
745// },
746// cx,
747// )
748// .await;
749
750// cx.set_state(indoc! {"
751// fn ˇtest() { do_work(); }
752// fn do_work() { test(); }
753// "});
754
755// // Basic hold cmd, expect highlight in region if response contains definition
756// let hover_point = cx.display_point(indoc! {"
757// fn test() { do_wˇork(); }
758// fn do_work() { test(); }
759// "});
760// let symbol_range = cx.lsp_range(indoc! {"
761// fn test() { «do_work»(); }
762// fn do_work() { test(); }
763// "});
764// let target_range = cx.lsp_range(indoc! {"
765// fn test() { do_work(); }
766// fn «do_work»() { test(); }
767// "});
768
769// let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
770// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
771// lsp::LocationLink {
772// origin_selection_range: Some(symbol_range),
773// target_uri: url.clone(),
774// target_range,
775// target_selection_range: target_range,
776// },
777// ])))
778// });
779
780// cx.update_editor(|editor, cx| {
781// update_go_to_definition_link(
782// editor,
783// Some(GoToDefinitionTrigger::Text(hover_point)),
784// true,
785// false,
786// cx,
787// );
788// });
789// requests.next().await;
790// cx.foreground().run_until_parked();
791// cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
792// fn test() { «do_work»(); }
793// fn do_work() { test(); }
794// "});
795
796// // Unpress cmd causes highlight to go away
797// cx.update_editor(|editor, cx| {
798// editor.modifiers_changed(&Default::default(), cx);
799// });
800
801// // Assert no link highlights
802// cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
803// fn test() { do_work(); }
804// fn do_work() { test(); }
805// "});
806
807// // Response without source range still highlights word
808// cx.update_editor(|editor, _| editor.link_go_to_definition_state.last_trigger_point = None);
809// let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
810// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
811// lsp::LocationLink {
812// // No origin range
813// origin_selection_range: None,
814// target_uri: url.clone(),
815// target_range,
816// target_selection_range: target_range,
817// },
818// ])))
819// });
820// cx.update_editor(|editor, cx| {
821// update_go_to_definition_link(
822// editor,
823// Some(GoToDefinitionTrigger::Text(hover_point)),
824// true,
825// false,
826// cx,
827// );
828// });
829// requests.next().await;
830// cx.foreground().run_until_parked();
831
832// cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
833// fn test() { «do_work»(); }
834// fn do_work() { test(); }
835// "});
836
837// // Moving mouse to location with no response dismisses highlight
838// let hover_point = cx.display_point(indoc! {"
839// fˇn test() { do_work(); }
840// fn do_work() { test(); }
841// "});
842// let mut requests = cx
843// .lsp
844// .handle_request::<GotoDefinition, _, _>(move |_, _| async move {
845// // No definitions returned
846// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
847// });
848// cx.update_editor(|editor, cx| {
849// update_go_to_definition_link(
850// editor,
851// Some(GoToDefinitionTrigger::Text(hover_point)),
852// true,
853// false,
854// cx,
855// );
856// });
857// requests.next().await;
858// cx.foreground().run_until_parked();
859
860// // Assert no link highlights
861// cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
862// fn test() { do_work(); }
863// fn do_work() { test(); }
864// "});
865
866// // Move mouse without cmd and then pressing cmd triggers highlight
867// let hover_point = cx.display_point(indoc! {"
868// fn test() { do_work(); }
869// fn do_work() { teˇst(); }
870// "});
871// cx.update_editor(|editor, cx| {
872// update_go_to_definition_link(
873// editor,
874// Some(GoToDefinitionTrigger::Text(hover_point)),
875// false,
876// false,
877// cx,
878// );
879// });
880// cx.foreground().run_until_parked();
881
882// // Assert no link highlights
883// cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
884// fn test() { do_work(); }
885// fn do_work() { test(); }
886// "});
887
888// let symbol_range = cx.lsp_range(indoc! {"
889// fn test() { do_work(); }
890// fn do_work() { «test»(); }
891// "});
892// let target_range = cx.lsp_range(indoc! {"
893// fn «test»() { do_work(); }
894// fn do_work() { test(); }
895// "});
896
897// let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
898// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
899// lsp::LocationLink {
900// origin_selection_range: Some(symbol_range),
901// target_uri: url,
902// target_range,
903// target_selection_range: target_range,
904// },
905// ])))
906// });
907// cx.update_editor(|editor, cx| {
908// editor.modifiers_changed(
909// &ModifiersChangedEvent {
910// modifiers: Modifiers {
911// cmd: true,
912// ..Default::default()
913// },
914// },
915// cx,
916// );
917// });
918// requests.next().await;
919// cx.foreground().run_until_parked();
920
921// cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
922// fn test() { do_work(); }
923// fn do_work() { «test»(); }
924// "});
925
926// // Deactivating the window dismisses the highlight
927// cx.update_workspace(|workspace, cx| {
928// workspace.on_window_activation_changed(false, cx);
929// });
930// cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
931// fn test() { do_work(); }
932// fn do_work() { test(); }
933// "});
934
935// // Moving the mouse restores the highlights.
936// cx.update_editor(|editor, cx| {
937// update_go_to_definition_link(
938// editor,
939// Some(GoToDefinitionTrigger::Text(hover_point)),
940// true,
941// false,
942// cx,
943// );
944// });
945// cx.foreground().run_until_parked();
946// cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
947// fn test() { do_work(); }
948// fn do_work() { «test»(); }
949// "});
950
951// // Moving again within the same symbol range doesn't re-request
952// let hover_point = cx.display_point(indoc! {"
953// fn test() { do_work(); }
954// fn do_work() { tesˇt(); }
955// "});
956// cx.update_editor(|editor, cx| {
957// update_go_to_definition_link(
958// editor,
959// Some(GoToDefinitionTrigger::Text(hover_point)),
960// true,
961// false,
962// cx,
963// );
964// });
965// cx.foreground().run_until_parked();
966// cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
967// fn test() { do_work(); }
968// fn do_work() { «test»(); }
969// "});
970
971// // Cmd click with existing definition doesn't re-request and dismisses highlight
972// cx.update_editor(|editor, cx| {
973// go_to_fetched_definition(editor, PointForPosition::valid(hover_point), false, cx);
974// });
975// // Assert selection moved to to definition
976// cx.lsp
977// .handle_request::<GotoDefinition, _, _>(move |_, _| async move {
978// // Empty definition response to make sure we aren't hitting the lsp and using
979// // the cached location instead
980// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
981// });
982// cx.foreground().run_until_parked();
983// cx.assert_editor_state(indoc! {"
984// fn «testˇ»() { do_work(); }
985// fn do_work() { test(); }
986// "});
987
988// // Assert no link highlights after jump
989// cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
990// fn test() { do_work(); }
991// fn do_work() { test(); }
992// "});
993
994// // Cmd click without existing definition requests and jumps
995// let hover_point = cx.display_point(indoc! {"
996// fn test() { do_wˇork(); }
997// fn do_work() { test(); }
998// "});
999// let target_range = cx.lsp_range(indoc! {"
1000// fn test() { do_work(); }
1001// fn «do_work»() { test(); }
1002// "});
1003
1004// let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
1005// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
1006// lsp::LocationLink {
1007// origin_selection_range: None,
1008// target_uri: url,
1009// target_range,
1010// target_selection_range: target_range,
1011// },
1012// ])))
1013// });
1014// cx.update_editor(|editor, cx| {
1015// go_to_fetched_definition(editor, PointForPosition::valid(hover_point), false, cx);
1016// });
1017// requests.next().await;
1018// cx.foreground().run_until_parked();
1019// cx.assert_editor_state(indoc! {"
1020// fn test() { do_work(); }
1021// fn «do_workˇ»() { test(); }
1022// "});
1023
1024// // 1. We have a pending selection, mouse point is over a symbol that we have a response for, hitting cmd and nothing happens
1025// // 2. Selection is completed, hovering
1026// let hover_point = cx.display_point(indoc! {"
1027// fn test() { do_wˇork(); }
1028// fn do_work() { test(); }
1029// "});
1030// let target_range = cx.lsp_range(indoc! {"
1031// fn test() { do_work(); }
1032// fn «do_work»() { test(); }
1033// "});
1034// let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
1035// Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
1036// lsp::LocationLink {
1037// origin_selection_range: None,
1038// target_uri: url,
1039// target_range,
1040// target_selection_range: target_range,
1041// },
1042// ])))
1043// });
1044
1045// // create a pending selection
1046// let selection_range = cx.ranges(indoc! {"
1047// fn «test() { do_w»ork(); }
1048// fn do_work() { test(); }
1049// "})[0]
1050// .clone();
1051// cx.update_editor(|editor, cx| {
1052// let snapshot = editor.buffer().read(cx).snapshot(cx);
1053// let anchor_range = snapshot.anchor_before(selection_range.start)
1054// ..snapshot.anchor_after(selection_range.end);
1055// editor.change_selections(Some(crate::Autoscroll::fit()), cx, |s| {
1056// s.set_pending_anchor_range(anchor_range, crate::SelectMode::Character)
1057// });
1058// });
1059// cx.update_editor(|editor, cx| {
1060// update_go_to_definition_link(
1061// editor,
1062// Some(GoToDefinitionTrigger::Text(hover_point)),
1063// true,
1064// false,
1065// cx,
1066// );
1067// });
1068// cx.foreground().run_until_parked();
1069// assert!(requests.try_next().is_err());
1070// cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
1071// fn test() { do_work(); }
1072// fn do_work() { test(); }
1073// "});
1074// cx.foreground().run_until_parked();
1075// }
1076
1077// #[gpui::test]
1078// async fn test_link_go_to_inlay(cx: &mut gpui::TestAppContext) {
1079// init_test(cx, |settings| {
1080// settings.defaults.inlay_hints = Some(InlayHintSettings {
1081// enabled: true,
1082// show_type_hints: true,
1083// show_parameter_hints: true,
1084// show_other_hints: true,
1085// })
1086// });
1087
1088// let mut cx = EditorLspTestContext::new_rust(
1089// lsp::ServerCapabilities {
1090// inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1091// ..Default::default()
1092// },
1093// cx,
1094// )
1095// .await;
1096// cx.set_state(indoc! {"
1097// struct TestStruct;
1098
1099// fn main() {
1100// let variableˇ = TestStruct;
1101// }
1102// "});
1103// let hint_start_offset = cx.ranges(indoc! {"
1104// struct TestStruct;
1105
1106// fn main() {
1107// let variableˇ = TestStruct;
1108// }
1109// "})[0]
1110// .start;
1111// let hint_position = cx.to_lsp(hint_start_offset);
1112// let target_range = cx.lsp_range(indoc! {"
1113// struct «TestStruct»;
1114
1115// fn main() {
1116// let variable = TestStruct;
1117// }
1118// "});
1119
1120// let expected_uri = cx.buffer_lsp_url.clone();
1121// let hint_label = ": TestStruct";
1122// cx.lsp
1123// .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1124// let expected_uri = expected_uri.clone();
1125// async move {
1126// assert_eq!(params.text_document.uri, expected_uri);
1127// Ok(Some(vec![lsp::InlayHint {
1128// position: hint_position,
1129// label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart {
1130// value: hint_label.to_string(),
1131// location: Some(lsp::Location {
1132// uri: params.text_document.uri,
1133// range: target_range,
1134// }),
1135// ..Default::default()
1136// }]),
1137// kind: Some(lsp::InlayHintKind::TYPE),
1138// text_edits: None,
1139// tooltip: None,
1140// padding_left: Some(false),
1141// padding_right: Some(false),
1142// data: None,
1143// }]))
1144// }
1145// })
1146// .next()
1147// .await;
1148// cx.foreground().run_until_parked();
1149// cx.update_editor(|editor, cx| {
1150// let expected_layers = vec![hint_label.to_string()];
1151// assert_eq!(expected_layers, cached_hint_labels(editor));
1152// assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1153// });
1154
1155// let inlay_range = cx
1156// .ranges(indoc! {"
1157// struct TestStruct;
1158
1159// fn main() {
1160// let variable« »= TestStruct;
1161// }
1162// "})
1163// .get(0)
1164// .cloned()
1165// .unwrap();
1166// let hint_hover_position = cx.update_editor(|editor, cx| {
1167// let snapshot = editor.snapshot(cx);
1168// let previous_valid = inlay_range.start.to_display_point(&snapshot);
1169// let next_valid = inlay_range.end.to_display_point(&snapshot);
1170// assert_eq!(previous_valid.row(), next_valid.row());
1171// assert!(previous_valid.column() < next_valid.column());
1172// let exact_unclipped = DisplayPoint::new(
1173// previous_valid.row(),
1174// previous_valid.column() + (hint_label.len() / 2) as u32,
1175// );
1176// PointForPosition {
1177// previous_valid,
1178// next_valid,
1179// exact_unclipped,
1180// column_overshoot_after_line_end: 0,
1181// }
1182// });
1183// // Press cmd to trigger highlight
1184// cx.update_editor(|editor, cx| {
1185// update_inlay_link_and_hover_points(
1186// &editor.snapshot(cx),
1187// hint_hover_position,
1188// editor,
1189// true,
1190// false,
1191// cx,
1192// );
1193// });
1194// cx.foreground().run_until_parked();
1195// cx.update_editor(|editor, cx| {
1196// let snapshot = editor.snapshot(cx);
1197// let actual_highlights = snapshot
1198// .inlay_highlights::<LinkGoToDefinitionState>()
1199// .into_iter()
1200// .flat_map(|highlights| highlights.values().map(|(_, highlight)| highlight))
1201// .collect::<Vec<_>>();
1202
1203// let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
1204// let expected_highlight = InlayHighlight {
1205// inlay: InlayId::Hint(0),
1206// inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right),
1207// range: 0..hint_label.len(),
1208// };
1209// assert_set_eq!(actual_highlights, vec![&expected_highlight]);
1210// });
1211
1212// // Unpress cmd causes highlight to go away
1213// cx.update_editor(|editor, cx| {
1214// editor.modifiers_changed(
1215// &platform::ModifiersChangedEvent {
1216// modifiers: Modifiers {
1217// cmd: false,
1218// ..Default::default()
1219// },
1220// ..Default::default()
1221// },
1222// cx,
1223// );
1224// });
1225// // Assert no link highlights
1226// cx.update_editor(|editor, cx| {
1227// let snapshot = editor.snapshot(cx);
1228// let actual_ranges = snapshot
1229// .text_highlight_ranges::<LinkGoToDefinitionState>()
1230// .map(|ranges| ranges.as_ref().clone().1)
1231// .unwrap_or_default();
1232
1233// assert!(actual_ranges.is_empty(), "When no cmd is pressed, should have no hint label selected, but got: {actual_ranges:?}");
1234// });
1235
1236// // Cmd+click without existing definition requests and jumps
1237// cx.update_editor(|editor, cx| {
1238// editor.modifiers_changed(
1239// &platform::ModifiersChangedEvent {
1240// modifiers: Modifiers {
1241// cmd: true,
1242// ..Default::default()
1243// },
1244// ..Default::default()
1245// },
1246// cx,
1247// );
1248// update_inlay_link_and_hover_points(
1249// &editor.snapshot(cx),
1250// hint_hover_position,
1251// editor,
1252// true,
1253// false,
1254// cx,
1255// );
1256// });
1257// cx.foreground().run_until_parked();
1258// cx.update_editor(|editor, cx| {
1259// go_to_fetched_type_definition(editor, hint_hover_position, false, cx);
1260// });
1261// cx.foreground().run_until_parked();
1262// cx.assert_editor_state(indoc! {"
1263// struct «TestStructˇ»;
1264
1265// fn main() {
1266// let variable = TestStruct;
1267// }
1268// "});
1269// }
1270// }