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| {
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)
.collect::<Vec<_>>();

View file

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

View file

@ -7511,6 +7511,22 @@ impl Editor {
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>(
&mut self,
cx: &mut ViewContext<Self>,
@ -7932,7 +7948,6 @@ impl Editor {
Some(
ranges
.iter()
// TODO kb mark inlays too
.filter_map(|range| range.as_text_range())
.map(move |range| {
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>> {
let snapshot = self.buffer.read(cx).read(cx);
let range = self.text_highlights::<InputComposition>(cx)?.1.get(0)?;
// TODO kb mark inlays too
let range = range.as_text_range()?;
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,
git::{diff_hunk_to_display, DisplayDiffHunk},
hover_popover::{
hide_hover, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH,
MIN_POPOVER_LINE_HEIGHT,
hide_hover, hover_at, hover_at_inlay, InlayHover, HOVER_POPOVER_GAP,
MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
},
link_go_to_definition::{
go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link,
@ -43,7 +43,8 @@ use language::{
};
use project::{
project_settings::{GitGutterSetting, ProjectSettings},
InlayHintLabelPart, Location, LocationLink, ProjectPath, ResolveState,
HoverBlock, HoverBlockKind, InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip,
Location, LocationLink, ProjectPath, ResolveState,
};
use smallvec::SmallVec;
use std::{
@ -1860,8 +1861,7 @@ fn update_inlay_link_and_hover_points(
None
};
if let Some(hovered_offset) = hovered_offset {
let buffer = editor.buffer().read(cx);
let snapshot = buffer.snapshot(cx);
let snapshot = editor.buffer().read(cx).snapshot(cx);
let previous_valid_anchor = snapshot.anchor_at(
point_for_position
.previous_valid
@ -1885,15 +1885,14 @@ fn update_inlay_link_and_hover_points(
.max_by_key(|hint| hint.id)
{
let inlay_hint_cache = editor.inlay_hint_cache();
if let Some(cached_hint) =
inlay_hint_cache.hint_by_id(previous_valid_anchor.excerpt_id, hovered_hint.id)
{
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,
previous_valid_anchor.excerpt_id,
excerpt_id,
hovered_hint.id,
cx,
);
@ -1902,9 +1901,33 @@ fn update_inlay_link_and_hover_points(
ResolveState::Resolved => {
match cached_hint.label {
project::InlayHintLabel::String(_) => {
if cached_hint.tooltip.is_some() {
dbg!(&cached_hint.tooltip); // TODO kb
// hover_at_point = Some(hovered_offset);
if let Some(tooltip) = cached_hint.tooltip {
hover_at_inlay(
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) => {
@ -1915,15 +1938,41 @@ fn update_inlay_link_and_hover_points(
hovered_offset,
)
{
if hovered_hint_part.tooltip.is_some() {
dbg!(&hovered_hint_part.tooltip); // TODO kb
// hover_at_point = Some(hovered_offset);
if let Some(tooltip) = hovered_hint_part.tooltip {
hover_at_inlay(
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(buffer) = cached_hint
.position
.buffer_id
.and_then(|buffer_id| buffer.buffer(buffer_id))
if let Some(buffer) =
cached_hint.position.buffer_id.and_then(|buffer_id| {
editor.buffer().read(cx).buffer(buffer_id)
})
{
go_to_definition_updated = true;
update_go_to_definition_link(

View file

@ -1,6 +1,8 @@
use crate::{
display_map::ToDisplayPoint, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings,
EditorSnapshot, EditorStyle, RangeToAnchorExt,
display_map::{InlayOffset, ToDisplayPoint},
link_go_to_definition::{DocumentRange, InlayRange},
Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle,
ExcerptId, RangeToAnchorExt,
};
use futures::FutureExt;
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.
/// Triggered by the `Hover` action when the cursor is not over a symbol or when the
/// selections changed.
@ -110,8 +190,13 @@ fn show_hover(
if !ignore_timeout {
if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover {
if symbol_range
.to_offset(&snapshot.buffer_snapshot)
.contains(&multibuffer_offset)
.as_text_range()
.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.
return;
@ -219,7 +304,7 @@ fn show_hover(
Some(InfoPopover {
project: project.clone(),
symbol_range: range,
symbol_range: DocumentRange::Text(range),
blocks: hover_result.contents,
language: hover_result.language,
rendered_content: None,
@ -227,10 +312,13 @@ fn show_hover(
});
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
this.highlight_background::<HoverState>(
vec![hover_popover.symbol_range.clone()],
vec![symbol_range],
|theme| theme.editor.hover_popover.highlight,
cx,
);
@ -497,7 +585,10 @@ impl HoverState {
.or_else(|| {
self.info_popover
.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);
@ -522,7 +613,7 @@ impl HoverState {
#[derive(Debug, Clone)]
pub struct InfoPopover {
pub project: ModelHandle<Project>,
pub symbol_range: Range<Anchor>,
symbol_range: DocumentRange,
pub blocks: Vec<HoverBlock>,
language: Option<Arc<Language>>,
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(
hint: InlayHint,
project: &ModelHandle<Project>,