@@ -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,82 +97,103 @@ 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 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
+ 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();
- vec![query_range]
+ query_ranges
}
};
- if !ranges_to_query.is_empty() {
- self.tasks.push(spawn_task(ranges_to_query));
+ 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
+ .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
}
}
@@ -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
+
+ let visible_range = if excerpt_visible_range.start == excerpt_visible_range.end {
+ return None;
+ } else {
+ 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(excerpt_visible_len)
- .min(full_excerpt_range.end.offset)
+ .saturating_add(1)
+ .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 after_visible_range = if after_visible_range_start == full_excerpt_range_end_offset {
+ Vec::new()
} else {
- Some(start..end)
- }
+ 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,24 +686,47 @@ 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| {
- fetch_and_update_hints(
- editor.clone(),
- multi_buffer_snapshot.clone(),
- buffer_snapshot.clone(),
- Arc::clone(&visible_hints),
- cached_excerpt_hints.as_ref().map(Arc::clone),
- query,
- range,
- cx.clone(),
- )
+ let fetch_and_update_hints = |invalidate, range| {
+ fetch_and_update_hints(
+ editor.clone(),
+ multi_buffer_snapshot.clone(),
+ buffer_snapshot.clone(),
+ 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 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;
- 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(false, invisible_range)),
+ )
+ .await;
+ for result in invisible_range_update_results {
if let Err(e) = result {
- error!("inlay hint update task failed: {e:#}");
+ 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 {
})
}
+ // 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, 1);
- cx.foreground().run_until_parked();
+ 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(),