Implement inlay highlighting

This commit is contained in:
Kirill Bulatov 2023-09-14 14:46:14 +03:00
parent 890a587254
commit 42bd2be2f3
4 changed files with 231 additions and 78 deletions

View file

@ -5,11 +5,11 @@ mod tab_map;
mod wrap_map; mod wrap_map;
use crate::{ use crate::{
link_go_to_definition::InlayRange, Anchor, AnchorRangeExt, InlayBackgroundHighlight, InlayId, link_go_to_definition::InlayRange, Anchor, AnchorRangeExt, InlayId, MultiBuffer,
MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, MultiBufferSnapshot, ToOffset, ToPoint,
}; };
pub use block_map::{BlockMap, BlockPoint}; pub use block_map::{BlockMap, BlockPoint};
use collections::{BTreeMap, HashMap, HashSet}; use collections::{HashMap, HashSet};
use fold_map::FoldMap; use fold_map::FoldMap;
use gpui::{ use gpui::{
color::Color, color::Color,
@ -304,6 +304,16 @@ impl DisplayMap {
} }
} }
#[derive(Debug, Default)]
pub struct Highlights<'a> {
pub text_highlights: Option<&'a TextHighlights>,
pub inlay_highlights: Option<&'a InlayHighlights>,
pub inlay_background_highlights:
Option<TreeMap<Option<TypeId>, Arc<(HighlightStyle, &'a [InlayRange])>>>,
pub inlay_highlight_style: Option<HighlightStyle>,
pub suggestion_highlight_style: Option<HighlightStyle>,
}
pub struct DisplaySnapshot { pub struct DisplaySnapshot {
pub buffer_snapshot: MultiBufferSnapshot, pub buffer_snapshot: MultiBufferSnapshot,
pub fold_snapshot: fold_map::FoldSnapshot, pub fold_snapshot: fold_map::FoldSnapshot,
@ -316,15 +326,6 @@ pub struct DisplaySnapshot {
clip_at_line_ends: bool, clip_at_line_ends: bool,
} }
#[derive(Debug, Default)]
pub struct Highlights<'a> {
pub text_highlights: Option<&'a TextHighlights>,
pub inlay_highlights: Option<&'a InlayHighlights>,
pub inlay_background_highlights: Option<&'a BTreeMap<TypeId, InlayBackgroundHighlight>>,
pub inlay_highlight_style: Option<HighlightStyle>,
pub suggestion_highlight_style: Option<HighlightStyle>,
}
impl DisplaySnapshot { impl DisplaySnapshot {
#[cfg(test)] #[cfg(test)]
pub fn fold_count(&self) -> usize { pub fn fold_count(&self) -> usize {
@ -480,7 +481,9 @@ impl DisplaySnapshot {
&'a self, &'a self,
display_rows: Range<u32>, display_rows: Range<u32>,
language_aware: bool, language_aware: bool,
inlay_background_highlights: Option<&'a BTreeMap<TypeId, InlayBackgroundHighlight>>, inlay_background_highlights: Option<
TreeMap<Option<TypeId>, Arc<(HighlightStyle, &'a [InlayRange])>>,
>,
inlay_highlight_style: Option<HighlightStyle>, inlay_highlight_style: Option<HighlightStyle>,
suggestion_highlight_style: Option<HighlightStyle>, suggestion_highlight_style: Option<HighlightStyle>,
) -> DisplayChunks<'_> { ) -> DisplayChunks<'_> {

View file

@ -1,4 +1,5 @@
use crate::{ use crate::{
link_go_to_definition::InlayRange,
multi_buffer::{MultiBufferChunks, MultiBufferRows}, multi_buffer::{MultiBufferChunks, MultiBufferRows},
Anchor, InlayId, MultiBufferSnapshot, ToOffset, Anchor, InlayId, MultiBufferSnapshot, ToOffset,
}; };
@ -10,22 +11,23 @@ use std::{
cmp, cmp,
iter::Peekable, iter::Peekable,
ops::{Add, AddAssign, Range, Sub, SubAssign}, ops::{Add, AddAssign, Range, Sub, SubAssign},
sync::Arc,
vec, vec,
}; };
use sum_tree::{Bias, Cursor, SumTree}; use sum_tree::{Bias, Cursor, SumTree, TreeMap};
use text::{Patch, Rope}; use text::{Patch, Rope};
use super::Highlights; use super::Highlights;
pub struct InlayMap { pub struct InlayMap {
snapshot: InlaySnapshot, snapshot: InlaySnapshot,
inlays: Vec<Inlay>,
} }
#[derive(Clone)] #[derive(Clone)]
pub struct InlaySnapshot { pub struct InlaySnapshot {
pub buffer: MultiBufferSnapshot, pub buffer: MultiBufferSnapshot,
transforms: SumTree<Transform>, transforms: SumTree<Transform>,
inlays: Vec<Inlay>,
pub version: usize, pub version: usize,
} }
@ -399,13 +401,13 @@ impl InlayMap {
let snapshot = InlaySnapshot { let snapshot = InlaySnapshot {
buffer: buffer.clone(), buffer: buffer.clone(),
transforms: SumTree::from_iter(Some(Transform::Isomorphic(buffer.text_summary())), &()), transforms: SumTree::from_iter(Some(Transform::Isomorphic(buffer.text_summary())), &()),
inlays: Vec::new(),
version, version,
}; };
( (
Self { Self {
snapshot: snapshot.clone(), snapshot: snapshot.clone(),
inlays: Vec::new(),
}, },
snapshot, snapshot,
) )
@ -474,7 +476,7 @@ impl InlayMap {
); );
let new_start = InlayOffset(new_transforms.summary().output.len); let new_start = InlayOffset(new_transforms.summary().output.len);
let start_ix = match self.inlays.binary_search_by(|probe| { let start_ix = match snapshot.inlays.binary_search_by(|probe| {
probe probe
.position .position
.to_offset(&buffer_snapshot) .to_offset(&buffer_snapshot)
@ -484,7 +486,7 @@ impl InlayMap {
Ok(ix) | Err(ix) => ix, Ok(ix) | Err(ix) => ix,
}; };
for inlay in &self.inlays[start_ix..] { for inlay in &snapshot.inlays[start_ix..] {
let buffer_offset = inlay.position.to_offset(&buffer_snapshot); let buffer_offset = inlay.position.to_offset(&buffer_snapshot);
if buffer_offset > buffer_edit.new.end { if buffer_offset > buffer_edit.new.end {
break; break;
@ -554,7 +556,7 @@ impl InlayMap {
let snapshot = &mut self.snapshot; let snapshot = &mut self.snapshot;
let mut edits = BTreeSet::new(); let mut edits = BTreeSet::new();
self.inlays.retain(|inlay| { snapshot.inlays.retain(|inlay| {
let retain = !to_remove.contains(&inlay.id); let retain = !to_remove.contains(&inlay.id);
if !retain { if !retain {
let offset = inlay.position.to_offset(&snapshot.buffer); let offset = inlay.position.to_offset(&snapshot.buffer);
@ -570,13 +572,13 @@ impl InlayMap {
} }
let offset = inlay_to_insert.position.to_offset(&snapshot.buffer); let offset = inlay_to_insert.position.to_offset(&snapshot.buffer);
match self.inlays.binary_search_by(|probe| { match snapshot.inlays.binary_search_by(|probe| {
probe probe
.position .position
.cmp(&inlay_to_insert.position, &snapshot.buffer) .cmp(&inlay_to_insert.position, &snapshot.buffer)
}) { }) {
Ok(ix) | Err(ix) => { Ok(ix) | Err(ix) => {
self.inlays.insert(ix, inlay_to_insert); snapshot.inlays.insert(ix, inlay_to_insert);
} }
} }
@ -596,7 +598,7 @@ impl InlayMap {
} }
pub fn current_inlays(&self) -> impl Iterator<Item = &Inlay> { pub fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
self.inlays.iter() self.snapshot.inlays.iter()
} }
#[cfg(test)] #[cfg(test)]
@ -612,7 +614,7 @@ impl InlayMap {
let mut to_insert = Vec::new(); let mut to_insert = Vec::new();
let snapshot = &mut self.snapshot; let snapshot = &mut self.snapshot;
for i in 0..rng.gen_range(1..=5) { for i in 0..rng.gen_range(1..=5) {
if self.inlays.is_empty() || rng.gen() { if snapshot.inlays.is_empty() || rng.gen() {
let position = snapshot.buffer.random_byte_range(0, rng).start; let position = snapshot.buffer.random_byte_range(0, rng).start;
let bias = if rng.gen() { Bias::Left } else { Bias::Right }; let bias = if rng.gen() { Bias::Left } else { Bias::Right };
let len = if rng.gen_bool(0.01) { let len = if rng.gen_bool(0.01) {
@ -643,7 +645,8 @@ impl InlayMap {
}); });
} else { } else {
to_remove.push( to_remove.push(
self.inlays snapshot
.inlays
.iter() .iter()
.choose(rng) .choose(rng)
.map(|inlay| inlay.id) .map(|inlay| inlay.id)
@ -997,61 +1000,50 @@ impl InlaySnapshot {
cursor.seek(&range.start, Bias::Right, &()); cursor.seek(&range.start, Bias::Right, &());
let mut highlight_endpoints = Vec::new(); let mut highlight_endpoints = Vec::new();
// TODO kb repeat this for all other highlights?
if let Some(text_highlights) = highlights.text_highlights { if let Some(text_highlights) = highlights.text_highlights {
if !text_highlights.is_empty() { if !text_highlights.is_empty() {
while cursor.start().0 < range.end { self.apply_text_highlights(
let transform_start = self.buffer.anchor_after( &mut cursor,
self.to_buffer_offset(cmp::max(range.start, cursor.start().0)), &range,
); text_highlights,
let transform_end = { &mut highlight_endpoints,
let overshoot = InlayOffset(range.end.0 - cursor.start().0 .0); );
self.buffer.anchor_before(self.to_buffer_offset(cmp::min( cursor.seek(&range.start, Bias::Right, &());
cursor.end(&()).0, }
cursor.start().0 + overshoot, }
))) if let Some(inlay_highlights) = highlights.inlay_highlights {
}; let adjusted_highlights = TreeMap::from_ordered_entries(inlay_highlights.iter().map(
|(type_id, styled_ranges)| {
for (tag, text_highlights) in text_highlights.iter() { (
let style = text_highlights.0; *type_id,
let ranges = &text_highlights.1; Arc::new((styled_ranges.0, styled_ranges.1.as_slice())),
)
let start_ix = match ranges.binary_search_by(|probe| { },
let cmp = probe.end.cmp(&transform_start, &self.buffer); ));
if cmp.is_gt() { if !inlay_highlights.is_empty() {
cmp::Ordering::Greater self.apply_inlay_highlights(
} else { &mut cursor,
cmp::Ordering::Less &range,
} &adjusted_highlights,
}) { &mut highlight_endpoints,
Ok(i) | Err(i) => i, );
}; cursor.seek(&range.start, Bias::Right, &());
for range in &ranges[start_ix..] { }
if range.start.cmp(&transform_end, &self.buffer).is_ge() { }
break; if let Some(inlay_background_highlights) = highlights.inlay_background_highlights.as_ref() {
} if !inlay_background_highlights.is_empty() {
self.apply_inlay_highlights(
highlight_endpoints.push(HighlightEndpoint { &mut cursor,
offset: self.to_inlay_offset(range.start.to_offset(&self.buffer)), &range,
is_start: true, inlay_background_highlights,
tag: *tag, &mut highlight_endpoints,
style, );
});
highlight_endpoints.push(HighlightEndpoint {
offset: self.to_inlay_offset(range.end.to_offset(&self.buffer)),
is_start: false,
tag: *tag,
style,
});
}
}
cursor.next(&());
}
highlight_endpoints.sort();
cursor.seek(&range.start, Bias::Right, &()); cursor.seek(&range.start, Bias::Right, &());
} }
} }
highlight_endpoints.sort();
let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end); let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end);
let buffer_chunks = self.buffer.chunks(buffer_range, language_aware); let buffer_chunks = self.buffer.chunks(buffer_range, language_aware);
@ -1071,6 +1063,137 @@ impl InlaySnapshot {
} }
} }
fn apply_text_highlights(
&self,
cursor: &mut Cursor<'_, Transform, (InlayOffset, usize)>,
range: &Range<InlayOffset>,
text_highlights: &TreeMap<Option<TypeId>, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>,
highlight_endpoints: &mut Vec<HighlightEndpoint>,
) {
while cursor.start().0 < range.end {
let transform_start = self
.buffer
.anchor_after(self.to_buffer_offset(cmp::max(range.start, cursor.start().0)));
let transform_end =
{
let overshoot = InlayOffset(range.end.0 - cursor.start().0 .0);
self.buffer.anchor_before(self.to_buffer_offset(cmp::min(
cursor.end(&()).0,
cursor.start().0 + overshoot,
)))
};
for (tag, text_highlights) in text_highlights.iter() {
let style = text_highlights.0;
let ranges = &text_highlights.1;
let start_ix = match ranges.binary_search_by(|probe| {
let cmp = probe.end.cmp(&transform_start, &self.buffer);
if cmp.is_gt() {
cmp::Ordering::Greater
} else {
cmp::Ordering::Less
}
}) {
Ok(i) | Err(i) => i,
};
for range in &ranges[start_ix..] {
if range.start.cmp(&transform_end, &self.buffer).is_ge() {
break;
}
highlight_endpoints.push(HighlightEndpoint {
offset: self.to_inlay_offset(range.start.to_offset(&self.buffer)),
is_start: true,
tag: *tag,
style,
});
highlight_endpoints.push(HighlightEndpoint {
offset: self.to_inlay_offset(range.end.to_offset(&self.buffer)),
is_start: false,
tag: *tag,
style,
});
}
}
cursor.next(&());
}
}
fn apply_inlay_highlights(
&self,
cursor: &mut Cursor<'_, Transform, (InlayOffset, usize)>,
range: &Range<InlayOffset>,
inlay_highlights: &TreeMap<Option<TypeId>, Arc<(HighlightStyle, &[InlayRange])>>,
highlight_endpoints: &mut Vec<HighlightEndpoint>,
) {
while cursor.start().0 < range.end {
let transform_start = self
.buffer
.anchor_after(self.to_buffer_offset(cmp::max(range.start, cursor.start().0)));
let transform_start = self.to_inlay_offset(transform_start.to_offset(&self.buffer));
let transform_end =
{
let overshoot = InlayOffset(range.end.0 - cursor.start().0 .0);
self.buffer.anchor_before(self.to_buffer_offset(cmp::min(
cursor.end(&()).0,
cursor.start().0 + overshoot,
)))
};
let transform_end = self.to_inlay_offset(transform_end.to_offset(&self.buffer));
// TODO kb add a map
let hint_for_id = |id| self.inlays.iter().find(|inlay| inlay.id == id);
for (tag, inlay_highlights) in inlay_highlights.iter() {
let style = inlay_highlights.0;
let ranges = inlay_highlights
.1
.iter()
.filter_map(|range| Some((hint_for_id(range.inlay)?, range)))
.map(|(hint, range)| {
let hint_start =
self.to_inlay_offset(hint.position.to_offset(&self.buffer));
let highlight_start = InlayOffset(hint_start.0 + range.highlight_start);
let highlight_end = InlayOffset(hint_start.0 + range.highlight_end);
highlight_start..highlight_end
})
.collect::<Vec<_>>();
let start_ix = match ranges.binary_search_by(|probe| {
if probe.end > transform_start {
cmp::Ordering::Greater
} else {
cmp::Ordering::Less
}
}) {
Ok(i) | Err(i) => i,
};
for range in &ranges[start_ix..] {
if range.start >= transform_end {
break;
}
highlight_endpoints.push(HighlightEndpoint {
offset: range.start,
is_start: true,
tag: *tag,
style,
});
highlight_endpoints.push(HighlightEndpoint {
offset: range.end,
is_start: false,
tag: *tag,
style,
});
}
}
cursor.next(&());
}
}
#[cfg(test)] #[cfg(test)]
pub fn text(&self) -> String { pub fn text(&self) -> String {
self.chunks(Default::default()..self.len(), false, Highlights::default()) self.chunks(Default::default()..self.len(), false, Highlights::default())
@ -1495,7 +1618,12 @@ mod tests {
// The inlays can be manually removed. // The inlays can be manually removed.
let (inlay_snapshot, _) = inlay_map.splice( let (inlay_snapshot, _) = inlay_map.splice(
inlay_map.inlays.iter().map(|inlay| inlay.id).collect(), inlay_map
.snapshot
.inlays
.iter()
.map(|inlay| inlay.id)
.collect(),
Vec::new(), Vec::new(),
); );
assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi"); assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi");
@ -1587,6 +1715,7 @@ mod tests {
log::info!("inlay text: {:?}", inlay_snapshot.text()); log::info!("inlay text: {:?}", inlay_snapshot.text());
let inlays = inlay_map let inlays = inlay_map
.snapshot
.inlays .inlays
.iter() .iter()
.filter(|inlay| inlay.position.is_valid(&buffer_snapshot)) .filter(|inlay| inlay.position.is_valid(&buffer_snapshot))

View file

@ -99,6 +99,7 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
pub use sum_tree::Bias; pub use sum_tree::Bias;
use sum_tree::TreeMap;
use text::Rope; use text::Rope;
use theme::{DiagnosticStyle, Theme, ThemeSettings}; use theme::{DiagnosticStyle, Theme, ThemeSettings};
use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
@ -581,7 +582,7 @@ pub struct Editor {
placeholder_text: Option<Arc<str>>, placeholder_text: Option<Arc<str>>,
highlighted_rows: Option<Range<u32>>, highlighted_rows: Option<Range<u32>>,
background_highlights: BTreeMap<TypeId, BackgroundHighlight>, background_highlights: BTreeMap<TypeId, BackgroundHighlight>,
inlay_background_highlights: BTreeMap<TypeId, InlayBackgroundHighlight>, inlay_background_highlights: TreeMap<Option<TypeId>, InlayBackgroundHighlight>,
nav_history: Option<ItemNavHistory>, nav_history: Option<ItemNavHistory>,
context_menu: Option<ContextMenu>, context_menu: Option<ContextMenu>,
mouse_context_menu: ViewHandle<context_menu::ContextMenu>, mouse_context_menu: ViewHandle<context_menu::ContextMenu>,
@ -7823,7 +7824,7 @@ impl Editor {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
self.inlay_background_highlights self.inlay_background_highlights
.insert(TypeId::of::<T>(), (color_fetcher, ranges)); .insert(Some(TypeId::of::<T>()), (color_fetcher, ranges));
cx.notify(); cx.notify();
} }
@ -7832,7 +7833,9 @@ impl Editor {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<BackgroundHighlight> { ) -> Option<BackgroundHighlight> {
let text_highlights = self.background_highlights.remove(&TypeId::of::<T>()); let text_highlights = self.background_highlights.remove(&TypeId::of::<T>());
let inlay_highlights = self.inlay_background_highlights.remove(&TypeId::of::<T>()); let inlay_highlights = self
.inlay_background_highlights
.remove(&Some(TypeId::of::<T>()));
if text_highlights.is_some() || inlay_highlights.is_some() { if text_highlights.is_some() || inlay_highlights.is_some() {
cx.notify(); cx.notify();
} }

View file

@ -54,6 +54,7 @@ use std::{
ops::Range, ops::Range,
sync::Arc, sync::Arc,
}; };
use sum_tree::TreeMap;
use text::Point; use text::Point;
use workspace::item::Item; use workspace::item::Item;
@ -1592,11 +1593,28 @@ impl EditorElement {
.collect() .collect()
} else { } else {
let style = &self.style; let style = &self.style;
let theme = theme::current(cx);
let inlay_background_highlights =
TreeMap::from_ordered_entries(editor.inlay_background_highlights.iter().map(
|(type_id, (color_fetcher, ranges))| {
let color = Some(color_fetcher(&theme));
(
*type_id,
Arc::new((
HighlightStyle {
color,
..HighlightStyle::default()
},
ranges.as_slice(),
)),
)
},
));
let chunks = snapshot let chunks = snapshot
.chunks( .chunks(
rows.clone(), rows.clone(),
true, true,
Some(&editor.inlay_background_highlights), Some(inlay_background_highlights),
Some(style.theme.hint), Some(style.theme.hint),
Some(style.theme.suggestion), Some(style.theme.suggestion),
) )