Defer querying inlay hints for invisible editor ranges
This way, only the visible part gets frequently queried on typing (and hint /refresh requests that follow), with queries for invisible ranges cancelled eagerly.
This commit is contained in:
parent
b50762c821
commit
a63e1571dc
1 changed files with 203 additions and 109 deletions
|
@ -2,6 +2,7 @@ use std::{
|
||||||
cmp,
|
cmp,
|
||||||
ops::{ControlFlow, Range},
|
ops::{ControlFlow, Range},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -9,6 +10,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use clock::Global;
|
use clock::Global;
|
||||||
|
use futures::future;
|
||||||
use gpui::{ModelContext, ModelHandle, Task, ViewContext};
|
use gpui::{ModelContext, ModelHandle, Task, ViewContext};
|
||||||
use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot};
|
use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot};
|
||||||
use log::error;
|
use log::error;
|
||||||
|
@ -17,7 +19,7 @@ use project::{InlayHint, ResolveState};
|
||||||
|
|
||||||
use collections::{hash_map, HashMap, HashSet};
|
use collections::{hash_map, HashMap, HashSet};
|
||||||
use language::language_settings::InlayHintSettings;
|
use language::language_settings::InlayHintSettings;
|
||||||
use sum_tree::Bias;
|
use text::ToOffset;
|
||||||
use util::post_inc;
|
use util::post_inc;
|
||||||
|
|
||||||
pub struct InlayHintCache {
|
pub struct InlayHintCache {
|
||||||
|
@ -81,7 +83,11 @@ impl InvalidationStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TasksForRanges {
|
impl TasksForRanges {
|
||||||
fn new(sorted_ranges: Vec<Range<language::Anchor>>, task: Task<()>) -> Self {
|
fn new(query_ranges: QueryRanges, task: Task<()>) -> Self {
|
||||||
|
let mut sorted_ranges = Vec::new();
|
||||||
|
sorted_ranges.extend(query_ranges.before_visible);
|
||||||
|
sorted_ranges.extend(query_ranges.visible);
|
||||||
|
sorted_ranges.extend(query_ranges.after_visible);
|
||||||
Self {
|
Self {
|
||||||
tasks: vec![task],
|
tasks: vec![task],
|
||||||
sorted_ranges,
|
sorted_ranges,
|
||||||
|
@ -91,83 +97,104 @@ impl TasksForRanges {
|
||||||
fn update_cached_tasks(
|
fn update_cached_tasks(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer_snapshot: &BufferSnapshot,
|
buffer_snapshot: &BufferSnapshot,
|
||||||
query_range: Range<text::Anchor>,
|
query_ranges: QueryRanges,
|
||||||
invalidate: InvalidationStrategy,
|
invalidate: InvalidationStrategy,
|
||||||
spawn_task: impl FnOnce(Vec<Range<language::Anchor>>) -> Task<()>,
|
spawn_task: impl FnOnce(QueryRanges) -> Task<()>,
|
||||||
) {
|
) {
|
||||||
let ranges_to_query = match invalidate {
|
let query_ranges = match invalidate {
|
||||||
InvalidationStrategy::None => {
|
InvalidationStrategy::None => {
|
||||||
let mut ranges_to_query = Vec::new();
|
let mut updated_ranges = query_ranges;
|
||||||
let mut latest_cached_range = None::<&mut Range<language::Anchor>>;
|
updated_ranges.before_visible = updated_ranges
|
||||||
for cached_range in self
|
.before_visible
|
||||||
.sorted_ranges
|
.into_iter()
|
||||||
.iter_mut()
|
.flat_map(|query_range| self.remove_cached_ranges(buffer_snapshot, query_range))
|
||||||
.skip_while(|cached_range| {
|
.collect();
|
||||||
cached_range
|
updated_ranges.visible = updated_ranges
|
||||||
.end
|
.visible
|
||||||
.cmp(&query_range.start, buffer_snapshot)
|
.into_iter()
|
||||||
.is_lt()
|
.flat_map(|query_range| self.remove_cached_ranges(buffer_snapshot, query_range))
|
||||||
})
|
.collect();
|
||||||
.take_while(|cached_range| {
|
updated_ranges.after_visible = updated_ranges
|
||||||
cached_range
|
.after_visible
|
||||||
.start
|
.into_iter()
|
||||||
.cmp(&query_range.end, buffer_snapshot)
|
.flat_map(|query_range| self.remove_cached_ranges(buffer_snapshot, query_range))
|
||||||
.is_le()
|
.collect();
|
||||||
})
|
updated_ranges
|
||||||
{
|
|
||||||
match latest_cached_range {
|
|
||||||
Some(latest_cached_range) => {
|
|
||||||
if latest_cached_range.end.offset.saturating_add(1)
|
|
||||||
< cached_range.start.offset
|
|
||||||
{
|
|
||||||
ranges_to_query.push(latest_cached_range.end..cached_range.start);
|
|
||||||
cached_range.start = latest_cached_range.end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
if query_range
|
|
||||||
.start
|
|
||||||
.cmp(&cached_range.start, buffer_snapshot)
|
|
||||||
.is_lt()
|
|
||||||
{
|
|
||||||
ranges_to_query.push(query_range.start..cached_range.start);
|
|
||||||
cached_range.start = query_range.start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
latest_cached_range = Some(cached_range);
|
|
||||||
}
|
|
||||||
|
|
||||||
match latest_cached_range {
|
|
||||||
Some(latest_cached_range) => {
|
|
||||||
if latest_cached_range.end.offset.saturating_add(1) < query_range.end.offset
|
|
||||||
{
|
|
||||||
ranges_to_query.push(latest_cached_range.end..query_range.end);
|
|
||||||
latest_cached_range.end = query_range.end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
ranges_to_query.push(query_range.clone());
|
|
||||||
self.sorted_ranges.push(query_range);
|
|
||||||
self.sorted_ranges.sort_by(|range_a, range_b| {
|
|
||||||
range_a.start.cmp(&range_b.start, buffer_snapshot)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ranges_to_query
|
|
||||||
}
|
}
|
||||||
InvalidationStrategy::RefreshRequested | InvalidationStrategy::BufferEdited => {
|
InvalidationStrategy::RefreshRequested | InvalidationStrategy::BufferEdited => {
|
||||||
self.tasks.clear();
|
self.tasks.clear();
|
||||||
self.sorted_ranges.clear();
|
self.sorted_ranges.clear();
|
||||||
vec![query_range]
|
query_ranges
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if !ranges_to_query.is_empty() {
|
if !query_ranges.is_empty() {
|
||||||
self.tasks.push(spawn_task(ranges_to_query));
|
self.tasks.push(spawn_task(query_ranges));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remove_cached_ranges(
|
||||||
|
&mut self,
|
||||||
|
buffer_snapshot: &BufferSnapshot,
|
||||||
|
query_range: Range<language::Anchor>,
|
||||||
|
) -> Vec<Range<language::Anchor>> {
|
||||||
|
let mut ranges_to_query = Vec::new();
|
||||||
|
let mut latest_cached_range = None::<&mut Range<language::Anchor>>;
|
||||||
|
for cached_range in self
|
||||||
|
.sorted_ranges
|
||||||
|
.iter_mut()
|
||||||
|
.skip_while(|cached_range| {
|
||||||
|
cached_range
|
||||||
|
.end
|
||||||
|
.cmp(&query_range.start, buffer_snapshot)
|
||||||
|
.is_lt()
|
||||||
|
})
|
||||||
|
.take_while(|cached_range| {
|
||||||
|
cached_range
|
||||||
|
.start
|
||||||
|
.cmp(&query_range.end, buffer_snapshot)
|
||||||
|
.is_le()
|
||||||
|
})
|
||||||
|
{
|
||||||
|
match latest_cached_range {
|
||||||
|
Some(latest_cached_range) => {
|
||||||
|
if latest_cached_range.end.offset.saturating_add(1) < cached_range.start.offset
|
||||||
|
{
|
||||||
|
ranges_to_query.push(latest_cached_range.end..cached_range.start);
|
||||||
|
cached_range.start = latest_cached_range.end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if query_range
|
||||||
|
.start
|
||||||
|
.cmp(&cached_range.start, buffer_snapshot)
|
||||||
|
.is_lt()
|
||||||
|
{
|
||||||
|
ranges_to_query.push(query_range.start..cached_range.start);
|
||||||
|
cached_range.start = query_range.start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
latest_cached_range = Some(cached_range);
|
||||||
|
}
|
||||||
|
|
||||||
|
match latest_cached_range {
|
||||||
|
Some(latest_cached_range) => {
|
||||||
|
if latest_cached_range.end.offset.saturating_add(1) < query_range.end.offset {
|
||||||
|
ranges_to_query.push(latest_cached_range.end..query_range.end);
|
||||||
|
latest_cached_range.end = query_range.end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
ranges_to_query.push(query_range.clone());
|
||||||
|
self.sorted_ranges.push(query_range);
|
||||||
|
self.sorted_ranges
|
||||||
|
.sort_by(|range_a, range_b| range_a.start.cmp(&range_b.start, buffer_snapshot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges_to_query
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InlayHintCache {
|
impl InlayHintCache {
|
||||||
|
@ -515,11 +542,11 @@ fn spawn_new_update_tasks(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (multi_buffer_snapshot, Some(query_range)) =
|
let (multi_buffer_snapshot, Some(query_ranges)) =
|
||||||
editor.buffer.update(cx, |multi_buffer, cx| {
|
editor.buffer.update(cx, |multi_buffer, cx| {
|
||||||
(
|
(
|
||||||
multi_buffer.snapshot(cx),
|
multi_buffer.snapshot(cx),
|
||||||
determine_query_range(
|
determine_query_ranges(
|
||||||
multi_buffer,
|
multi_buffer,
|
||||||
excerpt_id,
|
excerpt_id,
|
||||||
&excerpt_buffer,
|
&excerpt_buffer,
|
||||||
|
@ -535,10 +562,10 @@ fn spawn_new_update_tasks(
|
||||||
invalidate,
|
invalidate,
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_update_task = |fetch_ranges| {
|
let new_update_task = |query_ranges| {
|
||||||
new_update_task(
|
new_update_task(
|
||||||
query,
|
query,
|
||||||
fetch_ranges,
|
query_ranges,
|
||||||
multi_buffer_snapshot,
|
multi_buffer_snapshot,
|
||||||
buffer_snapshot.clone(),
|
buffer_snapshot.clone(),
|
||||||
Arc::clone(&visible_hints),
|
Arc::clone(&visible_hints),
|
||||||
|
@ -551,57 +578,100 @@ fn spawn_new_update_tasks(
|
||||||
hash_map::Entry::Occupied(mut o) => {
|
hash_map::Entry::Occupied(mut o) => {
|
||||||
o.get_mut().update_cached_tasks(
|
o.get_mut().update_cached_tasks(
|
||||||
&buffer_snapshot,
|
&buffer_snapshot,
|
||||||
query_range,
|
query_ranges,
|
||||||
invalidate,
|
invalidate,
|
||||||
new_update_task,
|
new_update_task,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
hash_map::Entry::Vacant(v) => {
|
hash_map::Entry::Vacant(v) => {
|
||||||
v.insert(TasksForRanges::new(
|
v.insert(TasksForRanges::new(
|
||||||
vec![query_range.clone()],
|
query_ranges.clone(),
|
||||||
new_update_task(vec![query_range]),
|
new_update_task(query_ranges),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn determine_query_range(
|
#[derive(Debug, Clone)]
|
||||||
|
struct QueryRanges {
|
||||||
|
before_visible: Vec<Range<language::Anchor>>,
|
||||||
|
visible: Vec<Range<language::Anchor>>,
|
||||||
|
after_visible: Vec<Range<language::Anchor>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryRanges {
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.before_visible.is_empty() && self.visible.is_empty() && self.after_visible.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn determine_query_ranges(
|
||||||
multi_buffer: &mut MultiBuffer,
|
multi_buffer: &mut MultiBuffer,
|
||||||
excerpt_id: ExcerptId,
|
excerpt_id: ExcerptId,
|
||||||
excerpt_buffer: &ModelHandle<Buffer>,
|
excerpt_buffer: &ModelHandle<Buffer>,
|
||||||
excerpt_visible_range: Range<usize>,
|
excerpt_visible_range: Range<usize>,
|
||||||
cx: &mut ModelContext<'_, MultiBuffer>,
|
cx: &mut ModelContext<'_, MultiBuffer>,
|
||||||
) -> Option<Range<language::Anchor>> {
|
) -> Option<QueryRanges> {
|
||||||
let full_excerpt_range = multi_buffer
|
let full_excerpt_range = multi_buffer
|
||||||
.excerpts_for_buffer(excerpt_buffer, cx)
|
.excerpts_for_buffer(excerpt_buffer, cx)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|(id, _)| id == &excerpt_id)
|
.find(|(id, _)| id == &excerpt_id)
|
||||||
.map(|(_, range)| range.context)?;
|
.map(|(_, range)| range.context)?;
|
||||||
|
|
||||||
let buffer = excerpt_buffer.read(cx);
|
let buffer = excerpt_buffer.read(cx);
|
||||||
|
let snapshot = buffer.snapshot();
|
||||||
let excerpt_visible_len = excerpt_visible_range.end - excerpt_visible_range.start;
|
let excerpt_visible_len = excerpt_visible_range.end - excerpt_visible_range.start;
|
||||||
let start_offset = excerpt_visible_range
|
|
||||||
.start
|
let visible_range = if excerpt_visible_range.start == excerpt_visible_range.end {
|
||||||
.saturating_sub(excerpt_visible_len)
|
return None;
|
||||||
.max(full_excerpt_range.start.offset);
|
|
||||||
let start = buffer.anchor_before(buffer.clip_offset(start_offset, Bias::Left));
|
|
||||||
let end_offset = excerpt_visible_range
|
|
||||||
.end
|
|
||||||
.saturating_add(excerpt_visible_len)
|
|
||||||
.min(full_excerpt_range.end.offset)
|
|
||||||
.min(buffer.len());
|
|
||||||
let end = buffer.anchor_after(buffer.clip_offset(end_offset, Bias::Right));
|
|
||||||
if start.cmp(&end, buffer).is_eq() {
|
|
||||||
None
|
|
||||||
} else {
|
} else {
|
||||||
Some(start..end)
|
vec![
|
||||||
}
|
buffer.anchor_before(excerpt_visible_range.start)
|
||||||
|
..buffer.anchor_after(excerpt_visible_range.end),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
let full_excerpt_range_end_offset = full_excerpt_range.end.to_offset(&snapshot);
|
||||||
|
let after_visible_range = if excerpt_visible_range.end == full_excerpt_range_end_offset {
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
let after_range_end_offset = excerpt_visible_range
|
||||||
|
.end
|
||||||
|
.saturating_add(excerpt_visible_len)
|
||||||
|
.min(full_excerpt_range_end_offset)
|
||||||
|
.min(buffer.len());
|
||||||
|
vec![
|
||||||
|
buffer.anchor_before(excerpt_visible_range.end)
|
||||||
|
..buffer.anchor_after(after_range_end_offset),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
let full_excerpt_range_start_offset = full_excerpt_range.start.to_offset(&snapshot);
|
||||||
|
let before_visible_range = if excerpt_visible_range.start == full_excerpt_range_start_offset {
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
let before_range_start_offset = excerpt_visible_range
|
||||||
|
.start
|
||||||
|
.saturating_sub(excerpt_visible_len)
|
||||||
|
.max(full_excerpt_range_start_offset);
|
||||||
|
vec![
|
||||||
|
buffer.anchor_before(before_range_start_offset)
|
||||||
|
..buffer.anchor_after(excerpt_visible_range.start),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(QueryRanges {
|
||||||
|
before_visible: before_visible_range,
|
||||||
|
visible: visible_range,
|
||||||
|
after_visible: after_visible_range,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS: u64 = 300;
|
||||||
|
|
||||||
fn new_update_task(
|
fn new_update_task(
|
||||||
query: ExcerptQuery,
|
query: ExcerptQuery,
|
||||||
hint_fetch_ranges: Vec<Range<language::Anchor>>,
|
query_ranges: QueryRanges,
|
||||||
multi_buffer_snapshot: MultiBufferSnapshot,
|
multi_buffer_snapshot: MultiBufferSnapshot,
|
||||||
buffer_snapshot: BufferSnapshot,
|
buffer_snapshot: BufferSnapshot,
|
||||||
visible_hints: Arc<Vec<Inlay>>,
|
visible_hints: Arc<Vec<Inlay>>,
|
||||||
|
@ -609,24 +679,48 @@ fn new_update_task(
|
||||||
cx: &mut ViewContext<'_, '_, Editor>,
|
cx: &mut ViewContext<'_, '_, Editor>,
|
||||||
) -> Task<()> {
|
) -> Task<()> {
|
||||||
cx.spawn(|editor, cx| async move {
|
cx.spawn(|editor, cx| async move {
|
||||||
let task_update_results =
|
let fetch_and_update_hints = |range| {
|
||||||
futures::future::join_all(hint_fetch_ranges.into_iter().map(|range| {
|
fetch_and_update_hints(
|
||||||
fetch_and_update_hints(
|
editor.clone(),
|
||||||
editor.clone(),
|
multi_buffer_snapshot.clone(),
|
||||||
multi_buffer_snapshot.clone(),
|
buffer_snapshot.clone(),
|
||||||
buffer_snapshot.clone(),
|
Arc::clone(&visible_hints),
|
||||||
Arc::clone(&visible_hints),
|
cached_excerpt_hints.as_ref().map(Arc::clone),
|
||||||
cached_excerpt_hints.as_ref().map(Arc::clone),
|
query,
|
||||||
query,
|
range,
|
||||||
range,
|
cx.clone(),
|
||||||
cx.clone(),
|
)
|
||||||
)
|
};
|
||||||
}))
|
let visible_range_update_results = future::join_all(
|
||||||
|
query_ranges
|
||||||
|
.visible
|
||||||
|
.into_iter()
|
||||||
|
.map(|visible_range| fetch_and_update_hints(visible_range)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
for result in visible_range_update_results {
|
||||||
|
if let Err(e) = result {
|
||||||
|
error!("visible range inlay hint update task failed: {e:#}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.background()
|
||||||
|
.timer(Duration::from_millis(
|
||||||
|
INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS,
|
||||||
|
))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
for result in task_update_results {
|
let invisible_range_update_results = future::join_all(
|
||||||
|
query_ranges
|
||||||
|
.before_visible
|
||||||
|
.into_iter()
|
||||||
|
.chain(query_ranges.after_visible.into_iter())
|
||||||
|
.map(|invisible_range| fetch_and_update_hints(invisible_range)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
for result in invisible_range_update_results {
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
error!("inlay hint update task failed: {e:#}");
|
error!("invisible range inlay hint update task failed: {e:#}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1816,7 +1910,7 @@ pub mod tests {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
let _ = futures::future::join_all(edits).await;
|
let _ = future::join_all(edits).await;
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
|
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue