Co-authored-by: Kirill <kirill@zed.dev>
This commit is contained in:
Piotr Osiewicz 2023-11-02 10:59:10 +01:00
parent a9e3d8c9a4
commit 575e193ea3
3 changed files with 435 additions and 361 deletions

View file

@ -1555,6 +1555,106 @@ impl FormatProvider for ModelHandle<Formatter> {
}
}
trait InlayHints {
fn refresh_inlay_hints(
&mut self,
reason: InlayHintRefreshReason,
editor: &mut Editor,
cx: &mut ViewContext<Editor>,
);
fn is_enabled(&self, cx: &AppContext) -> bool;
}
impl InlayHints for ModelHandle<InlayHintCache> {
fn refresh_inlay_hints(
&mut self,
reason: InlayHintRefreshReason,
editor: &mut Editor,
cx: &mut ViewContext<Editor>,
) {
if editor.mode != EditorMode::Full {
return;
}
let cache_handle = self.clone();
self.update(cx, |this, cx| {
let reason_description = reason.description();
let (invalidate_cache, required_languages) = match reason {
InlayHintRefreshReason::Toggle(enabled) => {
this.enabled = enabled;
if enabled {
(InvalidationStrategy::RefreshRequested, None)
} else {
this.clear();
editor.splice_inlay_hints(
editor
.visible_inlay_hints(cx)
.iter()
.map(|inlay| inlay.id)
.collect(),
Vec::new(),
cx,
);
return;
}
}
InlayHintRefreshReason::SettingsChange(new_settings) => {
match this.update_settings(
&editor.buffer,
new_settings,
editor.visible_inlay_hints(cx),
cx,
) {
ControlFlow::Break(Some(InlaySplice {
to_remove,
to_insert,
})) => {
editor.splice_inlay_hints(to_remove, to_insert, cx);
return;
}
ControlFlow::Break(None) => return,
ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
}
}
InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
if let Some(InlaySplice {
to_remove,
to_insert,
}) = this.remove_excerpts(excerpts_removed)
{
editor.splice_inlay_hints(to_remove, to_insert, cx);
}
return;
}
InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
InlayHintRefreshReason::BufferEdited(buffer_languages) => {
(InvalidationStrategy::BufferEdited, Some(buffer_languages))
}
InlayHintRefreshReason::RefreshRequested => {
(InvalidationStrategy::RefreshRequested, None)
}
};
if let Some(InlaySplice {
to_remove,
to_insert,
}) = this.spawn_hint_refresh(
reason_description,
editor.excerpt_visible_offsets(required_languages.as_ref(), cx),
invalidate_cache,
cache_handle,
cx,
) {
editor.splice_inlay_hints(to_remove, to_insert, cx);
}
});
}
fn is_enabled(&self, cx: &AppContext) -> bool {
self.read(cx).enabled
}
}
pub struct Editor {
handle: WeakViewHandle<Self>,
buffer: ModelHandle<MultiBuffer>,
@ -1604,7 +1704,6 @@ pub struct Editor {
gutter_hovered: bool,
link_go_to_definition_state: LinkGoToDefinitionState,
copilot_state: CopilotState,
inlay_hint_cache: InlayHintCache,
next_inlay_id: usize,
_subscriptions: Vec<Subscription>,
pixel_position_of_newest_cursor: Option<Vector2F>,
@ -1613,6 +1712,7 @@ pub struct Editor {
go_to_definition_provider: Option<Box<dyn GoToDefinitionProvider>>,
rename_provider: Option<Box<dyn RenameProvider>>,
format_provider: Option<Box<dyn FormatProvider>>,
inlay_hints: Option<ModelHandle<InlayHintCache>>,
}
pub struct EditorSnapshot {
@ -2881,7 +2981,6 @@ impl Editor {
hover_state: Default::default(),
link_go_to_definition_state: Default::default(),
copilot_state: Default::default(),
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
gutter_hovered: false,
pixel_position_of_newest_cursor: None,
_subscriptions: vec![
@ -2906,6 +3005,9 @@ impl Editor {
go_to_definition_provider,
rename_provider,
format_provider,
inlay_hints: project
.clone()
.map(|project, _| InlayHintCache::new(project, inlay_hint_settings)),
};
this._subscriptions.extend(project_subscriptions);
@ -4255,87 +4357,23 @@ impl Editor {
}
pub fn toggle_inlay_hints(&mut self, _: &ToggleInlayHints, cx: &mut ViewContext<Self>) {
self.refresh_inlay_hints(
InlayHintRefreshReason::Toggle(!self.inlay_hint_cache.enabled),
cx,
);
if let Some(enabled) = self.inlay_hints.map(|hints| hints.is_enabled(cx)) {
self.refresh_inlay_hints(InlayHintRefreshReason::Toggle(!enabled), cx);
}
}
pub fn inlay_hints_enabled(&self) -> bool {
self.inlay_hint_cache.enabled
self.inlay_hints
.map(|hints| hints.is_enabled(cx))
.unwrap_or_default()
}
fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut ViewContext<Self>) {
if self.project.is_none() || self.mode != EditorMode::Full {
return;
}
let reason_description = reason.description();
let (invalidate_cache, required_languages) = match reason {
InlayHintRefreshReason::Toggle(enabled) => {
self.inlay_hint_cache.enabled = enabled;
if enabled {
(InvalidationStrategy::RefreshRequested, None)
} else {
self.inlay_hint_cache.clear();
self.splice_inlay_hints(
self.visible_inlay_hints(cx)
.iter()
.map(|inlay| inlay.id)
.collect(),
Vec::new(),
cx,
);
return;
}
}
InlayHintRefreshReason::SettingsChange(new_settings) => {
match self.inlay_hint_cache.update_settings(
&self.buffer,
new_settings,
self.visible_inlay_hints(cx),
cx,
) {
ControlFlow::Break(Some(InlaySplice {
to_remove,
to_insert,
})) => {
self.splice_inlay_hints(to_remove, to_insert, cx);
return;
}
ControlFlow::Break(None) => return,
ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
}
}
InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
if let Some(InlaySplice {
to_remove,
to_insert,
}) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
{
self.splice_inlay_hints(to_remove, to_insert, cx);
}
return;
}
InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
InlayHintRefreshReason::BufferEdited(buffer_languages) => {
(InvalidationStrategy::BufferEdited, Some(buffer_languages))
}
InlayHintRefreshReason::RefreshRequested => {
(InvalidationStrategy::RefreshRequested, None)
}
};
if let Some(InlaySplice {
to_remove,
to_insert,
}) = self.inlay_hint_cache.spawn_hint_refresh(
reason_description,
self.excerpt_visible_offsets(required_languages.as_ref(), cx),
invalidate_cache,
cx,
) {
self.splice_inlay_hints(to_remove, to_insert, cx);
if let Some(inlay_hints) = &self.inlay_hints {
cx.spawn(|editor, cx| {
inlay_hints.refresh_inlay_hints(reason, editor, cx);
})
.detach()
}
}
@ -9353,10 +9391,6 @@ impl Editor {
cx.write_to_clipboard(ClipboardItem::new(lines));
}
pub fn inlay_hint_cache(&self) -> &InlayHintCache {
&self.inlay_hint_cache
}
pub fn replay_insert_event(
&mut self,
text: &str,
@ -9414,6 +9448,24 @@ impl Editor {
});
supports
}
#[cfg(any(test, feature = "test-support"))]
fn inlay_hint_cache_version(&self, cx: &mut AppContext) -> usize {
self.inlay_hints
.expect("test code does not have hints enabled")
.update(cx, |hints, _| hints.version)
}
#[cfg(any(test, feature = "test-support"))]
fn inlay_hint_cache_entries(
&self,
excerpt_id: ExcerptId,
cx: &mut AppContext,
) -> Option<&Arc<RwLock<CachedExcerptHints>>> {
self.inlay_hints
.expect("test code does not have hints enabled")
.update(cx, |hints, _| hints.hints.get(&excerpt_id).cloned())
}
}
pub trait CollaborationHub {
@ -9424,6 +9476,8 @@ pub trait CollaborationHub {
) -> &'a HashMap<u64, ParticipantIndex>;
}
// Option<ModelHandle<Project>>
// Option<Box<dyn LspBridge>>
impl CollaborationHub for ModelHandle<Project> {
fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator> {
self.read(cx).collaborators()

View file

@ -11,10 +11,10 @@ use crate::{
use anyhow::Context;
use clock::Global;
use futures::future;
use gpui::{ModelContext, ModelHandle, Task, ViewContext};
use gpui::{Entity, ModelContext, ModelHandle, Task, ViewContext};
use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot};
use parking_lot::RwLock;
use project::{InlayHint, ResolveState};
use project::{InlayHint, Project, ResolveState};
use collections::{hash_map, HashMap, HashSet};
use language::language_settings::InlayHintSettings;
@ -24,9 +24,11 @@ use text::{ToOffset, ToPoint};
use util::post_inc;
pub struct InlayHintCache {
hints: HashMap<ExcerptId, Arc<RwLock<CachedExcerptHints>>>,
// TODO kb consider weak handles
project: ModelHandle<Project>,
pub(super) hints: HashMap<ExcerptId, Arc<RwLock<CachedExcerptHints>>>,
allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
version: usize,
pub(super) version: usize,
pub(super) enabled: bool,
update_tasks: HashMap<ExcerptId, TasksForRanges>,
lsp_request_limiter: Arc<Semaphore>,
@ -236,9 +238,13 @@ impl TasksForRanges {
}
}
impl Entity for InlayHintCache {
type Event = ();
}
impl InlayHintCache {
pub fn new(inlay_hint_settings: InlayHintSettings) -> Self {
pub fn new(project: ModelHandle<Project>, inlay_hint_settings: InlayHintSettings) -> Self {
Self {
project,
allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(),
enabled: inlay_hint_settings.enabled,
hints: HashMap::default(),
@ -304,6 +310,7 @@ impl InlayHintCache {
reason: &'static str,
excerpts_to_query: HashMap<ExcerptId, (ModelHandle<Buffer>, Global, Range<usize>)>,
invalidate: InvalidationStrategy,
cache: ModelHandle<Self>,
cx: &mut ViewContext<Editor>,
) -> Option<InlaySplice> {
if !self.enabled {
@ -336,6 +343,7 @@ impl InlayHintCache {
excerpts_to_query,
invalidate,
cache_version,
cache,
cx,
)
})
@ -538,7 +546,7 @@ impl InlayHintCache {
.read(cx)
.buffer(buffer_id)
.and_then(|buffer| {
let project = editor.project.as_ref()?;
let project = self.project;
Some(project.update(cx, |project, cx| {
project.resolve_inlay_hint(
hint_to_resolve,
@ -582,6 +590,7 @@ fn spawn_new_update_tasks(
excerpts_to_query: HashMap<ExcerptId, (ModelHandle<Buffer>, Global, Range<usize>)>,
invalidate: InvalidationStrategy,
update_cache_version: usize,
cache: ModelHandle<InlayHintCache>,
cx: &mut ViewContext<'_, '_, Editor>,
) {
let visible_hints = Arc::new(editor.visible_inlay_hints(cx));
@ -601,7 +610,8 @@ fn spawn_new_update_tasks(
continue;
}
let cached_excerpt_hints = editor.inlay_hint_cache.hints.get(&excerpt_id).cloned();
let cached_excerpt_hints =
cache.update(cx, |cache, _| cache.hints.get(&excerpt_id).cloned());
if let Some(cached_excerpt_hints) = &cached_excerpt_hints {
let cached_excerpt_hints = cached_excerpt_hints.read();
let cached_buffer_version = &cached_excerpt_hints.buffer_version;
@ -636,35 +646,39 @@ fn spawn_new_update_tasks(
reason,
};
let new_update_task = |query_ranges| {
new_update_task(
query,
query_ranges,
multi_buffer_snapshot,
buffer_snapshot.clone(),
Arc::clone(&visible_hints),
cached_excerpt_hints,
Arc::clone(&editor.inlay_hint_cache.lsp_request_limiter),
cx,
)
};
match editor.inlay_hint_cache.update_tasks.entry(excerpt_id) {
hash_map::Entry::Occupied(mut o) => {
o.get_mut().update_cached_tasks(
&buffer_snapshot,
let cache_handle = cache.clone();
cache.update(cx, |cache, _| {
let new_update_task = |query_ranges| {
new_update_task(
query,
query_ranges,
invalidate,
new_update_task,
);
multi_buffer_snapshot,
buffer_snapshot.clone(),
Arc::clone(&visible_hints),
cached_excerpt_hints,
Arc::clone(&cache.lsp_request_limiter),
cache_handle,
cx,
)
};
match cache.update_tasks.entry(excerpt_id) {
hash_map::Entry::Occupied(mut o) => {
o.get_mut().update_cached_tasks(
&buffer_snapshot,
query_ranges,
invalidate,
new_update_task,
);
}
hash_map::Entry::Vacant(v) => {
v.insert(TasksForRanges::new(
query_ranges.clone(),
new_update_task(query_ranges),
));
}
}
hash_map::Entry::Vacant(v) => {
v.insert(TasksForRanges::new(
query_ranges.clone(),
new_update_task(query_ranges),
));
}
}
})
}
}
@ -760,6 +774,7 @@ fn new_update_task(
visible_hints: Arc<Vec<Inlay>>,
cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
lsp_request_limiter: Arc<Semaphore>,
cache: ModelHandle<InlayHintCache>,
cx: &mut ViewContext<'_, '_, Editor>,
) -> Task<()> {
cx.spawn(|editor, mut cx| async move {
@ -775,6 +790,7 @@ fn new_update_task(
invalidate,
range,
Arc::clone(&lsp_request_limiter),
cache,
closure_cx.clone(),
)
};
@ -795,13 +811,9 @@ fn new_update_task(
let mut query_range_failed = |range: &Range<language::Anchor>, e: anyhow::Error| {
log::error!("inlay hint update task for range {range:?} failed: {e:#}");
editor
.update(&mut cx, |editor, _| {
if let Some(task_ranges) = editor
.inlay_hint_cache
.update_tasks
.get_mut(&query.excerpt_id)
{
cache
.update(&mut cx, |cache, _| {
if let Some(task_ranges) = cache.update_tasks.get_mut(&query.excerpt_id) {
task_ranges.invalidate_range(&buffer_snapshot, &range);
}
})
@ -846,6 +858,7 @@ async fn fetch_and_update_hints(
invalidate: bool,
fetch_range: Range<language::Anchor>,
lsp_request_limiter: Arc<Semaphore>,
cache: ModelHandle<InlayHintCache>,
mut cx: gpui::AsyncAppContext,
) -> anyhow::Result<()> {
let (lsp_request_guard, got_throttled) = if query.invalidate.should_invalidate() {
@ -880,13 +893,14 @@ async fn fetch_and_update_hints(
};
if query_not_around_visible_range {
log::trace!("Fetching inlay hints for range {fetch_range_to_log:?} got throttled and fell off the current visible range, skipping.");
if let Some(task_ranges) = editor
.inlay_hint_cache
.update_tasks
.get_mut(&query.excerpt_id)
{
task_ranges.invalidate_range(&buffer_snapshot, &fetch_range);
}
cache.update(cx, |cache, _| {
if let Some(task_ranges) = cache
.update_tasks
.get_mut(&query.excerpt_id)
{
task_ranges.invalidate_range(&buffer_snapshot, &fetch_range);
}
});
return None;
}
}
@ -895,7 +909,7 @@ async fn fetch_and_update_hints(
.read(cx)
.buffer(query.buffer_id)
.and_then(|buffer| {
let project = editor.project.as_ref()?;
let project = cache.read(cx).project;
Some(project.update(cx, |project, cx| {
project.inlay_hints(buffer, fetch_range.clone(), cx)
}))
@ -957,6 +971,7 @@ async fn fetch_and_update_hints(
invalidate,
buffer_snapshot,
multi_buffer_snapshot,
cache,
cx,
);
})
@ -1071,13 +1086,11 @@ fn apply_hint_update(
invalidate: bool,
buffer_snapshot: BufferSnapshot,
multi_buffer_snapshot: MultiBufferSnapshot,
cache: ModelHandle<InlayHintsCache>,
cx: &mut ViewContext<'_, '_, Editor>,
) {
let cached_excerpt_hints = editor
.inlay_hint_cache
.hints
.entry(new_update.excerpt_id)
.or_insert_with(|| {
cache.update(cx, |cache, cx| {
let cached_excerpt_hints = cache.hints.entry(new_update.excerpt_id).or_insert_with(|| {
Arc::new(RwLock::new(CachedExcerptHints {
version: query.cache_version,
buffer_version: buffer_snapshot.version().clone(),
@ -1086,112 +1099,111 @@ fn apply_hint_update(
hints_by_id: HashMap::default(),
}))
});
let mut cached_excerpt_hints = cached_excerpt_hints.write();
match query.cache_version.cmp(&cached_excerpt_hints.version) {
cmp::Ordering::Less => return,
cmp::Ordering::Greater | cmp::Ordering::Equal => {
cached_excerpt_hints.version = query.cache_version;
let mut cached_excerpt_hints = cached_excerpt_hints.write();
match query.cache_version.cmp(&cached_excerpt_hints.version) {
cmp::Ordering::Less => return,
cmp::Ordering::Greater | cmp::Ordering::Equal => {
cached_excerpt_hints.version = query.cache_version;
}
}
}
let mut cached_inlays_changed = !new_update.remove_from_cache.is_empty();
cached_excerpt_hints
.ordered_hints
.retain(|hint_id| !new_update.remove_from_cache.contains(hint_id));
cached_excerpt_hints
.hints_by_id
.retain(|hint_id, _| !new_update.remove_from_cache.contains(hint_id));
let mut splice = InlaySplice {
to_remove: new_update.remove_from_visible,
to_insert: Vec::new(),
};
for new_hint in new_update.add_to_cache {
let insert_position = match cached_excerpt_hints
let mut cached_inlays_changed = !new_update.remove_from_cache.is_empty();
cached_excerpt_hints
.ordered_hints
.binary_search_by(|probe| {
cached_excerpt_hints.hints_by_id[probe]
.position
.cmp(&new_hint.position, &buffer_snapshot)
}) {
Ok(i) => {
let mut insert_position = Some(i);
for id in &cached_excerpt_hints.ordered_hints[i..] {
let cached_hint = &cached_excerpt_hints.hints_by_id[id];
if new_hint
.position
.cmp(&cached_hint.position, &buffer_snapshot)
.is_gt()
{
break;
}
if cached_hint.text() == new_hint.text() {
insert_position = None;
break;
}
}
insert_position
}
Err(i) => Some(i),
.retain(|hint_id| !new_update.remove_from_cache.contains(hint_id));
cached_excerpt_hints
.hints_by_id
.retain(|hint_id, _| !new_update.remove_from_cache.contains(hint_id));
let mut splice = InlaySplice {
to_remove: new_update.remove_from_visible,
to_insert: Vec::new(),
};
for new_hint in new_update.add_to_cache {
let insert_position =
match cached_excerpt_hints
.ordered_hints
.binary_search_by(|probe| {
cached_excerpt_hints.hints_by_id[probe]
.position
.cmp(&new_hint.position, &buffer_snapshot)
}) {
Ok(i) => {
let mut insert_position = Some(i);
for id in &cached_excerpt_hints.ordered_hints[i..] {
let cached_hint = &cached_excerpt_hints.hints_by_id[id];
if new_hint
.position
.cmp(&cached_hint.position, &buffer_snapshot)
.is_gt()
{
break;
}
if cached_hint.text() == new_hint.text() {
insert_position = None;
break;
}
}
insert_position
}
Err(i) => Some(i),
};
if let Some(insert_position) = insert_position {
let new_inlay_id = post_inc(&mut editor.next_inlay_id);
if editor
.inlay_hint_cache
.allowed_hint_kinds
.contains(&new_hint.kind)
{
let new_hint_position =
multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position);
splice
.to_insert
.push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
}
let new_id = InlayId::Hint(new_inlay_id);
cached_excerpt_hints.hints_by_id.insert(new_id, new_hint);
cached_excerpt_hints
.ordered_hints
.insert(insert_position, new_id);
cached_inlays_changed = true;
}
}
cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone();
drop(cached_excerpt_hints);
if invalidate {
let mut outdated_excerpt_caches = HashSet::default();
for (excerpt_id, excerpt_hints) in &editor.inlay_hint_cache().hints {
let excerpt_hints = excerpt_hints.read();
if excerpt_hints.buffer_id == query.buffer_id
&& excerpt_id != &query.excerpt_id
&& buffer_snapshot
.version()
.changed_since(&excerpt_hints.buffer_version)
{
outdated_excerpt_caches.insert(*excerpt_id);
splice
.to_remove
.extend(excerpt_hints.ordered_hints.iter().copied());
if let Some(insert_position) = insert_position {
let new_inlay_id = post_inc(&mut editor.next_inlay_id);
if cache.allowed_hint_kinds.contains(&new_hint.kind) {
let new_hint_position = multi_buffer_snapshot
.anchor_in_excerpt(query.excerpt_id, new_hint.position);
splice
.to_insert
.push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
}
let new_id = InlayId::Hint(new_inlay_id);
cached_excerpt_hints.hints_by_id.insert(new_id, new_hint);
cached_excerpt_hints
.ordered_hints
.insert(insert_position, new_id);
cached_inlays_changed = true;
}
}
cached_inlays_changed |= !outdated_excerpt_caches.is_empty();
editor
.inlay_hint_cache
.hints
.retain(|excerpt_id, _| !outdated_excerpt_caches.contains(excerpt_id));
}
cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone();
drop(cached_excerpt_hints);
});
let InlaySplice {
to_remove,
to_insert,
} = splice;
let displayed_inlays_changed = !to_remove.is_empty() || !to_insert.is_empty();
if cached_inlays_changed || displayed_inlays_changed {
editor.inlay_hint_cache.version += 1;
}
if displayed_inlays_changed {
editor.splice_inlay_hints(to_remove, to_insert, cx)
}
cache.update(cx, |cache, _| {
if invalidate {
let mut outdated_excerpt_caches = HashSet::default();
for (excerpt_id, excerpt_hints) in &cache.hints {
let excerpt_hints = excerpt_hints.read();
if excerpt_hints.buffer_id == query.buffer_id
&& excerpt_id != &query.excerpt_id
&& buffer_snapshot
.version()
.changed_since(&excerpt_hints.buffer_version)
{
outdated_excerpt_caches.insert(*excerpt_id);
splice
.to_remove
.extend(excerpt_hints.ordered_hints.iter().copied());
}
}
cached_inlays_changed |= !outdated_excerpt_caches.is_empty();
cache
.hints
.retain(|excerpt_id, _| !outdated_excerpt_caches.contains(excerpt_id));
}
let InlaySplice {
to_remove,
to_insert,
} = splice;
let displayed_inlays_changed = !to_remove.is_empty() || !to_insert.is_empty();
if cached_inlays_changed || displayed_inlays_changed {
cache.version += 1;
}
if displayed_inlays_changed {
editor.splice_inlay_hints(to_remove, to_insert, cx)
}
})
}
#[cfg(test)]
@ -1287,7 +1299,8 @@ pub mod tests {
"Cache should use editor settings to get the allowed hint kinds"
);
assert_eq!(
inlay_cache.version, edits_made,
editor.inlay_hint_cache_version(),
edits_made,
"The editor update the cache version after every cache/view change"
);
});
@ -1312,7 +1325,8 @@ pub mod tests {
"Cache should use editor settings to get the allowed hint kinds"
);
assert_eq!(
inlay_cache.version, edits_made,
editor.inlay_hint_cache_version(),
edits_made,
"The editor update the cache version after every cache/view change"
);
});
@ -1392,7 +1406,7 @@ pub mod tests {
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
edits_made,
"The editor update the cache version after every cache/view change"
);
@ -1423,7 +1437,7 @@ pub mod tests {
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
edits_made,
"Should not update the cache while the work task is running"
);
@ -1447,7 +1461,7 @@ pub mod tests {
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
edits_made,
"Cache version should udpate once after the work task is done"
);
@ -1566,7 +1580,7 @@ pub mod tests {
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
1,
"Rust editor update the cache version after every cache/view change"
);
@ -1623,7 +1637,7 @@ pub mod tests {
"Markdown editor should have a separate verison, repeating Rust editor rules"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 1);
assert_eq!(editor.inlay_hint_cache_version(), 1);
});
rs_editor.update(cx, |editor, cx| {
@ -1640,7 +1654,7 @@ pub mod tests {
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
2,
"Every time hint cache changes, cache version should be incremented"
);
@ -1653,7 +1667,7 @@ pub mod tests {
"Markdown editor should not be affected by Rust editor changes"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 1);
assert_eq!(editor.inlay_hint_cache_version(), 1);
});
md_editor.update(cx, |editor, cx| {
@ -1669,7 +1683,7 @@ pub mod tests {
"Rust editor should not be affected by Markdown editor changes"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 2);
assert_eq!(editor.inlay_hint_cache_version(), 2);
});
rs_editor.update(cx, |editor, cx| {
let expected_hints = vec!["1".to_string()];
@ -1679,7 +1693,7 @@ pub mod tests {
"Markdown editor should also change independently"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 2);
assert_eq!(editor.inlay_hint_cache_version(), 2);
});
}
@ -1801,7 +1815,7 @@ pub mod tests {
visible_hint_labels(editor, cx)
);
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
edits_made,
"Should not update cache version due to new loaded hints being the same"
);
@ -1936,7 +1950,7 @@ pub mod tests {
assert!(cached_hint_labels(editor).is_empty());
assert!(visible_hint_labels(editor, cx).is_empty());
assert_eq!(
editor.inlay_hint_cache().version, edits_made,
editor.inlay_hint_cache_version(), edits_made,
"The editor should not update the cache version after /refresh query without updates"
);
});
@ -2007,7 +2021,7 @@ pub mod tests {
vec!["parameter hint".to_string()],
visible_hint_labels(editor, cx),
);
assert_eq!(editor.inlay_hint_cache().version, edits_made);
assert_eq!(editor.inlay_hint_cache_version(), edits_made);
});
}
@ -2086,7 +2100,7 @@ pub mod tests {
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version, 1,
editor.inlay_hint_cache_version(), 1,
"Only one update should be registered in the cache after all cancellations"
);
});
@ -2131,7 +2145,7 @@ pub mod tests {
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
2,
"Should update the cache version once more, for the new change"
);
@ -2301,7 +2315,7 @@ pub mod tests {
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx), "Should display only hints from the visible range");
assert_eq!(
editor.inlay_hint_cache().version, requests_count,
editor.inlay_hint_cache_version(), requests_count,
"LSP queries should've bumped the cache version"
);
});
@ -2363,7 +2377,7 @@ pub mod tests {
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
lsp_requests,
"Should update the cache for every LSP response with hints added"
);
@ -2427,7 +2441,7 @@ pub mod tests {
assert_eq!(expected_hints, cached_hint_labels(editor),
"Should have hints from the new LSP response after the edit");
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, lsp_requests, "Should update the cache for every LSP response with hints added");
assert_eq!(editor.inlay_hint_cache_version(), lsp_requests, "Should update the cache for every LSP response with hints added");
});
}
@ -2650,7 +2664,7 @@ pub mod tests {
"When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the verison");
assert_eq!(editor.inlay_hint_cache_version(), expected_hints.len(), "Every visible excerpt hints should bump the verison");
});
editor.update(cx, |editor, cx| {
@ -2680,7 +2694,7 @@ pub mod tests {
assert_eq!(expected_hints, cached_hint_labels(editor),
"With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(),
assert_eq!(editor.inlay_hint_cache_version(), expected_hints.len(),
"Due to every excerpt having one hint, we update cache per new excerpt scrolled");
});
@ -2711,7 +2725,7 @@ pub mod tests {
assert_eq!(expected_hints, cached_hint_labels(editor),
"After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, expected_hints.len());
assert_eq!(editor.inlay_hint_cache_version(), expected_hints.len());
expected_hints.len()
});
@ -2739,7 +2753,7 @@ pub mod tests {
assert_eq!(expected_hints, cached_hint_labels(editor),
"After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
assert_eq!(editor.inlay_hint_cache_version(), last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
});
editor_edited.store(true, Ordering::Release);
@ -2769,7 +2783,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
let current_cache_version = editor.inlay_hint_cache().version;
let current_cache_version = editor.inlay_hint_cache_version();
let minimum_expected_version = last_scroll_update_version + expected_hints.len();
assert!(
current_cache_version == minimum_expected_version || current_cache_version == minimum_expected_version + 1,
@ -2953,7 +2967,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
"All hints are disabled and should not be shown despite being present in the cache"
);
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
2,
"Cache should update once per excerpt query"
);
@ -2976,7 +2990,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
"All hints are disabled and should not be shown despite being present in the cache"
);
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
3,
"Excerpt removal should trigger a cache update"
);
@ -3004,7 +3018,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
"Settings change should make cached hints visible"
);
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
4,
"Settings change should trigger a cache update"
);
@ -3114,7 +3128,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
let expected_hints = vec!["1".to_string()];
assert_eq!(expected_hints, cached_hint_labels(editor));
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 1);
assert_eq!(editor.inlay_hint_cache_version(), 1);
});
}
@ -3171,7 +3185,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
1,
"First toggle should be cache's first update"
);
@ -3187,7 +3201,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
"Should clear hints after 2nd toggle"
);
assert!(visible_hint_labels(editor, cx).is_empty());
assert_eq!(editor.inlay_hint_cache().version, 2);
assert_eq!(editor.inlay_hint_cache_version(), 2);
});
update_test_language_settings(cx, |settings| {
@ -3207,7 +3221,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
"Should query LSP hints for the 2nd time after enabling hints in settings"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 3);
assert_eq!(editor.inlay_hint_cache_version(), 3);
});
editor.update(cx, |editor, cx| {
@ -3220,7 +3234,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
"Should clear hints after enabling in settings and a 3rd toggle"
);
assert!(visible_hint_labels(editor, cx).is_empty());
assert_eq!(editor.inlay_hint_cache().version, 4);
assert_eq!(editor.inlay_hint_cache_version(), 4);
});
editor.update(cx, |editor, cx| {
@ -3235,7 +3249,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
"Should query LSP hints for the 3rd time after enabling hints in settings and toggling them back on"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 5);
assert_eq!(editor.inlay_hint_cache_version(), 5);
});
}
@ -3318,7 +3332,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
editor.update(cx, |editor, cx| {
assert!(cached_hint_labels(editor).is_empty());
assert!(visible_hint_labels(editor, cx).is_empty());
assert_eq!(editor.inlay_hint_cache().version, 0);
assert_eq!(editor.inlay_hint_cache_version(), 0);
});
("/a/main.rs", editor, fake_server)

