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. Load more
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 hover pub 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 {