Detailed changes
@@ -6,8 +6,8 @@ mod tab_map;
mod wrap_map;
use crate::{
- display_map::inlay_map::InlayProperties, Anchor, AnchorRangeExt, MultiBuffer,
- MultiBufferSnapshot, ToOffset, ToPoint,
+ display_map::inlay_map::InlayProperties, inlay_cache::InlayId, Anchor, AnchorRangeExt,
+ MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint,
};
pub use block_map::{BlockMap, BlockPoint};
use collections::{HashMap, HashSet};
@@ -287,7 +287,8 @@ impl DisplayMap {
pub fn splice_inlays(
&mut self,
- new_hints: Vec<(Anchor, project::InlayHint)>,
+ to_remove: Vec<InlayId>,
+ to_insert: Vec<(InlayId, Anchor, project::InlayHint)>,
cx: &mut ModelContext<Self>,
) {
let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
@@ -302,18 +303,19 @@ impl DisplayMap {
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
self.block_map.read(snapshot, edits);
- let new_inlays = new_hints
+ let new_inlays = to_insert
.into_iter()
- .map(|(hint_anchor, hint)| InlayProperties {
- position: hint_anchor.bias_left(&buffer_snapshot),
- text: hint.text(),
+ .map(|(inlay_id, hint_anchor, hint)| {
+ (
+ inlay_id,
+ InlayProperties {
+ position: hint_anchor.bias_left(&buffer_snapshot),
+ text: hint.text(),
+ },
+ )
})
.collect();
- let (snapshot, edits, _) = self.inlay_map.splice(
- // TODO kb this is wrong, calc diffs in the editor instead.
- self.inlay_map.inlays.keys().copied().collect(),
- new_inlays,
- );
+ let (snapshot, edits) = self.inlay_map.splice(to_remove, new_inlays);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map
@@ -5,8 +5,8 @@ use super::{
},
TextHighlights,
};
-use crate::{Anchor, MultiBufferSnapshot, ToPoint};
-use collections::{BTreeMap, HashMap, HashSet};
+use crate::{inlay_cache::InlayId, Anchor, MultiBufferSnapshot, ToPoint};
+use collections::{BTreeMap, HashMap};
use gpui::fonts::HighlightStyle;
use language::{Chunk, Edit, Point, Rope, TextSummary};
use parking_lot::Mutex;
@@ -16,14 +16,9 @@ use std::{
};
use sum_tree::{Bias, Cursor, SumTree};
use text::Patch;
-use util::post_inc;
-
-#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub struct InlayId(usize);
pub struct InlayMap {
snapshot: Mutex<InlaySnapshot>,
- next_inlay_id: usize,
pub(super) inlays: HashMap<InlayId, Inlay>,
}
@@ -247,7 +242,6 @@ impl InlayMap {
(
Self {
snapshot: Mutex::new(snapshot.clone()),
- next_inlay_id: 0,
inlays: HashMap::default(),
},
snapshot,
@@ -372,21 +366,19 @@ impl InlayMap {
pub fn splice<T: Into<Rope>>(
&mut self,
- to_remove: HashSet<InlayId>,
- to_insert: Vec<InlayProperties<T>>,
- ) -> (InlaySnapshot, Vec<InlayEdit>, Vec<InlayId>) {
+ to_remove: Vec<InlayId>,
+ to_insert: Vec<(InlayId, InlayProperties<T>)>,
+ ) -> (InlaySnapshot, Vec<InlayEdit>) {
let mut snapshot = self.snapshot.lock();
let mut inlays = BTreeMap::new();
- let mut new_ids = Vec::new();
- for properties in to_insert {
+ for (id, properties) in to_insert {
let inlay = Inlay {
- id: InlayId(post_inc(&mut self.next_inlay_id)),
+ id,
position: properties.position,
text: properties.text.into(),
};
self.inlays.insert(inlay.id, inlay.clone());
- new_ids.push(inlay.id);
let buffer_point = inlay.position.to_point(snapshot.buffer_snapshot());
let fold_point = snapshot
@@ -501,20 +493,20 @@ impl InlayMap {
snapshot.version += 1;
snapshot.check_invariants();
- (snapshot.clone(), inlay_edits.into_inner(), new_ids)
+ (snapshot.clone(), inlay_edits.into_inner())
}
#[cfg(any(test, feature = "test-support"))]
pub fn randomly_mutate(
&mut self,
rng: &mut rand::rngs::StdRng,
- ) -> (InlaySnapshot, Vec<InlayEdit>, Vec<InlayId>) {
+ ) -> (InlaySnapshot, Vec<InlayEdit>) {
use rand::prelude::*;
- let mut to_remove = HashSet::default();
- let mut to_insert = Vec::default();
+ let mut to_remove = Vec::new();
+ let mut to_insert = Vec::new();
let snapshot = self.snapshot.lock();
- for _ in 0..rng.gen_range(1..=5) {
+ for i in 0..rng.gen_range(1..=5) {
if self.inlays.is_empty() || rng.gen() {
let buffer_snapshot = snapshot.buffer_snapshot();
let position = buffer_snapshot.random_byte_range(0, rng).start;
@@ -529,12 +521,15 @@ impl InlayMap {
bias,
text
);
- to_insert.push(InlayProperties {
- position: buffer_snapshot.anchor_at(position, bias),
- text,
- });
+ to_insert.push((
+ InlayId(i),
+ InlayProperties {
+ position: buffer_snapshot.anchor_at(position, bias),
+ text,
+ },
+ ));
} else {
- to_remove.insert(*self.inlays.keys().choose(rng).unwrap());
+ to_remove.push(*self.inlays.keys().choose(rng).unwrap());
}
}
@@ -841,6 +836,7 @@ mod tests {
use settings::SettingsStore;
use std::env;
use text::Patch;
+ use util::post_inc;
#[gpui::test]
fn test_basic_inlays(cx: &mut AppContext) {
@@ -850,13 +846,17 @@ mod tests {
let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone());
let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone());
assert_eq!(inlay_snapshot.text(), "abcdefghi");
+ let mut next_inlay_id = 0;
- let (inlay_snapshot, _, _) = inlay_map.splice(
- HashSet::default(),
- vec![InlayProperties {
- position: buffer.read(cx).snapshot(cx).anchor_after(3),
- text: "|123|",
- }],
+ let (inlay_snapshot, _) = inlay_map.splice(
+ Vec::new(),
+ vec![(
+ InlayId(post_inc(&mut next_inlay_id)),
+ InlayProperties {
+ position: buffer.read(cx).snapshot(cx).anchor_after(3),
+ text: "|123|",
+ },
+ )],
);
assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
assert_eq!(
@@ -932,17 +932,23 @@ mod tests {
let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
- let (inlay_snapshot, _, inlay_ids) = inlay_map.splice(
- HashSet::default(),
+ let (inlay_snapshot, _) = inlay_map.splice(
+ Vec::new(),
vec![
- InlayProperties {
- position: buffer.read(cx).snapshot(cx).anchor_before(3),
- text: "|123|",
- },
- InlayProperties {
- position: buffer.read(cx).snapshot(cx).anchor_after(3),
- text: "|456|",
- },
+ (
+ InlayId(post_inc(&mut next_inlay_id)),
+ InlayProperties {
+ position: buffer.read(cx).snapshot(cx).anchor_before(3),
+ text: "|123|",
+ },
+ ),
+ (
+ InlayId(post_inc(&mut next_inlay_id)),
+ InlayProperties {
+ position: buffer.read(cx).snapshot(cx).anchor_after(3),
+ text: "|456|",
+ },
+ ),
],
);
assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
@@ -959,8 +965,8 @@ mod tests {
assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
// The inlays can be manually removed.
- let (inlay_snapshot, _, _) =
- inlay_map.splice::<String>(HashSet::from_iter(inlay_ids), Default::default());
+ let (inlay_snapshot, _) =
+ inlay_map.splice::<String>(inlay_map.inlays.keys().copied().collect(), Vec::new());
assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi");
}
@@ -996,8 +1002,8 @@ mod tests {
let mut buffer_edits = Vec::new();
match rng.gen_range(0..=100) {
0..=29 => {
- let (snapshot, edits, _) = inlay_map.randomly_mutate(&mut rng);
- inlay_snapshot = snapshot;
+ let (snapshot, edits) = inlay_map.randomly_mutate(&mut rng);
+ log::info!("mutated text: {:?}", snapshot.text());
inlay_edits = Patch::new(edits);
}
30..=59 => {
@@ -2,7 +2,7 @@ mod blink_manager;
pub mod display_map;
mod editor_settings;
mod element;
-mod inlay_hint_storage;
+mod inlay_cache;
mod git;
mod highlight_matching_bracket;
@@ -26,8 +26,8 @@ use aho_corasick::AhoCorasick;
use anyhow::{anyhow, Context, Result};
use blink_manager::BlinkManager;
use client::{ClickhouseEvent, TelemetrySettings};
-use clock::ReplicaId;
-use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
+use clock::{Global, ReplicaId};
+use collections::{hash_map, BTreeMap, Bound, HashMap, HashSet, VecDeque};
use copilot::Copilot;
pub use display_map::DisplayPoint;
use display_map::*;
@@ -53,7 +53,7 @@ use gpui::{
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
-use inlay_hint_storage::InlayHintStorage;
+use inlay_cache::{InlayCache, InlaysUpdate, OrderedByAnchorOffset};
pub use items::MAX_TAB_TITLE_LEN;
use itertools::Itertools;
pub use language::{char_kind, CharKind};
@@ -540,7 +540,7 @@ pub struct Editor {
gutter_hovered: bool,
link_go_to_definition_state: LinkGoToDefinitionState,
copilot_state: CopilotState,
- inlay_hint_storage: InlayHintStorage,
+ inlay_hint_cache: InlayCache,
_subscriptions: Vec<Subscription>,
}
@@ -1355,7 +1355,7 @@ impl Editor {
hover_state: Default::default(),
link_go_to_definition_state: Default::default(),
copilot_state: Default::default(),
- inlay_hint_storage: InlayHintStorage::default(),
+ inlay_hint_cache: InlayCache::default(),
gutter_hovered: false,
_subscriptions: vec![
cx.observe(&buffer, Self::on_buffer_changed),
@@ -2604,7 +2604,14 @@ impl Editor {
// TODO kb every time I reopen the same buffer, it's different.
// Find a way to understand it's the same buffer. Use paths?
let buffer_id = buffer_snapshot.remote_id();
+ let buffer_version = buffer_snapshot.version().clone();
let buffer_handle = multi_buffer.read(cx).buffer(buffer_id)?;
+ if self
+ .inlay_hint_cache
+ .inlays_up_to_date(buffer_id, &buffer_version, excerpt_id)
+ {
+ return None;
+ }
let task = cx.spawn(|editor, mut cx| async move {
let task = editor
@@ -2622,6 +2629,8 @@ impl Editor {
.context("inlay hints fecth task spawn")?;
anyhow::Ok((
+ buffer_id,
+ buffer_version,
excerpt_id,
match task {
Some(task) => task.await.context("inlay hints for buffer task")?,
@@ -2634,29 +2643,52 @@ impl Editor {
.collect::<Vec<_>>();
cx.spawn(|editor, mut cx| async move {
- let mut hints_to_draw: Vec<(Anchor, InlayHint)> = Vec::new();
+ let mut hints_response: HashMap<
+ u64,
+ (Global, HashMap<ExcerptId, OrderedByAnchorOffset<InlayHint>>),
+ > = HashMap::default();
let multi_buffer_snapshot =
editor.read_with(&cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))?;
for task_result in futures::future::join_all(hint_fetch_tasks).await {
match task_result {
- Ok((excerpt_id, excerpt_hints)) => {
- if !excerpt_hints.is_empty() {
- hints_to_draw.extend(excerpt_hints.into_iter().map(|hint| {
- let anchor = multi_buffer_snapshot
- .anchor_in_excerpt(excerpt_id, hint.position);
- (anchor, hint)
- }));
+ Ok((buffer_id, buffer_version, excerpt_id, excerpt_hints)) => {
+ let excerpt_hints_response = HashMap::from_iter([(
+ excerpt_id,
+ excerpt_hints.into_iter().fold(
+ OrderedByAnchorOffset::default(),
+ |mut ordered_hints, hint| {
+ let anchor = multi_buffer_snapshot
+ .anchor_in_excerpt(excerpt_id, hint.position);
+ ordered_hints.add(anchor, hint);
+ ordered_hints
+ },
+ ),
+ )]);
+ match hints_response.entry(buffer_id) {
+ hash_map::Entry::Occupied(mut o) => {
+ o.get_mut().1.extend(excerpt_hints_response);
+ }
+ hash_map::Entry::Vacant(v) => {
+ v.insert((buffer_version, excerpt_hints_response));
+ }
}
}
Err(e) => error!("Failed to update hints for buffer: {e:#}"),
}
}
- if !hints_to_draw.is_empty() {
+ if !hints_response.is_empty() {
+ let InlaysUpdate {
+ to_remove,
+ to_insert,
+ } = editor.update(&mut cx, |editor, _| {
+ editor.inlay_hint_cache.update_inlays(hints_response)
+ })?;
+
editor.update(&mut cx, |editor, cx| {
editor.display_map.update(cx, |display_map, cx| {
- display_map.splice_inlays(hints_to_draw, cx);
+ display_map.splice_inlays(to_remove, to_insert, cx);
});
})?;
}
@@ -0,0 +1,204 @@
+use std::cmp;
+
+use crate::{Anchor, ExcerptId};
+use clock::Global;
+use project::InlayHint;
+use util::post_inc;
+
+use collections::{BTreeMap, HashMap};
+
+#[derive(Clone, Debug, Default)]
+pub struct InlayCache {
+ inlays_per_buffer: HashMap<u64, BufferInlays>,
+ next_inlay_id: usize,
+}
+
+#[derive(Clone, Debug)]
+pub struct OrderedByAnchorOffset<T>(pub BTreeMap<usize, (Anchor, T)>);
+
+impl<T> OrderedByAnchorOffset<T> {
+ pub fn add(&mut self, anchor: Anchor, t: T) {
+ self.0.insert(anchor.text_anchor.offset, (anchor, t));
+ }
+
+ fn into_ordered_elements(self) -> impl Iterator<Item = (Anchor, T)> {
+ self.0.into_values()
+ }
+}
+
+impl<T> Default for OrderedByAnchorOffset<T> {
+ fn default() -> Self {
+ Self(BTreeMap::default())
+ }
+}
+
+#[derive(Clone, Debug, Default)]
+struct BufferInlays {
+ buffer_version: Global,
+ inlays_per_excerpts: HashMap<ExcerptId, OrderedByAnchorOffset<(InlayId, InlayHint)>>,
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct InlayId(pub usize);
+
+#[derive(Debug)]
+pub struct InlaysUpdate {
+ pub to_remove: Vec<InlayId>,
+ pub to_insert: Vec<(InlayId, Anchor, InlayHint)>,
+}
+
+impl InlayCache {
+ pub fn inlays_up_to_date(
+ &self,
+ buffer_id: u64,
+ buffer_version: &Global,
+ excerpt_id: ExcerptId,
+ ) -> bool {
+ let Some(buffer_inlays) = self.inlays_per_buffer.get(&buffer_id) else { return false };
+ let buffer_up_to_date = buffer_version == &buffer_inlays.buffer_version
+ || buffer_inlays.buffer_version.changed_since(buffer_version);
+ buffer_up_to_date && buffer_inlays.inlays_per_excerpts.contains_key(&excerpt_id)
+ }
+
+ pub fn update_inlays(
+ &mut self,
+ new_inlays: HashMap<u64, (Global, HashMap<ExcerptId, OrderedByAnchorOffset<InlayHint>>)>,
+ ) -> InlaysUpdate {
+ let mut old_inlays = self.inlays_per_buffer.clone();
+ let mut to_remove = Vec::new();
+ let mut to_insert = Vec::new();
+
+ for (buffer_id, (buffer_version, new_buffer_inlays)) in new_inlays {
+ match old_inlays.remove(&buffer_id) {
+ Some(mut old_buffer_inlays) => {
+ for (excerpt_id, new_excerpt_inlays) in new_buffer_inlays {
+ if self.inlays_up_to_date(buffer_id, &buffer_version, excerpt_id) {
+ continue;
+ }
+
+ let self_inlays_per_buffer = self
+ .inlays_per_buffer
+ .get_mut(&buffer_id)
+ .expect("element expected: `old_inlays.remove` returned `Some`");
+ let mut new_excerpt_inlays =
+ new_excerpt_inlays.into_ordered_elements().fuse().peekable();
+ if old_buffer_inlays
+ .inlays_per_excerpts
+ .remove(&excerpt_id)
+ .is_some()
+ {
+ let self_excerpt_inlays = self_inlays_per_buffer
+ .inlays_per_excerpts
+ .get_mut(&excerpt_id)
+ .expect("element expected: `old_excerpt_inlays` is `Some`");
+ let mut hints_to_add = Vec::<(Anchor, (InlayId, InlayHint))>::new();
+ self_excerpt_inlays.0.retain(
+ |_, (old_anchor, (old_inlay_id, old_inlay))| {
+ let mut retain = false;
+
+ while let Some(new_offset) = new_excerpt_inlays
+ .peek()
+ .map(|(new_anchor, _)| new_anchor.text_anchor.offset)
+ {
+ let old_offset = old_anchor.text_anchor.offset;
+ match new_offset.cmp(&old_offset) {
+ cmp::Ordering::Less => {
+ let (new_anchor, new_inlay) =
+ new_excerpt_inlays.next().expect(
+ "element expected: `peek` returned `Some`",
+ );
+ hints_to_add.push((
+ new_anchor,
+ (
+ InlayId(post_inc(&mut self.next_inlay_id)),
+ new_inlay,
+ ),
+ ));
+ }
+ cmp::Ordering::Equal => {
+ let (new_anchor, new_inlay) =
+ new_excerpt_inlays.next().expect(
+ "element expected: `peek` returned `Some`",
+ );
+ if &new_inlay == old_inlay {
+ retain = true;
+ } else {
+ hints_to_add.push((
+ new_anchor,
+ (
+ InlayId(post_inc(
+ &mut self.next_inlay_id,
+ )),
+ new_inlay,
+ ),
+ ));
+ }
+ }
+ cmp::Ordering::Greater => break,
+ }
+ }
+
+ if !retain {
+ to_remove.push(*old_inlay_id);
+ }
+ retain
+ },
+ );
+
+ for (new_anchor, (id, new_inlay)) in hints_to_add {
+ self_excerpt_inlays.add(new_anchor, (id, new_inlay.clone()));
+ to_insert.push((id, new_anchor, new_inlay));
+ }
+ }
+
+ for (new_anchor, new_inlay) in new_excerpt_inlays {
+ let id = InlayId(post_inc(&mut self.next_inlay_id));
+ self_inlays_per_buffer
+ .inlays_per_excerpts
+ .entry(excerpt_id)
+ .or_default()
+ .add(new_anchor, (id, new_inlay.clone()));
+ to_insert.push((id, new_anchor, new_inlay));
+ }
+ }
+ }
+ None => {
+ let mut inlays_per_excerpts: HashMap<
+ ExcerptId,
+ OrderedByAnchorOffset<(InlayId, InlayHint)>,
+ > = HashMap::default();
+ for (new_excerpt_id, new_ordered_inlays) in new_buffer_inlays {
+ for (new_anchor, new_inlay) in new_ordered_inlays.into_ordered_elements() {
+ let id = InlayId(post_inc(&mut self.next_inlay_id));
+ inlays_per_excerpts
+ .entry(new_excerpt_id)
+ .or_default()
+ .add(new_anchor, (id, new_inlay.clone()));
+ to_insert.push((id, new_anchor, new_inlay));
+ }
+ }
+ self.inlays_per_buffer.insert(
+ buffer_id,
+ BufferInlays {
+ buffer_version,
+ inlays_per_excerpts,
+ },
+ );
+ }
+ }
+ }
+
+ for (_, old_buffer_inlays) in old_inlays {
+ for (_, old_excerpt_inlays) in old_buffer_inlays.inlays_per_excerpts {
+ for (_, (id_to_remove, _)) in old_excerpt_inlays.into_ordered_elements() {
+ to_remove.push(id_to_remove);
+ }
+ }
+ }
+
+ InlaysUpdate {
+ to_remove,
+ to_insert,
+ }
+ }
+}
@@ -1,46 +0,0 @@
-use crate::Anchor;
-use project::InlayHint;
-
-use collections::BTreeMap;
-
-#[derive(Debug, Default)]
-pub struct InlayHintStorage {
- hints: BTreeMap<Anchor, InlayHint>,
-}
-
-impl InlayHintStorage {
- fn insert(&mut self) -> bool {
- todo!("TODO kb")
- }
-}
-// TODO kb need to understand different inlay hint update cases:
-// * new hints from the new excerpt (no need to invalidate the cache)
-// * new hints after /refresh or a text edit (whole cache should be purged)
-// ??? revert/reopened files could get a speedup, if we don't truly delete the hints, but hide them in another var?
-
-// let buffer_version =
-// cx.read(|cx| buffer.read(cx).version().clone());
-
-// #[derive(Debug, Default, Clone)]
-// struct InlayHintVersions {
-// last_buffer_versions_with_hints: HashMap<InlayHintLocation, Global>,
-// }
-
-// impl InlayHintVersions {
-// fn absent_or_newer(&self, location: &InlayHintLocation, new_version: &Global) -> bool {
-// self.last_buffer_versions_with_hints
-// .get(location)
-// .map(|last_version_with_hints| new_version.changed_since(&last_version_with_hints))
-// .unwrap_or(true)
-// }
-
-// fn insert(&mut self, location: InlayHintLocation, new_version: Global) -> bool {
-// if self.absent_or_newer(&location, &new_version) {
-// self.last_buffer_versions_with_hints
-// .insert(location, new_version);
-// true
-// } else {
-// false
-// }
-// }
-// }