link_go_to_definition.rs

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