diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index b0c7d9e0f1a7c5bd64758126b199425f59b77151..54ed8f8f1182610213b91c51d909adcd0a8f1df8 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -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>, 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, + query_ranges: QueryRanges, invalidate: InvalidationStrategy, - spawn_task: impl FnOnce(Vec>) -> 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>; - 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, + ) -> Vec> { + let mut ranges_to_query = Vec::new(); + let mut latest_cached_range = None::<&mut Range>; + 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>, + visible: Vec>, + after_visible: Vec>, +} + +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, excerpt_visible_range: Range, cx: &mut ModelContext<'_, MultiBuffer>, -) -> Option> { +) -> Option { 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>, + query_ranges: QueryRanges, multi_buffer_snapshot: MultiBufferSnapshot, buffer_snapshot: BufferSnapshot, visible_hints: Arc>, @@ -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>, cached_excerpt_hints: Option>>, query: ExcerptQuery, + invalidate: bool, fetch_range: Range, 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, new_excerpt_hints: Vec, 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::() .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::>(); - 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::>(); 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::>(); - 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); }); }