1615 lines · 60.5 KB · Rust
RawDownload
use crate::{    hover_popover::{self, InlayHover},    scroll::ScrollAmount,    Anchor, Editor, EditorSnapshot, FindAllReferences, GoToDefinition, GoToTypeDefinition,    GotoDefinitionKind, InlayId, Navigated, PointForPosition, SelectPhase,};use gpui::{px, AppContext, AsyncWindowContext, Model, Modifiers, Task, ViewContext};use language::{Bias, ToOffset};use linkify::{LinkFinder, LinkKind};use lsp::LanguageServerId;use project::{    HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink, Project,    ResolveState, ResolvedPath,};use std::ops::Range;use theme::ActiveTheme as _;use util::{maybe, ResultExt, TryFutureExt as _}; #[derive(Debug)]pub struct HoveredLinkState {    pub last_trigger_point: TriggerPoint,    pub preferred_kind: GotoDefinitionKind,    pub symbol_range: Option<RangeInEditor>,    pub links: Vec<HoverLink>,    pub task: Option<Task<Option<()>>>,} #[derive(Debug, Eq, PartialEq, Clone)]pub enum RangeInEditor {    Text(Range<Anchor>),    Inlay(InlayHighlight),} impl RangeInEditor {    pub fn as_text_range(&self) -> Option<Range<Anchor>> {        match self {            Self::Text(range) => Some(range.clone()),            Self::Inlay(_) => None,        }    }     pub fn point_within_range(        &self,        trigger_point: &TriggerPoint,        snapshot: &EditorSnapshot,    ) -> bool {        match (self, trigger_point) {            (Self::Text(range), TriggerPoint::Text(point)) => {                let point_after_start = range.start.cmp(point, &snapshot.buffer_snapshot).is_le();                point_after_start && range.end.cmp(point, &snapshot.buffer_snapshot).is_ge()            }            (Self::Inlay(highlight), TriggerPoint::InlayHint(point, _, _)) => {                highlight.inlay == point.inlay                    && highlight.range.contains(&point.range.start)                    && highlight.range.contains(&point.range.end)            }            (Self::Inlay(_), TriggerPoint::Text(_))            | (Self::Text(_), TriggerPoint::InlayHint(_, _, _)) => false,        }    }} #[derive(Debug, Clone)]pub enum HoverLink {    Url(String),    File(ResolvedPath),    Text(LocationLink),    InlayHint(lsp::Location, LanguageServerId),} #[derive(Debug, Clone, PartialEq, Eq)]pub(crate) struct InlayHighlight {    pub inlay: InlayId,    pub inlay_position: Anchor,    pub range: Range<usize>,} #[derive(Debug, Clone, PartialEq)]pub enum TriggerPoint {    Text(Anchor),    InlayHint(InlayHighlight, lsp::Location, LanguageServerId),} impl TriggerPoint {    fn anchor(&self) -> &Anchor {        match self {            TriggerPoint::Text(anchor) => anchor,            TriggerPoint::InlayHint(inlay_range, _, _) => &inlay_range.inlay_position,        }    }} pub fn exclude_link_to_position(    buffer: &Model<language::Buffer>,    current_position: &text::Anchor,    location: &LocationLink,    cx: &AppContext,) -> bool {    // Exclude definition links that points back to cursor position.    // (i.e., currently cursor upon definition).    let snapshot = buffer.read(cx).snapshot();    !(buffer == &location.target.buffer        && current_position            .bias_right(&snapshot)            .cmp(&location.target.range.start, &snapshot)            .is_ge()        && current_position            .cmp(&location.target.range.end, &snapshot)            .is_le())} impl Editor {    pub(crate) fn update_hovered_link(        &mut self,        point_for_position: PointForPosition,        snapshot: &EditorSnapshot,        modifiers: Modifiers,        cx: &mut ViewContext<Self>,    ) {        if !modifiers.secondary() || self.has_pending_selection() {            self.hide_hovered_link(cx);            return;        }         match point_for_position.as_valid() {            Some(point) => {                let trigger_point = TriggerPoint::Text(                    snapshot                        .buffer_snapshot                        .anchor_before(point.to_offset(&snapshot.display_snapshot, Bias::Left)),                );                 show_link_definition(modifiers.shift, self, trigger_point, snapshot, cx);            }            None => {                update_inlay_link_and_hover_points(                    snapshot,                    point_for_position,                    self,                    modifiers.secondary(),                    modifiers.shift,                    cx,                );            }        }    }     pub(crate) fn hide_hovered_link(&mut self, cx: &mut ViewContext<Self>) {        self.hovered_link_state.take();        self.clear_highlights::<HoveredLinkState>(cx);    }     pub(crate) fn handle_click_hovered_link(        &mut self,        point: PointForPosition,        modifiers: Modifiers,        cx: &mut ViewContext<Editor>,    ) {        let reveal_task = self.cmd_click_reveal_task(point, modifiers, cx);        cx.spawn(|editor, mut cx| async move {            let definition_revealed = reveal_task.await.log_err().unwrap_or(Navigated::No);            let find_references = editor                .update(&mut cx, |editor, cx| {                    if definition_revealed == Navigated::Yes {                        return None;                    }                    editor.find_all_references(&FindAllReferences, cx)                })                .ok()                .flatten();            if let Some(find_references) = find_references {                find_references.await.log_err();            }        })        .detach();    }     pub fn scroll_hover(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) -> bool {        let selection = self.selections.newest_anchor().head();        let snapshot = self.snapshot(cx);         let Some(popover) = self.hover_state.info_popovers.iter().find(|popover| {            popover                .symbol_range                .point_within_range(&TriggerPoint::Text(selection), &snapshot)        }) else {            return false;        };        popover.scroll(amount, cx);        true    }     fn cmd_click_reveal_task(        &mut self,        point: PointForPosition,        modifiers: Modifiers,        cx: &mut ViewContext<Editor>,    ) -> Task<anyhow::Result<Navigated>> {        if let Some(hovered_link_state) = self.hovered_link_state.take() {            self.hide_hovered_link(cx);            if !hovered_link_state.links.is_empty() {                if !self.focus_handle.is_focused(cx) {                    cx.focus(&self.focus_handle);                }                 // exclude links pointing back to the current anchor                let current_position = point                    .next_valid                    .to_point(&self.snapshot(cx).display_snapshot);                let Some((buffer, anchor)) = self                    .buffer()                    .read(cx)                    .text_anchor_for_position(current_position, cx)                else {                    return Task::ready(Ok(Navigated::No));                };                let links = hovered_link_state                    .links                    .into_iter()                    .filter(|link| {                        if let HoverLink::Text(location) = link {                            exclude_link_to_position(&buffer, &anchor, location, cx)                        } else {                            true                        }                    })                    .collect();                 return self.navigate_to_hover_links(None, links, modifiers.alt, cx);            }        }         // We don't have the correct kind of link cached, set the selection on        // click and immediately trigger GoToDefinition.        self.select(            SelectPhase::Begin {                position: point.next_valid,                add: false,                click_count: 1,            },            cx,        );         if point.as_valid().is_some() {            if modifiers.shift {                self.go_to_type_definition(&GoToTypeDefinition, cx)            } else {                self.go_to_definition(&GoToDefinition, cx)            }        } else {            Task::ready(Ok(Navigated::No))        }    }} pub fn update_inlay_link_and_hover_points(    snapshot: &EditorSnapshot,    point_for_position: PointForPosition,    editor: &mut Editor,    secondary_held: bool,    shift_held: bool,    cx: &mut ViewContext<'_, Editor>,) {    let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 {        Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left))    } else {        None    };    let mut go_to_definition_updated = false;    let mut hover_updated = false;    if let Some(hovered_offset) = hovered_offset {        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);        let previous_valid_anchor = buffer_snapshot.anchor_at(            point_for_position.previous_valid.to_point(snapshot),            Bias::Left,        );        let next_valid_anchor = buffer_snapshot.anchor_at(            point_for_position.next_valid.to_point(snapshot),            Bias::Right,        );        if let Some(hovered_hint) = editor            .visible_inlay_hints(cx)            .into_iter()            .skip_while(|hint| {                hint.position                    .cmp(&previous_valid_anchor, &buffer_snapshot)                    .is_lt()            })            .take_while(|hint| {                hint.position                    .cmp(&next_valid_anchor, &buffer_snapshot)                    .is_le()            })            .max_by_key(|hint| hint.id)        {            let inlay_hint_cache = editor.inlay_hint_cache();            let excerpt_id = previous_valid_anchor.excerpt_id;            if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id) {                match cached_hint.resolve_state {                    ResolveState::CanResolve(_, _) => {                        if let Some(buffer_id) = previous_valid_anchor.buffer_id {                            inlay_hint_cache.spawn_hint_resolve(                                buffer_id,                                excerpt_id,                                hovered_hint.id,                                cx,                            );                        }                    }                    ResolveState::Resolved => {                        let mut extra_shift_left = 0;                        let mut extra_shift_right = 0;                        if cached_hint.padding_left {                            extra_shift_left += 1;                            extra_shift_right += 1;                        }                        if cached_hint.padding_right {                            extra_shift_right += 1;                        }                        match cached_hint.label {                            project::InlayHintLabel::String(_) => {                                if let Some(tooltip) = cached_hint.tooltip {                                    hover_popover::hover_at_inlay(                                        editor,                                        InlayHover {                                            tooltip: match tooltip {                                                InlayHintTooltip::String(text) => HoverBlock {                                                    text,                                                    kind: HoverBlockKind::PlainText,                                                },                                                InlayHintTooltip::MarkupContent(content) => {                                                    HoverBlock {                                                        text: content.value,                                                        kind: content.kind,                                                    }                                                }                                            },                                            range: InlayHighlight {                                                inlay: hovered_hint.id,                                                inlay_position: hovered_hint.position,                                                range: extra_shift_left                                                    ..hovered_hint.text.len() + extra_shift_right,                                            },                                        },                                        cx,                                    );                                    hover_updated = true;                                }                            }                            project::InlayHintLabel::LabelParts(label_parts) => {                                let hint_start =                                    snapshot.anchor_to_inlay_offset(hovered_hint.position);                                if let Some((hovered_hint_part, part_range)) =                                    hover_popover::find_hovered_hint_part(                                        label_parts,                                        hint_start,                                        hovered_offset,                                    )                                {                                    let highlight_start =                                        (part_range.start - hint_start).0 + extra_shift_left;                                    let highlight_end =                                        (part_range.end - hint_start).0 + extra_shift_right;                                    let highlight = InlayHighlight {                                        inlay: hovered_hint.id,                                        inlay_position: hovered_hint.position,                                        range: highlight_start..highlight_end,                                    };                                    if let Some(tooltip) = hovered_hint_part.tooltip {                                        hover_popover::hover_at_inlay(                                            editor,                                            InlayHover {                                                tooltip: match tooltip {                                                    InlayHintLabelPartTooltip::String(text) => {                                                        HoverBlock {                                                            text,                                                            kind: HoverBlockKind::PlainText,                                                        }                                                    }                                                    InlayHintLabelPartTooltip::MarkupContent(                                                        content,                                                    ) => HoverBlock {                                                        text: content.value,                                                        kind: content.kind,                                                    },                                                },                                                range: highlight.clone(),                                            },                                            cx,                                        );                                        hover_updated = true;                                    }                                    if let Some((language_server_id, location)) =                                        hovered_hint_part.location                                    {                                        if secondary_held                                            && !editor.has_pending_nonempty_selection()                                        {                                            go_to_definition_updated = true;                                            show_link_definition(                                                shift_held,                                                editor,                                                TriggerPoint::InlayHint(                                                    highlight,                                                    location,                                                    language_server_id,                                                ),                                                snapshot,                                                cx,                                            );                                        }                                    }                                }                            }                        };                    }                    ResolveState::Resolving => {}                }            }        }    }     if !go_to_definition_updated {        editor.hide_hovered_link(cx)    }    if !hover_updated {        hover_popover::hover_at(editor, None, cx);    }} pub fn show_link_definition(    shift_held: bool,    editor: &mut Editor,    trigger_point: TriggerPoint,    snapshot: &EditorSnapshot,    cx: &mut ViewContext<Editor>,) {    let preferred_kind = match trigger_point {        TriggerPoint::Text(_) if !shift_held => GotoDefinitionKind::Symbol,        _ => GotoDefinitionKind::Type,    };     let (mut hovered_link_state, is_cached) =        if let Some(existing) = editor.hovered_link_state.take() {            (existing, true)        } else {            (                HoveredLinkState {                    last_trigger_point: trigger_point.clone(),                    symbol_range: None,                    preferred_kind,                    links: vec![],                    task: None,                },                false,            )        };     if editor.pending_rename.is_some() {        return;    }     let trigger_anchor = trigger_point.anchor();    let Some((buffer, buffer_position)) = editor        .buffer        .read(cx)        .text_anchor_for_position(*trigger_anchor, cx)    else {        return;    };     let Some((excerpt_id, _, _)) = editor        .buffer()        .read(cx)        .excerpt_containing(*trigger_anchor, cx)    else {        return;    };     let same_kind = hovered_link_state.preferred_kind == preferred_kind        || hovered_link_state            .links            .first()            .is_some_and(|d| matches!(d, HoverLink::Url(_)));     if same_kind {        if is_cached && (hovered_link_state.last_trigger_point == trigger_point)            || hovered_link_state                .symbol_range                .as_ref()                .is_some_and(|symbol_range| {                    symbol_range.point_within_range(&trigger_point, snapshot)                })        {            editor.hovered_link_state = Some(hovered_link_state);            return;        }    } else {        editor.hide_hovered_link(cx)    }

Lines 1–500 of 1615.