use crate::{ ActiveDiagnostic, Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings, EditorSnapshot, GlobalDiagnosticRenderer, Hover, display_map::{InlayOffset, ToDisplayPoint, invisibles::is_invisible}, hover_links::{InlayHighlight, RangeInEditor}, scroll::{Autoscroll, ScrollAmount},};use anyhow::Context as _;use gpui::{ AnyElement, AsyncWindowContext, Context, Entity, Focusable as _, FontWeight, Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, ScrollHandle, Size, Stateful, StatefulInteractiveElement, StyleRefinement, Styled, Subscription, Task, TextStyleRefinement, Window, div, px,};use itertools::Itertools;use language::{DiagnosticEntry, Language, LanguageRegistry};use lsp::DiagnosticSeverity;use markdown::{Markdown, MarkdownElement, MarkdownStyle};use multi_buffer::{MultiOrSingleBufferOffsetRange, ToOffset, ToPoint};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::{Scrollbar, ScrollbarState, prelude::*, theme_is_transparent};use url::Url;use util::TryFutureExt;use workspace::{OpenOptions, OpenVisible, Workspace};pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200; 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>, 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 { 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 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, 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, &language_registry, None, cx).await; let scroll_handle = ScrollHandle::new(); let subscription = this .update(cx, |_, cx| { if let Some(parsed_content) = &parsed_content { Some(cx.observe(parsed_content, |_, _, cx| cx.notify())) } else { None } }) .ok() .flatten(); 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, _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.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 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 { // 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 = if all_diagnostics_active { None } else { snapshot .buffer_snapshot .diagnostics_with_buffer_ids_in_range::<usize>(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.len()) }; 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, 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, range: snapshot .buffer_snapshot .anchor_before(local_diagnostic.range.start) ..snapshot .buffer_snapshot .anchor_after(local_diagnostic.range.end), }; Some(DiagnosticPopover { local_diagnostic, markdown, border_color, background_color, keyboard_grace: Rc::new(RefCell::new(ignore_timeout)), anchor, _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 } 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, None, cx).await; let scroll_handle = ScrollHandle::new(); let subscription = this .update(cx, |_, cx| { if let Some(parsed_content) = &parsed_content { Some(cx.observe(parsed_content, |_, _, cx| cx.notify())) } else { None } }) .ok() .flatten(); 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), _subscription: subscription, }) } 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, cx).await; let scroll_handle = ScrollHandle::new(); hover_highlights.push(range.clone()); let subscription = this .update(cx, |_, cx| { if let Some(parsed_content) = &parsed_content { Some(cx.observe(parsed_content, |_, _, cx| cx.notify())) } else { NoneLines 1–500 of 1885. Load more
use crate::{ ActiveDiagnostic, Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings, EditorSnapshot, GlobalDiagnosticRenderer, Hover, display_map::{InlayOffset, ToDisplayPoint, invisibles::is_invisible}, hover_links::{InlayHighlight, RangeInEditor}, scroll::{Autoscroll, ScrollAmount}, }; use anyhow::Context as _; use gpui::{ AnyElement, AsyncWindowContext, Context, Entity, Focusable as _, FontWeight, Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, ScrollHandle, Size, Stateful, StatefulInteractiveElement, StyleRefinement, Styled, Subscription, Task, TextStyleRefinement, Window, div, px, }; use itertools::Itertools; use language::{DiagnosticEntry, Language, LanguageRegistry}; use lsp::DiagnosticSeverity; use markdown::{Markdown, MarkdownElement, MarkdownStyle}; use multi_buffer::{MultiOrSingleBufferOffsetRange, ToOffset, ToPoint}; 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::{Scrollbar, ScrollbarState, prelude::*, theme_is_transparent}; use url::Url; use util::TryFutureExt; use workspace::{OpenOptions, OpenVisible, Workspace}; pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200; 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>, 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 { 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 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, 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, &language_registry, None, cx).await; let scroll_handle = ScrollHandle::new(); let subscription = this .update(cx, |_, cx| { if let Some(parsed_content) = &parsed_content { Some(cx.observe(parsed_content, |_, _, cx| cx.notify())) } else { None } }) .ok() .flatten(); 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, _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.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 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 { // 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 = if all_diagnostics_active { None } else { snapshot .buffer_snapshot .diagnostics_with_buffer_ids_in_range::<usize>(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.len()) }; 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, 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, range: snapshot .buffer_snapshot .anchor_before(local_diagnostic.range.start) ..snapshot .buffer_snapshot .anchor_after(local_diagnostic.range.end), }; Some(DiagnosticPopover { local_diagnostic, markdown, border_color, background_color, keyboard_grace: Rc::new(RefCell::new(ignore_timeout)), anchor, _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 } 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, None, cx).await; let scroll_handle = ScrollHandle::new(); let subscription = this .update(cx, |_, cx| { if let Some(parsed_content) = &parsed_content { Some(cx.observe(parsed_content, |_, _, cx| cx.notify())) } else { None } }) .ok() .flatten(); 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), _subscription: subscription, }) } 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, cx).await; let scroll_handle = ScrollHandle::new(); hover_highlights.push(range.clone()); let subscription = this .update(cx, |_, cx| { if let Some(parsed_content) = &parsed_content { Some(cx.observe(parsed_content, |_, _, cx| cx.notify())) } else { None