link_go_to_definition.rs

   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// }