diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs
index 350b1d41be..a9b8f5b0a7 100644
--- a/crates/collab/src/tests/editor_tests.rs
+++ b/crates/collab/src/tests/editor_tests.rs
@@ -1637,12 +1637,6 @@ async fn test_mutual_editor_inlay_hint_cache_update(
extract_hint_labels(editor),
"Host should get its first hints when opens an editor"
);
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(
- inlay_cache.version(),
- 1,
- "Host editor update the cache version after every cache/view change",
- );
});
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b
@@ -1661,12 +1655,6 @@ async fn test_mutual_editor_inlay_hint_cache_update(
extract_hint_labels(editor),
"Client should get its first hints when opens an editor"
);
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(
- inlay_cache.version(),
- 1,
- "Guest editor update the cache version after every cache/view change"
- );
});
let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
@@ -1682,16 +1670,12 @@ async fn test_mutual_editor_inlay_hint_cache_update(
vec![after_client_edit.to_string()],
extract_hint_labels(editor),
);
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version(), 2);
});
editor_b.update(cx_b, |editor, _| {
assert_eq!(
vec![after_client_edit.to_string()],
extract_hint_labels(editor),
);
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version(), 2);
});
let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
@@ -1707,16 +1691,12 @@ async fn test_mutual_editor_inlay_hint_cache_update(
vec![after_host_edit.to_string()],
extract_hint_labels(editor),
);
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version(), 3);
});
editor_b.update(cx_b, |editor, _| {
assert_eq!(
vec![after_host_edit.to_string()],
extract_hint_labels(editor),
);
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version(), 3);
});
let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
@@ -1732,12 +1712,6 @@ async fn test_mutual_editor_inlay_hint_cache_update(
extract_hint_labels(editor),
"Host should react to /refresh LSP request"
);
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(
- inlay_cache.version(),
- 4,
- "Host should accepted all edits and bump its cache version every time"
- );
});
editor_b.update(cx_b, |editor, _| {
assert_eq!(
@@ -1745,12 +1719,6 @@ async fn test_mutual_editor_inlay_hint_cache_update(
extract_hint_labels(editor),
"Guest should get a /refresh LSP request propagated by host"
);
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(
- inlay_cache.version(),
- 4,
- "Guest should accepted all edits and bump its cache version every time"
- );
});
}
@@ -1906,12 +1874,6 @@ async fn test_inlay_hint_refresh_is_forwarded(
extract_hint_labels(editor).is_empty(),
"Host should get no hints due to them turned off"
);
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(
- inlay_cache.version(),
- 0,
- "Turned off hints should not generate version updates"
- );
});
executor.run_until_parked();
@@ -1921,12 +1883,6 @@ async fn test_inlay_hint_refresh_is_forwarded(
extract_hint_labels(editor),
"Client should get its first hints when opens an editor"
);
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(
- inlay_cache.version(),
- 1,
- "Should update cache version after first hints"
- );
});
other_hints.fetch_or(true, atomic::Ordering::Release);
@@ -1938,13 +1894,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
editor_a.update(cx_a, |editor, _| {
assert!(
extract_hint_labels(editor).is_empty(),
- "Host should get nop hints due to them turned off, even after the /refresh"
- );
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(
- inlay_cache.version(),
- 0,
- "Turned off hints should not generate version updates, again"
+ "Host should get no hints due to them turned off, even after the /refresh"
);
});
@@ -1955,12 +1905,6 @@ async fn test_inlay_hint_refresh_is_forwarded(
extract_hint_labels(editor),
"Guest should get a /refresh LSP request propagated by host despite host hints are off"
);
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(
- inlay_cache.version(),
- 2,
- "Guest should accepted all edits and bump its cache version every time"
- );
});
}
diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs
index 01dd12cc98..cbf75943ba 100644
--- a/crates/editor/src/display_map/inlay_map.rs
+++ b/crates/editor/src/display_map/inlay_map.rs
@@ -569,6 +569,7 @@ impl InlayMap {
probe
.position
.cmp(&inlay_to_insert.position, &snapshot.buffer)
+ .then(std::cmp::Ordering::Less)
}) {
Ok(ix) | Err(ix) => {
self.inlays.insert(ix, inlay_to_insert);
diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs
index bc2735b891..2f7962e57e 100644
--- a/crates/editor/src/editor.rs
+++ b/crates/editor/src/editor.rs
@@ -12441,6 +12441,7 @@ impl Editor {
cx.emit(EditorEvent::ExcerptsEdited { ids: ids.clone() })
}
multi_buffer::Event::ExcerptsExpanded { ids } => {
+ self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
}
multi_buffer::Event::Reparsed(buffer_id) => {
diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs
index 327ebbb5e2..8df011e7dd 100644
--- a/crates/editor/src/inlay_hint_cache.rs
+++ b/crates/editor/src/inlay_hint_cache.rs
@@ -1,5 +1,5 @@
/// Stores and updates all data received from LSP textDocument/inlayHint requests.
-/// Has nothing to do with other inlays, e.g. copilot suggestions — those are stored elsewhere.
+/// Has nothing to do with other inlays, e.g. copilot suggestions — those are stored elsewhere.
/// On every update, cache may query for more inlay hints and update inlays on the screen.
///
/// Inlays stored on screen are in [`crate::display_map::inlay_map`] and this cache is the only way to update any inlay hint data in the visible hints in the inlay map.
@@ -38,7 +38,7 @@ pub struct InlayHintCache {
pub(super) enabled: bool,
enabled_in_settings: bool,
update_tasks: HashMap,
- refresh_task: Option>,
+ refresh_task: Task<()>,
invalidate_debounce: Option,
append_debounce: Option,
lsp_request_limiter: Arc,
@@ -116,13 +116,9 @@ impl InvalidationStrategy {
impl TasksForRanges {
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,
+ sorted_ranges: query_ranges.into_sorted_query_ranges(),
}
}
@@ -135,7 +131,7 @@ impl TasksForRanges {
) {
let query_ranges = if invalidate.should_invalidate() {
self.tasks.clear();
- self.sorted_ranges.clear();
+ self.sorted_ranges = query_ranges.clone().into_sorted_query_ranges();
query_ranges
} else {
let mut non_cached_query_ranges = query_ranges;
@@ -272,7 +268,7 @@ impl InlayHintCache {
enabled_in_settings: inlay_hint_settings.enabled,
hints: HashMap::default(),
update_tasks: HashMap::default(),
- refresh_task: None,
+ refresh_task: Task::ready(()),
invalidate_debounce: debounce_value(inlay_hint_settings.edit_debounce_ms),
append_debounce: debounce_value(inlay_hint_settings.scroll_debounce_ms),
version: 0,
@@ -384,7 +380,7 @@ impl InlayHintCache {
} else {
self.append_debounce
};
- self.refresh_task = Some(cx.spawn(|editor, mut cx| async move {
+ self.refresh_task = cx.spawn(|editor, mut cx| async move {
if let Some(debounce_duration) = debounce_duration {
cx.background_executor().timer(debounce_duration).await;
}
@@ -401,7 +397,7 @@ impl InlayHintCache {
)
})
.ok();
- }));
+ });
if invalidated_hints.is_empty() {
None
@@ -580,10 +576,6 @@ impl InlayHintCache {
hints
}
- pub fn version(&self) -> usize {
- self.version
- }
-
/// Queries a certain hint from the cache for extra data via the LSP resolve request.
pub(super) fn spawn_hint_resolve(
&self,
@@ -731,6 +723,16 @@ impl QueryRanges {
fn is_empty(&self) -> bool {
self.before_visible.is_empty() && self.visible.is_empty() && self.after_visible.is_empty()
}
+
+ fn into_sorted_query_ranges(self) -> Vec> {
+ let mut sorted_ranges = Vec::with_capacity(
+ self.before_visible.len() + self.visible.len() + self.after_visible.len(),
+ );
+ sorted_ranges.extend(self.before_visible);
+ sorted_ranges.extend(self.visible);
+ sorted_ranges.extend(self.after_visible);
+ sorted_ranges
+ }
}
fn determine_query_ranges(
@@ -940,7 +942,7 @@ fn fetch_and_update_hints(
None => true,
};
if query_not_around_visible_range {
- // log::trace!("Fetching inlay hints for range {fetch_range_to_log:?} got throttled and fell off the current visible range, skipping.");
+ log::trace!("Fetching inlay hints for range {fetch_range_to_log:?} got throttled and fell off the current visible range, skipping.");
if let Some(task_ranges) = editor
.inlay_hint_cache
.update_tasks
@@ -1008,12 +1010,12 @@ fn fetch_and_update_hints(
})
.await;
if let Some(new_update) = new_update {
- // log::debug!(
- // "Applying update for range {fetch_range_to_log:?}: remove from editor: {}, remove from cache: {}, add to cache: {}",
- // new_update.remove_from_visible.len(),
- // new_update.remove_from_cache.len(),
- // new_update.add_to_cache.len()
- // );
+ log::debug!(
+ "Applying update for range {fetch_range_to_log:?}: remove from editor: {}, remove from cache: {}, add to cache: {}",
+ new_update.remove_from_visible.len(),
+ new_update.remove_from_cache.len(),
+ new_update.add_to_cache.len()
+ );
log::trace!("New update: {new_update:?}");
editor
.update(&mut cx, |editor, cx| {
@@ -1181,48 +1183,39 @@ fn apply_hint_update(
.cmp(&new_hint.position, &buffer_snapshot)
}) {
Ok(i) => {
- let mut insert_position = Some(i);
- for id in &cached_excerpt_hints.ordered_hints[i..] {
- let cached_hint = &cached_excerpt_hints.hints_by_id[id];
- if new_hint
- .position
- .cmp(&cached_hint.position, &buffer_snapshot)
- .is_gt()
- {
- break;
- }
- if cached_hint.text() == new_hint.text() {
- insert_position = None;
- break;
- }
- }
- insert_position
+ // When a hint is added to the same position where existing ones are present,
+ // do not deduplicate it: we split hint queries into non-overlapping ranges
+ // and each hint batch returned by the server should already contain unique hints.
+ i + cached_excerpt_hints.ordered_hints[i..].len() + 1
}
- Err(i) => Some(i),
+ Err(i) => i,
};
- if let Some(insert_position) = insert_position {
- let new_inlay_id = post_inc(&mut editor.next_inlay_id);
- if editor
- .inlay_hint_cache
- .allowed_hint_kinds
- .contains(&new_hint.kind)
+ let new_inlay_id = post_inc(&mut editor.next_inlay_id);
+ if editor
+ .inlay_hint_cache
+ .allowed_hint_kinds
+ .contains(&new_hint.kind)
+ {
+ if let Some(new_hint_position) =
+ multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position)
{
- if let Some(new_hint_position) =
- multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position)
- {
- splice
- .to_insert
- .push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
- }
+ splice
+ .to_insert
+ .push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
}
- let new_id = InlayId::Hint(new_inlay_id);
- cached_excerpt_hints.hints_by_id.insert(new_id, new_hint);
+ }
+ let new_id = InlayId::Hint(new_inlay_id);
+ cached_excerpt_hints.hints_by_id.insert(new_id, new_hint);
+ if cached_excerpt_hints.ordered_hints.len() <= insert_position {
+ cached_excerpt_hints.ordered_hints.push(new_id);
+ } else {
cached_excerpt_hints
.ordered_hints
.insert(insert_position, new_id);
- cached_inlays_changed = true;
}
+
+ cached_inlays_changed = true;
}
cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone();
drop(cached_excerpt_hints);
@@ -1266,15 +1259,19 @@ fn apply_hint_update(
#[cfg(test)]
pub mod tests {
use crate::editor_tests::update_test_language_settings;
+ use crate::scroll::ScrollAmount;
use crate::{scroll::Autoscroll, test::editor_lsp_test_context::rust_lang, ExcerptRange};
use futures::StreamExt;
use gpui::{Context, SemanticVersion, TestAppContext, WindowHandle};
+ use itertools::Itertools as _;
use language::{language_settings::AllLanguageSettingsContent, Capability, FakeLspAdapter};
+ use language::{Language, LanguageConfig, LanguageMatcher};
use lsp::FakeLanguageServer;
+ use parking_lot::Mutex;
use project::{FakeFs, Project};
use serde_json::json;
use settings::SettingsStore;
- use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
+ use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
use text::Point;
use super::*;
@@ -1293,50 +1290,35 @@ pub mod tests {
show_background: false,
})
});
-
let (_, editor, fake_server) = prepare_test_objects(cx, |fake_server, file_with_hints| {
let lsp_request_count = Arc::new(AtomicU32::new(0));
fake_server.handle_request::(move |params, _| {
let task_lsp_request_count = Arc::clone(&lsp_request_count);
async move {
+ let i = task_lsp_request_count.fetch_add(1, Ordering::Release) + 1;
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path(file_with_hints).unwrap(),
);
- let current_call_id =
- Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
- let mut new_hints = Vec::with_capacity(2 * current_call_id as usize);
- for _ in 0..2 {
- let mut i = current_call_id;
- loop {
- new_hints.push(lsp::InlayHint {
- position: lsp::Position::new(0, i),
- label: lsp::InlayHintLabel::String(i.to_string()),
- kind: None,
- text_edits: None,
- tooltip: None,
- padding_left: None,
- padding_right: None,
- data: None,
- });
- if i == 0 {
- break;
- }
- i -= 1;
- }
- }
-
- Ok(Some(new_hints))
+ Ok(Some(vec![lsp::InlayHint {
+ position: lsp::Position::new(0, i),
+ label: lsp::InlayHintLabel::String(i.to_string()),
+ kind: None,
+ text_edits: None,
+ tooltip: None,
+ padding_left: None,
+ padding_right: None,
+ data: None,
+ }]))
}
});
})
.await;
cx.executor().run_until_parked();
- let mut edits_made = 1;
editor
.update(cx, |editor, cx| {
- let expected_hints = vec!["0".to_string()];
+ let expected_hints = vec!["1".to_string()];
assert_eq!(
expected_hints,
cached_hint_labels(editor),
@@ -1348,10 +1330,6 @@ pub mod tests {
inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
"Cache should use editor settings to get the allowed hint kinds"
);
- assert_eq!(
- inlay_cache.version, edits_made,
- "The editor update the cache version after every cache/view change"
- );
})
.unwrap();
@@ -1359,13 +1337,12 @@ pub mod tests {
.update(cx, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
editor.handle_input("some change", cx);
- edits_made += 1;
})
.unwrap();
cx.executor().run_until_parked();
editor
.update(cx, |editor, cx| {
- let expected_hints = vec!["0".to_string(), "1".to_string()];
+ let expected_hints = vec!["2".to_string()];
assert_eq!(
expected_hints,
cached_hint_labels(editor),
@@ -1377,10 +1354,6 @@ pub mod tests {
inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
"Cache should use editor settings to get the allowed hint kinds"
);
- assert_eq!(
- inlay_cache.version, edits_made,
- "The editor update the cache version after every cache/view change"
- );
})
.unwrap();
@@ -1388,11 +1361,10 @@ pub mod tests {
.request::(())
.await
.expect("inlay refresh request failed");
- edits_made += 1;
cx.executor().run_until_parked();
editor
.update(cx, |editor, cx| {
- let expected_hints = vec!["0".to_string(), "1".to_string(), "2".to_string()];
+ let expected_hints = vec!["3".to_string()];
assert_eq!(
expected_hints,
cached_hint_labels(editor),
@@ -1404,10 +1376,6 @@ pub mod tests {
inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
"Cache should use editor settings to get the allowed hint kinds"
);
- assert_eq!(
- inlay_cache.version, edits_made,
- "The editor update the cache version after every cache/view change"
- );
})
.unwrap();
}
@@ -1453,7 +1421,6 @@ pub mod tests {
.await;
cx.executor().run_until_parked();
- let mut edits_made = 1;
editor
.update(cx, |editor, cx| {
let expected_hints = vec!["0".to_string()];
@@ -1463,11 +1430,6 @@ pub mod tests {
"Should get its first hints when opening the editor"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- assert_eq!(
- editor.inlay_hint_cache().version,
- edits_made,
- "The editor update the cache version after every cache/view change"
- );
})
.unwrap();
@@ -1496,11 +1458,6 @@ pub mod tests {
"Should not update hints while the work task is running"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- assert_eq!(
- editor.inlay_hint_cache().version,
- edits_made,
- "Should not update the cache while the work task is running"
- );
})
.unwrap();
@@ -1512,7 +1469,6 @@ pub mod tests {
});
cx.executor().run_until_parked();
- edits_made += 1;
editor
.update(cx, |editor, cx| {
let expected_hints = vec!["1".to_string()];
@@ -1522,246 +1478,223 @@ pub mod tests {
"New hints should be queried after the work task is done"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- assert_eq!(
- editor.inlay_hint_cache().version,
- edits_made,
- "Cache version should update once after the work task is done"
- );
})
.unwrap();
}
- // #[gpui::test]
- // async fn test_no_hint_updates_for_unrelated_language_files(cx: &mut gpui::TestAppContext) {
- // init_test(cx, |settings| {
- // settings.defaults.inlay_hints = Some(InlayHintSettings {
- // enabled: true,
- // edit_debounce_ms: 0,
- // scroll_debounce_ms: 0,
- // show_type_hints: true,
- // show_parameter_hints: true,
- // show_other_hints: true,
- // show_background: false,
- // })
- // });
+ #[gpui::test]
+ async fn test_no_hint_updates_for_unrelated_language_files(cx: &mut gpui::TestAppContext) {
+ init_test(cx, |settings| {
+ settings.defaults.inlay_hints = Some(InlayHintSettings {
+ enabled: true,
+ edit_debounce_ms: 0,
+ scroll_debounce_ms: 0,
+ show_type_hints: true,
+ show_parameter_hints: true,
+ show_other_hints: true,
+ show_background: false,
+ })
+ });
- // let fs = FakeFs::new(cx.background_executor.clone());
- // fs.insert_tree(
- // "/a",
- // json!({
- // "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
- // "other.md": "Test md file with some text",
- // }),
- // )
- // .await;
+ let fs = FakeFs::new(cx.background_executor.clone());
+ fs.insert_tree(
+ "/a",
+ json!({
+ "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
+ "other.md": "Test md file with some text",
+ }),
+ )
+ .await;
- // let project = Project::test(fs, ["/a".as_ref()], cx).await;
+ let project = Project::test(fs, ["/a".as_ref()], cx).await;
- // let language_registry = project.read_with(cx, |project, _| project.languages().clone());
- // let mut rs_fake_servers = None;
- // let mut md_fake_servers = None;
- // for (name, path_suffix) in [("Rust", "rs"), ("Markdown", "md")] {
- // language_registry.add(Arc::new(Language::new(
- // LanguageConfig {
- // name: name.into(),
- // matcher: LanguageMatcher {
- // path_suffixes: vec![path_suffix.to_string()],
- // ..Default::default()
- // },
- // ..Default::default()
- // },
- // Some(tree_sitter_rust::LANGUAGE.into()),
- // )));
- // let fake_servers = language_registry.register_fake_lsp(
- // name,
- // FakeLspAdapter {
- // name,
- // capabilities: lsp::ServerCapabilities {
- // inlay_hint_provider: Some(lsp::OneOf::Left(true)),
- // ..Default::default()
- // },
- // ..Default::default()
- // },
- // );
- // match name {
- // "Rust" => rs_fake_servers = Some(fake_servers),
- // "Markdown" => md_fake_servers = Some(fake_servers),
- // _ => unreachable!(),
- // }
- // }
+ let language_registry = project.read_with(cx, |project, _| project.languages().clone());
+ let mut rs_fake_servers = None;
+ let mut md_fake_servers = None;
+ for (name, path_suffix) in [("Rust", "rs"), ("Markdown", "md")] {
+ language_registry.add(Arc::new(Language::new(
+ LanguageConfig {
+ name: name.into(),
+ matcher: LanguageMatcher {
+ path_suffixes: vec![path_suffix.to_string()],
+ ..Default::default()
+ },
+ ..Default::default()
+ },
+ Some(tree_sitter_rust::LANGUAGE.into()),
+ )));
+ let fake_servers = language_registry.register_fake_lsp(
+ name,
+ FakeLspAdapter {
+ name,
+ capabilities: lsp::ServerCapabilities {
+ inlay_hint_provider: Some(lsp::OneOf::Left(true)),
+ ..Default::default()
+ },
+ initializer: Some(Box::new({
+ move |fake_server| {
+ let rs_lsp_request_count = Arc::new(AtomicU32::new(0));
+ let md_lsp_request_count = Arc::new(AtomicU32::new(0));
+ fake_server.handle_request::(
+ move |params, _| {
+ let i = match name {
+ "Rust" => {
+ assert_eq!(
+ params.text_document.uri,
+ lsp::Url::from_file_path("/a/main.rs").unwrap(),
+ );
+ rs_lsp_request_count.fetch_add(1, Ordering::Release) + 1
+ }
+ "Markdown" => {
+ assert_eq!(
+ params.text_document.uri,
+ lsp::Url::from_file_path("/a/other.md").unwrap(),
+ );
+ md_lsp_request_count.fetch_add(1, Ordering::Release) + 1
+ }
+ unexpected => panic!("Unexpected language: {unexpected}"),
+ };
- // let rs_buffer = project
- // .update(cx, |project, cx| {
- // project.open_local_buffer("/a/main.rs", cx)
- // })
- // .await
- // .unwrap();
- // let rs_editor =
- // cx.add_window(|cx| Editor::for_buffer(rs_buffer, Some(project.clone()), cx));
+ async move {
+ let query_start = params.range.start;
+ Ok(Some(vec![lsp::InlayHint {
+ position: query_start,
+ label: lsp::InlayHintLabel::String(i.to_string()),
+ kind: None,
+ text_edits: None,
+ tooltip: None,
+ padding_left: None,
+ padding_right: None,
+ data: None,
+ }]))
+ }
+ },
+ );
+ }
+ })),
+ ..Default::default()
+ },
+ );
+ match name {
+ "Rust" => rs_fake_servers = Some(fake_servers),
+ "Markdown" => md_fake_servers = Some(fake_servers),
+ _ => unreachable!(),
+ }
+ }
- // cx.executor().run_until_parked();
- // cx.executor().start_waiting();
- // let rs_fake_server = rs_fake_servers.unwrap().next().await.unwrap();
- // let rs_lsp_request_count = Arc::new(AtomicU32::new(0));
- // rs_fake_server
- // .handle_request::(move |params, _| {
- // let task_lsp_request_count = Arc::clone(&rs_lsp_request_count);
- // async move {
- // assert_eq!(
- // params.text_document.uri,
- // lsp::Url::from_file_path("/a/main.rs").unwrap(),
- // );
- // let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
- // Ok(Some(vec![lsp::InlayHint {
- // position: lsp::Position::new(0, i),
- // label: lsp::InlayHintLabel::String(i.to_string()),
- // kind: None,
- // text_edits: None,
- // tooltip: None,
- // padding_left: None,
- // padding_right: None,
- // data: None,
- // }]))
- // }
- // })
- // .next()
- // .await;
- // cx.executor().run_until_parked();
- // rs_editor
- // .update(cx, |editor, cx| {
- // let expected_hints = vec!["0".to_string()];
- // assert_eq!(
- // expected_hints,
- // cached_hint_labels(editor),
- // "Should get its first hints when opening the editor"
- // );
- // assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- // assert_eq!(
- // editor.inlay_hint_cache().version,
- // 1,
- // "Rust editor update the cache version after every cache/view change"
- // );
- // })
- // .unwrap();
+ let rs_buffer = project
+ .update(cx, |project, cx| {
+ project.open_local_buffer("/a/main.rs", cx)
+ })
+ .await
+ .unwrap();
+ let rs_editor =
+ cx.add_window(|cx| Editor::for_buffer(rs_buffer, Some(project.clone()), cx));
+ cx.executor().run_until_parked();
- // cx.executor().run_until_parked();
- // let md_buffer = project
- // .update(cx, |project, cx| {
- // project.open_local_buffer("/a/other.md", cx)
- // })
- // .await
- // .unwrap();
- // let md_editor = cx.add_window(|cx| Editor::for_buffer(md_buffer, Some(project), cx));
+ let _rs_fake_server = rs_fake_servers.unwrap().next().await.unwrap();
+ cx.executor().run_until_parked();
+ rs_editor
+ .update(cx, |editor, cx| {
+ let expected_hints = vec!["1".to_string()];
+ assert_eq!(
+ expected_hints,
+ cached_hint_labels(editor),
+ "Should get its first hints when opening the editor"
+ );
+ assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+ })
+ .unwrap();
- // cx.executor().run_until_parked();
- // cx.executor().start_waiting();
- // let md_fake_server = md_fake_servers.unwrap().next().await.unwrap();
- // let md_lsp_request_count = Arc::new(AtomicU32::new(0));
- // md_fake_server
- // .handle_request::(move |params, _| {
- // let task_lsp_request_count = Arc::clone(&md_lsp_request_count);
- // async move {
- // assert_eq!(
- // params.text_document.uri,
- // lsp::Url::from_file_path("/a/other.md").unwrap(),
- // );
- // let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
- // Ok(Some(vec![lsp::InlayHint {
- // position: lsp::Position::new(0, i),
- // label: lsp::InlayHintLabel::String(i.to_string()),
- // kind: None,
- // text_edits: None,
- // tooltip: None,
- // padding_left: None,
- // padding_right: None,
- // data: None,
- // }]))
- // }
- // })
- // .next()
- // .await;
- // cx.executor().run_until_parked();
- // md_editor
- // .update(cx, |editor, cx| {
- // let expected_hints = vec!["0".to_string()];
- // assert_eq!(
- // expected_hints,
- // cached_hint_labels(editor),
- // "Markdown editor should have a separate version, repeating Rust editor rules"
- // );
- // assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- // assert_eq!(editor.inlay_hint_cache().version, 1);
- // })
- // .unwrap();
+ cx.executor().run_until_parked();
+ let md_buffer = project
+ .update(cx, |project, cx| {
+ project.open_local_buffer("/a/other.md", cx)
+ })
+ .await
+ .unwrap();
+ let md_editor = cx.add_window(|cx| Editor::for_buffer(md_buffer, Some(project), cx));
+ cx.executor().run_until_parked();
- // rs_editor
- // .update(cx, |editor, cx| {
- // editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
- // editor.handle_input("some rs change", cx);
- // })
- // .unwrap();
- // cx.executor().run_until_parked();
- // rs_editor
- // .update(cx, |editor, cx| {
- // let expected_hints = vec!["1".to_string()];
- // assert_eq!(
- // expected_hints,
- // cached_hint_labels(editor),
- // "Rust inlay cache should change after the edit"
- // );
- // assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- // assert_eq!(
- // editor.inlay_hint_cache().version,
- // 2,
- // "Every time hint cache changes, cache version should be incremented"
- // );
- // })
- // .unwrap();
- // md_editor
- // .update(cx, |editor, cx| {
- // let expected_hints = vec!["0".to_string()];
- // assert_eq!(
- // expected_hints,
- // cached_hint_labels(editor),
- // "Markdown editor should not be affected by Rust editor changes"
- // );
- // assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- // assert_eq!(editor.inlay_hint_cache().version, 1);
- // })
- // .unwrap();
+ let _md_fake_server = md_fake_servers.unwrap().next().await.unwrap();
+ cx.executor().run_until_parked();
+ md_editor
+ .update(cx, |editor, cx| {
+ let expected_hints = vec!["1".to_string()];
+ assert_eq!(
+ expected_hints,
+ cached_hint_labels(editor),
+ "Markdown editor should have a separate version, repeating Rust editor rules"
+ );
+ assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+ })
+ .unwrap();
- // md_editor
- // .update(cx, |editor, cx| {
- // editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
- // editor.handle_input("some md change", cx);
- // })
- // .unwrap();
- // cx.executor().run_until_parked();
- // md_editor
- // .update(cx, |editor, cx| {
- // let expected_hints = vec!["1".to_string()];
- // assert_eq!(
- // expected_hints,
- // cached_hint_labels(editor),
- // "Rust editor should not be affected by Markdown editor changes"
- // );
- // assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- // assert_eq!(editor.inlay_hint_cache().version, 2);
- // })
- // .unwrap();
- // rs_editor
- // .update(cx, |editor, cx| {
- // let expected_hints = vec!["1".to_string()];
- // assert_eq!(
- // expected_hints,
- // cached_hint_labels(editor),
- // "Markdown editor should also change independently"
- // );
- // assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- // assert_eq!(editor.inlay_hint_cache().version, 2);
- // })
- // .unwrap();
- // }
+ rs_editor
+ .update(cx, |editor, cx| {
+ editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
+ editor.handle_input("some rs change", cx);
+ })
+ .unwrap();
+ cx.executor().run_until_parked();
+ rs_editor
+ .update(cx, |editor, cx| {
+ // TODO: Here, we do not get "2", because inserting another language server will trigger `RefreshInlayHints` event from the `LspStore`
+ // A project is listened in every editor, so each of them will react to this event.
+ //
+ // We do not have language server IDs for remote projects, so cannot easily say on the editor level,
+ // whether we should ignore a particular `RefreshInlayHints` event.
+ let expected_hints = vec!["3".to_string()];
+ assert_eq!(
+ expected_hints,
+ cached_hint_labels(editor),
+ "Rust inlay cache should change after the edit"
+ );
+ assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+ })
+ .unwrap();
+ md_editor
+ .update(cx, |editor, cx| {
+ let expected_hints = vec!["1".to_string()];
+ assert_eq!(
+ expected_hints,
+ cached_hint_labels(editor),
+ "Markdown editor should not be affected by Rust editor changes"
+ );
+ assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+ })
+ .unwrap();
+
+ md_editor
+ .update(cx, |editor, cx| {
+ editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
+ editor.handle_input("some md change", cx);
+ })
+ .unwrap();
+ cx.executor().run_until_parked();
+ md_editor
+ .update(cx, |editor, cx| {
+ let expected_hints = vec!["2".to_string()];
+ assert_eq!(
+ expected_hints,
+ cached_hint_labels(editor),
+ "Rust editor should not be affected by Markdown editor changes"
+ );
+ assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+ })
+ .unwrap();
+ rs_editor
+ .update(cx, |editor, cx| {
+ let expected_hints = vec!["3".to_string()];
+ assert_eq!(
+ expected_hints,
+ cached_hint_labels(editor),
+ "Markdown editor should also change independently"
+ );
+ assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+ })
+ .unwrap();
+ }
#[gpui::test]
async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) {
@@ -1778,14 +1711,14 @@ pub mod tests {
})
});
- let lsp_request_count = Arc::new(AtomicU32::new(0));
+ let lsp_request_count = Arc::new(AtomicUsize::new(0));
let (_, editor, fake_server) = prepare_test_objects(cx, {
let lsp_request_count = lsp_request_count.clone();
move |fake_server, file_with_hints| {
let lsp_request_count = lsp_request_count.clone();
fake_server.handle_request::(
move |params, _| {
- lsp_request_count.fetch_add(1, Ordering::SeqCst);
+ lsp_request_count.fetch_add(1, Ordering::Release);
async move {
assert_eq!(
params.text_document.uri,
@@ -1833,7 +1766,6 @@ pub mod tests {
.await;
cx.executor().run_until_parked();
- let mut edits_made = 1;
editor
.update(cx, |editor, cx| {
assert_eq!(
@@ -1843,15 +1775,15 @@ pub mod tests {
);
assert_eq!(
vec![
- "other hint".to_string(),
- "parameter hint".to_string(),
"type hint".to_string(),
+ "parameter hint".to_string(),
+ "other hint".to_string(),
],
cached_hint_labels(editor),
"Should get its first hints when opening the editor"
);
assert_eq!(
- vec!["other hint".to_string(), "type hint".to_string()],
+ vec!["type hint".to_string(), "other hint".to_string()],
visible_hint_labels(editor, cx)
);
let inlay_cache = editor.inlay_hint_cache();
@@ -1859,10 +1791,6 @@ pub mod tests {
inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
"Cache should use editor settings to get the allowed hint kinds"
);
- assert_eq!(
- inlay_cache.version, edits_made,
- "The editor update the cache version after every cache/view change"
- );
})
.unwrap();
@@ -1880,22 +1808,17 @@ pub mod tests {
);
assert_eq!(
vec![
- "other hint".to_string(),
- "parameter hint".to_string(),
"type hint".to_string(),
+ "parameter hint".to_string(),
+ "other hint".to_string(),
],
cached_hint_labels(editor),
"Cached hints should not change due to allowed hint kinds settings update"
);
assert_eq!(
- vec!["other hint".to_string(), "type hint".to_string()],
+ vec!["type hint".to_string(), "other hint".to_string()],
visible_hint_labels(editor, cx)
);
- assert_eq!(
- editor.inlay_hint_cache().version,
- edits_made,
- "Should not update cache version due to new loaded hints being the same"
- );
})
.unwrap();
@@ -1911,15 +1834,15 @@ pub mod tests {
),
(
HashSet::from_iter([None, Some(InlayHintKind::Type)]),
- vec!["other hint".to_string(), "type hint".to_string()],
+ vec!["type hint".to_string(), "other hint".to_string()],
),
(
HashSet::from_iter([None, Some(InlayHintKind::Parameter)]),
- vec!["other hint".to_string(), "parameter hint".to_string()],
+ vec!["parameter hint".to_string(), "other hint".to_string()],
),
(
HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]),
- vec!["parameter hint".to_string(), "type hint".to_string()],
+ vec!["type hint".to_string(), "parameter hint".to_string()],
),
(
HashSet::from_iter([
@@ -1928,13 +1851,12 @@ pub mod tests {
Some(InlayHintKind::Parameter),
]),
vec![
- "other hint".to_string(),
- "parameter hint".to_string(),
"type hint".to_string(),
+ "parameter hint".to_string(),
+ "other hint".to_string(),
],
),
] {
- edits_made += 1;
update_test_language_settings(cx, |settings| {
settings.defaults.inlay_hints = Some(InlayHintSettings {
enabled: true,
@@ -1956,9 +1878,9 @@ pub mod tests {
);
assert_eq!(
vec![
- "other hint".to_string(),
- "parameter hint".to_string(),
"type hint".to_string(),
+ "parameter hint".to_string(),
+ "other hint".to_string(),
],
cached_hint_labels(editor),
"Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}"
@@ -1973,14 +1895,9 @@ pub mod tests {
inlay_cache.allowed_hint_kinds, new_allowed_hint_kinds,
"Cache should use editor settings to get the allowed hint kinds for hint kinds {new_allowed_hint_kinds:?}"
);
- assert_eq!(
- inlay_cache.version, edits_made,
- "The editor should update the cache version after every cache/view change for hint kinds {new_allowed_hint_kinds:?} due to visible hints change"
- );
}).unwrap();
}
- edits_made += 1;
let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]);
update_test_language_settings(cx, |settings| {
settings.defaults.inlay_hints = Some(InlayHintSettings {
@@ -2015,10 +1932,6 @@ pub mod tests {
inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds,
"Should update its allowed hint kinds even when hints got disabled"
);
- assert_eq!(
- inlay_cache.version, edits_made,
- "The editor should update the cache version after hints got disabled"
- );
})
.unwrap();
@@ -2027,22 +1940,19 @@ pub mod tests {
.await
.expect("inlay refresh request failed");
cx.executor().run_until_parked();
- editor.update(cx, |editor, cx| {
- assert_eq!(
- lsp_request_count.load(Ordering::Relaxed),
- 2,
- "Should not load new hints when they got disabled"
- );
- assert!(cached_hint_labels(editor).is_empty());
- assert!(visible_hint_labels(editor, cx).is_empty());
- assert_eq!(
- editor.inlay_hint_cache().version, edits_made,
- "The editor should not update the cache version after /refresh query without updates"
- );
- }).unwrap();
+ editor
+ .update(cx, |editor, cx| {
+ assert_eq!(
+ lsp_request_count.load(Ordering::Relaxed),
+ 2,
+ "Should not load new hints when they got disabled"
+ );
+ assert!(cached_hint_labels(editor).is_empty());
+ assert!(visible_hint_labels(editor, cx).is_empty());
+ })
+ .unwrap();
let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]);
- edits_made += 1;
update_test_language_settings(cx, |settings| {
settings.defaults.inlay_hints = Some(InlayHintSettings {
enabled: true,
@@ -2065,9 +1975,9 @@ pub mod tests {
);
assert_eq!(
vec![
- "other hint".to_string(),
- "parameter hint".to_string(),
"type hint".to_string(),
+ "parameter hint".to_string(),
+ "other hint".to_string(),
],
cached_hint_labels(editor),
"Should get its cached hints fully repopulated after the hints got re-enabled"
@@ -2082,10 +1992,6 @@ pub mod tests {
inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds,
"Cache should update editor settings when hints got re-enabled"
);
- assert_eq!(
- inlay_cache.version, edits_made,
- "Cache should update its version after hints got re-enabled"
- );
})
.unwrap();
@@ -2103,9 +2009,9 @@ pub mod tests {
);
assert_eq!(
vec![
- "other hint".to_string(),
- "parameter hint".to_string(),
"type hint".to_string(),
+ "parameter hint".to_string(),
+ "other hint".to_string(),
],
cached_hint_labels(editor),
);
@@ -2113,7 +2019,6 @@ pub mod tests {
vec!["parameter hint".to_string()],
visible_hint_labels(editor, cx),
);
- assert_eq!(editor.inlay_hint_cache().version, edits_made);
})
.unwrap();
}
@@ -2180,27 +2085,29 @@ pub mod tests {
cx.executor().run_until_parked();
- editor.update(cx, |editor, cx| {
- let current_text = editor.text(cx);
- for change in &expected_changes {
- assert!(
- current_text.contains(change),
- "Should apply all changes made"
+ editor
+ .update(cx, |editor, cx| {
+ let current_text = editor.text(cx);
+ for change in &expected_changes {
+ assert!(
+ current_text.contains(change),
+ "Should apply all changes made"
+ );
+ }
+ assert_eq!(
+ lsp_request_count.load(Ordering::Relaxed),
+ 2,
+ "Should query new hints twice: for editor init and for the last edit that interrupted all others"
);
- }
- assert_eq!(
- lsp_request_count.load(Ordering::Relaxed),
- 2,
- "Should query new hints twice: for editor init and for the last edit that interrupted all others"
- );
- let expected_hints = vec!["2".to_string()];
- assert_eq!(
- expected_hints,
- cached_hint_labels(editor),
- "Should get hints from the last edit landed only"
- );
- assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- }).unwrap();
+ let expected_hints = vec!["2".to_string()];
+ assert_eq!(
+ expected_hints,
+ cached_hint_labels(editor),
+ "Should get hints from the last edit landed only"
+ );
+ assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+ })
+ .unwrap();
let mut edits = Vec::new();
for async_later_change in [
@@ -2247,309 +2154,300 @@ pub mod tests {
.unwrap();
}
- // #[gpui::test(iterations = 10)]
- // async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) {
- // init_test(cx, |settings| {
- // settings.defaults.inlay_hints = Some(InlayHintSettings {
- // enabled: true,
- // edit_debounce_ms: 0,
- // scroll_debounce_ms: 0,
- // show_type_hints: true,
- // show_parameter_hints: true,
- // show_other_hints: true,
- // show_background: false,
- // })
- // });
+ #[gpui::test(iterations = 10)]
+ async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) {
+ init_test(cx, |settings| {
+ settings.defaults.inlay_hints = Some(InlayHintSettings {
+ enabled: true,
+ edit_debounce_ms: 0,
+ scroll_debounce_ms: 0,
+ show_type_hints: true,
+ show_parameter_hints: true,
+ show_other_hints: true,
+ show_background: false,
+ })
+ });
- // let fs = FakeFs::new(cx.background_executor.clone());
- // fs.insert_tree(
- // "/a",
- // json!({
- // "main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)),
- // "other.rs": "// Test file",
- // }),
- // )
- // .await;
+ let fs = FakeFs::new(cx.background_executor.clone());
+ fs.insert_tree(
+ "/a",
+ json!({
+ "main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)),
+ "other.rs": "// Test file",
+ }),
+ )
+ .await;
- // let project = Project::test(fs, ["/a".as_ref()], cx).await;
+ let project = Project::test(fs, ["/a".as_ref()], cx).await;
- // let language_registry = project.read_with(cx, |project, _| project.languages().clone());
- // language_registry.add(rust_lang());
+ let language_registry = project.read_with(cx, |project, _| project.languages().clone());
+ language_registry.add(rust_lang());
- // let lsp_request_ranges = Arc::new(Mutex::new(Vec::new()));
- // let lsp_request_count = Arc::new(AtomicUsize::new(0));
- // let mut fake_servers = language_registry.register_fake_lsp(
- // "Rust",
- // FakeLspAdapter {
- // capabilities: lsp::ServerCapabilities {
- // inlay_hint_provider: Some(lsp::OneOf::Left(true)),
- // ..Default::default()
- // },
- // initializer: Some(Box::new({
- // let lsp_request_ranges = lsp_request_ranges.clone();
- // let lsp_request_count = lsp_request_count.clone();
- // move |fake_server| {
- // let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges);
- // let closure_lsp_request_count = Arc::clone(&lsp_request_count);
- // fake_server.handle_request::(
- // move |params, _| {
- // let task_lsp_request_ranges =
- // Arc::clone(&closure_lsp_request_ranges);
- // let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
- // async move {
- // assert_eq!(
- // params.text_document.uri,
- // lsp::Url::from_file_path("/a/main.rs").unwrap(),
- // );
+ let lsp_request_ranges = Arc::new(Mutex::new(Vec::new()));
+ let lsp_request_count = Arc::new(AtomicUsize::new(0));
+ let mut fake_servers = language_registry.register_fake_lsp(
+ "Rust",
+ FakeLspAdapter {
+ capabilities: lsp::ServerCapabilities {
+ inlay_hint_provider: Some(lsp::OneOf::Left(true)),
+ ..Default::default()
+ },
+ initializer: Some(Box::new({
+ let lsp_request_ranges = lsp_request_ranges.clone();
+ let lsp_request_count = lsp_request_count.clone();
+ move |fake_server| {
+ let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges);
+ let closure_lsp_request_count = Arc::clone(&lsp_request_count);
+ fake_server.handle_request::(
+ move |params, _| {
+ let task_lsp_request_ranges =
+ Arc::clone(&closure_lsp_request_ranges);
+ let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
+ async move {
+ assert_eq!(
+ params.text_document.uri,
+ lsp::Url::from_file_path("/a/main.rs").unwrap(),
+ );
- // task_lsp_request_ranges.lock().push(params.range);
- // let i = Arc::clone(&task_lsp_request_count)
- // .fetch_add(1, Ordering::Release)
- // + 1;
- // Ok(Some(vec![lsp::InlayHint {
- // position: params.range.end,
- // label: lsp::InlayHintLabel::String(i.to_string()),
- // kind: None,
- // text_edits: None,
- // tooltip: None,
- // padding_left: None,
- // padding_right: None,
- // data: None,
- // }]))
- // }
- // },
- // );
- // }
- // })),
- // ..Default::default()
- // },
- // );
+ task_lsp_request_ranges.lock().push(params.range);
+ task_lsp_request_count.fetch_add(1, Ordering::Release);
+ Ok(Some(vec![lsp::InlayHint {
+ position: params.range.end,
+ label: lsp::InlayHintLabel::String(
+ params.range.end.line.to_string(),
+ ),
+ kind: None,
+ text_edits: None,
+ tooltip: None,
+ padding_left: None,
+ padding_right: None,
+ data: None,
+ }]))
+ }
+ },
+ );
+ }
+ })),
+ ..Default::default()
+ },
+ );
- // let buffer = project
- // .update(cx, |project, cx| {
- // project.open_local_buffer("/a/main.rs", cx)
- // })
- // .await
- // .unwrap();
- // let editor = cx.add_window(|cx| Editor::for_buffer(buffer, Some(project), cx));
+ let buffer = project
+ .update(cx, |project, cx| {
+ project.open_local_buffer("/a/main.rs", cx)
+ })
+ .await
+ .unwrap();
+ let editor = cx.add_window(|cx| Editor::for_buffer(buffer, Some(project), cx));
- // cx.executor().run_until_parked();
+ cx.executor().run_until_parked();
- // fn editor_visible_range(
- // editor: &WindowHandle,
- // cx: &mut gpui::TestAppContext,
- // ) -> Range {
- // let ranges = editor
- // .update(cx, |editor, cx| {
- // editor.excerpts_for_inlay_hints_query(None, cx)
- // })
- // .unwrap();
- // assert_eq!(
- // ranges.len(),
- // 1,
- // "Single buffer should produce a single excerpt with visible range"
- // );
- // let (_, (excerpt_buffer, _, excerpt_visible_range)) =
- // ranges.into_iter().next().unwrap();
- // excerpt_buffer.update(cx, |buffer, _| {
- // let snapshot = buffer.snapshot();
- // let start = buffer
- // .anchor_before(excerpt_visible_range.start)
- // .to_point(&snapshot);
- // let end = buffer
- // .anchor_after(excerpt_visible_range.end)
- // .to_point(&snapshot);
- // start..end
- // })
- // }
+ let _fake_server = fake_servers.next().await.unwrap();
- // // 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.executor().advance_clock(Duration::from_millis(
- // INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
- // ));
- // cx.executor().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::>();
- // 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];
+ // 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.executor().advance_clock(Duration::from_millis(
+ INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
+ ));
+ cx.executor().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::>();
+ 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!(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");
+ 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_hints,
- // cached_hint_labels(editor),
- // "Should have hints from both LSP requests made for a big file"
- // );
- // assert_eq!(expected_hints, visible_hint_labels(editor, cx), "Should display only hints from the visible range");
- // assert_eq!(
- // editor.inlay_hint_cache().version, requests_count,
- // "LSP queries should've bumped the cache version"
- // );
- // }).unwrap();
+ let requests_count = lsp_request_count.load(Ordering::Acquire);
+ assert_eq!(requests_count, 2, "Visible + invisible request");
+ let expected_hints = vec!["47".to_string(), "94".to_string()];
+ assert_eq!(
+ expected_hints,
+ cached_hint_labels(editor),
+ "Should have hints from both LSP requests made for a big file"
+ );
+ assert_eq!(expected_hints, visible_hint_labels(editor, cx), "Should display only hints from the visible range");
+ }).unwrap();
- // editor
- // .update(cx, |editor, cx| {
- // editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
- // })
- // .unwrap();
- // cx.executor().run_until_parked();
- // editor
- // .update(cx, |editor, cx| {
- // editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
- // })
- // .unwrap();
- // cx.executor().advance_clock(Duration::from_millis(
- // INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
- // ));
- // cx.executor().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())
- // .unwrap();
- // let selection_in_cached_range = editor
- // .update(cx, |editor, cx| {
- // let ranges = lsp_request_ranges
- // .lock()
- // .drain(..)
- // .sorted_by_key(|r| r.start)
- // .collect::>();
- // assert_eq!(
- // ranges.len(),
- // 2,
- // "Should query 2 ranges after both scrolls, but got: {ranges:?}"
- // );
- // let first_scroll = &ranges[0];
- // let second_scroll = &ranges[1];
- // assert_eq!(
- // first_scroll.end, second_scroll.start,
- // "Should query 2 adjacent ranges after the scrolls, but got: {ranges:?}"
- // );
- // assert_eq!(
- // first_scroll.start, expected_initial_query_range_end,
- // "First scroll should start the query right after the end of the original scroll",
- // );
- // assert_eq!(
- // second_scroll.end,
- // lsp::Position::new(
- // visible_range_after_scrolls.end.row
- // + visible_line_count.ceil() as u32,
- // 1,
- // ),
- // "Second scroll should query one more screen down after the end of the visible range"
- // );
+ editor
+ .update(cx, |editor, cx| {
+ editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
+ })
+ .unwrap();
+ cx.executor().run_until_parked();
+ editor
+ .update(cx, |editor, cx| {
+ editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
+ })
+ .unwrap();
+ cx.executor().advance_clock(Duration::from_millis(
+ INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
+ ));
+ cx.executor().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())
+ .unwrap();
+ let selection_in_cached_range = editor
+ .update(cx, |editor, cx| {
+ let ranges = lsp_request_ranges
+ .lock()
+ .drain(..)
+ .sorted_by_key(|r| r.start)
+ .collect::>();
+ assert_eq!(
+ ranges.len(),
+ 2,
+ "Should query 2 ranges after both scrolls, but got: {ranges:?}"
+ );
+ let first_scroll = &ranges[0];
+ let second_scroll = &ranges[1];
+ assert_eq!(
+ first_scroll.end, second_scroll.start,
+ "Should query 2 adjacent ranges after the scrolls, but got: {ranges:?}"
+ );
+ assert_eq!(
+ first_scroll.start, expected_initial_query_range_end,
+ "First scroll should start the query right after the end of the original scroll",
+ );
+ assert_eq!(
+ second_scroll.end,
+ lsp::Position::new(
+ visible_range_after_scrolls.end.row
+ + visible_line_count.ceil() as u32,
+ 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!(
- // expected_hints,
- // cached_hint_labels(editor),
- // "Should have hints from the new LSP response after the edit"
- // );
- // 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"
- // );
+ 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![
+ "47".to_string(),
+ "94".to_string(),
+ "139".to_string(),
+ "184".to_string(),
+ ];
+ assert_eq!(
+ expected_hints,
+ cached_hint_labels(editor),
+ "Should have hints from the new LSP response after the edit"
+ );
+ assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- // let mut selection_in_cached_range = visible_range_after_scrolls.end;
- // selection_in_cached_range.row -= visible_line_count.ceil() as u32;
- // selection_in_cached_range
- // })
- // .unwrap();
+ let mut selection_in_cached_range = visible_range_after_scrolls.end;
+ selection_in_cached_range.row -= visible_line_count.ceil() as u32;
+ selection_in_cached_range
+ })
+ .unwrap();
- // editor
- // .update(cx, |editor, cx| {
- // editor.change_selections(Some(Autoscroll::center()), cx, |s| {
- // s.select_ranges([selection_in_cached_range..selection_in_cached_range])
- // });
- // })
- // .unwrap();
- // cx.executor().advance_clock(Duration::from_millis(
- // INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
- // ));
- // cx.executor().run_until_parked();
- // editor.update(cx, |_, _| {
- // let ranges = lsp_request_ranges
- // .lock()
- // .drain(..)
- // .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), 4);
- // }).unwrap();
+ editor
+ .update(cx, |editor, cx| {
+ editor.change_selections(Some(Autoscroll::center()), cx, |s| {
+ s.select_ranges([selection_in_cached_range..selection_in_cached_range])
+ });
+ })
+ .unwrap();
+ cx.executor().advance_clock(Duration::from_millis(
+ INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
+ ));
+ cx.executor().run_until_parked();
+ editor.update(cx, |_, _| {
+ let ranges = lsp_request_ranges
+ .lock()
+ .drain(..)
+ .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), 4);
+ }).unwrap();
- // editor
- // .update(cx, |editor, cx| {
- // editor.handle_input("++++more text++++", cx);
- // })
- // .unwrap();
- // cx.executor().advance_clock(Duration::from_millis(
- // INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
- // ));
- // cx.executor().run_until_parked();
- // editor.update(cx, |editor, cx| {
- // let mut ranges = lsp_request_ranges.lock().drain(..).collect::>();
- // ranges.sort_by_key(|r| r.start);
+ editor
+ .update(cx, |editor, cx| {
+ editor.handle_input("++++more text++++", cx);
+ })
+ .unwrap();
+ cx.executor().advance_clock(Duration::from_millis(
+ INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
+ ));
+ cx.executor().run_until_parked();
+ editor.update(cx, |editor, cx| {
+ let mut ranges = lsp_request_ranges.lock().drain(..).collect::>();
+ ranges.sort_by_key(|r| r.start);
- // 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 above_query_range = &ranges[0];
- // let visible_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!(below_query_range.end.line > selection_in_cached_range.row,
- // "Hints should be queried with the selected range before the query range end");
- // 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!(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!(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 above_query_range = &ranges[0];
+ let visible_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!(below_query_range.end.line > selection_in_cached_range.row,
+ "Hints should be queried with the selected range before the query range end");
+ 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!(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");
- // 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_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");
- // }).unwrap();
- // }
+ 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!["67".to_string(), "115".to_string(), "163".to_string()];
+ assert_eq!(expected_hints, cached_hint_labels(editor),
+ "Should have hints from the new LSP response after the edit");
+ assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+ }).unwrap();
+ }
+
+ fn editor_visible_range(
+ editor: &WindowHandle,
+ cx: &mut gpui::TestAppContext,
+ ) -> Range {
+ let ranges = editor
+ .update(cx, |editor, cx| {
+ editor.excerpts_for_inlay_hints_query(None, cx)
+ })
+ .unwrap();
+ assert_eq!(
+ ranges.len(),
+ 1,
+ "Single buffer should produce a single excerpt with visible range"
+ );
+ let (_, (excerpt_buffer, _, excerpt_visible_range)) = ranges.into_iter().next().unwrap();
+ excerpt_buffer.update(cx, |buffer, _| {
+ let snapshot = buffer.snapshot();
+ let start = buffer
+ .anchor_before(excerpt_visible_range.start)
+ .to_point(&snapshot);
+ let end = buffer
+ .anchor_after(excerpt_visible_range.end)
+ .to_point(&snapshot);
+ start..end
+ })
+ }
#[gpui::test]
async fn test_multiple_excerpts_large_multibuffer(cx: &mut gpui::TestAppContext) {
@@ -2723,8 +2621,8 @@ pub mod tests {
lsp::InlayHint {
position,
label: lsp::InlayHintLabel::String(format!(
- "{hint_text}{} #{i}",
- if edited { "(edited)" } else { "" },
+ "{hint_text}{E} #{i}",
+ E = if edited { "(edited)" } else { "" },
)),
kind: None,
text_edits: None,
@@ -2742,7 +2640,8 @@ pub mod tests {
.await;
cx.executor().run_until_parked();
- editor.update(cx, |editor, cx| {
+ editor
+ .update(cx, |editor, cx| {
let expected_hints = vec![
"main hint #0".to_string(),
"main hint #1".to_string(),
@@ -2753,12 +2652,12 @@ pub mod tests {
];
assert_eq!(
expected_hints,
- cached_hint_labels(editor),
+ sorted_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_hints, visible_hint_labels(editor, cx));
- assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the version");
- }).unwrap();
+ })
+ .unwrap();
editor
.update(cx, |editor, cx| {
@@ -2774,7 +2673,8 @@ pub mod tests {
})
.unwrap();
cx.executor().run_until_parked();
- editor.update(cx, |editor, cx| {
+ editor
+ .update(cx, |editor, cx| {
let expected_hints = vec![
"main hint #0".to_string(),
"main hint #1".to_string(),
@@ -2786,12 +2686,11 @@ pub mod tests {
"other hint #1".to_string(),
"other hint #2".to_string(),
];
- assert_eq!(expected_hints, cached_hint_labels(editor),
+ assert_eq!(expected_hints, sorted_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_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");
- }).unwrap();
+ })
+ .unwrap();
editor
.update(cx, |editor, cx| {
@@ -2804,7 +2703,8 @@ pub mod tests {
INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
));
cx.executor().run_until_parked();
- let last_scroll_update_version = editor.update(cx, |editor, cx| {
+ editor
+ .update(cx, |editor, cx| {
let expected_hints = vec![
"main hint #0".to_string(),
"main hint #1".to_string(),
@@ -2819,12 +2719,11 @@ pub mod tests {
"other hint #4".to_string(),
"other hint #5".to_string(),
];
- assert_eq!(expected_hints, cached_hint_labels(editor),
+ assert_eq!(expected_hints, sorted_cached_hint_labels(editor),
"After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- assert_eq!(editor.inlay_hint_cache().version, expected_hints.len());
- expected_hints.len()
- }).unwrap();
+ })
+ .unwrap();
editor
.update(cx, |editor, cx| {
@@ -2837,7 +2736,8 @@ pub mod tests {
INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
));
cx.executor().run_until_parked();
- editor.update(cx, |editor, cx| {
+ editor
+ .update(cx, |editor, cx| {
let expected_hints = vec![
"main hint #0".to_string(),
"main hint #1".to_string(),
@@ -2852,11 +2752,11 @@ pub mod tests {
"other hint #4".to_string(),
"other hint #5".to_string(),
];
- assert_eq!(expected_hints, cached_hint_labels(editor),
+ assert_eq!(expected_hints, sorted_cached_hint_labels(editor),
"After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
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 scrolled buffer");
- }).unwrap();
+ })
+ .unwrap();
editor_edited.store(true, Ordering::Release);
editor
@@ -2868,35 +2768,28 @@ pub mod tests {
})
.unwrap();
cx.executor().run_until_parked();
- editor.update(cx, |editor, cx| {
- let expected_hints = vec![
- "main hint #0".to_string(),
- "main hint #1".to_string(),
- "main hint #2".to_string(),
- "main hint #3".to_string(),
- "main hint #4".to_string(),
- "main hint #5".to_string(),
- "other hint(edited) #0".to_string(),
- "other hint(edited) #1".to_string(),
- "other hint(edited) #2".to_string(),
- ];
- assert_eq!(
- expected_hints,
- cached_hint_labels(editor),
- "After multibuffer edit, editor gets scrolled back to the last selection; \
- all hints should be invalidated and required for all of its visible excerpts"
- );
- assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-
- let current_cache_version = editor.inlay_hint_cache().version;
- // We expect three new hints for the excerpts from `other.rs`:
- let expected_version = last_scroll_update_version + 3;
- assert_eq!(
- current_cache_version,
- expected_version,
- "We should have updated cache N times == N of new hints arrived (separately from each edited excerpt)"
- );
- }).unwrap();
+ editor
+ .update(cx, |editor, cx| {
+ let expected_hints = vec![
+ "main hint #0".to_string(),
+ "main hint #1".to_string(),
+ "main hint #2".to_string(),
+ "main hint #3".to_string(),
+ "main hint #4".to_string(),
+ "main hint #5".to_string(),
+ "other hint(edited) #0".to_string(),
+ "other hint(edited) #1".to_string(),
+ "other hint(edited) #2".to_string(),
+ ];
+ assert_eq!(
+ expected_hints,
+ sorted_cached_hint_labels(editor),
+ "After multibuffer edit, editor gets scrolled back to the last selection; \
+ all hints should be invalidated and required for all of its visible excerpts"
+ );
+ assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+ })
+ .unwrap();
}
#[gpui::test]
@@ -3056,11 +2949,6 @@ pub mod tests {
visible_hint_labels(editor, cx).is_empty(),
"All hints are disabled and should not be shown despite being present in the cache"
);
- assert_eq!(
- editor.inlay_hint_cache().version,
- 2,
- "Cache should update once per excerpt query"
- );
})
.unwrap();
@@ -3083,11 +2971,6 @@ pub mod tests {
visible_hint_labels(editor, cx).is_empty(),
"All hints are disabled and should not be shown despite being present in the cache"
);
- assert_eq!(
- editor.inlay_hint_cache().version,
- 3,
- "Excerpt removal should trigger a cache update"
- );
})
.unwrap();
@@ -3116,11 +2999,6 @@ pub mod tests {
visible_hint_labels(editor, cx),
"Settings change should make cached hints visible"
);
- assert_eq!(
- editor.inlay_hint_cache().version,
- 4,
- "Settings change should trigger a cache update"
- );
})
.unwrap();
}
@@ -3197,9 +3075,6 @@ pub mod tests {
.unwrap();
let editor = cx.add_window(|cx| Editor::for_buffer(buffer, Some(project), cx));
- cx.executor().run_until_parked();
- cx.executor().start_waiting();
-
cx.executor().run_until_parked();
editor
.update(cx, |editor, cx| {
@@ -3214,7 +3089,6 @@ pub mod tests {
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);
})
.unwrap();
}
@@ -3264,7 +3138,6 @@ pub mod tests {
editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx)
})
.unwrap();
- cx.executor().start_waiting();
cx.executor().run_until_parked();
editor
@@ -3276,11 +3149,6 @@ pub mod tests {
"Should display inlays after toggle despite them disabled in settings"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- assert_eq!(
- editor.inlay_hint_cache().version,
- 1,
- "First toggle should be cache's first update"
- );
})
.unwrap();
@@ -3297,7 +3165,6 @@ pub mod tests {
"Should clear hints after 2nd toggle"
);
assert!(visible_hint_labels(editor, cx).is_empty());
- assert_eq!(editor.inlay_hint_cache().version, 2);
})
.unwrap();
@@ -3322,7 +3189,6 @@ pub mod tests {
"Should query LSP hints for the 2nd time after enabling hints in settings"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- assert_eq!(editor.inlay_hint_cache().version, 3);
})
.unwrap();
@@ -3339,7 +3205,6 @@ pub mod tests {
"Should clear hints after enabling in settings and a 3rd toggle"
);
assert!(visible_hint_labels(editor, cx).is_empty());
- assert_eq!(editor.inlay_hint_cache().version, 4);
})
.unwrap();
@@ -3357,10 +3222,160 @@ pub mod tests {
"Should query LSP hints for the 3rd time after enabling hints in settings and toggling them back on"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- assert_eq!(editor.inlay_hint_cache().version, 5);
}).unwrap();
}
+ #[gpui::test]
+ async fn test_inlays_at_the_same_place(cx: &mut gpui::TestAppContext) {
+ init_test(cx, |settings| {
+ settings.defaults.inlay_hints = Some(InlayHintSettings {
+ enabled: true,
+ edit_debounce_ms: 0,
+ scroll_debounce_ms: 0,
+ show_type_hints: true,
+ show_parameter_hints: true,
+ show_other_hints: true,
+ show_background: false,
+ })
+ });
+
+ let fs = FakeFs::new(cx.background_executor.clone());
+ fs.insert_tree(
+ "/a",
+ json!({
+ "main.rs": "fn main() {
+ let x = 42;
+ std::thread::scope(|s| {
+ s.spawn(|| {
+ let _x = x;
+ });
+ });
+ }",
+ "other.rs": "// Test file",
+ }),
+ )
+ .await;
+
+ let project = Project::test(fs, ["/a".as_ref()], cx).await;
+
+ let language_registry = project.read_with(cx, |project, _| project.languages().clone());
+ language_registry.add(rust_lang());
+ language_registry.register_fake_lsp(
+ "Rust",
+ FakeLspAdapter {
+ capabilities: lsp::ServerCapabilities {
+ inlay_hint_provider: Some(lsp::OneOf::Left(true)),
+ ..Default::default()
+ },
+ initializer: Some(Box::new(move |fake_server| {
+ fake_server.handle_request::(
+ move |params, _| async move {
+ assert_eq!(
+ params.text_document.uri,
+ lsp::Url::from_file_path("/a/main.rs").unwrap(),
+ );
+ Ok(Some(
+ serde_json::from_value(json!([
+ {
+ "position": {
+ "line": 3,
+ "character": 16
+ },
+ "label": "move",
+ "paddingLeft": false,
+ "paddingRight": false
+ },
+ {
+ "position": {
+ "line": 3,
+ "character": 16
+ },
+ "label": "(",
+ "paddingLeft": false,
+ "paddingRight": false
+ },
+ {
+ "position": {
+ "line": 3,
+ "character": 16
+ },
+ "label": [
+ {
+ "value": "&x"
+ }
+ ],
+ "paddingLeft": false,
+ "paddingRight": false,
+ "data": {
+ "file_id": 0
+ }
+ },
+ {
+ "position": {
+ "line": 3,
+ "character": 16
+ },
+ "label": ")",
+ "paddingLeft": false,
+ "paddingRight": true
+ },
+ // not a correct syntax, but checks that same symbols at the same place
+ // are not deduplicated
+ {
+ "position": {
+ "line": 3,
+ "character": 16
+ },
+ "label": ")",
+ "paddingLeft": false,
+ "paddingRight": true
+ },
+ ]))
+ .unwrap(),
+ ))
+ },
+ );
+ })),
+ ..FakeLspAdapter::default()
+ },
+ );
+
+ let buffer = project
+ .update(cx, |project, cx| {
+ project.open_local_buffer("/a/main.rs", cx)
+ })
+ .await
+ .unwrap();
+ let editor = cx.add_window(|cx| Editor::for_buffer(buffer, Some(project), cx));
+
+ cx.executor().run_until_parked();
+ editor
+ .update(cx, |editor, cx| {
+ editor.change_selections(None, cx, |s| {
+ s.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
+ })
+ })
+ .unwrap();
+ cx.executor().run_until_parked();
+ editor
+ .update(cx, |editor, cx| {
+ let expected_hints = vec![
+ "move".to_string(),
+ "(".to_string(),
+ "&x".to_string(),
+ ") ".to_string(),
+ ") ".to_string(),
+ ];
+ assert_eq!(
+ expected_hints,
+ cached_hint_labels(editor),
+ "Editor inlay hints should repeat server's order when placed at the same spot"
+ );
+ assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+ })
+ .unwrap();
+ }
+
pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
@@ -3420,38 +3435,47 @@ pub mod tests {
.update(cx, |editor, cx| {
assert!(cached_hint_labels(editor).is_empty());
assert!(visible_hint_labels(editor, cx).is_empty());
- assert_eq!(editor.inlay_hint_cache().version, 0);
})
.unwrap();
cx.executor().run_until_parked();
- cx.executor().start_waiting();
let fake_server = fake_servers.next().await.unwrap();
- cx.executor().finish_waiting();
-
(file_path, editor, fake_server)
}
+ // Inlay hints in the cache are stored per excerpt as a key, and those keys are guaranteed to be ordered same as in the multi buffer.
+ // Ensure a stable order for testing.
+ fn sorted_cached_hint_labels(editor: &Editor) -> Vec {
+ let mut labels = cached_hint_labels(editor);
+ labels.sort();
+ labels
+ }
+
pub fn cached_hint_labels(editor: &Editor) -> Vec {
let mut labels = Vec::new();
for excerpt_hints in editor.inlay_hint_cache().hints.values() {
let excerpt_hints = excerpt_hints.read();
for id in &excerpt_hints.ordered_hints {
- labels.push(excerpt_hints.hints_by_id[id].text());
+ let hint = &excerpt_hints.hints_by_id[id];
+ let mut label = hint.text();
+ if hint.padding_left {
+ label.insert(0, ' ');
+ }
+ if hint.padding_right {
+ label.push_str(" ");
+ }
+ labels.push(label);
}
}
- labels.sort();
labels
}
pub fn visible_hint_labels(editor: &Editor, cx: &ViewContext) -> Vec {
- let mut hints = editor
+ editor
.visible_inlay_hints(cx)
.into_iter()
.map(|hint| hint.text.to_string())
- .collect::>();
- hints.sort();
- hints
+ .collect()
}
}