Propagate inlay background highlights to data storage

This commit is contained in:
Kirill Bulatov 2023-08-23 15:14:25 +03:00
parent 4b78678923
commit bcaff0a18a
6 changed files with 185 additions and 33 deletions

View file

@ -1596,7 +1596,7 @@ mod tests {
.map(|range| { .map(|range| {
buffer_snapshot.anchor_before(range.start)..buffer_snapshot.anchor_after(range.end) buffer_snapshot.anchor_before(range.start)..buffer_snapshot.anchor_after(range.end)
}) })
// TODO add inlay highlight tests // TODO kb add inlay highlight tests
.map(DocumentRange::Text) .map(DocumentRange::Text)
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View file

@ -223,7 +223,6 @@ impl TabSnapshot {
&'a self, &'a self,
range: Range<TabPoint>, range: Range<TabPoint>,
language_aware: bool, language_aware: bool,
// TODO kb extract into one struct?
text_highlights: Option<&'a TextHighlights>, text_highlights: Option<&'a TextHighlights>,
inlay_highlight_style: Option<HighlightStyle>, inlay_highlight_style: Option<HighlightStyle>,
suggestion_highlight_style: Option<HighlightStyle>, suggestion_highlight_style: Option<HighlightStyle>,

View file

@ -7511,6 +7511,22 @@ impl Editor {
cx.notify(); cx.notify();
} }
pub fn highlight_inlay_background<T: 'static>(
&mut self,
ranges: Vec<InlayRange>,
color_fetcher: fn(&Theme) -> Color,
cx: &mut ViewContext<Self>,
) {
self.background_highlights.insert(
TypeId::of::<T>(),
(
color_fetcher,
ranges.into_iter().map(DocumentRange::Inlay).collect(),
),
);
cx.notify();
}
pub fn clear_background_highlights<T: 'static>( pub fn clear_background_highlights<T: 'static>(
&mut self, &mut self,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
@ -7932,7 +7948,6 @@ impl Editor {
Some( Some(
ranges ranges
.iter() .iter()
// TODO kb mark inlays too
.filter_map(|range| range.as_text_range()) .filter_map(|range| range.as_text_range())
.map(move |range| { .map(move |range| {
range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot) range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
@ -8400,7 +8415,6 @@ impl View for Editor {
fn marked_text_range(&self, cx: &AppContext) -> Option<Range<usize>> { fn marked_text_range(&self, cx: &AppContext) -> Option<Range<usize>> {
let snapshot = self.buffer.read(cx).read(cx); let snapshot = self.buffer.read(cx).read(cx);
let range = self.text_highlights::<InputComposition>(cx)?.1.get(0)?; let range = self.text_highlights::<InputComposition>(cx)?.1.get(0)?;
// TODO kb mark inlays too
let range = range.as_text_range()?; let range = range.as_text_range()?;
Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0) Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
} }

View file

@ -8,8 +8,8 @@ use crate::{
editor_settings::ShowScrollbar, editor_settings::ShowScrollbar,
git::{diff_hunk_to_display, DisplayDiffHunk}, git::{diff_hunk_to_display, DisplayDiffHunk},
hover_popover::{ hover_popover::{
hide_hover, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, hide_hover, hover_at, hover_at_inlay, InlayHover, HOVER_POPOVER_GAP,
MIN_POPOVER_LINE_HEIGHT, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
}, },
link_go_to_definition::{ link_go_to_definition::{
go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link, go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link,
@ -43,7 +43,8 @@ use language::{
}; };
use project::{ use project::{
project_settings::{GitGutterSetting, ProjectSettings}, project_settings::{GitGutterSetting, ProjectSettings},
InlayHintLabelPart, Location, LocationLink, ProjectPath, ResolveState, HoverBlock, HoverBlockKind, InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip,
Location, LocationLink, ProjectPath, ResolveState,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{ use std::{
@ -1860,8 +1861,7 @@ fn update_inlay_link_and_hover_points(
None None
}; };
if let Some(hovered_offset) = hovered_offset { if let Some(hovered_offset) = hovered_offset {
let buffer = editor.buffer().read(cx); let snapshot = editor.buffer().read(cx).snapshot(cx);
let snapshot = buffer.snapshot(cx);
let previous_valid_anchor = snapshot.anchor_at( let previous_valid_anchor = snapshot.anchor_at(
point_for_position point_for_position
.previous_valid .previous_valid
@ -1885,15 +1885,14 @@ fn update_inlay_link_and_hover_points(
.max_by_key(|hint| hint.id) .max_by_key(|hint| hint.id)
{ {
let inlay_hint_cache = editor.inlay_hint_cache(); let inlay_hint_cache = editor.inlay_hint_cache();
if let Some(cached_hint) = let excerpt_id = previous_valid_anchor.excerpt_id;
inlay_hint_cache.hint_by_id(previous_valid_anchor.excerpt_id, hovered_hint.id) if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id) {
{
match cached_hint.resolve_state { match cached_hint.resolve_state {
ResolveState::CanResolve(_, _) => { ResolveState::CanResolve(_, _) => {
if let Some(buffer_id) = previous_valid_anchor.buffer_id { if let Some(buffer_id) = previous_valid_anchor.buffer_id {
inlay_hint_cache.spawn_hint_resolve( inlay_hint_cache.spawn_hint_resolve(
buffer_id, buffer_id,
previous_valid_anchor.excerpt_id, excerpt_id,
hovered_hint.id, hovered_hint.id,
cx, cx,
); );
@ -1902,9 +1901,33 @@ fn update_inlay_link_and_hover_points(
ResolveState::Resolved => { ResolveState::Resolved => {
match cached_hint.label { match cached_hint.label {
project::InlayHintLabel::String(_) => { project::InlayHintLabel::String(_) => {
if cached_hint.tooltip.is_some() { if let Some(tooltip) = cached_hint.tooltip {
dbg!(&cached_hint.tooltip); // TODO kb hover_at_inlay(
// hover_at_point = Some(hovered_offset); editor,
InlayHover {
excerpt: excerpt_id,
tooltip: match tooltip {
InlayHintTooltip::String(text) => HoverBlock {
text,
kind: HoverBlockKind::PlainText,
},
InlayHintTooltip::MarkupContent(content) => {
HoverBlock {
text: content.value,
kind: content.kind,
}
}
},
triggered_from: hovered_offset,
range: InlayRange {
inlay_position: hovered_hint.position,
highlight_start: hint_start_offset,
highlight_end: hint_end_offset,
},
},
cx,
);
hover_updated = true;
} }
} }
project::InlayHintLabel::LabelParts(label_parts) => { project::InlayHintLabel::LabelParts(label_parts) => {
@ -1915,15 +1938,41 @@ fn update_inlay_link_and_hover_points(
hovered_offset, hovered_offset,
) )
{ {
if hovered_hint_part.tooltip.is_some() { if let Some(tooltip) = hovered_hint_part.tooltip {
dbg!(&hovered_hint_part.tooltip); // TODO kb hover_at_inlay(
// hover_at_point = Some(hovered_offset); editor,
InlayHover {
excerpt: excerpt_id,
tooltip: match tooltip {
InlayHintLabelPartTooltip::String(text) => {
HoverBlock {
text,
kind: HoverBlockKind::PlainText,
}
}
InlayHintLabelPartTooltip::MarkupContent(
content,
) => HoverBlock {
text: content.value,
kind: content.kind,
},
},
triggered_from: hovered_offset,
range: InlayRange {
inlay_position: hovered_hint.position,
highlight_start: part_range.start,
highlight_end: part_range.end,
},
},
cx,
);
hover_updated = true;
} }
if let Some(location) = hovered_hint_part.location { if let Some(location) = hovered_hint_part.location {
if let Some(buffer) = cached_hint if let Some(buffer) =
.position cached_hint.position.buffer_id.and_then(|buffer_id| {
.buffer_id editor.buffer().read(cx).buffer(buffer_id)
.and_then(|buffer_id| buffer.buffer(buffer_id)) })
{ {
go_to_definition_updated = true; go_to_definition_updated = true;
update_go_to_definition_link( update_go_to_definition_link(

View file

@ -1,6 +1,8 @@
use crate::{ use crate::{
display_map::ToDisplayPoint, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, display_map::{InlayOffset, ToDisplayPoint},
EditorSnapshot, EditorStyle, RangeToAnchorExt, link_go_to_definition::{DocumentRange, InlayRange},
Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle,
ExcerptId, RangeToAnchorExt,
}; };
use futures::FutureExt; use futures::FutureExt;
use gpui::{ use gpui::{
@ -46,6 +48,84 @@ pub fn hover_at(editor: &mut Editor, point: Option<DisplayPoint>, cx: &mut ViewC
} }
} }
pub struct InlayHover {
pub excerpt: ExcerptId,
pub triggered_from: InlayOffset,
pub range: InlayRange,
pub tooltip: HoverBlock,
}
pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut ViewContext<Editor>) {
if settings::get::<EditorSettings>(cx).hover_popover_enabled {
if editor.pending_rename.is_some() {
return;
}
let Some(project) = editor.project.clone() else {
return;
};
if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover {
if let DocumentRange::Inlay(range) = symbol_range {
if (range.highlight_start..=range.highlight_end)
.contains(&inlay_hover.triggered_from)
{
// Hover triggered from same location as last time. Don't show again.
return;
}
}
hide_hover(editor, cx);
}
let snapshot = editor.snapshot(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 inlay_hover.triggered_from
== snapshot
.display_snapshot
.anchor_to_inlay_offset(triggered_from)
{
return;
}
}
let task = cx.spawn(|this, mut cx| {
async move {
cx.background()
.timer(Duration::from_millis(HOVER_DELAY_MILLIS))
.await;
this.update(&mut cx, |this, _| {
this.hover_state.diagnostic_popover = None;
})?;
let hover_popover = InfoPopover {
project: project.clone(),
symbol_range: DocumentRange::Inlay(inlay_hover.range),
blocks: vec![inlay_hover.tooltip],
language: None,
rendered_content: None,
};
this.update(&mut cx, |this, cx| {
// Highlight the selected symbol using a background highlight
this.highlight_inlay_background::<HoverState>(
vec![inlay_hover.range],
|theme| theme.editor.hover_popover.highlight,
cx,
);
this.hover_state.info_popover = Some(hover_popover);
cx.notify();
})?;
anyhow::Ok(())
}
.log_err()
});
editor.hover_state.info_task = Some(task);
}
}
/// Hides the type information popup. /// Hides the type information popup.
/// Triggered by the `Hover` action when the cursor is not over a symbol or when the /// Triggered by the `Hover` action when the cursor is not over a symbol or when the
/// selections changed. /// selections changed.
@ -110,8 +190,13 @@ fn show_hover(
if !ignore_timeout { if !ignore_timeout {
if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover { if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover {
if symbol_range if symbol_range
.to_offset(&snapshot.buffer_snapshot) .as_text_range()
.contains(&multibuffer_offset) .map(|range| {
range
.to_offset(&snapshot.buffer_snapshot)
.contains(&multibuffer_offset)
})
.unwrap_or(false)
{ {
// Hover triggered from same location as last time. Don't show again. // Hover triggered from same location as last time. Don't show again.
return; return;
@ -219,7 +304,7 @@ fn show_hover(
Some(InfoPopover { Some(InfoPopover {
project: project.clone(), project: project.clone(),
symbol_range: range, symbol_range: DocumentRange::Text(range),
blocks: hover_result.contents, blocks: hover_result.contents,
language: hover_result.language, language: hover_result.language,
rendered_content: None, rendered_content: None,
@ -227,10 +312,13 @@ fn show_hover(
}); });
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
if let Some(hover_popover) = hover_popover.as_ref() { if let Some(symbol_range) = hover_popover
.as_ref()
.and_then(|hover_popover| hover_popover.symbol_range.as_text_range())
{
// Highlight the selected symbol using a background highlight // Highlight the selected symbol using a background highlight
this.highlight_background::<HoverState>( this.highlight_background::<HoverState>(
vec![hover_popover.symbol_range.clone()], vec![symbol_range],
|theme| theme.editor.hover_popover.highlight, |theme| theme.editor.hover_popover.highlight,
cx, cx,
); );
@ -497,7 +585,10 @@ impl HoverState {
.or_else(|| { .or_else(|| {
self.info_popover self.info_popover
.as_ref() .as_ref()
.map(|info_popover| &info_popover.symbol_range.start) .map(|info_popover| match &info_popover.symbol_range {
DocumentRange::Text(range) => &range.start,
DocumentRange::Inlay(range) => &range.inlay_position,
})
})?; })?;
let point = anchor.to_display_point(&snapshot.display_snapshot); let point = anchor.to_display_point(&snapshot.display_snapshot);
@ -522,7 +613,7 @@ impl HoverState {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct InfoPopover { pub struct InfoPopover {
pub project: ModelHandle<Project>, pub project: ModelHandle<Project>,
pub symbol_range: Range<Anchor>, symbol_range: DocumentRange,
pub blocks: Vec<HoverBlock>, pub blocks: Vec<HoverBlock>,
language: Option<Arc<Language>>, language: Option<Arc<Language>>,
rendered_content: Option<RenderedInfo>, rendered_content: Option<RenderedInfo>,

View file

@ -2126,7 +2126,6 @@ impl InlayHints {
}) })
} }
// TODO kb instead, store all LSP data inside the project::InlayHint?
pub fn project_to_lsp_hint( pub fn project_to_lsp_hint(
hint: InlayHint, hint: InlayHint,
project: &ModelHandle<Project>, project: &ModelHandle<Project>,