diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b11e15b567450d66c16b7562e01627823cdc4e62..8af10cd0c90a52eb35f2ac0588687889ba6e3751 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1181,9 +1181,9 @@ impl CompletionsMenu { let delay = Duration::from_millis(delay_ms); completion_resolve.lock().fire_new(delay, cx, |_, cx| { - cx.spawn(move |this, mut cx| async move { + cx.spawn(move |editor, mut cx| async move { if let Some(true) = resolve_task.await.log_err() { - this.update(&mut cx, |_, cx| cx.notify()).ok(); + editor.update(&mut cx, |_, cx| cx.notify()).ok(); } }) }); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 0c15719ab599dbaef39c3187511ff64534d3ddd4..136003dcc326d5cdd797e95bd008dfa37655bd57 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -10625,6 +10625,97 @@ async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) { cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"}); } +#[gpui::test] +async fn test_completions_resolve_updates_labels(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![".".to_string()]), + resolve_provider: Some(true), + ..Default::default() + }), + ..Default::default() + }, + cx, + ) + .await; + + cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"}); + cx.simulate_keystroke("."); + + let completion_item = lsp::CompletionItem { + label: "unresolved".to_string(), + detail: None, + documentation: None, + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)), + new_text: ".unresolved".to_string(), + })), + ..lsp::CompletionItem::default() + }; + + cx.handle_request::(move |_, _, _| { + let item = completion_item.clone(); + async move { Ok(Some(lsp::CompletionResponse::Array(vec![item]))) } + }) + .next() + .await; + + cx.condition(|editor, _| editor.context_menu_visible()) + .await; + cx.update_editor(|editor, _| { + let context_menu = editor.context_menu.read(); + let context_menu = context_menu + .as_ref() + .expect("Should have the context menu deployed"); + match context_menu { + ContextMenu::Completions(completions_menu) => { + let completions = completions_menu.completions.read(); + assert_eq!(completions.len(), 1, "Should have one completion"); + assert_eq!(completions.get(0).unwrap().label.text, "unresolved"); + } + ContextMenu::CodeActions(_) => panic!("Should show the completions menu"), + } + }); + + cx.handle_request::(move |_, _, _| async move { + Ok(lsp::CompletionItem { + label: "resolved".to_string(), + detail: Some("Now resolved!".to_string()), + documentation: Some(lsp::Documentation::String("Docs".to_string())), + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)), + new_text: ".resolved".to_string(), + })), + ..lsp::CompletionItem::default() + }) + }) + .next() + .await; + cx.run_until_parked(); + + cx.update_editor(|editor, _| { + let context_menu = editor.context_menu.read(); + let context_menu = context_menu + .as_ref() + .expect("Should have the context menu deployed"); + match context_menu { + ContextMenu::Completions(completions_menu) => { + let completions = completions_menu.completions.read(); + assert_eq!(completions.len(), 1, "Should have one completion"); + assert_eq!( + completions.get(0).unwrap().label.text, + "resolved", + "Should update the completion label after resolving" + ); + } + ContextMenu::CodeActions(_) => panic!("Should show the completions menu"), + } + }); +} + #[gpui::test] async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 41a3ccc0a30490dba5e2c8017d049baf58c37690..ff2a3d47e75836f00044e30e6c6a28343b7aac12 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -2241,17 +2241,23 @@ impl LspStore { (server_id, completion) }; - let server = this - .read_with(&cx, |this, _| this.language_server_for_id(server_id)) + let server_and_adapter = this + .read_with(&cx, |lsp_store, _| { + let server = lsp_store.language_server_for_id(server_id)?; + let adapter = + lsp_store.language_server_adapter_for_id(server.server_id())?; + Some((server, adapter)) + }) .ok() .flatten(); - let Some(server) = server else { + let Some((server, adapter)) = server_and_adapter else { continue; }; did_resolve = true; Self::resolve_completion_local( server, + adapter, &buffer_snapshot, completions.clone(), completion_index, @@ -2268,6 +2274,7 @@ impl LspStore { async fn resolve_completion_local( server: Arc, + adapter: Arc, snapshot: &BufferSnapshot, completions: Arc>>, completion_index: usize, @@ -2293,7 +2300,7 @@ impl LspStore { let documentation = language::prepare_completion_documentation( lsp_documentation, &language_registry, - None, // TODO: Try to reasonably work out which language the completion is for + snapshot.language().cloned(), ) .await; @@ -2332,9 +2339,29 @@ impl LspStore { } } + // NB: Zed does not have `details` inside the completion resolve capabilities, but certain language servers violate the spec and do not return `details` immediately, e.g. https://github.com/yioneko/vtsls/issues/213 + // So we have to update the label here anyway... + let new_label = match snapshot.language() { + Some(language) => adapter + .labels_for_completions(&[completion_item.clone()], language) + .await + .log_err() + .unwrap_or_default(), + None => Vec::new(), + } + .pop() + .flatten() + .unwrap_or_else(|| { + CodeLabel::plain( + completion_item.label.clone(), + completion_item.filter_text.as_deref(), + ) + }); + let mut completions = completions.write(); let completion = &mut completions[completion_index]; completion.lsp_completion = completion_item; + completion.label = new_label; } #[allow(clippy::too_many_arguments)]