Fix racy inlay hints queries (#41816)
Follow-up of https://github.com/zed-industries/zed/pull/40183 Release Notes: - (Preview only) Fixed inlay hints duplicating when multiple editors are open for the same buffer --------- Co-authored-by: Lukas Wirth <lukas@zed.dev>
This commit is contained in:
@@ -37,6 +37,7 @@ use std::{
|
||||
Arc,
|
||||
atomic::{self, AtomicBool, AtomicUsize},
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
use text::Point;
|
||||
use util::{path, rel_path::rel_path, uri};
|
||||
@@ -1813,14 +1814,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
settings.project.all_languages.defaults.inlay_hints =
|
||||
Some(InlayHintSettingsContent {
|
||||
enabled: Some(true),
|
||||
show_value_hints: Some(true),
|
||||
edit_debounce_ms: Some(0),
|
||||
scroll_debounce_ms: Some(0),
|
||||
show_type_hints: Some(true),
|
||||
show_parameter_hints: Some(false),
|
||||
show_other_hints: Some(true),
|
||||
show_background: Some(false),
|
||||
toggle_on_modifiers_press: None,
|
||||
..InlayHintSettingsContent::default()
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -1830,15 +1824,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.all_languages.defaults.inlay_hints =
|
||||
Some(InlayHintSettingsContent {
|
||||
show_value_hints: Some(true),
|
||||
enabled: Some(true),
|
||||
edit_debounce_ms: Some(0),
|
||||
scroll_debounce_ms: Some(0),
|
||||
show_type_hints: Some(true),
|
||||
show_parameter_hints: Some(false),
|
||||
show_other_hints: Some(true),
|
||||
show_background: Some(false),
|
||||
toggle_on_modifiers_press: None,
|
||||
..InlayHintSettingsContent::default()
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -1931,6 +1918,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
});
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
let editor_a = file_a.await.unwrap().downcast::<Editor>().unwrap();
|
||||
executor.advance_clock(Duration::from_millis(100));
|
||||
executor.run_until_parked();
|
||||
|
||||
let initial_edit = edits_made.load(atomic::Ordering::Acquire);
|
||||
@@ -1951,6 +1939,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
|
||||
executor.advance_clock(Duration::from_millis(100));
|
||||
executor.run_until_parked();
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(
|
||||
@@ -1969,6 +1958,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
});
|
||||
cx_b.focus(&editor_b);
|
||||
|
||||
executor.advance_clock(Duration::from_secs(1));
|
||||
executor.run_until_parked();
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_eq!(
|
||||
@@ -1992,6 +1982,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
});
|
||||
cx_a.focus(&editor_a);
|
||||
|
||||
executor.advance_clock(Duration::from_secs(1));
|
||||
executor.run_until_parked();
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_eq!(
|
||||
@@ -2013,6 +2004,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
.into_response()
|
||||
.expect("inlay refresh request failed");
|
||||
|
||||
executor.advance_clock(Duration::from_secs(1));
|
||||
executor.run_until_parked();
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_eq!(
|
||||
|
||||
@@ -1833,9 +1833,15 @@ impl Editor {
|
||||
project::Event::RefreshCodeLens => {
|
||||
// we always query lens with actions, without storing them, always refreshing them
|
||||
}
|
||||
project::Event::RefreshInlayHints(server_id) => {
|
||||
project::Event::RefreshInlayHints {
|
||||
server_id,
|
||||
request_id,
|
||||
} => {
|
||||
editor.refresh_inlay_hints(
|
||||
InlayHintRefreshReason::RefreshRequested(*server_id),
|
||||
InlayHintRefreshReason::RefreshRequested {
|
||||
server_id: *server_id,
|
||||
request_id: *request_id,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::{
|
||||
collections::hash_map,
|
||||
ops::{ControlFlow, Range},
|
||||
time::Duration,
|
||||
};
|
||||
@@ -49,8 +48,8 @@ pub struct LspInlayHintData {
|
||||
allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
|
||||
invalidate_debounce: Option<Duration>,
|
||||
append_debounce: Option<Duration>,
|
||||
hint_refresh_tasks: HashMap<BufferId, HashMap<Vec<Range<BufferRow>>, Vec<Task<()>>>>,
|
||||
hint_chunk_fetched: HashMap<BufferId, (Global, HashSet<Range<BufferRow>>)>,
|
||||
hint_refresh_tasks: HashMap<BufferId, Vec<Task<()>>>,
|
||||
hint_chunk_fetching: HashMap<BufferId, (Global, HashSet<Range<BufferRow>>)>,
|
||||
invalidate_hints_for_buffers: HashSet<BufferId>,
|
||||
pub added_hints: HashMap<InlayId, Option<InlayHintKind>>,
|
||||
}
|
||||
@@ -63,7 +62,7 @@ impl LspInlayHintData {
|
||||
enabled_in_settings: settings.enabled,
|
||||
hint_refresh_tasks: HashMap::default(),
|
||||
added_hints: HashMap::default(),
|
||||
hint_chunk_fetched: HashMap::default(),
|
||||
hint_chunk_fetching: HashMap::default(),
|
||||
invalidate_hints_for_buffers: HashSet::default(),
|
||||
invalidate_debounce: debounce_value(settings.edit_debounce_ms),
|
||||
append_debounce: debounce_value(settings.scroll_debounce_ms),
|
||||
@@ -99,9 +98,8 @@ impl LspInlayHintData {
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.hint_refresh_tasks.clear();
|
||||
self.hint_chunk_fetched.clear();
|
||||
self.hint_chunk_fetching.clear();
|
||||
self.added_hints.clear();
|
||||
self.invalidate_hints_for_buffers.clear();
|
||||
}
|
||||
|
||||
/// Checks inlay hint settings for enabled hint kinds and general enabled state.
|
||||
@@ -199,7 +197,7 @@ impl LspInlayHintData {
|
||||
) {
|
||||
for buffer_id in removed_buffer_ids {
|
||||
self.hint_refresh_tasks.remove(buffer_id);
|
||||
self.hint_chunk_fetched.remove(buffer_id);
|
||||
self.hint_chunk_fetching.remove(buffer_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -211,7 +209,10 @@ pub enum InlayHintRefreshReason {
|
||||
SettingsChange(InlayHintSettings),
|
||||
NewLinesShown,
|
||||
BufferEdited(BufferId),
|
||||
RefreshRequested(LanguageServerId),
|
||||
RefreshRequested {
|
||||
server_id: LanguageServerId,
|
||||
request_id: Option<usize>,
|
||||
},
|
||||
ExcerptsRemoved(Vec<ExcerptId>),
|
||||
}
|
||||
|
||||
@@ -296,7 +297,7 @@ impl Editor {
|
||||
| InlayHintRefreshReason::Toggle(_)
|
||||
| InlayHintRefreshReason::SettingsChange(_) => true,
|
||||
InlayHintRefreshReason::NewLinesShown
|
||||
| InlayHintRefreshReason::RefreshRequested(_)
|
||||
| InlayHintRefreshReason::RefreshRequested { .. }
|
||||
| InlayHintRefreshReason::ExcerptsRemoved(_) => false,
|
||||
InlayHintRefreshReason::BufferEdited(buffer_id) => {
|
||||
let Some(affected_language) = self
|
||||
@@ -370,48 +371,45 @@ impl Editor {
|
||||
let Some(buffer) = multi_buffer.read(cx).buffer(buffer_id) else {
|
||||
continue;
|
||||
};
|
||||
let fetched_tasks = inlay_hints.hint_chunk_fetched.entry(buffer_id).or_default();
|
||||
|
||||
let (fetched_for_version, fetched_chunks) = inlay_hints
|
||||
.hint_chunk_fetching
|
||||
.entry(buffer_id)
|
||||
.or_default();
|
||||
if visible_excerpts
|
||||
.buffer_version
|
||||
.changed_since(&fetched_tasks.0)
|
||||
.changed_since(fetched_for_version)
|
||||
{
|
||||
fetched_tasks.1.clear();
|
||||
fetched_tasks.0 = visible_excerpts.buffer_version.clone();
|
||||
*fetched_for_version = visible_excerpts.buffer_version.clone();
|
||||
fetched_chunks.clear();
|
||||
inlay_hints.hint_refresh_tasks.remove(&buffer_id);
|
||||
}
|
||||
|
||||
let applicable_chunks =
|
||||
semantics_provider.applicable_inlay_chunks(&buffer, &visible_excerpts.ranges, cx);
|
||||
let known_chunks = if ignore_previous_fetches {
|
||||
None
|
||||
} else {
|
||||
Some((fetched_for_version.clone(), fetched_chunks.clone()))
|
||||
};
|
||||
|
||||
match inlay_hints
|
||||
let mut applicable_chunks =
|
||||
semantics_provider.applicable_inlay_chunks(&buffer, &visible_excerpts.ranges, cx);
|
||||
applicable_chunks.retain(|chunk| fetched_chunks.insert(chunk.clone()));
|
||||
if applicable_chunks.is_empty() && !ignore_previous_fetches {
|
||||
continue;
|
||||
}
|
||||
inlay_hints
|
||||
.hint_refresh_tasks
|
||||
.entry(buffer_id)
|
||||
.or_default()
|
||||
.entry(applicable_chunks)
|
||||
{
|
||||
hash_map::Entry::Occupied(mut o) => {
|
||||
if invalidate_cache.should_invalidate() || ignore_previous_fetches {
|
||||
o.get_mut().push(spawn_editor_hints_refresh(
|
||||
buffer_id,
|
||||
invalidate_cache,
|
||||
ignore_previous_fetches,
|
||||
debounce,
|
||||
visible_excerpts,
|
||||
cx,
|
||||
));
|
||||
}
|
||||
}
|
||||
hash_map::Entry::Vacant(v) => {
|
||||
v.insert(Vec::new()).push(spawn_editor_hints_refresh(
|
||||
buffer_id,
|
||||
invalidate_cache,
|
||||
ignore_previous_fetches,
|
||||
debounce,
|
||||
visible_excerpts,
|
||||
cx,
|
||||
));
|
||||
}
|
||||
}
|
||||
.push(spawn_editor_hints_refresh(
|
||||
buffer_id,
|
||||
invalidate_cache,
|
||||
debounce,
|
||||
visible_excerpts,
|
||||
known_chunks,
|
||||
applicable_chunks,
|
||||
cx,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,9 +504,13 @@ impl Editor {
|
||||
}
|
||||
InlayHintRefreshReason::NewLinesShown => InvalidationStrategy::None,
|
||||
InlayHintRefreshReason::BufferEdited(_) => InvalidationStrategy::BufferEdited,
|
||||
InlayHintRefreshReason::RefreshRequested(server_id) => {
|
||||
InvalidationStrategy::RefreshRequested(*server_id)
|
||||
}
|
||||
InlayHintRefreshReason::RefreshRequested {
|
||||
server_id,
|
||||
request_id,
|
||||
} => InvalidationStrategy::RefreshRequested {
|
||||
server_id: *server_id,
|
||||
request_id: *request_id,
|
||||
},
|
||||
};
|
||||
|
||||
match &mut self.inlay_hints {
|
||||
@@ -718,44 +720,29 @@ impl Editor {
|
||||
fn inlay_hints_for_buffer(
|
||||
&mut self,
|
||||
invalidate_cache: InvalidationStrategy,
|
||||
ignore_previous_fetches: bool,
|
||||
buffer_excerpts: VisibleExcerpts,
|
||||
known_chunks: Option<(Global, HashSet<Range<BufferRow>>)>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<Vec<Task<(Range<BufferRow>, anyhow::Result<CacheInlayHints>)>>> {
|
||||
let semantics_provider = self.semantics_provider()?;
|
||||
let inlay_hints = self.inlay_hints.as_mut()?;
|
||||
let buffer_id = buffer_excerpts.buffer.read(cx).remote_id();
|
||||
|
||||
let new_hint_tasks = semantics_provider
|
||||
.inlay_hints(
|
||||
invalidate_cache,
|
||||
buffer_excerpts.buffer,
|
||||
buffer_excerpts.ranges,
|
||||
inlay_hints
|
||||
.hint_chunk_fetched
|
||||
.get(&buffer_id)
|
||||
.filter(|_| !ignore_previous_fetches && !invalidate_cache.should_invalidate())
|
||||
.cloned(),
|
||||
known_chunks,
|
||||
cx,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
let (known_version, known_chunks) =
|
||||
inlay_hints.hint_chunk_fetched.entry(buffer_id).or_default();
|
||||
if buffer_excerpts.buffer_version.changed_since(known_version) {
|
||||
known_chunks.clear();
|
||||
*known_version = buffer_excerpts.buffer_version;
|
||||
}
|
||||
|
||||
let mut hint_tasks = Vec::new();
|
||||
let mut hint_tasks = None;
|
||||
for (row_range, new_hints_task) in new_hint_tasks {
|
||||
let inserted = known_chunks.insert(row_range.clone());
|
||||
if inserted || ignore_previous_fetches || invalidate_cache.should_invalidate() {
|
||||
hint_tasks.push(cx.spawn(async move |_, _| (row_range, new_hints_task.await)));
|
||||
}
|
||||
hint_tasks
|
||||
.get_or_insert_with(Vec::new)
|
||||
.push(cx.spawn(async move |_, _| (row_range, new_hints_task.await)));
|
||||
}
|
||||
|
||||
Some(hint_tasks)
|
||||
hint_tasks
|
||||
}
|
||||
|
||||
fn apply_fetched_hints(
|
||||
@@ -793,20 +780,28 @@ impl Editor {
|
||||
let excerpts = self.buffer.read(cx).excerpt_ids();
|
||||
let hints_to_insert = new_hints
|
||||
.into_iter()
|
||||
.filter_map(|(chunk_range, hints_result)| match hints_result {
|
||||
Ok(new_hints) => Some(new_hints),
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
"Failed to query inlays for buffer row range {chunk_range:?}, {e:#}"
|
||||
);
|
||||
if let Some((for_version, chunks_fetched)) =
|
||||
inlay_hints.hint_chunk_fetched.get_mut(&buffer_id)
|
||||
{
|
||||
if for_version == &query_version {
|
||||
chunks_fetched.remove(&chunk_range);
|
||||
.filter_map(|(chunk_range, hints_result)| {
|
||||
let chunks_fetched = inlay_hints.hint_chunk_fetching.get_mut(&buffer_id);
|
||||
match hints_result {
|
||||
Ok(new_hints) => {
|
||||
if new_hints.is_empty() {
|
||||
if let Some((_, chunks_fetched)) = chunks_fetched {
|
||||
chunks_fetched.remove(&chunk_range);
|
||||
}
|
||||
}
|
||||
Some(new_hints)
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
"Failed to query inlays for buffer row range {chunk_range:?}, {e:#}"
|
||||
);
|
||||
if let Some((for_version, chunks_fetched)) = chunks_fetched {
|
||||
if for_version == &query_version {
|
||||
chunks_fetched.remove(&chunk_range);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
None
|
||||
}
|
||||
})
|
||||
.flat_map(|hints| hints.into_values())
|
||||
@@ -856,9 +851,10 @@ struct VisibleExcerpts {
|
||||
fn spawn_editor_hints_refresh(
|
||||
buffer_id: BufferId,
|
||||
invalidate_cache: InvalidationStrategy,
|
||||
ignore_previous_fetches: bool,
|
||||
debounce: Option<Duration>,
|
||||
buffer_excerpts: VisibleExcerpts,
|
||||
known_chunks: Option<(Global, HashSet<Range<BufferRow>>)>,
|
||||
applicable_chunks: Vec<Range<BufferRow>>,
|
||||
cx: &mut Context<'_, Editor>,
|
||||
) -> Task<()> {
|
||||
cx.spawn(async move |editor, cx| {
|
||||
@@ -869,12 +865,7 @@ fn spawn_editor_hints_refresh(
|
||||
let query_version = buffer_excerpts.buffer_version.clone();
|
||||
let Some(hint_tasks) = editor
|
||||
.update(cx, |editor, cx| {
|
||||
editor.inlay_hints_for_buffer(
|
||||
invalidate_cache,
|
||||
ignore_previous_fetches,
|
||||
buffer_excerpts,
|
||||
cx,
|
||||
)
|
||||
editor.inlay_hints_for_buffer(invalidate_cache, buffer_excerpts, known_chunks, cx)
|
||||
})
|
||||
.ok()
|
||||
else {
|
||||
@@ -882,6 +873,19 @@ fn spawn_editor_hints_refresh(
|
||||
};
|
||||
let hint_tasks = hint_tasks.unwrap_or_default();
|
||||
if hint_tasks.is_empty() {
|
||||
editor
|
||||
.update(cx, |editor, _| {
|
||||
if let Some((_, hint_chunk_fetching)) = editor
|
||||
.inlay_hints
|
||||
.as_mut()
|
||||
.and_then(|inlay_hints| inlay_hints.hint_chunk_fetching.get_mut(&buffer_id))
|
||||
{
|
||||
for applicable_chunks in &applicable_chunks {
|
||||
hint_chunk_fetching.remove(applicable_chunks);
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
return;
|
||||
}
|
||||
let new_hints = join_all(hint_tasks).await;
|
||||
@@ -1102,7 +1106,10 @@ pub mod tests {
|
||||
editor
|
||||
.update(cx, |editor, _window, cx| {
|
||||
editor.refresh_inlay_hints(
|
||||
InlayHintRefreshReason::RefreshRequested(fake_server.server.server_id()),
|
||||
InlayHintRefreshReason::RefreshRequested {
|
||||
server_id: fake_server.server.server_id(),
|
||||
request_id: Some(1),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
})
|
||||
@@ -1958,15 +1965,8 @@ pub mod tests {
|
||||
async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |settings| {
|
||||
settings.defaults.inlay_hints = Some(InlayHintSettingsContent {
|
||||
show_value_hints: Some(true),
|
||||
enabled: Some(true),
|
||||
edit_debounce_ms: Some(0),
|
||||
scroll_debounce_ms: Some(0),
|
||||
show_type_hints: Some(true),
|
||||
show_parameter_hints: Some(true),
|
||||
show_other_hints: Some(true),
|
||||
show_background: Some(false),
|
||||
toggle_on_modifiers_press: None,
|
||||
..InlayHintSettingsContent::default()
|
||||
})
|
||||
});
|
||||
|
||||
@@ -2044,6 +2044,7 @@ pub mod tests {
|
||||
cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
|
||||
cx.executor().run_until_parked();
|
||||
let _fake_server = fake_servers.next().await.unwrap();
|
||||
cx.executor().advance_clock(Duration::from_millis(100));
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let ranges = lsp_request_ranges
|
||||
@@ -2129,6 +2130,7 @@ pub mod tests {
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
cx.executor().advance_clock(Duration::from_millis(100));
|
||||
cx.executor().run_until_parked();
|
||||
editor.update(cx, |_, _, _| {
|
||||
let ranges = lsp_request_ranges
|
||||
@@ -2145,6 +2147,7 @@ pub mod tests {
|
||||
editor.handle_input("++++more text++++", window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
cx.executor().advance_clock(Duration::from_secs(1));
|
||||
cx.executor().run_until_parked();
|
||||
editor.update(cx, |editor, _window, cx| {
|
||||
let mut ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
|
||||
@@ -3887,7 +3890,10 @@ let c = 3;"#
|
||||
editor
|
||||
.update(cx, |editor, _, cx| {
|
||||
editor.refresh_inlay_hints(
|
||||
InlayHintRefreshReason::RefreshRequested(fake_server.server.server_id()),
|
||||
InlayHintRefreshReason::RefreshRequested {
|
||||
server_id: fake_server.server.server_id(),
|
||||
request_id: Some(1),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
})
|
||||
@@ -4022,7 +4028,7 @@ let c = 3;"#
|
||||
let mut all_fetched_hints = Vec::new();
|
||||
for buffer in editor.buffer.read(cx).all_buffers() {
|
||||
lsp_store.update(cx, |lsp_store, cx| {
|
||||
let hints = &lsp_store.latest_lsp_data(&buffer, cx).inlay_hints();
|
||||
let hints = lsp_store.latest_lsp_data(&buffer, cx).inlay_hints();
|
||||
all_cached_labels.extend(hints.all_cached_hints().into_iter().map(|hint| {
|
||||
let mut label = hint.text().to_string();
|
||||
if hint.padding_left {
|
||||
|
||||
@@ -804,23 +804,32 @@ impl LocalLspStore {
|
||||
language_server
|
||||
.on_request::<lsp::request::InlayHintRefreshRequest, _, _>({
|
||||
let lsp_store = lsp_store.clone();
|
||||
let request_id = Arc::new(AtomicUsize::new(0));
|
||||
move |(), cx| {
|
||||
let this = lsp_store.clone();
|
||||
let lsp_store = lsp_store.clone();
|
||||
let request_id = request_id.clone();
|
||||
let mut cx = cx.clone();
|
||||
async move {
|
||||
this.update(&mut cx, |lsp_store, cx| {
|
||||
cx.emit(LspStoreEvent::RefreshInlayHints(server_id));
|
||||
lsp_store
|
||||
.downstream_client
|
||||
.as_ref()
|
||||
.map(|(client, project_id)| {
|
||||
client.send(proto::RefreshInlayHints {
|
||||
project_id: *project_id,
|
||||
server_id: server_id.to_proto(),
|
||||
lsp_store
|
||||
.update(&mut cx, |lsp_store, cx| {
|
||||
let request_id =
|
||||
Some(request_id.fetch_add(1, atomic::Ordering::AcqRel));
|
||||
cx.emit(LspStoreEvent::RefreshInlayHints {
|
||||
server_id,
|
||||
request_id,
|
||||
});
|
||||
lsp_store
|
||||
.downstream_client
|
||||
.as_ref()
|
||||
.map(|(client, project_id)| {
|
||||
client.send(proto::RefreshInlayHints {
|
||||
project_id: *project_id,
|
||||
server_id: server_id.to_proto(),
|
||||
request_id: request_id.map(|id| id as u64),
|
||||
})
|
||||
})
|
||||
})
|
||||
})?
|
||||
.transpose()?;
|
||||
})?
|
||||
.transpose()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -3610,7 +3619,10 @@ pub enum LspStoreEvent {
|
||||
new_language: Option<Arc<Language>>,
|
||||
},
|
||||
Notification(String),
|
||||
RefreshInlayHints(LanguageServerId),
|
||||
RefreshInlayHints {
|
||||
server_id: LanguageServerId,
|
||||
request_id: Option<usize>,
|
||||
},
|
||||
RefreshCodeLens,
|
||||
DiagnosticsUpdated {
|
||||
server_id: LanguageServerId,
|
||||
@@ -6583,14 +6595,22 @@ impl LspStore {
|
||||
cx: &mut Context<Self>,
|
||||
) -> HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>> {
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let for_server = if let InvalidationStrategy::RefreshRequested(server_id) = invalidate {
|
||||
let next_hint_id = self.next_hint_id.clone();
|
||||
let lsp_data = self.latest_lsp_data(&buffer, cx);
|
||||
let mut lsp_refresh_requested = false;
|
||||
let for_server = if let InvalidationStrategy::RefreshRequested {
|
||||
server_id,
|
||||
request_id,
|
||||
} = invalidate
|
||||
{
|
||||
let invalidated = lsp_data
|
||||
.inlay_hints
|
||||
.invalidate_for_server_refresh(server_id, request_id);
|
||||
lsp_refresh_requested = invalidated;
|
||||
Some(server_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let invalidate_cache = invalidate.should_invalidate();
|
||||
let next_hint_id = self.next_hint_id.clone();
|
||||
let lsp_data = self.latest_lsp_data(&buffer, cx);
|
||||
let existing_inlay_hints = &mut lsp_data.inlay_hints;
|
||||
let known_chunks = known_chunks
|
||||
.filter(|(known_version, _)| !lsp_data.buffer_version.changed_since(known_version))
|
||||
@@ -6598,8 +6618,8 @@ impl LspStore {
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut hint_fetch_tasks = Vec::new();
|
||||
let mut cached_inlay_hints = HashMap::default();
|
||||
let mut ranges_to_query = Vec::new();
|
||||
let mut cached_inlay_hints = None;
|
||||
let mut ranges_to_query = None;
|
||||
let applicable_chunks = existing_inlay_hints
|
||||
.applicable_chunks(ranges.as_slice())
|
||||
.filter(|chunk| !known_chunks.contains(&(chunk.start..chunk.end)))
|
||||
@@ -6614,12 +6634,12 @@ impl LspStore {
|
||||
match (
|
||||
existing_inlay_hints
|
||||
.cached_hints(&row_chunk)
|
||||
.filter(|_| !invalidate_cache)
|
||||
.filter(|_| !lsp_refresh_requested)
|
||||
.cloned(),
|
||||
existing_inlay_hints
|
||||
.fetched_hints(&row_chunk)
|
||||
.as_ref()
|
||||
.filter(|_| !invalidate_cache)
|
||||
.filter(|_| !lsp_refresh_requested)
|
||||
.cloned(),
|
||||
) {
|
||||
(None, None) => {
|
||||
@@ -6628,19 +6648,18 @@ impl LspStore {
|
||||
} else {
|
||||
Point::new(row_chunk.end, 0)
|
||||
};
|
||||
ranges_to_query.push((
|
||||
ranges_to_query.get_or_insert_with(Vec::new).push((
|
||||
row_chunk,
|
||||
buffer_snapshot.anchor_before(Point::new(row_chunk.start, 0))
|
||||
..buffer_snapshot.anchor_after(end),
|
||||
));
|
||||
}
|
||||
(None, Some(fetched_hints)) => {
|
||||
hint_fetch_tasks.push((row_chunk, fetched_hints.clone()))
|
||||
}
|
||||
(None, Some(fetched_hints)) => hint_fetch_tasks.push((row_chunk, fetched_hints)),
|
||||
(Some(cached_hints), None) => {
|
||||
for (server_id, cached_hints) in cached_hints {
|
||||
if for_server.is_none_or(|for_server| for_server == server_id) {
|
||||
cached_inlay_hints
|
||||
.get_or_insert_with(HashMap::default)
|
||||
.entry(row_chunk.start..row_chunk.end)
|
||||
.or_insert_with(HashMap::default)
|
||||
.entry(server_id)
|
||||
@@ -6650,10 +6669,11 @@ impl LspStore {
|
||||
}
|
||||
}
|
||||
(Some(cached_hints), Some(fetched_hints)) => {
|
||||
hint_fetch_tasks.push((row_chunk, fetched_hints.clone()));
|
||||
hint_fetch_tasks.push((row_chunk, fetched_hints));
|
||||
for (server_id, cached_hints) in cached_hints {
|
||||
if for_server.is_none_or(|for_server| for_server == server_id) {
|
||||
cached_inlay_hints
|
||||
.get_or_insert_with(HashMap::default)
|
||||
.entry(row_chunk.start..row_chunk.end)
|
||||
.or_insert_with(HashMap::default)
|
||||
.entry(server_id)
|
||||
@@ -6665,18 +6685,18 @@ impl LspStore {
|
||||
}
|
||||
}
|
||||
|
||||
let cached_chunk_data = cached_inlay_hints
|
||||
.into_iter()
|
||||
.map(|(row_chunk, hints)| (row_chunk, Task::ready(Ok(hints))))
|
||||
.collect();
|
||||
if hint_fetch_tasks.is_empty() && ranges_to_query.is_empty() {
|
||||
cached_chunk_data
|
||||
if hint_fetch_tasks.is_empty()
|
||||
&& ranges_to_query
|
||||
.as_ref()
|
||||
.is_none_or(|ranges| ranges.is_empty())
|
||||
&& let Some(cached_inlay_hints) = cached_inlay_hints
|
||||
{
|
||||
cached_inlay_hints
|
||||
.into_iter()
|
||||
.map(|(row_chunk, hints)| (row_chunk, Task::ready(Ok(hints))))
|
||||
.collect()
|
||||
} else {
|
||||
if invalidate_cache {
|
||||
lsp_data.inlay_hints.clear();
|
||||
}
|
||||
|
||||
for (chunk, range_to_query) in ranges_to_query {
|
||||
for (chunk, range_to_query) in ranges_to_query.into_iter().flatten() {
|
||||
let next_hint_id = next_hint_id.clone();
|
||||
let buffer = buffer.clone();
|
||||
let new_inlay_hints = cx
|
||||
@@ -6692,31 +6712,38 @@ impl LspStore {
|
||||
let update_cache = !lsp_data
|
||||
.buffer_version
|
||||
.changed_since(&buffer.read(cx).version());
|
||||
new_hints_by_server
|
||||
.into_iter()
|
||||
.map(|(server_id, new_hints)| {
|
||||
let new_hints = new_hints
|
||||
.into_iter()
|
||||
.map(|new_hint| {
|
||||
(
|
||||
InlayId::Hint(next_hint_id.fetch_add(
|
||||
1,
|
||||
atomic::Ordering::AcqRel,
|
||||
)),
|
||||
new_hint,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if update_cache {
|
||||
lsp_data.inlay_hints.insert_new_hints(
|
||||
chunk,
|
||||
server_id,
|
||||
new_hints.clone(),
|
||||
);
|
||||
}
|
||||
(server_id, new_hints)
|
||||
})
|
||||
.collect()
|
||||
if new_hints_by_server.is_empty() {
|
||||
if update_cache {
|
||||
lsp_data.inlay_hints.invalidate_for_chunk(chunk);
|
||||
}
|
||||
HashMap::default()
|
||||
} else {
|
||||
new_hints_by_server
|
||||
.into_iter()
|
||||
.map(|(server_id, new_hints)| {
|
||||
let new_hints = new_hints
|
||||
.into_iter()
|
||||
.map(|new_hint| {
|
||||
(
|
||||
InlayId::Hint(next_hint_id.fetch_add(
|
||||
1,
|
||||
atomic::Ordering::AcqRel,
|
||||
)),
|
||||
new_hint,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if update_cache {
|
||||
lsp_data.inlay_hints.insert_new_hints(
|
||||
chunk,
|
||||
server_id,
|
||||
new_hints.clone(),
|
||||
);
|
||||
}
|
||||
(server_id, new_hints)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
})
|
||||
})
|
||||
.map_err(Arc::new)
|
||||
@@ -6728,22 +6755,25 @@ impl LspStore {
|
||||
hint_fetch_tasks.push((chunk, new_inlay_hints));
|
||||
}
|
||||
|
||||
let mut combined_data = cached_chunk_data;
|
||||
combined_data.extend(hint_fetch_tasks.into_iter().map(|(chunk, hints_fetch)| {
|
||||
(
|
||||
chunk.start..chunk.end,
|
||||
cx.spawn(async move |_, _| {
|
||||
hints_fetch.await.map_err(|e| {
|
||||
if e.error_code() != ErrorCode::Internal {
|
||||
anyhow!(e.error_code())
|
||||
} else {
|
||||
anyhow!("{e:#}")
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
}));
|
||||
combined_data
|
||||
cached_inlay_hints
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|(row_chunk, hints)| (row_chunk, Task::ready(Ok(hints))))
|
||||
.chain(hint_fetch_tasks.into_iter().map(|(chunk, hints_fetch)| {
|
||||
(
|
||||
chunk.start..chunk.end,
|
||||
cx.spawn(async move |_, _| {
|
||||
hints_fetch.await.map_err(|e| {
|
||||
if e.error_code() != ErrorCode::Internal {
|
||||
anyhow!(e.error_code())
|
||||
} else {
|
||||
anyhow!("{e:#}")
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
}))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9542,7 +9572,10 @@ impl LspStore {
|
||||
if let Some(work) = status.pending_work.remove(&token)
|
||||
&& !work.is_disk_based_diagnostics_progress
|
||||
{
|
||||
cx.emit(LspStoreEvent::RefreshInlayHints(language_server_id));
|
||||
cx.emit(LspStoreEvent::RefreshInlayHints {
|
||||
server_id: language_server_id,
|
||||
request_id: None,
|
||||
});
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
@@ -9679,9 +9712,10 @@ impl LspStore {
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::Ack> {
|
||||
lsp_store.update(&mut cx, |_, cx| {
|
||||
cx.emit(LspStoreEvent::RefreshInlayHints(
|
||||
LanguageServerId::from_proto(envelope.payload.server_id),
|
||||
));
|
||||
cx.emit(LspStoreEvent::RefreshInlayHints {
|
||||
server_id: LanguageServerId::from_proto(envelope.payload.server_id),
|
||||
request_id: envelope.payload.request_id.map(|id| id as usize),
|
||||
});
|
||||
})?;
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
@@ -10900,7 +10934,6 @@ impl LspStore {
|
||||
language_server.name(),
|
||||
Some(key.worktree_id),
|
||||
));
|
||||
cx.emit(LspStoreEvent::RefreshInlayHints(server_id));
|
||||
|
||||
let server_capabilities = language_server.capabilities();
|
||||
if let Some((downstream_client, project_id)) = self.downstream_client.as_ref() {
|
||||
|
||||
@@ -19,7 +19,10 @@ pub enum InvalidationStrategy {
|
||||
/// Demands to re-query all inlay hints needed and invalidate all cached entries, but does not require instant update with invalidation.
|
||||
///
|
||||
/// Despite nothing forbids language server from sending this request on every edit, it is expected to be sent only when certain internal server state update, invisible for the editor otherwise.
|
||||
RefreshRequested(LanguageServerId),
|
||||
RefreshRequested {
|
||||
server_id: LanguageServerId,
|
||||
request_id: Option<usize>,
|
||||
},
|
||||
/// Multibuffer excerpt(s) and/or singleton buffer(s) were edited at least on one place.
|
||||
/// Neither editor nor LSP is able to tell which open file hints' are not affected, so all of them have to be invalidated, re-queried and do that fast enough to avoid being slow, but also debounce to avoid loading hints on every fast keystroke sequence.
|
||||
BufferEdited,
|
||||
@@ -36,7 +39,7 @@ impl InvalidationStrategy {
|
||||
pub fn should_invalidate(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
InvalidationStrategy::RefreshRequested(_) | InvalidationStrategy::BufferEdited
|
||||
InvalidationStrategy::RefreshRequested { .. } | InvalidationStrategy::BufferEdited
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -47,6 +50,7 @@ pub struct BufferInlayHints {
|
||||
hints_by_chunks: Vec<Option<CacheInlayHints>>,
|
||||
fetches_by_chunks: Vec<Option<CacheInlayHintsTask>>,
|
||||
hints_by_id: HashMap<InlayId, HintForId>,
|
||||
latest_invalidation_requests: HashMap<LanguageServerId, Option<usize>>,
|
||||
pub(super) hint_resolves: HashMap<InlayId, Shared<Task<()>>>,
|
||||
}
|
||||
|
||||
@@ -104,6 +108,7 @@ impl BufferInlayHints {
|
||||
Self {
|
||||
hints_by_chunks: vec![None; buffer_chunks.len()],
|
||||
fetches_by_chunks: vec![None; buffer_chunks.len()],
|
||||
latest_invalidation_requests: HashMap::default(),
|
||||
hints_by_id: HashMap::default(),
|
||||
hint_resolves: HashMap::default(),
|
||||
snapshot,
|
||||
@@ -176,6 +181,7 @@ impl BufferInlayHints {
|
||||
self.fetches_by_chunks = vec![None; self.buffer_chunks.len()];
|
||||
self.hints_by_id.clear();
|
||||
self.hint_resolves.clear();
|
||||
self.latest_invalidation_requests.clear();
|
||||
}
|
||||
|
||||
pub fn insert_new_hints(
|
||||
@@ -222,4 +228,48 @@ impl BufferInlayHints {
|
||||
pub fn buffer_chunks_len(&self) -> usize {
|
||||
self.buffer_chunks.len()
|
||||
}
|
||||
|
||||
pub(crate) fn invalidate_for_server_refresh(
|
||||
&mut self,
|
||||
for_server: LanguageServerId,
|
||||
request_id: Option<usize>,
|
||||
) -> bool {
|
||||
match self.latest_invalidation_requests.entry(for_server) {
|
||||
hash_map::Entry::Occupied(mut o) => {
|
||||
if request_id > *o.get() {
|
||||
o.insert(request_id);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
hash_map::Entry::Vacant(v) => {
|
||||
v.insert(request_id);
|
||||
}
|
||||
}
|
||||
|
||||
for (chunk_id, chunk_data) in self.hints_by_chunks.iter_mut().enumerate() {
|
||||
if let Some(removed_hints) = chunk_data
|
||||
.as_mut()
|
||||
.and_then(|chunk_data| chunk_data.remove(&for_server))
|
||||
{
|
||||
for (id, _) in removed_hints {
|
||||
self.hints_by_id.remove(&id);
|
||||
self.hint_resolves.remove(&id);
|
||||
}
|
||||
self.fetches_by_chunks[chunk_id] = None;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn invalidate_for_chunk(&mut self, chunk: BufferChunk) {
|
||||
self.fetches_by_chunks[chunk.id] = None;
|
||||
if let Some(hints_by_server) = self.hints_by_chunks[chunk.id].take() {
|
||||
for (hint_id, _) in hints_by_server.into_values().flatten() {
|
||||
self.hints_by_id.remove(&hint_id);
|
||||
self.hint_resolves.remove(&hint_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,7 +337,10 @@ pub enum Event {
|
||||
HostReshared,
|
||||
Reshared,
|
||||
Rejoined,
|
||||
RefreshInlayHints(LanguageServerId),
|
||||
RefreshInlayHints {
|
||||
server_id: LanguageServerId,
|
||||
request_id: Option<usize>,
|
||||
},
|
||||
RefreshCodeLens,
|
||||
RevealInProjectPanel(ProjectEntryId),
|
||||
SnippetEdit(BufferId, Vec<(lsp::Range, Snippet)>),
|
||||
@@ -3074,9 +3077,13 @@ impl Project {
|
||||
return;
|
||||
};
|
||||
}
|
||||
LspStoreEvent::RefreshInlayHints(server_id) => {
|
||||
cx.emit(Event::RefreshInlayHints(*server_id))
|
||||
}
|
||||
LspStoreEvent::RefreshInlayHints {
|
||||
server_id,
|
||||
request_id,
|
||||
} => cx.emit(Event::RefreshInlayHints {
|
||||
server_id: *server_id,
|
||||
request_id: *request_id,
|
||||
}),
|
||||
LspStoreEvent::RefreshCodeLens => cx.emit(Event::RefreshCodeLens),
|
||||
LspStoreEvent::LanguageServerPrompt(prompt) => {
|
||||
cx.emit(Event::LanguageServerPrompt(prompt.clone()))
|
||||
|
||||
@@ -1815,10 +1815,6 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
|
||||
fake_server
|
||||
.start_progress(format!("{}/0", progress_token))
|
||||
.await;
|
||||
assert_eq!(
|
||||
events.next().await.unwrap(),
|
||||
Event::RefreshInlayHints(fake_server.server.server_id())
|
||||
);
|
||||
assert_eq!(
|
||||
events.next().await.unwrap(),
|
||||
Event::DiskBasedDiagnosticsStarted {
|
||||
@@ -1957,10 +1953,6 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
|
||||
Some(worktree_id)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
events.next().await.unwrap(),
|
||||
Event::RefreshInlayHints(fake_server.server.server_id())
|
||||
);
|
||||
fake_server.start_progress(progress_token).await;
|
||||
assert_eq!(
|
||||
events.next().await.unwrap(),
|
||||
|
||||
@@ -466,6 +466,7 @@ message ResolveInlayHintResponse {
|
||||
message RefreshInlayHints {
|
||||
uint64 project_id = 1;
|
||||
uint64 server_id = 2;
|
||||
optional uint64 request_id = 3;
|
||||
}
|
||||
|
||||
message CodeLens {
|
||||
|
||||
@@ -2338,7 +2338,15 @@ pub fn perform_project_search(
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::{ops::Deref as _, sync::Arc, time::Duration};
|
||||
use std::{
|
||||
ops::Deref as _,
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{self, AtomicUsize},
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use editor::{DisplayPoint, display_map::DisplayRow};
|
||||
@@ -4239,6 +4247,8 @@ pub mod tests {
|
||||
)
|
||||
.await;
|
||||
|
||||
let requests_count = Arc::new(AtomicUsize::new(0));
|
||||
let closure_requests_count = requests_count.clone();
|
||||
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
let language = rust_lang();
|
||||
@@ -4250,21 +4260,26 @@ pub mod tests {
|
||||
inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
..lsp::ServerCapabilities::default()
|
||||
},
|
||||
initializer: Some(Box::new(|fake_server| {
|
||||
fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
|
||||
move |_, _| async move {
|
||||
Ok(Some(vec![lsp::InlayHint {
|
||||
position: lsp::Position::new(0, 17),
|
||||
label: lsp::InlayHintLabel::String(": i32".to_owned()),
|
||||
kind: Some(lsp::InlayHintKind::TYPE),
|
||||
text_edits: None,
|
||||
tooltip: None,
|
||||
padding_left: None,
|
||||
padding_right: None,
|
||||
data: None,
|
||||
}]))
|
||||
},
|
||||
);
|
||||
initializer: Some(Box::new(move |fake_server| {
|
||||
let requests_count = closure_requests_count.clone();
|
||||
fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>({
|
||||
move |_, _| {
|
||||
let requests_count = requests_count.clone();
|
||||
async move {
|
||||
requests_count.fetch_add(1, atomic::Ordering::Release);
|
||||
Ok(Some(vec![lsp::InlayHint {
|
||||
position: lsp::Position::new(0, 17),
|
||||
label: lsp::InlayHintLabel::String(": i32".to_owned()),
|
||||
kind: Some(lsp::InlayHintKind::TYPE),
|
||||
text_edits: None,
|
||||
tooltip: None,
|
||||
padding_left: None,
|
||||
padding_right: None,
|
||||
data: None,
|
||||
}]))
|
||||
}
|
||||
}
|
||||
});
|
||||
})),
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
@@ -4278,7 +4293,7 @@ pub mod tests {
|
||||
});
|
||||
|
||||
perform_search(search_view, "let ", cx);
|
||||
let _fake_server = fake_servers.next().await.unwrap();
|
||||
let fake_server = fake_servers.next().await.unwrap();
|
||||
cx.executor().advance_clock(Duration::from_secs(1));
|
||||
cx.executor().run_until_parked();
|
||||
search_view
|
||||
@@ -4291,11 +4306,127 @@ pub mod tests {
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
requests_count.load(atomic::Ordering::Acquire),
|
||||
1,
|
||||
"New hints should have been queried",
|
||||
);
|
||||
|
||||
// Can do the 2nd search without any panics
|
||||
perform_search(search_view, "let ", cx);
|
||||
cx.executor().advance_clock(Duration::from_secs(1));
|
||||
cx.executor().run_until_parked();
|
||||
search_view
|
||||
.update(cx, |search_view, _, cx| {
|
||||
assert_eq!(
|
||||
search_view
|
||||
.results_editor
|
||||
.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
"\n\nfn main() { let a: i32 = 2; }\n"
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
requests_count.load(atomic::Ordering::Acquire),
|
||||
2,
|
||||
"We did drop the previous buffer when cleared the old project search results, hence another query was made",
|
||||
);
|
||||
|
||||
let singleton_editor = window
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace.open_abs_path(
|
||||
PathBuf::from(path!("/dir/main.rs")),
|
||||
workspace::OpenOptions::default(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
cx.executor().advance_clock(Duration::from_millis(100));
|
||||
cx.executor().run_until_parked();
|
||||
singleton_editor.update(cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.display_text(cx),
|
||||
"fn main() { let a: i32 = 2; }\n",
|
||||
"Newly opened editor should have the correct text with hints",
|
||||
);
|
||||
});
|
||||
assert_eq!(
|
||||
requests_count.load(atomic::Ordering::Acquire),
|
||||
2,
|
||||
"Opening the same buffer again should reuse the cached hints",
|
||||
);
|
||||
|
||||
window
|
||||
.update(cx, |_, window, cx| {
|
||||
singleton_editor.update(cx, |editor, cx| {
|
||||
editor.handle_input("test", window, cx);
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
cx.executor().advance_clock(Duration::from_secs(1));
|
||||
cx.executor().run_until_parked();
|
||||
singleton_editor.update(cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.display_text(cx),
|
||||
"testfn main() { l: i32et a = 2; }\n",
|
||||
"Newly opened editor should have the correct text with hints",
|
||||
);
|
||||
});
|
||||
assert_eq!(
|
||||
requests_count.load(atomic::Ordering::Acquire),
|
||||
3,
|
||||
"We have edited the buffer and should send a new request",
|
||||
);
|
||||
|
||||
window
|
||||
.update(cx, |_, window, cx| {
|
||||
singleton_editor.update(cx, |editor, cx| {
|
||||
editor.undo(&editor::actions::Undo, window, cx);
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
cx.executor().advance_clock(Duration::from_secs(1));
|
||||
cx.executor().run_until_parked();
|
||||
assert_eq!(
|
||||
requests_count.load(atomic::Ordering::Acquire),
|
||||
4,
|
||||
"We have edited the buffer again and should send a new request again",
|
||||
);
|
||||
singleton_editor.update(cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.display_text(cx),
|
||||
"fn main() { let a: i32 = 2; }\n",
|
||||
"Newly opened editor should have the correct text with hints",
|
||||
);
|
||||
});
|
||||
project.update(cx, |_, cx| {
|
||||
cx.emit(project::Event::RefreshInlayHints {
|
||||
server_id: fake_server.server.server_id(),
|
||||
request_id: Some(1),
|
||||
});
|
||||
});
|
||||
cx.executor().advance_clock(Duration::from_secs(1));
|
||||
cx.executor().run_until_parked();
|
||||
assert_eq!(
|
||||
requests_count.load(atomic::Ordering::Acquire),
|
||||
5,
|
||||
"After a simulated server refresh request, we should have sent another request",
|
||||
);
|
||||
|
||||
perform_search(search_view, "let ", cx);
|
||||
cx.executor().advance_clock(Duration::from_secs(1));
|
||||
cx.executor().run_until_parked();
|
||||
assert_eq!(
|
||||
requests_count.load(atomic::Ordering::Acquire),
|
||||
5,
|
||||
"New project search should reuse the cached hints",
|
||||
);
|
||||
search_view
|
||||
.update(cx, |search_view, _, cx| {
|
||||
assert_eq!(
|
||||
|
||||
Reference in New Issue
Block a user