Query certain editor ranges for inlays with a delay (#2891)

Part of
https://linear.app/zed-industries/issue/Z-2750/investigate-performance-of-collaborating-on-large-files-with-inlay
Fixes
https://linear.app/zed-industries/issue/Z-2824/inlay-hints-affect-code-layout-in-multibuffer

We query hints for visible part of the screen, and two parts above and
below the visible range, of the same range (if applicable, we can be on
the edge of the document).

When rapidly typing, we do not care about the invisible range updates,
yet still query a lot of them + rust-analyzer sends /refresh hint
requests shortly after every modification too, forcing us to re-query.

Instead querying both visible and invisible ranges altogether, wait for
visible range query first and wait add a `400ms` delay afterwards before
querying the invisible ranges.
This allows any /refresh requests or rapid typing to avoid 2 extra
requests, cancelling them before they start.
Visible part of the screen is still queried after every change, without
any debouncing.

Release Notes:

- Delay certain inlay hint requests to reduce general LSP server load
This commit is contained in:
Kirill Bulatov 2023-08-25 16:33:21 +03:00 committed by GitHub
commit 205e101dd0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -2,6 +2,7 @@ use std::{
cmp,
ops::{ControlFlow, Range},
sync::Arc,
time::Duration,
};
use crate::{
@ -9,6 +10,7 @@ use crate::{
};
use anyhow::Context;
use clock::Global;
use futures::future;
use gpui::{ModelContext, ModelHandle, Task, ViewContext};
use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot};
use log::error;
@ -17,7 +19,7 @@ use project::{InlayHint, ResolveState};
use collections::{hash_map, HashMap, HashSet};
use language::language_settings::InlayHintSettings;
use sum_tree::Bias;
use text::ToOffset;
use util::post_inc;
pub struct InlayHintCache {
@ -81,7 +83,11 @@ impl InvalidationStrategy {
}
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 {
tasks: vec![task],
sorted_ranges,
@ -91,12 +97,47 @@ impl TasksForRanges {
fn update_cached_tasks(
&mut self,
buffer_snapshot: &BufferSnapshot,
query_range: Range<text::Anchor>,
query_ranges: QueryRanges,
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 => {
let mut updated_ranges = query_ranges;
updated_ranges.before_visible = updated_ranges
.before_visible
.into_iter()
.flat_map(|query_range| self.remove_cached_ranges(buffer_snapshot, query_range))
.collect();
updated_ranges.visible = updated_ranges
.visible
.into_iter()
.flat_map(|query_range| self.remove_cached_ranges(buffer_snapshot, query_range))
.collect();
updated_ranges.after_visible = updated_ranges
.after_visible
.into_iter()
.flat_map(|query_range| self.remove_cached_ranges(buffer_snapshot, query_range))
.collect();
updated_ranges
}
InvalidationStrategy::RefreshRequested | InvalidationStrategy::BufferEdited => {
self.tasks.clear();
self.sorted_ranges.clear();
query_ranges
}
};
if !query_ranges.is_empty() {
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
@ -117,8 +158,7 @@ impl TasksForRanges {
{
match latest_cached_range {
Some(latest_cached_range) => {
if latest_cached_range.end.offset.saturating_add(1)
< cached_range.start.offset
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;
@ -140,8 +180,7 @@ impl TasksForRanges {
match latest_cached_range {
Some(latest_cached_range) => {
if latest_cached_range.end.offset.saturating_add(1) < query_range.end.offset
{
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;
}
@ -149,25 +188,13 @@ impl TasksForRanges {
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)
});
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 => {
self.tasks.clear();
self.sorted_ranges.clear();
vec![query_range]
}
};
if !ranges_to_query.is_empty() {
self.tasks.push(spawn_task(ranges_to_query));
}
}
}
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| {
(
multi_buffer.snapshot(cx),
determine_query_range(
determine_query_ranges(
multi_buffer,
excerpt_id,
&excerpt_buffer,
@ -535,10 +562,10 @@ fn spawn_new_update_tasks(
invalidate,
};
let new_update_task = |fetch_ranges| {
let new_update_task = |query_ranges| {
new_update_task(
query,
fetch_ranges,
query_ranges,
multi_buffer_snapshot,
buffer_snapshot.clone(),
Arc::clone(&visible_hints),
@ -551,57 +578,107 @@ fn spawn_new_update_tasks(
hash_map::Entry::Occupied(mut o) => {
o.get_mut().update_cached_tasks(
&buffer_snapshot,
query_range,
query_ranges,
invalidate,
new_update_task,
);
}
hash_map::Entry::Vacant(v) => {
v.insert(TasksForRanges::new(
vec![query_range.clone()],
new_update_task(vec![query_range]),
query_ranges.clone(),
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,
excerpt_id: ExcerptId,
excerpt_buffer: &ModelHandle<Buffer>,
excerpt_visible_range: Range<usize>,
cx: &mut ModelContext<'_, MultiBuffer>,
) -> Option<Range<language::Anchor>> {
) -> Option<QueryRanges> {
let full_excerpt_range = multi_buffer
.excerpts_for_buffer(excerpt_buffer, cx)
.into_iter()
.find(|(id, _)| id == &excerpt_id)
.map(|(_, range)| range.context)?;
let buffer = excerpt_buffer.read(cx);
let snapshot = buffer.snapshot();
let excerpt_visible_len = excerpt_visible_range.end - excerpt_visible_range.start;
let start_offset = excerpt_visible_range
.start
.saturating_sub(excerpt_visible_len)
.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
let visible_range = if excerpt_visible_range.start == excerpt_visible_range.end {
return None;
} 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_start = excerpt_visible_range
.end
.saturating_add(1)
.min(full_excerpt_range_end_offset)
.min(buffer.len());
let after_visible_range = if after_visible_range_start == full_excerpt_range_end_offset {
Vec::new()
} else {
let after_range_end_offset = after_visible_range_start
.saturating_add(excerpt_visible_len)
.min(full_excerpt_range_end_offset)
.min(buffer.len());
vec![
buffer.anchor_before(after_visible_range_start)
..buffer.anchor_after(after_range_end_offset),
]
};
let full_excerpt_range_start_offset = full_excerpt_range.start.to_offset(&snapshot);
let before_visible_range_end = excerpt_visible_range
.start
.saturating_sub(1)
.max(full_excerpt_range_start_offset);
let before_visible_range = if before_visible_range_end == full_excerpt_range_start_offset {
Vec::new()
} else {
let before_range_start_offset = before_visible_range_end
.saturating_sub(excerpt_visible_len)
.max(full_excerpt_range_start_offset);
vec![
buffer.anchor_before(before_range_start_offset)
..buffer.anchor_after(before_visible_range_end),
]
};
Some(QueryRanges {
before_visible: before_visible_range,
visible: visible_range,
after_visible: after_visible_range,
})
}
const INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS: u64 = 400;
fn new_update_task(
query: ExcerptQuery,
hint_fetch_ranges: Vec<Range<language::Anchor>>,
query_ranges: QueryRanges,
multi_buffer_snapshot: MultiBufferSnapshot,
buffer_snapshot: BufferSnapshot,
visible_hints: Arc<Vec<Inlay>>,
@ -609,8 +686,7 @@ fn new_update_task(
cx: &mut ViewContext<'_, '_, Editor>,
) -> Task<()> {
cx.spawn(|editor, cx| async move {
let task_update_results =
futures::future::join_all(hint_fetch_ranges.into_iter().map(|range| {
let fetch_and_update_hints = |invalidate, range| {
fetch_and_update_hints(
editor.clone(),
multi_buffer_snapshot.clone(),
@ -618,15 +694,39 @@ fn new_update_task(
Arc::clone(&visible_hints),
cached_excerpt_hints.as_ref().map(Arc::clone),
query,
invalidate,
range,
cx.clone(),
)
};
let visible_range_update_results =
future::join_all(query_ranges.visible.into_iter().map(|visible_range| {
fetch_and_update_hints(query.invalidate.should_invalidate(), visible_range)
}))
.await;
for result in task_update_results {
for result in visible_range_update_results {
if let Err(e) = result {
error!("inlay hint update task failed: {e:#}");
error!("visible range inlay hint update task failed: {e:#}");
}
}
cx.background()
.timer(Duration::from_millis(
INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS,
))
.await;
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(false, invisible_range)),
)
.await;
for result in invisible_range_update_results {
if let Err(e) = result {
error!("invisible range inlay hint update task failed: {e:#}");
}
}
})
@ -639,6 +739,7 @@ async fn fetch_and_update_hints(
visible_hints: Arc<Vec<Inlay>>,
cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
query: ExcerptQuery,
invalidate: bool,
fetch_range: Range<language::Anchor>,
mut cx: gpui::AsyncAppContext,
) -> anyhow::Result<()> {
@ -667,7 +768,8 @@ async fn fetch_and_update_hints(
.background()
.spawn(async move {
calculate_hint_updates(
query,
query.excerpt_id,
invalidate,
backround_fetch_range,
new_hints,
&background_task_buffer_snapshot,
@ -694,7 +796,8 @@ async fn fetch_and_update_hints(
}
fn calculate_hint_updates(
query: ExcerptQuery,
excerpt_id: ExcerptId,
invalidate: bool,
fetch_range: Range<language::Anchor>,
new_excerpt_hints: Vec<InlayHint>,
buffer_snapshot: &BufferSnapshot,
@ -742,11 +845,11 @@ fn calculate_hint_updates(
let mut remove_from_visible = Vec::new();
let mut remove_from_cache = HashSet::default();
if query.invalidate.should_invalidate() {
if invalidate {
remove_from_visible.extend(
visible_hints
.iter()
.filter(|hint| hint.position.excerpt_id == query.excerpt_id)
.filter(|hint| hint.position.excerpt_id == excerpt_id)
.map(|inlay_hint| inlay_hint.id)
.filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)),
);
@ -769,7 +872,7 @@ fn calculate_hint_updates(
None
} else {
Some(ExcerptHintsUpdate {
excerpt_id: query.excerpt_id,
excerpt_id,
remove_from_visible,
remove_from_cache,
add_to_cache,
@ -905,7 +1008,7 @@ fn apply_hint_update(
#[cfg(test)]
pub mod tests {
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
use crate::{
scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount},
@ -983,13 +1086,13 @@ pub mod tests {
let mut edits_made = 1;
editor.update(cx, |editor, cx| {
let expected_layers = vec!["0".to_string()];
let expected_hints = vec!["0".to_string()];
assert_eq!(
expected_layers,
expected_hints,
cached_hint_labels(editor),
"Should get its first hints when opening the editor"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
@ -1008,13 +1111,13 @@ pub mod tests {
});
cx.foreground().run_until_parked();
editor.update(cx, |editor, cx| {
let expected_layers = vec!["0".to_string(), "1".to_string()];
let expected_hints = vec!["0".to_string(), "1".to_string()];
assert_eq!(
expected_layers,
expected_hints,
cached_hint_labels(editor),
"Should get new hints after an edit"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
@ -1033,13 +1136,13 @@ pub mod tests {
edits_made += 1;
cx.foreground().run_until_parked();
editor.update(cx, |editor, cx| {
let expected_layers = vec!["0".to_string(), "1".to_string(), "2".to_string()];
let expected_hints = vec!["0".to_string(), "1".to_string(), "2".to_string()];
assert_eq!(
expected_layers,
expected_hints,
cached_hint_labels(editor),
"Should get new hints after hint refresh/ request"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
let inlay_cache = editor.inlay_hint_cache();
assert_eq!(
inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
@ -1093,13 +1196,13 @@ pub mod tests {
let mut edits_made = 1;
editor.update(cx, |editor, cx| {
let expected_layers = vec!["0".to_string()];
let expected_hints = vec!["0".to_string()];
assert_eq!(
expected_layers,
expected_hints,
cached_hint_labels(editor),
"Should get its first hints when opening the editor"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
edits_made,
@ -1124,13 +1227,13 @@ pub mod tests {
cx.foreground().run_until_parked();
editor.update(cx, |editor, cx| {
let expected_layers = vec!["0".to_string()];
let expected_hints = vec!["0".to_string()];
assert_eq!(
expected_layers,
expected_hints,
cached_hint_labels(editor),
"Should not update hints while the work task is running"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
edits_made,
@ -1148,13 +1251,13 @@ pub mod tests {
edits_made += 1;
editor.update(cx, |editor, cx| {
let expected_layers = vec!["1".to_string()];
let expected_hints = vec!["1".to_string()];
assert_eq!(
expected_layers,
expected_hints,
cached_hint_labels(editor),
"New hints should be queried after the work task is done"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
edits_made,
@ -1267,13 +1370,13 @@ pub mod tests {
.await;
cx.foreground().run_until_parked();
rs_editor.update(cx, |editor, cx| {
let expected_layers = vec!["0".to_string()];
let expected_hints = vec!["0".to_string()];
assert_eq!(
expected_layers,
expected_hints,
cached_hint_labels(editor),
"Should get its first hints when opening the editor"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
1,
@ -1325,13 +1428,13 @@ pub mod tests {
.await;
cx.foreground().run_until_parked();
md_editor.update(cx, |editor, cx| {
let expected_layers = vec!["0".to_string()];
let expected_hints = vec!["0".to_string()];
assert_eq!(
expected_layers,
expected_hints,
cached_hint_labels(editor),
"Markdown editor should have a separate verison, repeating Rust editor rules"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 1);
});
@ -1341,13 +1444,13 @@ pub mod tests {
});
cx.foreground().run_until_parked();
rs_editor.update(cx, |editor, cx| {
let expected_layers = vec!["1".to_string()];
let expected_hints = vec!["1".to_string()];
assert_eq!(
expected_layers,
expected_hints,
cached_hint_labels(editor),
"Rust inlay cache should change after the edit"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
2,
@ -1355,13 +1458,13 @@ pub mod tests {
);
});
md_editor.update(cx, |editor, cx| {
let expected_layers = vec!["0".to_string()];
let expected_hints = vec!["0".to_string()];
assert_eq!(
expected_layers,
expected_hints,
cached_hint_labels(editor),
"Markdown editor should not be affected by Rust editor changes"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 1);
});
@ -1371,23 +1474,23 @@ pub mod tests {
});
cx.foreground().run_until_parked();
md_editor.update(cx, |editor, cx| {
let expected_layers = vec!["1".to_string()];
let expected_hints = vec!["1".to_string()];
assert_eq!(
expected_layers,
expected_hints,
cached_hint_labels(editor),
"Rust editor should not be affected by Markdown editor changes"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 2);
});
rs_editor.update(cx, |editor, cx| {
let expected_layers = vec!["1".to_string()];
let expected_hints = vec!["1".to_string()];
assert_eq!(
expected_layers,
expected_hints,
cached_hint_labels(editor),
"Markdown editor should also change independently"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 2);
});
}
@ -1816,7 +1919,7 @@ pub mod tests {
});
}));
}
let _ = futures::future::join_all(edits).await;
let _ = future::join_all(edits).await;
cx.foreground().run_until_parked();
editor.update(cx, |editor, cx| {
@ -1913,7 +2016,7 @@ pub mod tests {
.downcast::<Editor>()
.unwrap();
let lsp_request_ranges = Arc::new(Mutex::new(Vec::new()));
let lsp_request_count = Arc::new(AtomicU32::new(0));
let lsp_request_count = Arc::new(AtomicUsize::new(0));
let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges);
let closure_lsp_request_count = Arc::clone(&lsp_request_count);
fake_server
@ -1927,10 +2030,9 @@ pub mod tests {
);
task_lsp_request_ranges.lock().push(params.range);
let query_start = params.range.start;
let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
Ok(Some(vec![lsp::InlayHint {
position: query_start,
position: params.range.end,
label: lsp::InlayHintLabel::String(i.to_string()),
kind: None,
text_edits: None,
@ -1967,28 +2069,51 @@ pub mod tests {
})
}
let initial_visible_range = editor_visible_range(&editor, cx);
let expected_initial_query_range_end =
lsp::Position::new(initial_visible_range.end.row * 2, 1);
// in large buffers, requests are made for more than visible range of a buffer.
// invisible parts are queried later, to avoid excessive requests on quick typing.
// wait the timeout needed to get all requests.
cx.foreground().advance_clock(Duration::from_millis(
INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
));
cx.foreground().run_until_parked();
let initial_visible_range = editor_visible_range(&editor, cx);
let lsp_initial_visible_range = lsp::Range::new(
lsp::Position::new(
initial_visible_range.start.row,
initial_visible_range.start.column,
),
lsp::Position::new(
initial_visible_range.end.row,
initial_visible_range.end.column,
),
);
let expected_initial_query_range_end =
lsp::Position::new(initial_visible_range.end.row * 2, 2);
let mut expected_invisible_query_start = lsp_initial_visible_range.end;
expected_invisible_query_start.character += 1;
editor.update(cx, |editor, cx| {
let ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
assert_eq!(ranges.len(), 1,
"When scroll is at the edge of a big document, double of its visible part range should be queried for hints in one single big request, but got: {ranges:?}");
let query_range = &ranges[0];
assert_eq!(query_range.start, lsp::Position::new(0, 0), "Should query initially from the beginning of the document");
assert_eq!(query_range.end, expected_initial_query_range_end, "Should query initially for double lines of the visible part of the document");
assert_eq!(ranges.len(), 2,
"When scroll is at the edge of a big document, its visible part and the same range further should be queried in order, but got: {ranges:?}");
let visible_query_range = &ranges[0];
assert_eq!(visible_query_range.start, lsp_initial_visible_range.start);
assert_eq!(visible_query_range.end, lsp_initial_visible_range.end);
let invisible_query_range = &ranges[1];
assert_eq!(lsp_request_count.load(Ordering::Acquire), 1);
let expected_layers = vec!["1".to_string()];
assert_eq!(invisible_query_range.start, expected_invisible_query_start, "Should initially query visible edge of the document");
assert_eq!(invisible_query_range.end, expected_initial_query_range_end, "Should initially query visible edge of the document");
let requests_count = lsp_request_count.load(Ordering::Acquire);
assert_eq!(requests_count, 2, "Visible + invisible request");
let expected_hints = vec!["1".to_string(), "2".to_string()];
assert_eq!(
expected_layers,
expected_hints,
cached_hint_labels(editor),
"Should have hints from both LSP requests made for a big file"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
assert_eq!(expected_hints, visible_hint_labels(editor, cx), "Should display only hints from the visible range");
assert_eq!(
editor.inlay_hint_cache().version, 1,
editor.inlay_hint_cache().version, requests_count,
"LSP queries should've bumped the cache version"
);
});
@ -1997,11 +2122,13 @@ pub mod tests {
editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
});
cx.foreground().advance_clock(Duration::from_millis(
INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
));
cx.foreground().run_until_parked();
let visible_range_after_scrolls = editor_visible_range(&editor, cx);
let visible_line_count =
editor.update(cx, |editor, _| editor.visible_line_count().unwrap());
cx.foreground().run_until_parked();
let selection_in_cached_range = editor.update(cx, |editor, cx| {
let ranges = lsp_request_ranges
.lock()
@ -2028,26 +2155,28 @@ pub mod tests {
lsp::Position::new(
visible_range_after_scrolls.end.row
+ visible_line_count.ceil() as u32,
0
1,
),
"Second scroll should query one more screen down after the end of the visible range"
);
let lsp_requests = lsp_request_count.load(Ordering::Acquire);
assert_eq!(lsp_requests, 4, "Should query for hints after every scroll");
let expected_hints = vec![
"1".to_string(),
"2".to_string(),
"3".to_string(),
"4".to_string(),
];
assert_eq!(
lsp_request_count.load(Ordering::Acquire),
3,
"Should query for hints after every scroll"
);
let expected_layers = vec!["1".to_string(), "2".to_string(), "3".to_string()];
assert_eq!(
expected_layers,
expected_hints,
cached_hint_labels(editor),
"Should have hints from the new LSP response after the edit"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
3,
lsp_requests,
"Should update the cache for every LSP response with hints added"
);
@ -2061,6 +2190,9 @@ pub mod tests {
s.select_ranges([selection_in_cached_range..selection_in_cached_range])
});
});
cx.foreground().advance_clock(Duration::from_millis(
INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
));
cx.foreground().run_until_parked();
editor.update(cx, |_, _| {
let ranges = lsp_request_ranges
@ -2069,33 +2201,43 @@ pub mod tests {
.sorted_by_key(|r| r.start)
.collect::<Vec<_>>();
assert!(ranges.is_empty(), "No new ranges or LSP queries should be made after returning to the selection with cached hints");
assert_eq!(lsp_request_count.load(Ordering::Acquire), 3);
assert_eq!(lsp_request_count.load(Ordering::Acquire), 4);
});
editor.update(cx, |editor, cx| {
editor.handle_input("++++more text++++", cx);
});
cx.foreground().advance_clock(Duration::from_millis(
INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
));
cx.foreground().run_until_parked();
editor.update(cx, |editor, cx| {
let ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
assert_eq!(ranges.len(), 1,
"On edit, should scroll to selection and query a range around it. Instead, got query ranges {ranges:?}");
let query_range = &ranges[0];
assert!(query_range.start.line < selection_in_cached_range.row,
assert_eq!(ranges.len(), 3,
"On edit, should scroll to selection and query a range around it: visible + same range above and below. Instead, got query ranges {ranges:?}");
let visible_query_range = &ranges[0];
let above_query_range = &ranges[1];
let below_query_range = &ranges[2];
assert!(above_query_range.end.character < visible_query_range.start.character || above_query_range.end.line + 1 == visible_query_range.start.line,
"Above range {above_query_range:?} should be before visible range {visible_query_range:?}");
assert!(visible_query_range.end.character < below_query_range.start.character || visible_query_range.end.line + 1 == below_query_range.start.line,
"Visible range {visible_query_range:?} should be before below range {below_query_range:?}");
assert!(above_query_range.start.line < selection_in_cached_range.row,
"Hints should be queried with the selected range after the query range start");
assert!(query_range.end.line > selection_in_cached_range.row,
assert!(below_query_range.end.line > selection_in_cached_range.row,
"Hints should be queried with the selected range before the query range end");
assert!(query_range.start.line <= selection_in_cached_range.row - (visible_line_count * 3.0 / 2.0) as u32,
assert!(above_query_range.start.line <= selection_in_cached_range.row - (visible_line_count * 3.0 / 2.0) as u32,
"Hints query range should contain one more screen before");
assert!(query_range.end.line >= selection_in_cached_range.row + (visible_line_count * 3.0 / 2.0) as u32,
assert!(below_query_range.end.line >= selection_in_cached_range.row + (visible_line_count * 3.0 / 2.0) as u32,
"Hints query range should contain one more screen after");
assert_eq!(lsp_request_count.load(Ordering::Acquire), 4, "Should query for hints once after the edit");
let expected_layers = vec!["4".to_string()];
assert_eq!(expected_layers, cached_hint_labels(editor),
let lsp_requests = lsp_request_count.load(Ordering::Acquire);
assert_eq!(lsp_requests, 7, "There should be a visible range and two ranges above and below it queried");
let expected_hints = vec!["5".to_string(), "6".to_string(), "7".to_string()];
assert_eq!(expected_hints, cached_hint_labels(editor),
"Should have hints from the new LSP response after the edit");
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 4, "Should update the cache for every LSP response with hints added");
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");
});
}
@ -2306,19 +2448,19 @@ pub mod tests {
cx.foreground().run_until_parked();
editor.update(cx, |editor, cx| {
let expected_layers = vec![
let expected_hints = vec![
"main hint #0".to_string(),
"main hint #1".to_string(),
"main hint #2".to_string(),
"main hint #3".to_string(),
];
assert_eq!(
expected_layers,
expected_hints,
cached_hint_labels(editor),
"When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, expected_layers.len(), "Every visible excerpt hints should bump the verison");
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");
});
editor.update(cx, |editor, cx| {
@ -2334,7 +2476,7 @@ pub mod tests {
});
cx.foreground().run_until_parked();
editor.update(cx, |editor, cx| {
let expected_layers = vec![
let expected_hints = vec![
"main hint #0".to_string(),
"main hint #1".to_string(),
"main hint #2".to_string(),
@ -2345,10 +2487,10 @@ pub mod tests {
"other hint #1".to_string(),
"other hint #2".to_string(),
];
assert_eq!(expected_layers, cached_hint_labels(editor),
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_layers, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, expected_layers.len(),
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(),
"Due to every excerpt having one hint, we update cache per new excerpt scrolled");
});
@ -2357,9 +2499,12 @@ pub mod tests {
s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
});
});
cx.foreground().advance_clock(Duration::from_millis(
INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
));
cx.foreground().run_until_parked();
let last_scroll_update_version = editor.update(cx, |editor, cx| {
let expected_layers = vec![
let expected_hints = vec![
"main hint #0".to_string(),
"main hint #1".to_string(),
"main hint #2".to_string(),
@ -2373,11 +2518,11 @@ pub mod tests {
"other hint #4".to_string(),
"other hint #5".to_string(),
];
assert_eq!(expected_layers, cached_hint_labels(editor),
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_layers, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, expected_layers.len());
expected_layers.len()
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, expected_hints.len());
expected_hints.len()
});
editor.update(cx, |editor, cx| {
@ -2387,7 +2532,7 @@ pub mod tests {
});
cx.foreground().run_until_parked();
editor.update(cx, |editor, cx| {
let expected_layers = vec![
let expected_hints = vec![
"main hint #0".to_string(),
"main hint #1".to_string(),
"main hint #2".to_string(),
@ -2401,9 +2546,9 @@ pub mod tests {
"other hint #4".to_string(),
"other hint #5".to_string(),
];
assert_eq!(expected_layers, cached_hint_labels(editor),
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_layers, visible_hint_labels(editor, cx));
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");
});
@ -2416,7 +2561,7 @@ pub mod tests {
});
cx.foreground().run_until_parked();
editor.update(cx, |editor, cx| {
let expected_layers = vec![
let expected_hints = vec![
"main hint(edited) #0".to_string(),
"main hint(edited) #1".to_string(),
"main hint(edited) #2".to_string(),
@ -2427,15 +2572,15 @@ pub mod tests {
"other hint(edited) #1".to_string(),
];
assert_eq!(
expected_layers,
expected_hints,
cached_hint_labels(editor),
"After multibuffer edit, editor gets scolled back to the last selection; \
all hints should be invalidated and requeried for all of its visible excerpts"
);
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
let current_cache_version = editor.inlay_hint_cache().version;
let minimum_expected_version = last_scroll_update_version + expected_layers.len();
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,
"Due to every excerpt having one hint, cache should update per new excerpt received + 1 potential sporadic update"
@ -2776,9 +2921,9 @@ all hints should be invalidated and requeried for all of its visible excerpts"
});
cx.foreground().run_until_parked();
editor.update(cx, |editor, cx| {
let expected_layers = vec!["1".to_string()];
assert_eq!(expected_layers, cached_hint_labels(editor));
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
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);
});
}