1837 lines · 67.5 KB · Rust
RawDownload
use crate::{    display_map::{invisibles::is_invisible, InlayOffset, ToDisplayPoint},    hover_links::{InlayHighlight, RangeInEditor},    scroll::{Autoscroll, ScrollAmount},    Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings, EditorSnapshot,    Hover,};use gpui::{    div, px, AnyElement, AsyncWindowContext, Context, Entity, Focusable as _, FontWeight, Hsla,    InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, ScrollHandle, Size,    Stateful, StatefulInteractiveElement, StyleRefinement, Styled, Task, TextStyleRefinement,    Window,};use itertools::Itertools;use language::{DiagnosticEntry, Language, LanguageRegistry};use lsp::DiagnosticSeverity;use markdown::{Markdown, MarkdownStyle};use multi_buffer::{MultiOrSingleBufferOffsetRange, ToOffset};use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart};use settings::Settings;use std::{borrow::Cow, cell::RefCell};use std::{ops::Range, sync::Arc, time::Duration};use std::{path::PathBuf, rc::Rc};use theme::ThemeSettings;use ui::{prelude::*, theme_is_transparent, Scrollbar, ScrollbarState};use url::Url;use util::TryFutureExt;use workspace::Workspace;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, 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>,    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 {            show_hover(editor, anchor, false, window, cx);        } else {            hide_hover(editor, cx);        }    }} pub fn show_keyboard_hover(    editor: &mut Editor,    window: &mut Window,    cx: &mut Context<Editor>,) -> bool {    let info_popovers = editor.hover_state.info_popovers.clone();    for p in info_popovers {        let keyboard_grace = p.keyboard_grace.borrow();        if *keyboard_grace {            if let Some(anchor) = p.anchor {                show_hover(editor, anchor, false, window, cx);                return true;            }        }    }     let diagnostic_popover = editor.hover_state.diagnostic_popover.clone();    if let Some(d) = diagnostic_popover {        let keyboard_grace = d.keyboard_grace.borrow();        if *keyboard_grace {            if let Some(anchor) = d.anchor {                show_hover(editor, anchor, false, window, cx);                return true;            }        }    }     false} pub struct InlayHover {    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,    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 {                    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 hover_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;         let task = cx.spawn_in(window, |this, mut cx| {            async move {                cx.background_executor()                    .timer(Duration::from_millis(hover_popover_delay))                    .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, &mut cx).await;                 let scroll_handle = ScrollHandle::new();                let hover_popover = InfoPopover {                    symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),                    parsed_content,                    scrollbar_state: ScrollbarState::new(scroll_handle.clone()),                    scroll_handle,                    keyboard_grace: Rc::new(RefCell::new(false)),                    anchor: None,                };                 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 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.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,    window: &mut Window,    cx: &mut Context<Editor>,) -> Option<()> {    if editor.pending_rename.is_some() {        return None;    }     let snapshot = editor.snapshot(window, cx);     let (buffer, buffer_position) = editor        .buffer        .read(cx)        .text_anchor_for_position(anchor, cx)?;     let (excerpt_id, _, _) = editor.buffer().read(cx).excerpt_containing(anchor, cx)?;     let language_registry = editor.project.as_ref()?.read(cx).languages().clone();    let provider = editor.semantics_provider.clone()?;     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 {        if triggered_from            .cmp(&anchor, &snapshot.buffer_snapshot)            .is_eq()        {            return None;        }    }     let hover_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;     let task = cx.spawn_in(window, |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_popover_delay)),                );                 cx.background_executor()                    .timer(Duration::from_millis(HOVER_REQUEST_DELAY_MILLIS))                    .await;                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 = snapshot                .buffer_snapshot                .diagnostics_in_range::<usize>(offset..offset)                // Find the entry with the most specific range                .min_by_key(|entry| entry.range.len());             let diagnostic_popover = if let Some(local_diagnostic) = local_diagnostic {                let text = match local_diagnostic.diagnostic.source {                    Some(ref source) => {                        format!("{source}: {}", local_diagnostic.diagnostic.message)                    }                    None => local_diagnostic.diagnostic.message.clone(),                };                let local_diagnostic = DiagnosticEntry {                    diagnostic: local_diagnostic.diagnostic,                    range: snapshot                        .buffer_snapshot                        .anchor_before(local_diagnostic.range.start)                        ..snapshot                            .buffer_snapshot                            .anchor_after(local_diagnostic.range.end),                };                 let mut border_color: Option<Hsla> = None;                let mut background_color: Option<Hsla> = None;                 let parsed_content = cx                    .new_window_entity(|window, cx| {                        let status_colors = cx.theme().status();                         match local_diagnostic.diagnostic.severity {                            DiagnosticSeverity::ERROR => {                                background_color = Some(status_colors.error_background);                                border_color = Some(status_colors.error_border);                            }                            DiagnosticSeverity::WARNING => {                                background_color = Some(status_colors.warning_background);                                border_color = Some(status_colors.warning_border);                            }                            DiagnosticSeverity::INFORMATION => {                                background_color = Some(status_colors.info_background);                                border_color = Some(status_colors.info_border);                            }                            DiagnosticSeverity::HINT => {                                background_color = Some(status_colors.hint_background);                                border_color = Some(status_colors.hint_border);                            }                            _ => {                                background_color = Some(status_colors.ignored_background);                                border_color = Some(status_colors.ignored_border);                            }                        };                        let settings = ThemeSettings::get_global(cx);                        let mut base_text_style = window.text_style();                        base_text_style.refine(&TextStyleRefinement {                            font_family: Some(settings.ui_font.family.clone()),                            font_fallbacks: settings.ui_font.fallbacks.clone(),                            font_size: Some(settings.ui_font_size(cx).into()),                            color: Some(cx.theme().colors().editor_foreground),                            background_color: Some(gpui::transparent_black()),                             ..Default::default()                        });                        let markdown_style = MarkdownStyle {                            base_text_style,                            selection_background_color: { cx.theme().players().local().selection },                            link: TextStyleRefinement {                                underline: Some(gpui::UnderlineStyle {                                    thickness: px(1.),                                    color: Some(cx.theme().colors().editor_foreground),                                    wavy: false,                                }),                                ..Default::default()                            },                            ..Default::default()                        };                        Markdown::new_text(SharedString::new(text), markdown_style.clone(), cx)                            .open_url(open_markdown_url)                    })                    .ok();                 Some(DiagnosticPopover {                    local_diagnostic,                    parsed_content,                    border_color,                    background_color,                    keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),                    anchor: Some(anchor),                })            } else {                None            };             this.update(&mut 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            } else {                Vec::new()            };            let snapshot = this.update_in(&mut 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, None, &mut cx).await;                let scroll_handle = ScrollHandle::new();                info_popovers.push(InfoPopover {                    symbol_range: RangeInEditor::Text(range),                    parsed_content,                    scrollbar_state: ScrollbarState::new(scroll_handle.clone()),                    scroll_handle,                    keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),                    anchor: Some(anchor),                })            }             for hover_result in hovers_response {                // 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)                    })                    .or_else(|| {                        let snapshot = &snapshot.buffer_snapshot;                        match snapshot.syntax_ancestor(anchor..anchor)?.1 {                            MultiOrSingleBufferOffsetRange::Multi(range) => Some(                                snapshot.anchor_before(range.start)                                    ..snapshot.anchor_after(range.end),                            ),                            MultiOrSingleBufferOffsetRange::Single(_) => None,                        }                    })                    .unwrap_or_else(|| anchor..anchor);                 let blocks = hover_result.contents;                let language = hover_result.language;                let parsed_content =                    parse_blocks(&blocks, &language_registry, language, &mut cx).await;                let scroll_handle = ScrollHandle::new();                hover_highlights.push(range.clone());                info_popovers.push(InfoPopover {                    symbol_range: RangeInEditor::Text(range),                    parsed_content,                    scrollbar_state: ScrollbarState::new(scroll_handle.clone()),                    scroll_handle,                    keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),                    anchor: Some(anchor),                });            }             this.update_in(&mut cx, |editor, window, 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();                window.refresh();            })?;             anyhow::Ok(())        }        .log_err()    });     editor.hover_state.info_task = Some(task);    None}

Lines 1–500 of 1837.