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:
Kirill Bulatov 2023-08-25 01:10:01 +03:00
parent b50762c821
commit a63e1571dc

View file

@ -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| {