Use text anchors as hint position in hints cache
co-authored-by: Max Brunsfeld <max@zed.dev>
This commit is contained in:
parent
781fa0cff4
commit
96a34ad0ee
2 changed files with 107 additions and 118 deletions
|
@ -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,
|
||||||
|
|
|
@ -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)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue