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