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