View file

@ -178,6 +178,9 @@ pub fn update_inlay_link_and_hover_points(
let mut go_to_definition_updated = false;
let mut hover_updated = false;
if let Some(hovered_offset) = hovered_offset {
let Some(inlay_hint_cache) = editor.inlay_hints.clone() else {
return;
};
let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
let previous_valid_anchor = buffer_snapshot.anchor_at(
point_for_position.previous_valid.to_point(snapshot),
@ -202,82 +205,84 @@ pub fn update_inlay_link_and_hover_points(
})
.max_by_key(|hint| hint.id)
{
let inlay_hint_cache = editor.inlay_hint_cache();
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,
excerpt_id,
hovered_hint.id,
cx,
);
}
}
ResolveState::Resolved => {
let mut extra_shift_left = 0;
let mut extra_shift_right = 0;
if cached_hint.padding_left {
extra_shift_left += 1;
extra_shift_right += 1;
}
if cached_hint.padding_right {
extra_shift_right += 1;
}
match cached_hint.label {
project::InlayHintLabel::String(_) => {
if let Some(tooltip) = cached_hint.tooltip {
hover_popover::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,
}
}
},
range: InlayHighlight {
inlay: hovered_hint.id,
inlay_position: hovered_hint.position,
range: extra_shift_left
..hovered_hint.text.len() + extra_shift_right,
},
},
cx,
);
hover_updated = true;
}
inlay_hint_cache.update(cx, |inlay_hint_cache, cx| {
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,
excerpt_id,
hovered_hint.id,
cx,
);
}
project::InlayHintLabel::LabelParts(label_parts) => {
let hint_start =
snapshot.anchor_to_inlay_offset(hovered_hint.position);
if let Some((hovered_hint_part, part_range)) =
hover_popover::find_hovered_hint_part(
label_parts,
hint_start,
hovered_offset,
)
{
let highlight_start =
(part_range.start - hint_start).0 + extra_shift_left;
let highlight_end =
(part_range.end - hint_start).0 + extra_shift_right;
let highlight = InlayHighlight {
inlay: hovered_hint.id,
inlay_position: hovered_hint.position,
range: highlight_start..highlight_end,
};
if let Some(tooltip) = hovered_hint_part.tooltip {
}
ResolveState::Resolved => {
let mut extra_shift_left = 0;
let mut extra_shift_right = 0;
if cached_hint.padding_left {
extra_shift_left += 1;
extra_shift_right += 1;
}
if cached_hint.padding_right {
extra_shift_right += 1;
}
match cached_hint.label {
project::InlayHintLabel::String(_) => {
if let Some(tooltip) = cached_hint.tooltip {
hover_popover::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,
}
}
},
range: InlayHighlight {
inlay: hovered_hint.id,
inlay_position: hovered_hint.position,
range: extra_shift_left
..hovered_hint.text.len()
+ extra_shift_right,
},
},
cx,
);
hover_updated = true;
}
}
project::InlayHintLabel::LabelParts(label_parts) => {
let hint_start =
snapshot.anchor_to_inlay_offset(hovered_hint.position);
if let Some((hovered_hint_part, part_range)) =
hover_popover::find_hovered_hint_part(
label_parts,
hint_start,
hovered_offset,
)
{
let highlight_start =
(part_range.start - hint_start).0 + extra_shift_left;
let highlight_end =
(part_range.end - hint_start).0 + extra_shift_right;
let highlight = InlayHighlight {
inlay: hovered_hint.id,
inlay_position: hovered_hint.position,
range: highlight_start..highlight_end,
};
if let Some(tooltip) = hovered_hint_part.tooltip {
hover_popover::hover_at_inlay(
editor,
InlayHover {
excerpt: excerpt_id,
@ -299,31 +304,32 @@ pub fn update_inlay_link_and_hover_points(
},
cx,
);
hover_updated = true;
}
if let Some((language_server_id, location)) =
hovered_hint_part.location
{
go_to_definition_updated = true;
update_go_to_definition_link(
editor,
Some(GoToDefinitionTrigger::InlayHint(
highlight,
location,
language_server_id,
)),
cmd_held,
shift_held,
cx,
);
hover_updated = true;
}
if let Some((language_server_id, location)) =
hovered_hint_part.location
{
go_to_definition_updated = true;
update_go_to_definition_link(
editor,
Some(GoToDefinitionTrigger::InlayHint(
highlight,
location,
language_server_id,
)),
cmd_held,
shift_held,
cx,
);
}
}
}
}
};
};
}
ResolveState::Resolving => {}
}
ResolveState::Resolving => {}
}
}
})
}
}