Refine word completions (#26779)

Follow-up of https://github.com/zed-industries/zed/pull/26410

* Extract word completions into their own, `editor::ShowWordCompletions`
action so those could be triggered independently of completions
* Assign `ctrl-shift-space` binding to this new action
* Still keep words returned along the completions as in the original PR,
but:
* Tone down regular completions' fallback logic, skip words when the
language server responds with empty list of completions, but keep on
adding words if nothing or an error were returned instead
    * Adjust the defaults to wait for LSP completions infinitely
* Skip "words" with digits such as `0_usize` or `2.f32` from completion
items, unless a completion query has digits in it

Release Notes:

- N/A
This commit is contained in:
Kirill Bulatov 2025-03-14 17:18:55 +02:00 committed by GitHub
parent 21057e3af7
commit 566c5f91a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 431 additions and 251 deletions

View file

@ -4402,7 +4402,7 @@ impl LspStore {
position: PointUtf16,
context: CompletionContext,
cx: &mut Context<Self>,
) -> Task<Result<Vec<Completion>>> {
) -> Task<Result<Option<Vec<Completion>>>> {
let language_registry = self.languages.clone();
if let Some((upstream_client, project_id)) = self.upstream_client() {
@ -4430,7 +4430,7 @@ impl LspStore {
let mut result = Vec::new();
populate_labels_for_completions(completions, language, lsp_adapter, &mut result)
.await;
Ok(result)
Ok(Some(result))
})
} else if let Some(local) = self.as_local() {
let snapshot = buffer.read(cx).snapshot();
@ -4444,7 +4444,7 @@ impl LspStore {
)
.completions;
if !completion_settings.lsp {
return Task::ready(Ok(Vec::new()));
return Task::ready(Ok(None));
}
let server_ids: Vec<_> = buffer.update(cx, |buffer, cx| {
@ -4495,13 +4495,14 @@ impl LspStore {
).fuse();
let new_task = cx.background_spawn(async move {
select_biased! {
response = lsp_request => response,
response = lsp_request => anyhow::Ok(Some(response?)),
timeout_happened = timeout => {
if timeout_happened {
log::warn!("Fetching completions from server {server_id} timed out, timeout ms: {}", completion_settings.lsp_fetch_timeout_ms);
return anyhow::Ok(Vec::new())
Ok(None)
} else {
lsp_request.await
let completions = lsp_request.await?;
Ok(Some(completions))
}
},
}
@ -4510,9 +4511,11 @@ impl LspStore {
}
})?;
let mut has_completions_returned = false;
let mut completions = Vec::new();
for (lsp_adapter, task) in tasks {
if let Ok(new_completions) = task.await {
if let Ok(Some(new_completions)) = task.await {
has_completions_returned = true;
populate_labels_for_completions(
new_completions,
language.clone(),
@ -4522,8 +4525,11 @@ impl LspStore {
.await;
}
}
Ok(completions)
if has_completions_returned {
Ok(Some(completions))
} else {
Ok(None)
}
})
} else {
Task::ready(Err(anyhow!("No upstream client or local language server")))

View file

@ -3142,7 +3142,7 @@ impl Project {
position: T,
context: CompletionContext,
cx: &mut Context<Self>,
) -> Task<Result<Vec<Completion>>> {
) -> Task<Result<Option<Vec<Completion>>>> {
let position = position.to_point_utf16(buffer.read(cx));
self.lsp_store.update(cx, |lsp_store, cx| {
lsp_store.completions(buffer, position, context, cx)

View file

@ -2825,7 +2825,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
})
.next()
.await;
let completions = completions.await.unwrap();
let completions = completions.await.unwrap().unwrap();
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
assert_eq!(completions.len(), 1);
assert_eq!(completions[0].new_text, "fullyQualifiedName");
@ -2851,7 +2851,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
})
.next()
.await;
let completions = completions.await.unwrap();
let completions = completions.await.unwrap().unwrap();
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
assert_eq!(completions.len(), 1);
assert_eq!(completions[0].new_text, "component");
@ -2919,7 +2919,7 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
})
.next()
.await;
let completions = completions.await.unwrap();
let completions = completions.await.unwrap().unwrap();
assert_eq!(completions.len(), 1);
assert_eq!(completions[0].new_text, "fully\nQualified\nName");
}