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

@ -12,7 +12,7 @@ use language::{
Anchor, Buffer, BufferSnapshot, CodeLabel, LanguageRegistry, ToOffset,
language_settings::SoftWrap,
};
use project::{Completion, CompletionSource, search::SearchQuery};
use project::{Completion, CompletionResponse, CompletionSource, search::SearchQuery};
use settings::Settings;
use std::{
cell::RefCell,
@ -64,9 +64,9 @@ impl CompletionProvider for MessageEditorCompletionProvider {
_: editor::CompletionContext,
_window: &mut Window,
cx: &mut Context<Editor>,
) -> Task<Result<Option<Vec<Completion>>>> {
) -> Task<Result<Vec<CompletionResponse>>> {
let Some(handle) = self.0.upgrade() else {
return Task::ready(Ok(None));
return Task::ready(Ok(Vec::new()));
};
handle.update(cx, |message_editor, cx| {
message_editor.completions(buffer, buffer_position, cx)
@ -248,22 +248,21 @@ impl MessageEditor {
buffer: &Entity<Buffer>,
end_anchor: Anchor,
cx: &mut Context<Self>,
) -> Task<Result<Option<Vec<Completion>>>> {
) -> Task<Result<Vec<CompletionResponse>>> {
if let Some((start_anchor, query, candidates)) =
self.collect_mention_candidates(buffer, end_anchor, cx)
{
if !candidates.is_empty() {
return cx.spawn(async move |_, cx| {
Ok(Some(
Self::resolve_completions_for_candidates(
&cx,
query.as_str(),
&candidates,
start_anchor..end_anchor,
Self::completion_for_mention,
)
.await,
))
let completion_response = Self::resolve_completions_for_candidates(
&cx,
query.as_str(),
&candidates,
start_anchor..end_anchor,
Self::completion_for_mention,
)
.await;
Ok(vec![completion_response])
});
}
}
@ -273,21 +272,23 @@ impl MessageEditor {
{
if !candidates.is_empty() {
return cx.spawn(async move |_, cx| {
Ok(Some(
Self::resolve_completions_for_candidates(
&cx,
query.as_str(),
candidates,
start_anchor..end_anchor,
Self::completion_for_emoji,
)
.await,
))
let completion_response = Self::resolve_completions_for_candidates(
&cx,
query.as_str(),
candidates,
start_anchor..end_anchor,
Self::completion_for_emoji,
)
.await;
Ok(vec![completion_response])
});
}
}
Task::ready(Ok(Some(Vec::new())))
Task::ready(Ok(vec![CompletionResponse {
completions: Vec::new(),
is_incomplete: false,
}]))
}
async fn resolve_completions_for_candidates(
@ -296,18 +297,19 @@ impl MessageEditor {
candidates: &[StringMatchCandidate],
range: Range<Anchor>,
completion_fn: impl Fn(&StringMatch) -> (String, CodeLabel),
) -> Vec<Completion> {
) -> CompletionResponse {
const LIMIT: usize = 10;
let matches = fuzzy::match_strings(
candidates,
query,
true,
10,
LIMIT,
&Default::default(),
cx.background_executor().clone(),
)
.await;
matches
let completions = matches
.into_iter()
.map(|mat| {
let (new_text, label) = completion_fn(&mat);
@ -322,7 +324,12 @@ impl MessageEditor {
source: CompletionSource::Custom,
}
})
.collect()
.collect::<Vec<_>>();
CompletionResponse {
is_incomplete: completions.len() >= LIMIT,
completions,
}
}
fn completion_for_mention(mat: &StringMatch) -> (String, CodeLabel) {