Use text anchors as hint position in hints cache

co-authored-by: Max Brunsfeld <max@zed.dev>
This commit is contained in:
Kirill Bulatov 2023-06-22 22:07:09 +03:00
parent 781fa0cff4
commit 96a34ad0ee
2 changed files with 107 additions and 118 deletions

View file

@ -2614,9 +2614,12 @@ impl Editor {
let invalidate_cache = match reason { let invalidate_cache = match reason {
InlayRefreshReason::SettingsChange(new_settings) => { InlayRefreshReason::SettingsChange(new_settings) => {
let new_splice = self let new_splice = self.inlay_hint_cache.update_settings(
.inlay_hint_cache &self.buffer,
.update_settings(new_settings, get_update_state(self, cx)); new_settings,
get_update_state(self, cx),
cx,
);
if let Some(InlaySplice { if let Some(InlaySplice {
to_remove, to_remove,
to_insert, to_insert,

View file

@ -1,14 +1,13 @@
use std::cmp; use std::cmp;
use crate::{ use crate::{display_map::Inlay, editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBuffer};
display_map::Inlay, editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBufferSnapshot,
};
use anyhow::Context; use anyhow::Context;
use gpui::{Task, ViewContext}; use gpui::{ModelHandle, Task, ViewContext};
use log::error; use log::error;
use project::{InlayHint, InlayHintKind}; use project::{InlayHint, InlayHintKind};
use collections::{hash_map, HashMap, HashSet}; use collections::{hash_map, HashMap, HashSet};
use text::BufferSnapshot;
use util::post_inc; use util::post_inc;
pub struct InlayHintCache { pub struct InlayHintCache {
@ -21,22 +20,21 @@ struct InlayHintUpdateTask {
_task: Task<()>, _task: Task<()>,
} }
#[derive(Debug, Clone)] #[derive(Clone)]
struct CacheSnapshot { struct CacheSnapshot {
hints: HashMap<ExcerptId, ExcerptCachedHints>, hints: HashMap<ExcerptId, ExcerptCachedHints>,
allowed_hint_kinds: HashSet<Option<InlayHintKind>>, allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
version: usize, version: usize,
} }
#[derive(Debug, Clone)] #[derive(Clone)]
struct ExcerptCachedHints { struct ExcerptCachedHints {
version: usize, version: usize,
hints: Vec<(Anchor, InlayId, InlayHint)>, hints: Vec<(InlayId, InlayHint)>,
} }
#[derive(Clone)] #[derive(Clone)]
pub struct HintsUpdateState { pub struct HintsUpdateState {
multi_buffer_snapshot: MultiBufferSnapshot,
visible_inlays: Vec<Inlay>, visible_inlays: Vec<Inlay>,
cache: Box<CacheSnapshot>, cache: Box<CacheSnapshot>,
} }
@ -53,7 +51,7 @@ struct ExcerptHintsUpdate {
cache_version: usize, cache_version: usize,
remove_from_visible: Vec<InlayId>, remove_from_visible: Vec<InlayId>,
remove_from_cache: HashSet<InlayId>, remove_from_cache: HashSet<InlayId>,
add_to_cache: Vec<(Anchor, InlayHint)>, add_to_cache: Vec<InlayHint>,
} }
impl InlayHintCache { impl InlayHintCache {
@ -70,8 +68,10 @@ impl InlayHintCache {
pub fn update_settings( pub fn update_settings(
&mut self, &mut self,
multi_buffer: &ModelHandle<MultiBuffer>,
inlay_hint_settings: editor_settings::InlayHints, inlay_hint_settings: editor_settings::InlayHints,
update_state: HintsUpdateState, update_state: HintsUpdateState,
cx: &mut ViewContext<Editor>,
) -> Option<InlaySplice> { ) -> Option<InlaySplice> {
let new_allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); let new_allowed_hint_kinds = allowed_hint_types(inlay_hint_settings);
if !inlay_hint_settings.enabled { if !inlay_hint_settings.enabled {
@ -97,7 +97,8 @@ impl InlayHintCache {
return None; return None;
} }
let new_splice = new_allowed_hint_kinds_splice(update_state, &new_allowed_hint_kinds); let new_splice =
new_allowed_hint_kinds_splice(multi_buffer, update_state, &new_allowed_hint_kinds, cx);
if new_splice.is_some() { if new_splice.is_some() {
self.snapshot.version += 1; self.snapshot.version += 1;
self.update_tasks.clear(); self.update_tasks.clear();
@ -199,13 +200,20 @@ fn new_update_task(
cx: &mut ViewContext<'_, '_, Editor>, cx: &mut ViewContext<'_, '_, Editor>,
) -> InlayHintUpdateTask { ) -> InlayHintUpdateTask {
let hints_fetch_task = hints_fetch_task(buffer_id, excerpt_id, cx); let hints_fetch_task = hints_fetch_task(buffer_id, excerpt_id, cx);
let task_multi_buffer_snapshot = state.multi_buffer_snapshot.clone();
InlayHintUpdateTask { InlayHintUpdateTask {
version: cache_version, version: cache_version,
_task: cx.spawn(|editor, mut cx| async move { _task: cx.spawn(|editor, mut cx| async move {
let Some((multi_buffer_snapshot, buffer_snapshot)) = editor
.update(&mut cx, |editor, cx| {
let multi_buffer = editor.buffer().read(cx);
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
let buffer_snapshot = multi_buffer.buffer(buffer_id)?.read(cx).snapshot();
Some((multi_buffer_snapshot, buffer_snapshot))
}).ok().flatten() else { return; };
match hints_fetch_task.await { match hints_fetch_task.await {
Ok(Some(new_hints)) => { Ok(Some(new_hints)) => {
let task_buffer_snapshot = buffer_snapshot.clone();
if let Some(new_update) = cx if let Some(new_update) = cx
.background() .background()
.spawn(async move { .spawn(async move {
@ -214,6 +222,7 @@ fn new_update_task(
excerpt_id, excerpt_id,
new_hints, new_hints,
invalidate_cache, invalidate_cache,
&task_buffer_snapshot,
) )
}) })
.await .await
@ -237,12 +246,15 @@ fn new_update_task(
} }
editor.inlay_hint_cache.snapshot.version += 1; editor.inlay_hint_cache.snapshot.version += 1;
let mut splice = InlaySplice { let mut splice = InlaySplice {
to_remove: new_update.remove_from_visible, to_remove: new_update.remove_from_visible,
to_insert: Vec::new(), to_insert: Vec::new(),
}; };
for (new_hint_position, new_hint) in new_update.add_to_cache { for new_hint in new_update.add_to_cache {
let new_hint_position = multi_buffer_snapshot
.anchor_in_excerpt(excerpt_id, new_hint.position);
let new_inlay_id = InlayId(post_inc(&mut editor.next_inlay_id)); let new_inlay_id = InlayId(post_inc(&mut editor.next_inlay_id));
if editor if editor
.inlay_hint_cache .inlay_hint_cache
@ -257,21 +269,17 @@ fn new_update_task(
)); ));
} }
cached_excerpt_hints.hints.push(( cached_excerpt_hints.hints.push((new_inlay_id, new_hint));
new_hint_position,
new_inlay_id,
new_hint,
));
} }
cached_excerpt_hints.hints.sort_by( cached_excerpt_hints
|(position_a, _, _), (position_b, _, _)| { .hints
position_a.cmp(position_b, &task_multi_buffer_snapshot) .sort_by(|(_, hint_a), (_, hint_b)| {
}, hint_a.position.cmp(&hint_b.position, &buffer_snapshot)
); });
editor.inlay_hint_cache.snapshot.hints.retain( editor.inlay_hint_cache.snapshot.hints.retain(
|_, excerpt_hints| { |_, excerpt_hints| {
excerpt_hints.hints.retain(|(_, hint_id, _)| { excerpt_hints.hints.retain(|(hint_id, _)| {
!new_update.remove_from_cache.contains(hint_id) !new_update.remove_from_cache.contains(hint_id)
}); });
!excerpt_hints.hints.is_empty() !excerpt_hints.hints.is_empty()
@ -302,13 +310,14 @@ pub fn get_update_state(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Hi
HintsUpdateState { HintsUpdateState {
visible_inlays: visible_inlay_hints(editor, cx).cloned().collect(), visible_inlays: visible_inlay_hints(editor, cx).cloned().collect(),
cache: editor.inlay_hint_cache.snapshot(), cache: editor.inlay_hint_cache.snapshot(),
multi_buffer_snapshot: editor.buffer().read(cx).snapshot(cx),
} }
} }
fn new_allowed_hint_kinds_splice( fn new_allowed_hint_kinds_splice(
multi_buffer: &ModelHandle<MultiBuffer>,
state: HintsUpdateState, state: HintsUpdateState,
new_kinds: &HashSet<Option<InlayHintKind>>, new_kinds: &HashSet<Option<InlayHintKind>>,
cx: &mut ViewContext<Editor>,
) -> Option<InlaySplice> { ) -> Option<InlaySplice> {
let old_kinds = &state.cache.allowed_hint_kinds; let old_kinds = &state.cache.allowed_hint_kinds;
if new_kinds == old_kinds { if new_kinds == old_kinds {
@ -328,42 +337,56 @@ fn new_allowed_hint_kinds_splice(
}, },
); );
let multi_buffer = multi_buffer.read(cx);
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
for (excerpt_id, excerpt_cached_hints) in &state.cache.hints { for (excerpt_id, excerpt_cached_hints) in &state.cache.hints {
let shown_excerpt_hints_to_remove = shown_hints_to_remove.entry(*excerpt_id).or_default(); let shown_excerpt_hints_to_remove = shown_hints_to_remove.entry(*excerpt_id).or_default();
let mut excerpt_cached_hints = excerpt_cached_hints.hints.iter().fuse().peekable(); let mut excerpt_cache = excerpt_cached_hints.hints.iter().fuse().peekable();
shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| loop { shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| {
match excerpt_cached_hints.peek() { let Some(buffer) = shown_anchor
Some((cached_anchor, cached_hint_id, cached_hint)) => { .buffer_id
if cached_hint_id == shown_hint_id { .and_then(|buffer_id| multi_buffer.buffer(buffer_id)) else { return false };
excerpt_cached_hints.next(); let buffer_snapshot = buffer.read(cx).snapshot();
return !new_kinds.contains(&cached_hint.kind); loop {
} match excerpt_cache.peek() {
Some((cached_hint_id, cached_hint)) => {
match cached_anchor.cmp(shown_anchor, &state.multi_buffer_snapshot) { if cached_hint_id == shown_hint_id {
cmp::Ordering::Less | cmp::Ordering::Equal => { excerpt_cache.next();
if !old_kinds.contains(&cached_hint.kind) return !new_kinds.contains(&cached_hint.kind);
&& new_kinds.contains(&cached_hint.kind) }
{
to_insert.push(( match cached_hint
*cached_anchor, .position
*cached_hint_id, .cmp(&shown_anchor.text_anchor, &buffer_snapshot)
cached_hint.clone(), {
)); cmp::Ordering::Less | cmp::Ordering::Equal => {
} if !old_kinds.contains(&cached_hint.kind)
excerpt_cached_hints.next(); && new_kinds.contains(&cached_hint.kind)
{
to_insert.push((
multi_buffer_snapshot
.anchor_in_excerpt(*excerpt_id, cached_hint.position),
*cached_hint_id,
cached_hint.clone(),
));
}
excerpt_cache.next();
}
cmp::Ordering::Greater => return true,
} }
cmp::Ordering::Greater => return true,
} }
None => return true,
} }
None => return true,
} }
}); });
for (cached_anchor, cached_hint_id, maybe_missed_cached_hint) in excerpt_cached_hints { for (cached_hint_id, maybe_missed_cached_hint) in excerpt_cache {
let cached_hint_kind = maybe_missed_cached_hint.kind; let cached_hint_kind = maybe_missed_cached_hint.kind;
if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) { if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) {
to_insert.push(( to_insert.push((
*cached_anchor, multi_buffer_snapshot
.anchor_in_excerpt(*excerpt_id, maybe_missed_cached_hint.position),
*cached_hint_id, *cached_hint_id,
maybe_missed_cached_hint.clone(), maybe_missed_cached_hint.clone(),
)); ));
@ -392,73 +415,34 @@ fn new_excerpt_hints_update_result(
excerpt_id: ExcerptId, excerpt_id: ExcerptId,
new_excerpt_hints: Vec<InlayHint>, new_excerpt_hints: Vec<InlayHint>,
invalidate_cache: bool, invalidate_cache: bool,
buffer_snapshot: &BufferSnapshot,
) -> Option<ExcerptHintsUpdate> { ) -> Option<ExcerptHintsUpdate> {
let mut add_to_cache: Vec<(Anchor, InlayHint)> = Vec::new(); let mut add_to_cache: Vec<InlayHint> = Vec::new();
let shown_excerpt_hints = state let cached_excerpt_hints = state.cache.hints.get(&excerpt_id);
.visible_inlays
.iter()
.filter(|hint| hint.position.excerpt_id == excerpt_id)
.collect::<Vec<_>>();
let empty = Vec::new();
let cached_excerpt_hints = state
.cache
.hints
.get(&excerpt_id)
.map(|buffer_excerpts| &buffer_excerpts.hints)
.unwrap_or(&empty);
let mut excerpt_hints_to_persist = HashSet::default(); let mut excerpt_hints_to_persist = HashMap::default();
for new_hint in new_excerpt_hints { for new_hint in new_excerpt_hints {
// TODO kb this somehow spoils anchors and make them equal for different text anchors. let missing_from_cache = match cached_excerpt_hints {
let new_hint_anchor = state Some(cached_excerpt_hints) => {
.multi_buffer_snapshot match cached_excerpt_hints.hints.binary_search_by(|probe| {
.anchor_in_excerpt(excerpt_id, new_hint.position); probe.1.position.cmp(&new_hint.position, buffer_snapshot)
let should_add_to_cache = match cached_excerpt_hints }) {
.binary_search_by(|probe| probe.0.cmp(&new_hint_anchor, &state.multi_buffer_snapshot)) Ok(ix) => {
{ let (cached_inlay_id, cached_hint) = &cached_excerpt_hints.hints[ix];
Ok(ix) => { if cached_hint == &new_hint {
let (_, cached_inlay_id, cached_hint) = &cached_excerpt_hints[ix]; excerpt_hints_to_persist.insert(*cached_inlay_id, cached_hint.kind);
if cached_hint == &new_hint { false
excerpt_hints_to_persist.insert(*cached_inlay_id); } else {
false true
} else { }
true }
Err(_) => true,
} }
} }
Err(_) => true, None => true,
}; };
if missing_from_cache {
let shown_inlay_id = match shown_excerpt_hints.binary_search_by(|probe| { add_to_cache.push(new_hint);
probe
.position
.cmp(&new_hint_anchor, &state.multi_buffer_snapshot)
}) {
Ok(ix) => {
let shown_hint = &shown_excerpt_hints[ix];
state
.cache
.hints
.get(&excerpt_id)
.and_then(|excerpt_hints| {
excerpt_hints
.hints
.iter()
.find_map(|(_, cached_id, cached_hint)| {
if cached_id == &shown_hint.id && cached_hint == &new_hint {
Some(cached_id)
} else {
None
}
})
})
}
Err(_) => None,
};
if should_add_to_cache {
if shown_inlay_id.is_none() {
add_to_cache.push((new_hint_anchor, new_hint.clone()));
}
} }
} }
@ -466,18 +450,20 @@ fn new_excerpt_hints_update_result(
let mut remove_from_cache = HashSet::default(); let mut remove_from_cache = HashSet::default();
if invalidate_cache { if invalidate_cache {
remove_from_visible.extend( remove_from_visible.extend(
shown_excerpt_hints state
.visible_inlays
.iter() .iter()
.filter(|hint| hint.position.excerpt_id == excerpt_id)
.map(|inlay_hint| inlay_hint.id) .map(|inlay_hint| inlay_hint.id)
.filter(|hint_id| !excerpt_hints_to_persist.contains(hint_id)), .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)),
); );
remove_from_cache.extend( remove_from_cache.extend(
state state
.cache .cache
.hints .hints
.values() .values()
.flat_map(|excerpt_hints| excerpt_hints.hints.iter().map(|(_, id, _)| id)) .flat_map(|excerpt_hints| excerpt_hints.hints.iter().map(|(id, _)| id))
.filter(|cached_inlay_id| !excerpt_hints_to_persist.contains(cached_inlay_id)), .filter(|cached_inlay_id| !excerpt_hints_to_persist.contains_key(cached_inlay_id)),
); );
} }