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