hover_popover.rs

   1use crate::{
   2    display_map::{InlayOffset, ToDisplayPoint},
   3    hover_links::{InlayHighlight, RangeInEditor},
   4    scroll::ScrollAmount,
   5    Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings, EditorSnapshot,
   6    EditorStyle, Hover, RangeToAnchorExt,
   7};
   8use futures::{stream::FuturesUnordered, FutureExt};
   9use gpui::{
  10    div, px, AnyElement, CursorStyle, Hsla, InteractiveElement, IntoElement, MouseButton,
  11    ParentElement, Pixels, ScrollHandle, SharedString, Size, StatefulInteractiveElement, Styled,
  12    Task, ViewContext, WeakView,
  13};
  14use language::{markdown, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown};
  15
  16use lsp::DiagnosticSeverity;
  17use multi_buffer::ToOffset;
  18use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart};
  19use settings::Settings;
  20use smol::stream::StreamExt;
  21use std::{ops::Range, sync::Arc, time::Duration};
  22use ui::{prelude::*, window_is_transparent, Tooltip};
  23use util::TryFutureExt;
  24use workspace::Workspace;
  25
  26pub const HOVER_DELAY_MILLIS: u64 = 350;
  27pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
  28
  29pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.;
  30pub const MIN_POPOVER_LINE_HEIGHT: Pixels = px(4.);
  31pub const HOVER_POPOVER_GAP: Pixels = px(10.);
  32
  33/// Bindable action which uses the most recent selection head to trigger a hover
  34pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) {
  35    let head = editor.selections.newest_anchor().head();
  36    show_hover(editor, head, true, cx);
  37}
  38
  39/// The internal hover action dispatches between `show_hover` or `hide_hover`
  40/// depending on whether a point to hover over is provided.
  41pub fn hover_at(editor: &mut Editor, anchor: Option<Anchor>, cx: &mut ViewContext<Editor>) {
  42    if EditorSettings::get_global(cx).hover_popover_enabled {
  43        if let Some(anchor) = anchor {
  44            show_hover(editor, anchor, false, cx);
  45        } else {
  46            hide_hover(editor, cx);
  47        }
  48    }
  49}
  50
  51pub struct InlayHover {
  52    pub range: InlayHighlight,
  53    pub tooltip: HoverBlock,
  54}
  55
  56pub fn find_hovered_hint_part(
  57    label_parts: Vec<InlayHintLabelPart>,
  58    hint_start: InlayOffset,
  59    hovered_offset: InlayOffset,
  60) -> Option<(InlayHintLabelPart, Range<InlayOffset>)> {
  61    if hovered_offset >= hint_start {
  62        let mut hovered_character = (hovered_offset - hint_start).0;
  63        let mut part_start = hint_start;
  64        for part in label_parts {
  65            let part_len = part.value.chars().count();
  66            if hovered_character > part_len {
  67                hovered_character -= part_len;
  68                part_start.0 += part_len;
  69            } else {
  70                let part_end = InlayOffset(part_start.0 + part_len);
  71                return Some((part, part_start..part_end));
  72            }
  73        }
  74    }
  75    None
  76}
  77
  78pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut ViewContext<Editor>) {
  79    if EditorSettings::get_global(cx).hover_popover_enabled {
  80        if editor.pending_rename.is_some() {
  81            return;
  82        }
  83
  84        let Some(project) = editor.project.clone() else {
  85            return;
  86        };
  87
  88        if editor
  89            .hover_state
  90            .info_popovers
  91            .iter()
  92            .any(|InfoPopover { symbol_range, .. }| {
  93                if let RangeInEditor::Inlay(range) = symbol_range {
  94                    if range == &inlay_hover.range {
  95                        // Hover triggered from same location as last time. Don't show again.
  96                        return true;
  97                    }
  98                }
  99                false
 100            })
 101        {
 102            hide_hover(editor, cx);
 103        }
 104
 105        let task = cx.spawn(|this, mut cx| {
 106            async move {
 107                cx.background_executor()
 108                    .timer(Duration::from_millis(HOVER_DELAY_MILLIS))
 109                    .await;
 110                this.update(&mut cx, |this, _| {
 111                    this.hover_state.diagnostic_popover = None;
 112                })?;
 113
 114                let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?;
 115                let blocks = vec![inlay_hover.tooltip];
 116                let parsed_content = parse_blocks(&blocks, &language_registry, None).await;
 117
 118                let hover_popover = InfoPopover {
 119                    symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
 120                    parsed_content,
 121                    scroll_handle: ScrollHandle::new(),
 122                };
 123
 124                this.update(&mut cx, |this, cx| {
 125                    // TODO: no background highlights happen for inlays currently
 126                    this.hover_state.info_popovers = vec![hover_popover];
 127                    cx.notify();
 128                })?;
 129
 130                anyhow::Ok(())
 131            }
 132            .log_err()
 133        });
 134
 135        editor.hover_state.info_task = Some(task);
 136    }
 137}
 138
 139/// Hides the type information popup.
 140/// Triggered by the `Hover` action when the cursor is not over a symbol or when the
 141/// selections changed.
 142pub fn hide_hover(editor: &mut Editor, cx: &mut ViewContext<Editor>) -> bool {
 143    let info_popovers = editor.hover_state.info_popovers.drain(..);
 144    let diagnostics_popover = editor.hover_state.diagnostic_popover.take();
 145    let did_hide = info_popovers.count() > 0 || diagnostics_popover.is_some();
 146
 147    editor.hover_state.info_task = None;
 148    editor.hover_state.triggered_from = None;
 149
 150    editor.clear_background_highlights::<HoverState>(cx);
 151
 152    if did_hide {
 153        cx.notify();
 154    }
 155
 156    did_hide
 157}
 158
 159/// Queries the LSP and shows type info and documentation
 160/// about the symbol the mouse is currently hovering over.
 161/// Triggered by the `Hover` action when the cursor may be over a symbol.
 162fn show_hover(
 163    editor: &mut Editor,
 164    anchor: Anchor,
 165    ignore_timeout: bool,
 166    cx: &mut ViewContext<Editor>,
 167) {
 168    if editor.pending_rename.is_some() {
 169        return;
 170    }
 171
 172    let snapshot = editor.snapshot(cx);
 173
 174    let (buffer, buffer_position) =
 175        if let Some(output) = editor.buffer.read(cx).text_anchor_for_position(anchor, cx) {
 176            output
 177        } else {
 178            return;
 179        };
 180
 181    let excerpt_id =
 182        if let Some((excerpt_id, _, _)) = editor.buffer().read(cx).excerpt_containing(anchor, cx) {
 183            excerpt_id
 184        } else {
 185            return;
 186        };
 187
 188    let project = if let Some(project) = editor.project.clone() {
 189        project
 190    } else {
 191        return;
 192    };
 193
 194    if !ignore_timeout {
 195        if editor
 196            .hover_state
 197            .info_popovers
 198            .iter()
 199            .any(|InfoPopover { symbol_range, .. }| {
 200                symbol_range
 201                    .as_text_range()
 202                    .map(|range| {
 203                        let hover_range = range.to_offset(&snapshot.buffer_snapshot);
 204                        let offset = anchor.to_offset(&snapshot.buffer_snapshot);
 205                        // LSP returns a hover result for the end index of ranges that should be hovered, so we need to
 206                        // use an inclusive range here to check if we should dismiss the popover
 207                        (hover_range.start..=hover_range.end).contains(&offset)
 208                    })
 209                    .unwrap_or(false)
 210            })
 211        {
 212            // Hover triggered from same location as last time. Don't show again.
 213            return;
 214        } else {
 215            hide_hover(editor, cx);
 216        }
 217    }
 218
 219    // Don't request again if the location is the same as the previous request
 220    if let Some(triggered_from) = &editor.hover_state.triggered_from {
 221        if triggered_from
 222            .cmp(&anchor, &snapshot.buffer_snapshot)
 223            .is_eq()
 224        {
 225            return;
 226        }
 227    }
 228
 229    let task = cx.spawn(|this, mut cx| {
 230        async move {
 231            // If we need to delay, delay a set amount initially before making the lsp request
 232            let delay = if ignore_timeout {
 233                None
 234            } else {
 235                // Construct delay task to wait for later
 236                let total_delay = Some(
 237                    cx.background_executor()
 238                        .timer(Duration::from_millis(HOVER_DELAY_MILLIS)),
 239                );
 240
 241                cx.background_executor()
 242                    .timer(Duration::from_millis(HOVER_REQUEST_DELAY_MILLIS))
 243                    .await;
 244                total_delay
 245            };
 246
 247            // query the LSP for hover info
 248            let hover_request = cx.update(|cx| {
 249                project.update(cx, |project, cx| {
 250                    project.hover(&buffer, buffer_position, cx)
 251                })
 252            })?;
 253
 254            if let Some(delay) = delay {
 255                delay.await;
 256            }
 257
 258            // If there's a diagnostic, assign it on the hover state and notify
 259            let local_diagnostic = snapshot
 260                .buffer_snapshot
 261                .diagnostics_in_range::<_, usize>(anchor..anchor, false)
 262                // Find the entry with the most specific range
 263                .min_by_key(|entry| entry.range.end - entry.range.start)
 264                .map(|entry| DiagnosticEntry {
 265                    diagnostic: entry.diagnostic,
 266                    range: entry.range.to_anchors(&snapshot.buffer_snapshot),
 267                });
 268
 269            // Pull the primary diagnostic out so we can jump to it if the popover is clicked
 270            let primary_diagnostic = local_diagnostic.as_ref().and_then(|local_diagnostic| {
 271                snapshot
 272                    .buffer_snapshot
 273                    .diagnostic_group::<usize>(local_diagnostic.diagnostic.group_id)
 274                    .find(|diagnostic| diagnostic.diagnostic.is_primary)
 275                    .map(|entry| DiagnosticEntry {
 276                        diagnostic: entry.diagnostic,
 277                        range: entry.range.to_anchors(&snapshot.buffer_snapshot),
 278                    })
 279            });
 280
 281            this.update(&mut cx, |this, _| {
 282                this.hover_state.diagnostic_popover =
 283                    local_diagnostic.map(|local_diagnostic| DiagnosticPopover {
 284                        local_diagnostic,
 285                        primary_diagnostic,
 286                    });
 287            })?;
 288
 289            let hovers_response = hover_request.await;
 290            let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?;
 291            let snapshot = this.update(&mut cx, |this, cx| this.snapshot(cx))?;
 292            let mut hover_highlights = Vec::with_capacity(hovers_response.len());
 293            let mut info_popovers = Vec::with_capacity(hovers_response.len());
 294            let mut info_popover_tasks = hovers_response
 295                .into_iter()
 296                .map(|hover_result| async {
 297                    // Create symbol range of anchors for highlighting and filtering of future requests.
 298                    let range = hover_result
 299                        .range
 300                        .and_then(|range| {
 301                            let start = snapshot
 302                                .buffer_snapshot
 303                                .anchor_in_excerpt(excerpt_id, range.start)?;
 304                            let end = snapshot
 305                                .buffer_snapshot
 306                                .anchor_in_excerpt(excerpt_id, range.end)?;
 307
 308                            Some(start..end)
 309                        })
 310                        .unwrap_or_else(|| anchor..anchor);
 311
 312                    let blocks = hover_result.contents;
 313                    let language = hover_result.language;
 314                    let parsed_content = parse_blocks(&blocks, &language_registry, language).await;
 315
 316                    (
 317                        range.clone(),
 318                        InfoPopover {
 319                            symbol_range: RangeInEditor::Text(range),
 320                            parsed_content,
 321                            scroll_handle: ScrollHandle::new(),
 322                        },
 323                    )
 324                })
 325                .collect::<FuturesUnordered<_>>();
 326            while let Some((highlight_range, info_popover)) = info_popover_tasks.next().await {
 327                hover_highlights.push(highlight_range);
 328                info_popovers.push(info_popover);
 329            }
 330
 331            this.update(&mut cx, |editor, cx| {
 332                if hover_highlights.is_empty() {
 333                    editor.clear_background_highlights::<HoverState>(cx);
 334                } else {
 335                    // Highlight the selected symbol using a background highlight
 336                    editor.highlight_background::<HoverState>(
 337                        &hover_highlights,
 338                        |theme| theme.element_hover, // todo update theme
 339                        cx,
 340                    );
 341                }
 342
 343                editor.hover_state.info_popovers = info_popovers;
 344                cx.notify();
 345                cx.refresh();
 346            })?;
 347
 348            anyhow::Ok(())
 349        }
 350        .log_err()
 351    });
 352
 353    editor.hover_state.info_task = Some(task);
 354}
 355
 356async fn parse_blocks(
 357    blocks: &[HoverBlock],
 358    language_registry: &Arc<LanguageRegistry>,
 359    language: Option<Arc<Language>>,
 360) -> markdown::ParsedMarkdown {
 361    let mut text = String::new();
 362    let mut highlights = Vec::new();
 363    let mut region_ranges = Vec::new();
 364    let mut regions = Vec::new();
 365
 366    for block in blocks {
 367        match &block.kind {
 368            HoverBlockKind::PlainText => {
 369                markdown::new_paragraph(&mut text, &mut Vec::new());
 370                text.push_str(&block.text.replace("\\n", "\n"));
 371            }
 372
 373            HoverBlockKind::Markdown => {
 374                markdown::parse_markdown_block(
 375                    &block.text.replace("\\n", "\n"),
 376                    language_registry,
 377                    language.clone(),
 378                    &mut text,
 379                    &mut highlights,
 380                    &mut region_ranges,
 381                    &mut regions,
 382                )
 383                .await
 384            }
 385
 386            HoverBlockKind::Code { language } => {
 387                if let Some(language) = language_registry
 388                    .language_for_name(language)
 389                    .now_or_never()
 390                    .and_then(Result::ok)
 391                {
 392                    markdown::highlight_code(&mut text, &mut highlights, &block.text, &language);
 393                } else {
 394                    text.push_str(&block.text);
 395                }
 396            }
 397        }
 398    }
 399
 400    let leading_space = text.chars().take_while(|c| c.is_whitespace()).count();
 401    if leading_space > 0 {
 402        highlights = highlights
 403            .into_iter()
 404            .map(|(range, style)| {
 405                (
 406                    range.start.saturating_sub(leading_space)
 407                        ..range.end.saturating_sub(leading_space),
 408                    style,
 409                )
 410            })
 411            .collect();
 412        region_ranges = region_ranges
 413            .into_iter()
 414            .map(|range| {
 415                range.start.saturating_sub(leading_space)..range.end.saturating_sub(leading_space)
 416            })
 417            .collect();
 418    }
 419
 420    ParsedMarkdown {
 421        text: text.trim().to_string(),
 422        highlights,
 423        region_ranges,
 424        regions,
 425    }
 426}
 427
 428#[derive(Default, Debug)]
 429pub struct HoverState {
 430    pub info_popovers: Vec<InfoPopover>,
 431    pub diagnostic_popover: Option<DiagnosticPopover>,
 432    pub triggered_from: Option<Anchor>,
 433    pub info_task: Option<Task<Option<()>>>,
 434}
 435
 436impl HoverState {
 437    pub fn visible(&self) -> bool {
 438        !self.info_popovers.is_empty() || self.diagnostic_popover.is_some()
 439    }
 440
 441    pub fn render(
 442        &mut self,
 443        snapshot: &EditorSnapshot,
 444        style: &EditorStyle,
 445        visible_rows: Range<DisplayRow>,
 446        max_size: Size<Pixels>,
 447        workspace: Option<WeakView<Workspace>>,
 448        cx: &mut ViewContext<Editor>,
 449    ) -> Option<(DisplayPoint, Vec<AnyElement>)> {
 450        // If there is a diagnostic, position the popovers based on that.
 451        // Otherwise use the start of the hover range
 452        let anchor = self
 453            .diagnostic_popover
 454            .as_ref()
 455            .map(|diagnostic_popover| &diagnostic_popover.local_diagnostic.range.start)
 456            .or_else(|| {
 457                self.info_popovers.iter().find_map(|info_popover| {
 458                    match &info_popover.symbol_range {
 459                        RangeInEditor::Text(range) => Some(&range.start),
 460                        RangeInEditor::Inlay(_) => None,
 461                    }
 462                })
 463            })
 464            .or_else(|| {
 465                self.info_popovers.iter().find_map(|info_popover| {
 466                    match &info_popover.symbol_range {
 467                        RangeInEditor::Text(_) => None,
 468                        RangeInEditor::Inlay(range) => Some(&range.inlay_position),
 469                    }
 470                })
 471            })?;
 472        let point = anchor.to_display_point(&snapshot.display_snapshot);
 473
 474        // Don't render if the relevant point isn't on screen
 475        if !self.visible() || !visible_rows.contains(&point.row()) {
 476            return None;
 477        }
 478
 479        let mut elements = Vec::new();
 480
 481        if let Some(diagnostic_popover) = self.diagnostic_popover.as_ref() {
 482            elements.push(diagnostic_popover.render(style, max_size, cx));
 483        }
 484        for info_popover in &mut self.info_popovers {
 485            elements.push(info_popover.render(style, max_size, workspace.clone(), cx));
 486        }
 487
 488        Some((point, elements))
 489    }
 490}
 491
 492#[derive(Clone, Debug)]
 493pub struct InfoPopover {
 494    pub symbol_range: RangeInEditor,
 495    pub parsed_content: ParsedMarkdown,
 496    pub scroll_handle: ScrollHandle,
 497}
 498
 499impl InfoPopover {
 500    pub fn render(
 501        &mut self,
 502        style: &EditorStyle,
 503        max_size: Size<Pixels>,
 504        workspace: Option<WeakView<Workspace>>,
 505        cx: &mut ViewContext<Editor>,
 506    ) -> AnyElement {
 507        div()
 508            .id("info_popover")
 509            .elevation_2(cx)
 510            .overflow_y_scroll()
 511            .track_scroll(&self.scroll_handle)
 512            .max_w(max_size.width)
 513            .max_h(max_size.height)
 514            // Prevent a mouse down/move on the popover from being propagated to the editor,
 515            // because that would dismiss the popover.
 516            .on_mouse_move(|_, cx| cx.stop_propagation())
 517            .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
 518            .child(div().p_2().child(crate::render_parsed_markdown(
 519                "content",
 520                &self.parsed_content,
 521                style,
 522                workspace,
 523                cx,
 524            )))
 525            .into_any_element()
 526    }
 527
 528    pub fn scroll(&self, amount: &ScrollAmount, cx: &mut ViewContext<Editor>) {
 529        let mut current = self.scroll_handle.offset();
 530        current.y -= amount.pixels(
 531            cx.line_height(),
 532            self.scroll_handle.bounds().size.height - px(16.),
 533        ) / 2.0;
 534        cx.notify();
 535        self.scroll_handle.set_offset(current);
 536    }
 537}
 538
 539#[derive(Debug, Clone)]
 540pub struct DiagnosticPopover {
 541    local_diagnostic: DiagnosticEntry<Anchor>,
 542    primary_diagnostic: Option<DiagnosticEntry<Anchor>>,
 543}
 544
 545impl DiagnosticPopover {
 546    pub fn render(
 547        &self,
 548        style: &EditorStyle,
 549        max_size: Size<Pixels>,
 550        cx: &mut ViewContext<Editor>,
 551    ) -> AnyElement {
 552        let text = match &self.local_diagnostic.diagnostic.source {
 553            Some(source) => format!("{source}: {}", self.local_diagnostic.diagnostic.message),
 554            None => self.local_diagnostic.diagnostic.message.clone(),
 555        };
 556
 557        let status_colors = cx.theme().status();
 558
 559        struct DiagnosticColors {
 560            pub background: Hsla,
 561            pub border: Hsla,
 562        }
 563
 564        let diagnostic_colors = match self.local_diagnostic.diagnostic.severity {
 565            DiagnosticSeverity::ERROR => DiagnosticColors {
 566                background: status_colors.error_background,
 567                border: status_colors.error_border,
 568            },
 569            DiagnosticSeverity::WARNING => DiagnosticColors {
 570                background: status_colors.warning_background,
 571                border: status_colors.warning_border,
 572            },
 573            DiagnosticSeverity::INFORMATION => DiagnosticColors {
 574                background: status_colors.info_background,
 575                border: status_colors.info_border,
 576            },
 577            DiagnosticSeverity::HINT => DiagnosticColors {
 578                background: status_colors.hint_background,
 579                border: status_colors.hint_border,
 580            },
 581            _ => DiagnosticColors {
 582                background: status_colors.ignored_background,
 583                border: status_colors.ignored_border,
 584            },
 585        };
 586
 587        div()
 588            .id("diagnostic")
 589            .block()
 590            .elevation_2_borderless(cx)
 591            // Don't draw the background color if the theme
 592            // allows transparent surfaces.
 593            .when(window_is_transparent(cx), |this| {
 594                this.bg(gpui::transparent_black())
 595            })
 596            .max_w(max_size.width)
 597            .max_h(max_size.height)
 598            .cursor(CursorStyle::PointingHand)
 599            .tooltip(move |cx| Tooltip::for_action("Go To Diagnostic", &crate::GoToDiagnostic, cx))
 600            // Prevent a mouse move on the popover from being propagated to the editor,
 601            // because that would dismiss the popover.
 602            .on_mouse_move(|_, cx| cx.stop_propagation())
 603            // Prevent a mouse down on the popover from being propagated to the editor,
 604            // because that would move the cursor.
 605            .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
 606            .on_click(cx.listener(|editor, _, cx| editor.go_to_diagnostic(&Default::default(), cx)))
 607            .child(
 608                div()
 609                    .id("diagnostic-inner")
 610                    .overflow_y_scroll()
 611                    .px_2()
 612                    .py_1()
 613                    .bg(diagnostic_colors.background)
 614                    .text_color(style.text.color)
 615                    .border_1()
 616                    .border_color(diagnostic_colors.border)
 617                    .rounded_lg()
 618                    .child(SharedString::from(text)),
 619            )
 620            .into_any_element()
 621    }
 622
 623    pub fn activation_info(&self) -> (usize, Anchor) {
 624        let entry = self
 625            .primary_diagnostic
 626            .as_ref()
 627            .unwrap_or(&self.local_diagnostic);
 628
 629        (entry.diagnostic.group_id, entry.range.start)
 630    }
 631}
 632
 633#[cfg(test)]
 634mod tests {
 635    use super::*;
 636    use crate::{
 637        actions::ConfirmCompletion,
 638        editor_tests::{handle_completion_request, init_test},
 639        hover_links::update_inlay_link_and_hover_points,
 640        inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels},
 641        test::editor_lsp_test_context::EditorLspTestContext,
 642        InlayId, PointForPosition,
 643    };
 644    use collections::BTreeSet;
 645    use gpui::{FontWeight, HighlightStyle, UnderlineStyle};
 646    use indoc::indoc;
 647    use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet};
 648    use lsp::LanguageServerId;
 649    use project::{HoverBlock, HoverBlockKind};
 650    use smol::stream::StreamExt;
 651    use std::sync::atomic;
 652    use std::sync::atomic::AtomicUsize;
 653    use text::Bias;
 654    use unindent::Unindent;
 655    use util::test::marked_text_ranges;
 656
 657    #[gpui::test]
 658    async fn test_mouse_hover_info_popover_with_autocomplete_popover(
 659        cx: &mut gpui::TestAppContext,
 660    ) {
 661        init_test(cx, |_| {});
 662        const HOVER_DELAY_MILLIS: u64 = 350;
 663
 664        let mut cx = EditorLspTestContext::new_rust(
 665            lsp::ServerCapabilities {
 666                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
 667                completion_provider: Some(lsp::CompletionOptions {
 668                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
 669                    resolve_provider: Some(true),
 670                    ..Default::default()
 671                }),
 672                ..Default::default()
 673            },
 674            cx,
 675        )
 676        .await;
 677        let counter = Arc::new(AtomicUsize::new(0));
 678        // Basic hover delays and then pops without moving the mouse
 679        cx.set_state(indoc! {"
 680                oneˇ
 681                two
 682                three
 683                fn test() { println!(); }
 684            "});
 685
 686        //prompt autocompletion menu
 687        cx.simulate_keystroke(".");
 688        handle_completion_request(
 689            &mut cx,
 690            indoc! {"
 691                        one.|<>
 692                        two
 693                        three
 694                    "},
 695            vec!["first_completion", "second_completion"],
 696            counter.clone(),
 697        )
 698        .await;
 699        cx.condition(|editor, _| editor.context_menu_visible()) // wait until completion menu is visible
 700            .await;
 701        assert_eq!(counter.load(atomic::Ordering::Acquire), 1); // 1 completion request
 702
 703        let hover_point = cx.display_point(indoc! {"
 704                one.
 705                two
 706                three
 707                fn test() { printˇln!(); }
 708            "});
 709        cx.update_editor(|editor, cx| {
 710            let snapshot = editor.snapshot(cx);
 711            let anchor = snapshot
 712                .buffer_snapshot
 713                .anchor_before(hover_point.to_offset(&snapshot, Bias::Left));
 714            hover_at(editor, Some(anchor), cx)
 715        });
 716        assert!(!cx.editor(|editor, _| editor.hover_state.visible()));
 717
 718        // After delay, hover should be visible.
 719        let symbol_range = cx.lsp_range(indoc! {"
 720                one.
 721                two
 722                three
 723                fn test() { «println!»(); }
 724            "});
 725        let mut requests =
 726            cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
 727                Ok(Some(lsp::Hover {
 728                    contents: lsp::HoverContents::Markup(lsp::MarkupContent {
 729                        kind: lsp::MarkupKind::Markdown,
 730                        value: "some basic docs".to_string(),
 731                    }),
 732                    range: Some(symbol_range),
 733                }))
 734            });
 735        cx.background_executor
 736            .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
 737        requests.next().await;
 738
 739        cx.editor(|editor, _| {
 740            assert!(editor.hover_state.visible());
 741            assert_eq!(
 742                editor.hover_state.info_popovers.len(),
 743                1,
 744                "Expected exactly one hover but got: {:?}",
 745                editor.hover_state.info_popovers
 746            );
 747            let rendered = editor
 748                .hover_state
 749                .info_popovers
 750                .first()
 751                .cloned()
 752                .unwrap()
 753                .parsed_content;
 754            assert_eq!(rendered.text, "some basic docs".to_string())
 755        });
 756
 757        // check that the completion menu is still visible and that there still has only been 1 completion request
 758        cx.editor(|editor, _| assert!(editor.context_menu_visible()));
 759        assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
 760
 761        //apply a completion and check it was successfully applied
 762        let _apply_additional_edits = cx.update_editor(|editor, cx| {
 763            editor.context_menu_next(&Default::default(), cx);
 764            editor
 765                .confirm_completion(&ConfirmCompletion::default(), cx)
 766                .unwrap()
 767        });
 768        cx.assert_editor_state(indoc! {"
 769            one.second_completionˇ
 770            two
 771            three
 772            fn test() { println!(); }
 773        "});
 774
 775        // check that the completion menu is no longer visible and that there still has only been 1 completion request
 776        cx.editor(|editor, _| assert!(!editor.context_menu_visible()));
 777        assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
 778
 779        //verify the information popover is still visible and unchanged
 780        cx.editor(|editor, _| {
 781            assert!(editor.hover_state.visible());
 782            assert_eq!(
 783                editor.hover_state.info_popovers.len(),
 784                1,
 785                "Expected exactly one hover but got: {:?}",
 786                editor.hover_state.info_popovers
 787            );
 788            let rendered = editor
 789                .hover_state
 790                .info_popovers
 791                .first()
 792                .cloned()
 793                .unwrap()
 794                .parsed_content;
 795            assert_eq!(rendered.text, "some basic docs".to_string())
 796        });
 797
 798        // Mouse moved with no hover response dismisses
 799        let hover_point = cx.display_point(indoc! {"
 800                one.second_completionˇ
 801                two
 802                three
 803                fn teˇst() { println!(); }
 804            "});
 805        let mut request = cx
 806            .lsp
 807            .handle_request::<lsp::request::HoverRequest, _, _>(|_, _| async move { Ok(None) });
 808        cx.update_editor(|editor, cx| {
 809            let snapshot = editor.snapshot(cx);
 810            let anchor = snapshot
 811                .buffer_snapshot
 812                .anchor_before(hover_point.to_offset(&snapshot, Bias::Left));
 813            hover_at(editor, Some(anchor), cx)
 814        });
 815        cx.background_executor
 816            .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
 817        request.next().await;
 818
 819        // verify that the information popover is no longer visible
 820        cx.editor(|editor, _| {
 821            assert!(!editor.hover_state.visible());
 822        });
 823    }
 824
 825    #[gpui::test]
 826    async fn test_mouse_hover_info_popover(cx: &mut gpui::TestAppContext) {
 827        init_test(cx, |_| {});
 828
 829        let mut cx = EditorLspTestContext::new_rust(
 830            lsp::ServerCapabilities {
 831                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
 832                ..Default::default()
 833            },
 834            cx,
 835        )
 836        .await;
 837
 838        // Basic hover delays and then pops without moving the mouse
 839        cx.set_state(indoc! {"
 840            fn ˇtest() { println!(); }
 841        "});
 842        let hover_point = cx.display_point(indoc! {"
 843            fn test() { printˇln!(); }
 844        "});
 845
 846        cx.update_editor(|editor, cx| {
 847            let snapshot = editor.snapshot(cx);
 848            let anchor = snapshot
 849                .buffer_snapshot
 850                .anchor_before(hover_point.to_offset(&snapshot, Bias::Left));
 851            hover_at(editor, Some(anchor), cx)
 852        });
 853        assert!(!cx.editor(|editor, _| editor.hover_state.visible()));
 854
 855        // After delay, hover should be visible.
 856        let symbol_range = cx.lsp_range(indoc! {"
 857            fn test() { «println!»(); }
 858        "});
 859        let mut requests =
 860            cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
 861                Ok(Some(lsp::Hover {
 862                    contents: lsp::HoverContents::Markup(lsp::MarkupContent {
 863                        kind: lsp::MarkupKind::Markdown,
 864                        value: "some basic docs".to_string(),
 865                    }),
 866                    range: Some(symbol_range),
 867                }))
 868            });
 869        cx.background_executor
 870            .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
 871        requests.next().await;
 872
 873        cx.editor(|editor, _| {
 874            assert!(editor.hover_state.visible());
 875            assert_eq!(
 876                editor.hover_state.info_popovers.len(),
 877                1,
 878                "Expected exactly one hover but got: {:?}",
 879                editor.hover_state.info_popovers
 880            );
 881            let rendered = editor
 882                .hover_state
 883                .info_popovers
 884                .first()
 885                .cloned()
 886                .unwrap()
 887                .parsed_content;
 888            assert_eq!(rendered.text, "some basic docs".to_string())
 889        });
 890
 891        // Mouse moved with no hover response dismisses
 892        let hover_point = cx.display_point(indoc! {"
 893            fn teˇst() { println!(); }
 894        "});
 895        let mut request = cx
 896            .lsp
 897            .handle_request::<lsp::request::HoverRequest, _, _>(|_, _| async move { Ok(None) });
 898        cx.update_editor(|editor, cx| {
 899            let snapshot = editor.snapshot(cx);
 900            let anchor = snapshot
 901                .buffer_snapshot
 902                .anchor_before(hover_point.to_offset(&snapshot, Bias::Left));
 903            hover_at(editor, Some(anchor), cx)
 904        });
 905        cx.background_executor
 906            .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
 907        request.next().await;
 908        cx.editor(|editor, _| {
 909            assert!(!editor.hover_state.visible());
 910        });
 911    }
 912
 913    #[gpui::test]
 914    async fn test_keyboard_hover_info_popover(cx: &mut gpui::TestAppContext) {
 915        init_test(cx, |_| {});
 916
 917        let mut cx = EditorLspTestContext::new_rust(
 918            lsp::ServerCapabilities {
 919                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
 920                ..Default::default()
 921            },
 922            cx,
 923        )
 924        .await;
 925
 926        // Hover with keyboard has no delay
 927        cx.set_state(indoc! {"
 928            fˇn test() { println!(); }
 929        "});
 930        cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
 931        let symbol_range = cx.lsp_range(indoc! {"
 932            «fn» test() { println!(); }
 933        "});
 934        cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
 935            Ok(Some(lsp::Hover {
 936                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
 937                    kind: lsp::MarkupKind::Markdown,
 938                    value: "some other basic docs".to_string(),
 939                }),
 940                range: Some(symbol_range),
 941            }))
 942        })
 943        .next()
 944        .await;
 945
 946        cx.condition(|editor, _| editor.hover_state.visible()).await;
 947        cx.editor(|editor, _| {
 948            assert_eq!(
 949                editor.hover_state.info_popovers.len(),
 950                1,
 951                "Expected exactly one hover but got: {:?}",
 952                editor.hover_state.info_popovers
 953            );
 954            let rendered = editor
 955                .hover_state
 956                .info_popovers
 957                .first()
 958                .cloned()
 959                .unwrap()
 960                .parsed_content;
 961            assert_eq!(rendered.text, "some other basic docs".to_string())
 962        });
 963    }
 964
 965    #[gpui::test]
 966    async fn test_empty_hovers_filtered(cx: &mut gpui::TestAppContext) {
 967        init_test(cx, |_| {});
 968
 969        let mut cx = EditorLspTestContext::new_rust(
 970            lsp::ServerCapabilities {
 971                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
 972                ..Default::default()
 973            },
 974            cx,
 975        )
 976        .await;
 977
 978        // Hover with keyboard has no delay
 979        cx.set_state(indoc! {"
 980            fˇn test() { println!(); }
 981        "});
 982        cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
 983        let symbol_range = cx.lsp_range(indoc! {"
 984            «fn» test() { println!(); }
 985        "});
 986        cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
 987            Ok(Some(lsp::Hover {
 988                contents: lsp::HoverContents::Array(vec![
 989                    lsp::MarkedString::String("regular text for hover to show".to_string()),
 990                    lsp::MarkedString::String("".to_string()),
 991                    lsp::MarkedString::LanguageString(lsp::LanguageString {
 992                        language: "Rust".to_string(),
 993                        value: "".to_string(),
 994                    }),
 995                ]),
 996                range: Some(symbol_range),
 997            }))
 998        })
 999        .next()
1000        .await;
1001
1002        cx.condition(|editor, _| editor.hover_state.visible()).await;
1003        cx.editor(|editor, _| {
1004            assert_eq!(
1005                editor.hover_state.info_popovers.len(),
1006                1,
1007                "Expected exactly one hover but got: {:?}",
1008                editor.hover_state.info_popovers
1009            );
1010            let rendered = editor
1011                .hover_state
1012                .info_popovers
1013                .first()
1014                .cloned()
1015                .unwrap()
1016                .parsed_content;
1017            assert_eq!(
1018                rendered.text,
1019                "regular text for hover to show".to_string(),
1020                "No empty string hovers should be shown"
1021            );
1022        });
1023    }
1024
1025    #[gpui::test]
1026    async fn test_line_ends_trimmed(cx: &mut gpui::TestAppContext) {
1027        init_test(cx, |_| {});
1028
1029        let mut cx = EditorLspTestContext::new_rust(
1030            lsp::ServerCapabilities {
1031                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
1032                ..Default::default()
1033            },
1034            cx,
1035        )
1036        .await;
1037
1038        // Hover with keyboard has no delay
1039        cx.set_state(indoc! {"
1040            fˇn test() { println!(); }
1041        "});
1042        cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
1043        let symbol_range = cx.lsp_range(indoc! {"
1044            «fn» test() { println!(); }
1045        "});
1046
1047        let code_str = "\nlet hovered_point: Vector2F // size = 8, align = 0x4\n";
1048        let markdown_string = format!("\n```rust\n{code_str}```");
1049
1050        let closure_markdown_string = markdown_string.clone();
1051        cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| {
1052            let future_markdown_string = closure_markdown_string.clone();
1053            async move {
1054                Ok(Some(lsp::Hover {
1055                    contents: lsp::HoverContents::Markup(lsp::MarkupContent {
1056                        kind: lsp::MarkupKind::Markdown,
1057                        value: future_markdown_string,
1058                    }),
1059                    range: Some(symbol_range),
1060                }))
1061            }
1062        })
1063        .next()
1064        .await;
1065
1066        cx.condition(|editor, _| editor.hover_state.visible()).await;
1067        cx.editor(|editor, _| {
1068            assert_eq!(
1069                editor.hover_state.info_popovers.len(),
1070                1,
1071                "Expected exactly one hover but got: {:?}",
1072                editor.hover_state.info_popovers
1073            );
1074            let rendered = editor
1075                .hover_state
1076                .info_popovers
1077                .first()
1078                .cloned()
1079                .unwrap()
1080                .parsed_content;
1081            assert_eq!(
1082                rendered.text,
1083                code_str.trim(),
1084                "Should not have extra line breaks at end of rendered hover"
1085            );
1086        });
1087    }
1088
1089    #[gpui::test]
1090    async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) {
1091        init_test(cx, |_| {});
1092
1093        let mut cx = EditorLspTestContext::new_rust(
1094            lsp::ServerCapabilities {
1095                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
1096                ..Default::default()
1097            },
1098            cx,
1099        )
1100        .await;
1101
1102        // Hover with just diagnostic, pops DiagnosticPopover immediately and then
1103        // info popover once request completes
1104        cx.set_state(indoc! {"
1105            fn teˇst() { println!(); }
1106        "});
1107
1108        // Send diagnostic to client
1109        let range = cx.text_anchor_range(indoc! {"
1110            fn «test»() { println!(); }
1111        "});
1112        cx.update_buffer(|buffer, cx| {
1113            let snapshot = buffer.text_snapshot();
1114            let set = DiagnosticSet::from_sorted_entries(
1115                vec![DiagnosticEntry {
1116                    range,
1117                    diagnostic: Diagnostic {
1118                        message: "A test diagnostic message.".to_string(),
1119                        ..Default::default()
1120                    },
1121                }],
1122                &snapshot,
1123            );
1124            buffer.update_diagnostics(LanguageServerId(0), set, cx);
1125        });
1126
1127        // Hover pops diagnostic immediately
1128        cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
1129        cx.background_executor.run_until_parked();
1130
1131        cx.editor(|Editor { hover_state, .. }, _| {
1132            assert!(
1133                hover_state.diagnostic_popover.is_some() && hover_state.info_popovers.is_empty()
1134            )
1135        });
1136
1137        // Info Popover shows after request responded to
1138        let range = cx.lsp_range(indoc! {"
1139            fn «test»() { println!(); }
1140        "});
1141        cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
1142            Ok(Some(lsp::Hover {
1143                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
1144                    kind: lsp::MarkupKind::Markdown,
1145                    value: "some new docs".to_string(),
1146                }),
1147                range: Some(range),
1148            }))
1149        });
1150        cx.background_executor
1151            .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
1152
1153        cx.background_executor.run_until_parked();
1154        cx.editor(|Editor { hover_state, .. }, _| {
1155            hover_state.diagnostic_popover.is_some() && hover_state.info_task.is_some()
1156        });
1157    }
1158
1159    #[gpui::test]
1160    fn test_render_blocks(cx: &mut gpui::TestAppContext) {
1161        init_test(cx, |_| {});
1162
1163        let languages = Arc::new(LanguageRegistry::test(cx.executor()));
1164        let editor = cx.add_window(|cx| Editor::single_line(cx));
1165        editor
1166            .update(cx, |editor, _cx| {
1167                let style = editor.style.clone().unwrap();
1168
1169                struct Row {
1170                    blocks: Vec<HoverBlock>,
1171                    expected_marked_text: String,
1172                    expected_styles: Vec<HighlightStyle>,
1173                }
1174
1175                let rows = &[
1176                    // Strong emphasis
1177                    Row {
1178                        blocks: vec![HoverBlock {
1179                            text: "one **two** three".to_string(),
1180                            kind: HoverBlockKind::Markdown,
1181                        }],
1182                        expected_marked_text: "one «two» three".to_string(),
1183                        expected_styles: vec![HighlightStyle {
1184                            font_weight: Some(FontWeight::BOLD),
1185                            ..Default::default()
1186                        }],
1187                    },
1188                    // Links
1189                    Row {
1190                        blocks: vec![HoverBlock {
1191                            text: "one [two](https://the-url) three".to_string(),
1192                            kind: HoverBlockKind::Markdown,
1193                        }],
1194                        expected_marked_text: "one «two» three".to_string(),
1195                        expected_styles: vec![HighlightStyle {
1196                            underline: Some(UnderlineStyle {
1197                                thickness: 1.0.into(),
1198                                ..Default::default()
1199                            }),
1200                            ..Default::default()
1201                        }],
1202                    },
1203                    // Lists
1204                    Row {
1205                        blocks: vec![HoverBlock {
1206                            text: "
1207                            lists:
1208                            * one
1209                                - a
1210                                - b
1211                            * two
1212                                - [c](https://the-url)
1213                                - d"
1214                            .unindent(),
1215                            kind: HoverBlockKind::Markdown,
1216                        }],
1217                        expected_marked_text: "
1218                        lists:
1219                        - one
1220                          - a
1221                          - b
1222                        - two
1223                          - «c»
1224                          - d"
1225                        .unindent(),
1226                        expected_styles: vec![HighlightStyle {
1227                            underline: Some(UnderlineStyle {
1228                                thickness: 1.0.into(),
1229                                ..Default::default()
1230                            }),
1231                            ..Default::default()
1232                        }],
1233                    },
1234                    // Multi-paragraph list items
1235                    Row {
1236                        blocks: vec![HoverBlock {
1237                            text: "
1238                            * one two
1239                              three
1240
1241                            * four five
1242                                * six seven
1243                                  eight
1244
1245                                  nine
1246                                * ten
1247                            * six"
1248                                .unindent(),
1249                            kind: HoverBlockKind::Markdown,
1250                        }],
1251                        expected_marked_text: "
1252                        - one two three
1253                        - four five
1254                          - six seven eight
1255
1256                            nine
1257                          - ten
1258                        - six"
1259                            .unindent(),
1260                        expected_styles: vec![HighlightStyle {
1261                            underline: Some(UnderlineStyle {
1262                                thickness: 1.0.into(),
1263                                ..Default::default()
1264                            }),
1265                            ..Default::default()
1266                        }],
1267                    },
1268                ];
1269
1270                for Row {
1271                    blocks,
1272                    expected_marked_text,
1273                    expected_styles,
1274                } in &rows[0..]
1275                {
1276                    let rendered = smol::block_on(parse_blocks(&blocks, &languages, None));
1277
1278                    let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false);
1279                    let expected_highlights = ranges
1280                        .into_iter()
1281                        .zip(expected_styles.iter().cloned())
1282                        .collect::<Vec<_>>();
1283                    assert_eq!(
1284                        rendered.text, expected_text,
1285                        "wrong text for input {blocks:?}"
1286                    );
1287
1288                    let rendered_highlights: Vec<_> = rendered
1289                        .highlights
1290                        .iter()
1291                        .filter_map(|(range, highlight)| {
1292                            let highlight = highlight.to_highlight_style(&style.syntax)?;
1293                            Some((range.clone(), highlight))
1294                        })
1295                        .collect();
1296
1297                    assert_eq!(
1298                        rendered_highlights, expected_highlights,
1299                        "wrong highlights for input {blocks:?}"
1300                    );
1301                }
1302            })
1303            .unwrap();
1304    }
1305
1306    #[gpui::test]
1307    async fn test_hover_inlay_label_parts(cx: &mut gpui::TestAppContext) {
1308        init_test(cx, |settings| {
1309            settings.defaults.inlay_hints = Some(InlayHintSettings {
1310                enabled: true,
1311                edit_debounce_ms: 0,
1312                scroll_debounce_ms: 0,
1313                show_type_hints: true,
1314                show_parameter_hints: true,
1315                show_other_hints: true,
1316            })
1317        });
1318
1319        let mut cx = EditorLspTestContext::new_rust(
1320            lsp::ServerCapabilities {
1321                inlay_hint_provider: Some(lsp::OneOf::Right(
1322                    lsp::InlayHintServerCapabilities::Options(lsp::InlayHintOptions {
1323                        resolve_provider: Some(true),
1324                        ..Default::default()
1325                    }),
1326                )),
1327                ..Default::default()
1328            },
1329            cx,
1330        )
1331        .await;
1332
1333        cx.set_state(indoc! {"
1334            struct TestStruct;
1335
1336            // ==================
1337
1338            struct TestNewType<T>(T);
1339
1340            fn main() {
1341                let variableˇ = TestNewType(TestStruct);
1342            }
1343        "});
1344
1345        let hint_start_offset = cx.ranges(indoc! {"
1346            struct TestStruct;
1347
1348            // ==================
1349
1350            struct TestNewType<T>(T);
1351
1352            fn main() {
1353                let variableˇ = TestNewType(TestStruct);
1354            }
1355        "})[0]
1356            .start;
1357        let hint_position = cx.to_lsp(hint_start_offset);
1358        let new_type_target_range = cx.lsp_range(indoc! {"
1359            struct TestStruct;
1360
1361            // ==================
1362
1363            struct «TestNewType»<T>(T);
1364
1365            fn main() {
1366                let variable = TestNewType(TestStruct);
1367            }
1368        "});
1369        let struct_target_range = cx.lsp_range(indoc! {"
1370            struct «TestStruct»;
1371
1372            // ==================
1373
1374            struct TestNewType<T>(T);
1375
1376            fn main() {
1377                let variable = TestNewType(TestStruct);
1378            }
1379        "});
1380
1381        let uri = cx.buffer_lsp_url.clone();
1382        let new_type_label = "TestNewType";
1383        let struct_label = "TestStruct";
1384        let entire_hint_label = ": TestNewType<TestStruct>";
1385        let closure_uri = uri.clone();
1386        cx.lsp
1387            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1388                let task_uri = closure_uri.clone();
1389                async move {
1390                    assert_eq!(params.text_document.uri, task_uri);
1391                    Ok(Some(vec![lsp::InlayHint {
1392                        position: hint_position,
1393                        label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart {
1394                            value: entire_hint_label.to_string(),
1395                            ..Default::default()
1396                        }]),
1397                        kind: Some(lsp::InlayHintKind::TYPE),
1398                        text_edits: None,
1399                        tooltip: None,
1400                        padding_left: Some(false),
1401                        padding_right: Some(false),
1402                        data: None,
1403                    }]))
1404                }
1405            })
1406            .next()
1407            .await;
1408        cx.background_executor.run_until_parked();
1409        cx.update_editor(|editor, cx| {
1410            let expected_layers = vec![entire_hint_label.to_string()];
1411            assert_eq!(expected_layers, cached_hint_labels(editor));
1412            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1413        });
1414
1415        let inlay_range = cx
1416            .ranges(indoc! {"
1417                struct TestStruct;
1418
1419                // ==================
1420
1421                struct TestNewType<T>(T);
1422
1423                fn main() {
1424                    let variable« »= TestNewType(TestStruct);
1425                }
1426        "})
1427            .get(0)
1428            .cloned()
1429            .unwrap();
1430        let new_type_hint_part_hover_position = cx.update_editor(|editor, cx| {
1431            let snapshot = editor.snapshot(cx);
1432            let previous_valid = inlay_range.start.to_display_point(&snapshot);
1433            let next_valid = inlay_range.end.to_display_point(&snapshot);
1434            assert_eq!(previous_valid.row(), next_valid.row());
1435            assert!(previous_valid.column() < next_valid.column());
1436            let exact_unclipped = DisplayPoint::new(
1437                previous_valid.row(),
1438                previous_valid.column()
1439                    + (entire_hint_label.find(new_type_label).unwrap() + new_type_label.len() / 2)
1440                        as u32,
1441            );
1442            PointForPosition {
1443                previous_valid,
1444                next_valid,
1445                exact_unclipped,
1446                column_overshoot_after_line_end: 0,
1447            }
1448        });
1449        cx.update_editor(|editor, cx| {
1450            update_inlay_link_and_hover_points(
1451                &editor.snapshot(cx),
1452                new_type_hint_part_hover_position,
1453                editor,
1454                true,
1455                false,
1456                cx,
1457            );
1458        });
1459
1460        let resolve_closure_uri = uri.clone();
1461        cx.lsp
1462            .handle_request::<lsp::request::InlayHintResolveRequest, _, _>(
1463                move |mut hint_to_resolve, _| {
1464                    let mut resolved_hint_positions = BTreeSet::new();
1465                    let task_uri = resolve_closure_uri.clone();
1466                    async move {
1467                        let inserted = resolved_hint_positions.insert(hint_to_resolve.position);
1468                        assert!(inserted, "Hint {hint_to_resolve:?} was resolved twice");
1469
1470                        // `: TestNewType<TestStruct>`
1471                        hint_to_resolve.label = lsp::InlayHintLabel::LabelParts(vec![
1472                            lsp::InlayHintLabelPart {
1473                                value: ": ".to_string(),
1474                                ..Default::default()
1475                            },
1476                            lsp::InlayHintLabelPart {
1477                                value: new_type_label.to_string(),
1478                                location: Some(lsp::Location {
1479                                    uri: task_uri.clone(),
1480                                    range: new_type_target_range,
1481                                }),
1482                                tooltip: Some(lsp::InlayHintLabelPartTooltip::String(format!(
1483                                    "A tooltip for `{new_type_label}`"
1484                                ))),
1485                                ..Default::default()
1486                            },
1487                            lsp::InlayHintLabelPart {
1488                                value: "<".to_string(),
1489                                ..Default::default()
1490                            },
1491                            lsp::InlayHintLabelPart {
1492                                value: struct_label.to_string(),
1493                                location: Some(lsp::Location {
1494                                    uri: task_uri,
1495                                    range: struct_target_range,
1496                                }),
1497                                tooltip: Some(lsp::InlayHintLabelPartTooltip::MarkupContent(
1498                                    lsp::MarkupContent {
1499                                        kind: lsp::MarkupKind::Markdown,
1500                                        value: format!("A tooltip for `{struct_label}`"),
1501                                    },
1502                                )),
1503                                ..Default::default()
1504                            },
1505                            lsp::InlayHintLabelPart {
1506                                value: ">".to_string(),
1507                                ..Default::default()
1508                            },
1509                        ]);
1510
1511                        Ok(hint_to_resolve)
1512                    }
1513                },
1514            )
1515            .next()
1516            .await;
1517        cx.background_executor.run_until_parked();
1518
1519        cx.update_editor(|editor, cx| {
1520            update_inlay_link_and_hover_points(
1521                &editor.snapshot(cx),
1522                new_type_hint_part_hover_position,
1523                editor,
1524                true,
1525                false,
1526                cx,
1527            );
1528        });
1529        cx.background_executor
1530            .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
1531        cx.background_executor.run_until_parked();
1532        cx.update_editor(|editor, cx| {
1533            let hover_state = &editor.hover_state;
1534            assert!(
1535                hover_state.diagnostic_popover.is_none() && hover_state.info_popovers.len() == 1
1536            );
1537            let popover = hover_state.info_popovers.first().cloned().unwrap();
1538            let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
1539            assert_eq!(
1540                popover.symbol_range,
1541                RangeInEditor::Inlay(InlayHighlight {
1542                    inlay: InlayId::Hint(0),
1543                    inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right),
1544                    range: ": ".len()..": ".len() + new_type_label.len(),
1545                }),
1546                "Popover range should match the new type label part"
1547            );
1548            assert_eq!(
1549                popover.parsed_content.text,
1550                format!("A tooltip for `{new_type_label}`"),
1551                "Rendered text should not anyhow alter backticks"
1552            );
1553        });
1554
1555        let struct_hint_part_hover_position = cx.update_editor(|editor, cx| {
1556            let snapshot = editor.snapshot(cx);
1557            let previous_valid = inlay_range.start.to_display_point(&snapshot);
1558            let next_valid = inlay_range.end.to_display_point(&snapshot);
1559            assert_eq!(previous_valid.row(), next_valid.row());
1560            assert!(previous_valid.column() < next_valid.column());
1561            let exact_unclipped = DisplayPoint::new(
1562                previous_valid.row(),
1563                previous_valid.column()
1564                    + (entire_hint_label.find(struct_label).unwrap() + struct_label.len() / 2)
1565                        as u32,
1566            );
1567            PointForPosition {
1568                previous_valid,
1569                next_valid,
1570                exact_unclipped,
1571                column_overshoot_after_line_end: 0,
1572            }
1573        });
1574        cx.update_editor(|editor, cx| {
1575            update_inlay_link_and_hover_points(
1576                &editor.snapshot(cx),
1577                struct_hint_part_hover_position,
1578                editor,
1579                true,
1580                false,
1581                cx,
1582            );
1583        });
1584        cx.background_executor
1585            .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
1586        cx.background_executor.run_until_parked();
1587        cx.update_editor(|editor, cx| {
1588            let hover_state = &editor.hover_state;
1589            assert!(
1590                hover_state.diagnostic_popover.is_none() && hover_state.info_popovers.len() == 1
1591            );
1592            let popover = hover_state.info_popovers.first().cloned().unwrap();
1593            let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
1594            assert_eq!(
1595                popover.symbol_range,
1596                RangeInEditor::Inlay(InlayHighlight {
1597                    inlay: InlayId::Hint(0),
1598                    inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right),
1599                    range: ": ".len() + new_type_label.len() + "<".len()
1600                        ..": ".len() + new_type_label.len() + "<".len() + struct_label.len(),
1601                }),
1602                "Popover range should match the struct label part"
1603            );
1604            assert_eq!(
1605                popover.parsed_content.text,
1606                format!("A tooltip for {struct_label}"),
1607                "Rendered markdown element should remove backticks from text"
1608            );
1609        });
1610    }
1611}