2152 lines · 79.5 KB · Rust
RawDownload
use crate::{    ActiveDiagnostic, Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings,    EditorSnapshot, GlobalDiagnosticRenderer, HighlightKey, Hover,    display_map::{InlayOffset, ToDisplayPoint, is_invisible},    editor_settings::EditorSettingsScrollbarProxy,    hover_links::{InlayHighlight, RangeInEditor},    movement::TextLayoutDetails,    scroll::ScrollAmount,};use anyhow::Context as _;use gpui::{    AnyElement, App, AsyncWindowContext, Bounds, Context, Entity, Focusable as _, FontWeight, Hsla,    InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, ScrollHandle, Size,    StatefulInteractiveElement, StyleRefinement, Styled, Subscription, Task, TextStyleRefinement,    Window, canvas, div, px,};use itertools::Itertools;use language::{DiagnosticEntry, Language, LanguageRegistry};use lsp::DiagnosticSeverity;use markdown::{CopyButtonVisibility, Markdown, MarkdownElement, MarkdownStyle};use multi_buffer::{MultiBufferOffset, ToOffset, ToPoint};use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart};use settings::Settings;use std::{    borrow::Cow,    cell::{Cell, RefCell},};use std::{ops::Range, sync::Arc, time::Duration};use std::{path::PathBuf, rc::Rc};use theme_settings::ThemeSettings;use ui::{CopyButton, Scrollbars, WithScrollbar, prelude::*, theme_is_transparent};use url::Url;use util::TryFutureExt;use workspace::{OpenOptions, OpenVisible, Workspace}; pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.;pub const MIN_POPOVER_LINE_HEIGHT: f32 = 4.;pub const POPOVER_RIGHT_OFFSET: Pixels = px(8.0);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, window: &mut Window, cx: &mut Context<Editor>) {    let head = editor.selections.newest_anchor().head();    show_hover(editor, head, true, window, 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>,    mouse_position: Option<gpui::Point<Pixels>>,    window: &mut Window,    cx: &mut Context<Editor>,) {    if EditorSettings::get_global(cx).hover_popover_enabled {        if show_keyboard_hover(editor, window, cx) {            return;        }         if let Some(anchor) = anchor {            editor.hover_state.hiding_delay_task = None;            editor.hover_state.closest_mouse_distance = None;            show_hover(editor, anchor, false, window, cx);        } else {            let mut getting_closer = false;            if let Some(mouse_position) = mouse_position {                getting_closer = editor.hover_state.is_mouse_getting_closer(mouse_position);            }             // If we are moving away and a timer is already running, just let it count down.            if !getting_closer && editor.hover_state.hiding_delay_task.is_some() {                return;            }             // If we are moving closer, or if no timer is running at all, start/restart the 300ms timer.            let delay = Duration::from_millis(300u64);            let task = cx.spawn(async move |this, cx| {                cx.background_executor().timer(delay).await;                this.update(cx, |editor, cx| {                    hide_hover(editor, cx);                })                .ok();            });            editor.hover_state.hiding_delay_task = Some(task);        }    }} pub fn show_keyboard_hover(    editor: &mut Editor,    window: &mut Window,    cx: &mut Context<Editor>,) -> bool {    if let Some(anchor) = editor.hover_state.info_popovers.iter().find_map(|p| {        if *p.keyboard_grace.borrow() {            p.anchor        } else {            None        }    }) {        show_hover(editor, anchor, false, window, cx);        return true;    }     if let Some(anchor) = editor        .hover_state        .diagnostic_popover        .as_ref()        .and_then(|d| {            if *d.keyboard_grace.borrow() {                Some(d.anchor)            } else {                None            }        })    {        show_hover(editor, anchor, false, window, cx);        return true;    }     false} pub struct InlayHover {    pub(crate) 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 offset_in_hint = hovered_offset - hint_start;        let mut part_start = hint_start;        for part in label_parts {            let part_len = part.value.len();            if offset_in_hint >= part_len {                offset_in_hint -= 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,    window: &mut Window,    cx: &mut Context<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                    && range == &inlay_hover.range                {                    // Hover triggered from same location as last time. Don't show again.                    return true;                }                false            })        {            return;        }         let hover_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;         editor.hover_state.hiding_delay_task = None;        editor.hover_state.closest_mouse_distance = None;         let task = cx.spawn_in(window, async move |this, cx| {            async move {                cx.background_executor()                    .timer(Duration::from_millis(hover_popover_delay))                    .await;                this.update(cx, |this, _| {                    this.hover_state.diagnostic_popover = None;                })?;                 let language_registry = project.read_with(cx, |p, _| p.languages().clone());                let blocks = vec![inlay_hover.tooltip];                let parsed_content =                    parse_blocks(&blocks, Some(&language_registry), None, cx).await;                 let scroll_handle = ScrollHandle::new();                 let subscription = this                    .update(cx, |_, cx| {                        parsed_content.as_ref().map(|parsed_content| {                            cx.observe(parsed_content, |_, _, cx| cx.notify())                        })                    })                    .ok()                    .flatten();                 let hover_popover = InfoPopover {                    symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),                    parsed_content,                    scroll_handle,                    keyboard_grace: Rc::new(RefCell::new(false)),                    anchor: None,                    last_bounds: Rc::new(Cell::new(None)),                    _subscription: subscription,                };                 this.update(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()            .await        });         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 Context<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.hover_state.hiding_delay_task = None;    editor.hover_state.closest_mouse_distance = None;     editor.clear_background_highlights(HighlightKey::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,    window: &mut Window,    cx: &mut Context<Editor>,) -> Option<()> {    if editor.pending_rename.is_some() {        return None;    }     let snapshot = editor.snapshot(window, cx);     let (buffer_position, _) = editor        .buffer        .read(cx)        .snapshot(cx)        .anchor_to_buffer_anchor(anchor)?;    let buffer = editor.buffer.read(cx).buffer(buffer_position.buffer_id)?;     let language_registry = editor        .project()        .map(|project| project.read(cx).languages().clone());    let provider = editor.semantics_provider.clone()?;     editor.hover_state.hiding_delay_task = None;    editor.hover_state.closest_mouse_distance = None;     if !ignore_timeout {        if same_info_hover(editor, &snapshot, anchor)            || same_diagnostic_hover(editor, &snapshot, anchor)            || editor.hover_state.diagnostic_popover.is_some()        {            // Hover triggered from same location as last time. Don't show again.            return None;        } 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        && triggered_from            .cmp(&anchor, &snapshot.buffer_snapshot())            .is_eq()    {        return None;    }     let hover_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;    let all_diagnostics_active = editor.active_diagnostics == ActiveDiagnostic::All;    let active_group_id = if let ActiveDiagnostic::Group(group) = &editor.active_diagnostics {        Some(group.group_id)    } else {        None    };     let renderer = GlobalDiagnosticRenderer::global(cx);    let task = cx.spawn_in(window, async move |this, 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 {                let lsp_request_early = hover_popover_delay / 2;                cx.background_executor()                    .timer(Duration::from_millis(                        hover_popover_delay - lsp_request_early,                    ))                    .await;                 // Construct delay task to wait for later                let total_delay = Some(                    cx.background_executor()                        .timer(Duration::from_millis(lsp_request_early)),                );                total_delay            };             let hover_request = cx.update(|_, cx| provider.hover(&buffer, buffer_position, cx))?;             if let Some(delay) = delay {                delay.await;            }            let offset = anchor.to_offset(&snapshot.buffer_snapshot());            let local_diagnostic = if all_diagnostics_active {                None            } else {                snapshot                    .buffer_snapshot()                    .diagnostics_with_buffer_ids_in_range::<MultiBufferOffset>(offset..offset)                    .filter(|(_, diagnostic)| {                        Some(diagnostic.diagnostic.group_id) != active_group_id                    })                    // Find the entry with the most specific range                    .min_by_key(|(_, entry)| entry.range.end - entry.range.start)            };             let diagnostic_popover = if let Some((buffer_id, local_diagnostic)) = local_diagnostic {                let group = snapshot                    .buffer_snapshot()                    .diagnostic_group(buffer_id, local_diagnostic.diagnostic.group_id)                    .collect::<Vec<_>>();                let point_range = local_diagnostic                    .range                    .start                    .to_point(&snapshot.buffer_snapshot())                    ..local_diagnostic                        .range                        .end                        .to_point(&snapshot.buffer_snapshot());                let markdown = cx.update(|_, cx| {                    renderer                        .as_ref()                        .and_then(|renderer| {                            renderer.render_hover(                                group,                                point_range,                                buffer_id,                                language_registry.clone(),                                cx,                            )                        })                        .context("no rendered diagnostic")                })??;                 let (background_color, border_color) = cx.update(|_, cx| {                    let status_colors = cx.theme().status();                    match local_diagnostic.diagnostic.severity {                        DiagnosticSeverity::ERROR => {                            (status_colors.error_background, status_colors.error_border)                        }                        DiagnosticSeverity::WARNING => (                            status_colors.warning_background,                            status_colors.warning_border,                        ),                        DiagnosticSeverity::INFORMATION => {                            (status_colors.info_background, status_colors.info_border)                        }                        DiagnosticSeverity::HINT => {                            (status_colors.hint_background, status_colors.hint_border)                        }                        _ => (                            status_colors.ignored_background,                            status_colors.ignored_border,                        ),                    }                })?;                 let subscription =                    this.update(cx, |_, cx| cx.observe(&markdown, |_, _, cx| cx.notify()))?;                 let local_diagnostic = DiagnosticEntry {                    diagnostic: local_diagnostic.diagnostic.to_owned(),                    range: snapshot                        .buffer_snapshot()                        .anchor_before(local_diagnostic.range.start)                        ..snapshot                            .buffer_snapshot()                            .anchor_after(local_diagnostic.range.end),                };                 let scroll_handle = ScrollHandle::new();                 Some(DiagnosticPopover {                    local_diagnostic,                    markdown,                    border_color,                    scroll_handle,                    background_color,                    keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),                    anchor,                    last_bounds: Rc::new(Cell::new(None)),                    _subscription: subscription,                })            } else {                None            };             this.update(cx, |this, _| {                this.hover_state.diagnostic_popover = diagnostic_popover;            })?;             let invisible_char = if let Some(invisible) = snapshot                .buffer_snapshot()                .chars_at(anchor)                .next()                .filter(|&c| is_invisible(c))            {                let after = snapshot.buffer_snapshot().anchor_after(                    anchor.to_offset(&snapshot.buffer_snapshot()) + invisible.len_utf8(),                );                Some((invisible, anchor..after))            } else if let Some(invisible) = snapshot                .buffer_snapshot()                .reversed_chars_at(anchor)                .next()                .filter(|&c| is_invisible(c))            {                let before = snapshot.buffer_snapshot().anchor_before(                    anchor.to_offset(&snapshot.buffer_snapshot()) - invisible.len_utf8(),                );                 Some((invisible, before..anchor))            } else {                None            };             let hovers_response = if let Some(hover_request) = hover_request {                hover_request.await.unwrap_or_default()            } else {                Vec::new()            };            let snapshot = this.update_in(cx, |this, window, cx| this.snapshot(window, cx))?;            let mut hover_highlights = Vec::with_capacity(hovers_response.len());            let mut info_popovers = Vec::with_capacity(                hovers_response.len() + if invisible_char.is_some() { 1 } else { 0 },            );             if let Some((invisible, range)) = invisible_char {                let blocks = vec![HoverBlock {                    text: format!("Unicode character U+{:02X}", invisible as u32),                    kind: HoverBlockKind::PlainText,                }];                let parsed_content =                    parse_blocks(&blocks, language_registry.as_ref(), None, cx).await;                let scroll_handle = ScrollHandle::new();                let subscription = this                    .update(cx, |_, cx| {                        parsed_content.as_ref().map(|parsed_content| {                            cx.observe(parsed_content, |_, _, cx| cx.notify())                        })                    })                    .ok()                    .flatten();                info_popovers.push(InfoPopover {

Lines 1–500 of 2152.