Avoid re-querying language server completions when possible (#31872)

Also adds reuse of the markdown documentation cache even when
completions are re-queried, so that markdown documentation doesn't
flicker when `is_incomplete: true` (completions provided by rust
analyzer always set this)

Release Notes:

- Added support for filtering language server completions instead of
re-querying.
This commit is contained in:
Michael Sloan 2025-06-02 16:19:09 -06:00 committed by GitHub
parent b7ec437b13
commit 17cf865d1e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 1221 additions and 720 deletions

View file

@ -3,8 +3,8 @@ pub mod lsp_ext_command;
pub mod rust_analyzer_ext;
use crate::{
CodeAction, Completion, CompletionSource, CoreCompletion, Hover, InlayHint, LspAction,
ProjectItem, ProjectPath, ProjectTransaction, ResolveState, Symbol, ToolchainStore,
CodeAction, Completion, CompletionResponse, CompletionSource, CoreCompletion, Hover, InlayHint,
LspAction, ProjectItem, ProjectPath, ProjectTransaction, ResolveState, Symbol, ToolchainStore,
buffer_store::{BufferStore, BufferStoreEvent},
environment::ProjectEnvironment,
lsp_command::{self, *},
@ -998,7 +998,7 @@ impl LocalLspStore {
.collect::<Vec<_>>();
async move {
futures::future::join_all(shutdown_futures).await;
join_all(shutdown_futures).await;
}
}
@ -5081,7 +5081,7 @@ impl LspStore {
position: PointUtf16,
context: CompletionContext,
cx: &mut Context<Self>,
) -> Task<Result<Option<Vec<Completion>>>> {
) -> Task<Result<Vec<CompletionResponse>>> {
let language_registry = self.languages.clone();
if let Some((upstream_client, project_id)) = self.upstream_client() {
@ -5105,11 +5105,17 @@ impl LspStore {
});
cx.foreground_executor().spawn(async move {
let completions = task.await?;
let mut result = Vec::new();
populate_labels_for_completions(completions, language, lsp_adapter, &mut result)
.await;
Ok(Some(result))
let completion_response = task.await?;
let completions = populate_labels_for_completions(
completion_response.completions,
language,
lsp_adapter,
)
.await;
Ok(vec![CompletionResponse {
completions,
is_incomplete: completion_response.is_incomplete,
}])
})
} else if let Some(local) = self.as_local() {
let snapshot = buffer.read(cx).snapshot();
@ -5123,7 +5129,7 @@ impl LspStore {
)
.completions;
if !completion_settings.lsp {
return Task::ready(Ok(None));
return Task::ready(Ok(Vec::new()));
}
let server_ids: Vec<_> = buffer.update(cx, |buffer, cx| {
@ -5190,25 +5196,23 @@ impl LspStore {
}
})?;
let mut has_completions_returned = false;
let mut completions = Vec::new();
for (lsp_adapter, task) in tasks {
if let Ok(Some(new_completions)) = task.await {
has_completions_returned = true;
populate_labels_for_completions(
new_completions,
let futures = tasks.into_iter().map(async |(lsp_adapter, task)| {
let completion_response = task.await.ok()??;
let completions = populate_labels_for_completions(
completion_response.completions,
language.clone(),
lsp_adapter,
&mut completions,
)
.await;
}
}
if has_completions_returned {
Ok(Some(completions))
} else {
Ok(None)
}
Some(CompletionResponse {
completions,
is_incomplete: completion_response.is_incomplete,
})
});
let responses: Vec<Option<CompletionResponse>> = join_all(futures).await;
Ok(responses.into_iter().flatten().collect())
})
} else {
Task::ready(Err(anyhow!("No upstream client or local language server")))
@ -9547,8 +9551,7 @@ async fn populate_labels_for_completions(
new_completions: Vec<CoreCompletion>,
language: Option<Arc<Language>>,
lsp_adapter: Option<Arc<CachedLspAdapter>>,
completions: &mut Vec<Completion>,
) {
) -> Vec<Completion> {
let lsp_completions = new_completions
.iter()
.filter_map(|new_completion| {
@ -9572,6 +9575,7 @@ async fn populate_labels_for_completions(
.into_iter()
.fuse();
let mut completions = Vec::new();
for completion in new_completions {
match completion.source.lsp_completion(true) {
Some(lsp_completion) => {
@ -9612,6 +9616,7 @@ async fn populate_labels_for_completions(
}
}
}
completions
}
#[derive(Debug)]