1418 lines · 50.9 KB · Rust
RawDownload
use crate::{    display_map::{InlayOffset, ToDisplayPoint},    hover_links::{InlayHighlight, RangeInEditor},    Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings, EditorSnapshot,    EditorStyle, ExcerptId, Hover, RangeToAnchorExt,};use futures::{stream::FuturesUnordered, FutureExt};use gpui::{    div, px, AnyElement, CursorStyle, Hsla, InteractiveElement, IntoElement, MouseButton,    ParentElement, Pixels, SharedString, Size, StatefulInteractiveElement, Styled, Task,    ViewContext, WeakView,};use language::{markdown, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown}; use lsp::DiagnosticSeverity;use multi_buffer::ToOffset;use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart};use settings::Settings;use smol::stream::StreamExt;use std::{ops::Range, sync::Arc, time::Duration};use ui::{prelude::*, Tooltip};use util::TryFutureExt;use workspace::Workspace; pub const HOVER_DELAY_MILLIS: u64 = 350;pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200; pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.;pub const MIN_POPOVER_LINE_HEIGHT: Pixels = px(4.);pub const HOVER_POPOVER_GAP: Pixels = px(10.); /// Bindable action which uses the most recent selection head to trigger a hoverpub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) {    let head = editor.selections.newest_anchor().head();    show_hover(editor, head, true, cx);} /// The internal hover action dispatches between `show_hover` or `hide_hover`/// depending on whether a point to hover over is provided.pub fn hover_at(editor: &mut Editor, anchor: Option<Anchor>, cx: &mut ViewContext<Editor>) {    if EditorSettings::get_global(cx).hover_popover_enabled {        if let Some(anchor) = anchor {            show_hover(editor, anchor, false, cx);        } else {            hide_hover(editor, cx);        }    }} pub struct InlayHover {    pub excerpt: ExcerptId,    pub range: InlayHighlight,    pub tooltip: HoverBlock,} pub fn find_hovered_hint_part(    label_parts: Vec<InlayHintLabelPart>,    hint_start: InlayOffset,    hovered_offset: InlayOffset,) -> Option<(InlayHintLabelPart, Range<InlayOffset>)> {    if hovered_offset >= hint_start {        let mut hovered_character = (hovered_offset - hint_start).0;        let mut part_start = hint_start;        for part in label_parts {            let part_len = part.value.chars().count();            if hovered_character > part_len {                hovered_character -= part_len;                part_start.0 += part_len;            } else {                let part_end = InlayOffset(part_start.0 + part_len);                return Some((part, part_start..part_end));            }        }    }    None} pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut ViewContext<Editor>) {    if EditorSettings::get_global(cx).hover_popover_enabled {        if editor.pending_rename.is_some() {            return;        }         let Some(project) = editor.project.clone() else {            return;        };         if editor            .hover_state            .info_popovers            .iter()            .any(|InfoPopover { symbol_range, .. }| {                if let RangeInEditor::Inlay(range) = symbol_range {                    if range == &inlay_hover.range {                        // Hover triggered from same location as last time. Don't show again.                        return true;                    }                }                false            })        {            hide_hover(editor, cx);        }         let task = cx.spawn(|this, mut cx| {            async move {                cx.background_executor()                    .timer(Duration::from_millis(HOVER_DELAY_MILLIS))                    .await;                this.update(&mut cx, |this, _| {                    this.hover_state.diagnostic_popover = None;                })?;                 let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?;                let blocks = vec![inlay_hover.tooltip];                let parsed_content = parse_blocks(&blocks, &language_registry, None).await;                 let hover_popover = InfoPopover {                    symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),                    parsed_content,                };                 this.update(&mut cx, |this, cx| {                    // TODO: no background highlights happen for inlays currently                    this.hover_state.info_popovers = vec![hover_popover];                    cx.notify();                })?;                 anyhow::Ok(())            }            .log_err()        });         editor.hover_state.info_task = Some(task);    }} /// Hides the type information popup./// Triggered by the `Hover` action when the cursor is not over a symbol or when the/// selections changed.pub fn hide_hover(editor: &mut Editor, cx: &mut ViewContext<Editor>) -> bool {    let info_popovers = editor.hover_state.info_popovers.drain(..);    let diagnostics_popover = editor.hover_state.diagnostic_popover.take();    let did_hide = info_popovers.count() > 0 || diagnostics_popover.is_some();     editor.hover_state.info_task = None;    editor.hover_state.triggered_from = None;     editor.clear_background_highlights::<HoverState>(cx);     if did_hide {        cx.notify();    }     did_hide} /// Queries the LSP and shows type info and documentation/// about the symbol the mouse is currently hovering over./// Triggered by the `Hover` action when the cursor may be over a symbol.fn show_hover(    editor: &mut Editor,    anchor: Anchor,    ignore_timeout: bool,    cx: &mut ViewContext<Editor>,) {    if editor.pending_rename.is_some() {        return;    }     let snapshot = editor.snapshot(cx);     let (buffer, buffer_position) =        if let Some(output) = editor.buffer.read(cx).text_anchor_for_position(anchor, cx) {            output        } else {            return;        };     let excerpt_id =        if let Some((excerpt_id, _, _)) = editor.buffer().read(cx).excerpt_containing(anchor, cx) {            excerpt_id        } else {            return;        };     let project = if let Some(project) = editor.project.clone() {        project    } else {        return;    };     if !ignore_timeout {        if editor            .hover_state            .info_popovers            .iter()            .any(|InfoPopover { symbol_range, .. }| {                symbol_range                    .as_text_range()                    .map(|range| {                        let hover_range = range.to_offset(&snapshot.buffer_snapshot);                        let offset = anchor.to_offset(&snapshot.buffer_snapshot);                        // LSP returns a hover result for the end index of ranges that should be hovered, so we need to                        // use an inclusive range here to check if we should dismiss the popover                        (hover_range.start..=hover_range.end).contains(&offset)                    })                    .unwrap_or(false)            })        {            // Hover triggered from same location as last time. Don't show again.            return;        } else {            hide_hover(editor, cx);        }    }     // Don't request again if the location is the same as the previous request    if let Some(triggered_from) = &editor.hover_state.triggered_from {        if triggered_from            .cmp(&anchor, &snapshot.buffer_snapshot)            .is_eq()        {            return;        }    }     let task = cx.spawn(|this, mut cx| {        async move {            // If we need to delay, delay a set amount initially before making the lsp request            let delay = if ignore_timeout {                None            } else {                // Construct delay task to wait for later                let total_delay = Some(                    cx.background_executor()                        .timer(Duration::from_millis(HOVER_DELAY_MILLIS)),                );                 cx.background_executor()                    .timer(Duration::from_millis(HOVER_REQUEST_DELAY_MILLIS))                    .await;                total_delay            };             // query the LSP for hover info            let hover_request = cx.update(|cx| {                project.update(cx, |project, cx| {                    project.hover(&buffer, buffer_position, cx)                })            })?;             if let Some(delay) = delay {                delay.await;            }             // If there's a diagnostic, assign it on the hover state and notify            let local_diagnostic = snapshot                .buffer_snapshot                .diagnostics_in_range::<_, usize>(anchor..anchor, false)                // Find the entry with the most specific range                .min_by_key(|entry| entry.range.end - entry.range.start)                .map(|entry| DiagnosticEntry {                    diagnostic: entry.diagnostic,                    range: entry.range.to_anchors(&snapshot.buffer_snapshot),                });             // Pull the primary diagnostic out so we can jump to it if the popover is clicked            let primary_diagnostic = local_diagnostic.as_ref().and_then(|local_diagnostic| {                snapshot                    .buffer_snapshot                    .diagnostic_group::<usize>(local_diagnostic.diagnostic.group_id)                    .find(|diagnostic| diagnostic.diagnostic.is_primary)                    .map(|entry| DiagnosticEntry {                        diagnostic: entry.diagnostic,                        range: entry.range.to_anchors(&snapshot.buffer_snapshot),                    })            });             this.update(&mut cx, |this, _| {                this.hover_state.diagnostic_popover =                    local_diagnostic.map(|local_diagnostic| DiagnosticPopover {                        local_diagnostic,                        primary_diagnostic,                    });            })?;             let hovers_response = hover_request.await;            let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?;            let snapshot = this.update(&mut cx, |this, cx| this.snapshot(cx))?;            let mut hover_highlights = Vec::with_capacity(hovers_response.len());            let mut info_popovers = Vec::with_capacity(hovers_response.len());            let mut info_popover_tasks = hovers_response                .into_iter()                .map(|hover_result| async {                    // Create symbol range of anchors for highlighting and filtering of future requests.                    let range = hover_result                        .range                        .and_then(|range| {                            let start = snapshot                                .buffer_snapshot                                .anchor_in_excerpt(excerpt_id, range.start)?;                            let end = snapshot                                .buffer_snapshot                                .anchor_in_excerpt(excerpt_id, range.end)?;                             Some(start..end)                        })                        .unwrap_or_else(|| anchor..anchor);                     let blocks = hover_result.contents;                    let language = hover_result.language;                    let parsed_content = parse_blocks(&blocks, &language_registry, language).await;                     (                        range.clone(),                        InfoPopover {                            symbol_range: RangeInEditor::Text(range),                            parsed_content,                        },                    )                })                .collect::<FuturesUnordered<_>>();            while let Some((highlight_range, info_popover)) = info_popover_tasks.next().await {                hover_highlights.push(highlight_range);                info_popovers.push(info_popover);            }             this.update(&mut cx, |editor, cx| {                if hover_highlights.is_empty() {                    editor.clear_background_highlights::<HoverState>(cx);                } else {                    // Highlight the selected symbol using a background highlight                    editor.highlight_background::<HoverState>(                        &hover_highlights,                        |theme| theme.element_hover, // todo update theme                        cx,                    );                }                 editor.hover_state.info_popovers = info_popovers;                cx.notify();                cx.refresh();            })?;             anyhow::Ok(())        }        .log_err()    });     editor.hover_state.info_task = Some(task);} async fn parse_blocks(    blocks: &[HoverBlock],    language_registry: &Arc<LanguageRegistry>,    language: Option<Arc<Language>>,) -> markdown::ParsedMarkdown {    let mut text = String::new();    let mut highlights = Vec::new();    let mut region_ranges = Vec::new();    let mut regions = Vec::new();     for block in blocks {        match &block.kind {            HoverBlockKind::PlainText => {                markdown::new_paragraph(&mut text, &mut Vec::new());                text.push_str(&block.text.replace("\\n", "\n"));            }             HoverBlockKind::Markdown => {                markdown::parse_markdown_block(                    &block.text.replace("\\n", "\n"),                    language_registry,                    language.clone(),                    &mut text,                    &mut highlights,                    &mut region_ranges,                    &mut regions,                )                .await            }             HoverBlockKind::Code { language } => {                if let Some(language) = language_registry                    .language_for_name(language)                    .now_or_never()                    .and_then(Result::ok)                {                    markdown::highlight_code(&mut text, &mut highlights, &block.text, &language);                } else {                    text.push_str(&block.text);                }            }        }    }     let leading_space = text.chars().take_while(|c| c.is_whitespace()).count();    if leading_space > 0 {        highlights = highlights            .into_iter()            .map(|(range, style)| {                (                    range.start.saturating_sub(leading_space)                        ..range.end.saturating_sub(leading_space),                    style,                )            })            .collect();        region_ranges = region_ranges            .into_iter()            .map(|range| {                range.start.saturating_sub(leading_space)..range.end.saturating_sub(leading_space)            })            .collect();    }     ParsedMarkdown {        text: text.trim().to_string(),        highlights,        region_ranges,        regions,    }} #[derive(Default)]pub struct HoverState {    pub info_popovers: Vec<InfoPopover>,    pub diagnostic_popover: Option<DiagnosticPopover>,    pub triggered_from: Option<Anchor>,    pub info_task: Option<Task<Option<()>>>,} impl HoverState {    pub fn visible(&self) -> bool {        !self.info_popovers.is_empty() || self.diagnostic_popover.is_some()    }     pub fn render(        &mut self,        snapshot: &EditorSnapshot,        style: &EditorStyle,        visible_rows: Range<DisplayRow>,        max_size: Size<Pixels>,        workspace: Option<WeakView<Workspace>>,        cx: &mut ViewContext<Editor>,    ) -> Option<(DisplayPoint, Vec<AnyElement>)> {        // If there is a diagnostic, position the popovers based on that.        // Otherwise use the start of the hover range        let anchor = self            .diagnostic_popover            .as_ref()            .map(|diagnostic_popover| &diagnostic_popover.local_diagnostic.range.start)            .or_else(|| {                self.info_popovers.iter().find_map(|info_popover| {                    match &info_popover.symbol_range {                        RangeInEditor::Text(range) => Some(&range.start),                        RangeInEditor::Inlay(_) => None,                    }                })            })            .or_else(|| {                self.info_popovers.iter().find_map(|info_popover| {                    match &info_popover.symbol_range {                        RangeInEditor::Text(_) => None,                        RangeInEditor::Inlay(range) => Some(&range.inlay_position),                    }                })            })?;        let point = anchor.to_display_point(&snapshot.display_snapshot);         // Don't render if the relevant point isn't on screen        if !self.visible() || !visible_rows.contains(&point.row()) {            return None;        }         let mut elements = Vec::new();         if let Some(diagnostic_popover) = self.diagnostic_popover.as_ref() {            elements.push(diagnostic_popover.render(style, max_size, cx));        }        for info_popover in &mut self.info_popovers {            elements.push(info_popover.render(style, max_size, workspace.clone(), cx));        }         Some((point, elements))    }} #[derive(Debug, Clone)]pub struct InfoPopover {    symbol_range: RangeInEditor,    parsed_content: ParsedMarkdown,} impl InfoPopover {    pub fn render(        &mut self,        style: &EditorStyle,        max_size: Size<Pixels>,

Lines 1–500 of 1418.