From 9e4555797daa028602fb902726c9a46983985fc9 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 30 Jan 2025 17:05:34 +0200 Subject: [PATCH] Use more LSP data when falling back to regular completions label (#23909) Closes https://github.com/zed-industries/zed/issues/23590 Closes https://x.com/steeve/status/1865129235536568555 Before: before After: after The list obviously needs some solution for the cut-off part of the completion label, but this is the reality for all extensions' completions too, so one step at a time. Release Notes: - Improved default completion label fallback --- crates/editor/src/editor_tests.rs | 6 ++-- crates/language/src/language.rs | 52 +++++++++++++++++++++++++++++++ crates/project/src/lsp_store.rs | 16 +++++----- 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index d35866afed..f7374fba20 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -11282,7 +11282,7 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches( cx.simulate_keystroke("."); let item1 = lsp::CompletionItem { - label: "id".to_string(), + label: "method id()".to_string(), filter_text: Some("id".to_string()), detail: None, documentation: None, @@ -11332,7 +11332,7 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches( .iter() .map(|completion| &completion.label.text) .collect::>(), - vec!["id", "other"] + vec!["method id()", "other"] ) } CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"), @@ -11387,7 +11387,7 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches( .iter() .map(|completion| &completion.label.text) .collect::>(), - vec!["method id()", "other"], + vec!["method id() Now resolved!", "other"], "Should update first completion label, but not second as the filter text did not match." ); } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index db533dcb78..a936725b2b 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -1750,6 +1750,58 @@ impl Grammar { } impl CodeLabel { + pub fn fallback_for_completion( + item: &lsp::CompletionItem, + language: Option<&Language>, + ) -> Self { + let highlight_id = item.kind.and_then(|kind| { + let grammar = language?.grammar()?; + use lsp::CompletionItemKind as Kind; + match kind { + Kind::CLASS => grammar.highlight_id_for_name("type"), + Kind::CONSTANT => grammar.highlight_id_for_name("constant"), + Kind::CONSTRUCTOR => grammar.highlight_id_for_name("constructor"), + Kind::ENUM => grammar + .highlight_id_for_name("enum") + .or_else(|| grammar.highlight_id_for_name("type")), + Kind::FIELD => grammar.highlight_id_for_name("property"), + Kind::FUNCTION => grammar.highlight_id_for_name("function"), + Kind::INTERFACE => grammar.highlight_id_for_name("type"), + Kind::METHOD => grammar + .highlight_id_for_name("function.method") + .or_else(|| grammar.highlight_id_for_name("function")), + Kind::OPERATOR => grammar.highlight_id_for_name("operator"), + Kind::PROPERTY => grammar.highlight_id_for_name("property"), + Kind::STRUCT => grammar.highlight_id_for_name("type"), + Kind::VARIABLE => grammar.highlight_id_for_name("variable"), + Kind::KEYWORD => grammar.highlight_id_for_name("keyword"), + _ => None, + } + }); + + let label = &item.label; + let label_length = label.len(); + let runs = highlight_id + .map(|highlight_id| vec![(0..label_length, highlight_id)]) + .unwrap_or_default(); + let text = if let Some(detail) = &item.detail { + format!("{label} {detail}") + } else if let Some(description) = item + .label_details + .as_ref() + .and_then(|label_details| label_details.description.as_ref()) + { + format!("{label} {description}") + } else { + label.clone() + }; + Self { + text, + runs, + filter_range: 0..label_length, + } + } + pub fn plain(text: String, filter_text: Option<&str>) -> Self { let mut result = Self { runs: Vec::new(), diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index e72f1e279c..4d2a00253d 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -4380,7 +4380,8 @@ 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 mut new_label = match snapshot.language() { + let language = snapshot.language(); + let mut new_label = match language { Some(language) => { adapter .labels_for_completions(&[completion_item.clone()], language) @@ -4391,9 +4392,9 @@ impl LspStore { .pop() .flatten() .unwrap_or_else(|| { - CodeLabel::plain( - completion_item.label, - completion_item.filter_text.as_deref(), + CodeLabel::fallback_for_completion( + &completion_item, + language.map(|language| language.as_ref()), ) }); ensure_uniform_list_compatible_label(&mut new_label); @@ -8079,10 +8080,7 @@ async fn populate_labels_for_completions( }; let mut label = label.unwrap_or_else(|| { - CodeLabel::plain( - lsp_completion.label.clone(), - lsp_completion.filter_text.as_deref(), - ) + CodeLabel::fallback_for_completion(&lsp_completion, language.as_deref()) }); ensure_uniform_list_compatible_label(&mut label); @@ -8883,7 +8881,7 @@ fn include_text(server: &lsp::LanguageServer) -> Option { /// Completion items are displayed in a `UniformList`. /// Usually, those items are single-line strings, but in LSP responses, /// completion items `label`, `detail` and `label_details.description` may contain newlines or long spaces. -/// Many language plugins construct these items by joining these parts together, and we may fall back to `CodeLabel::plain` that uses `label`. +/// Many language plugins construct these items by joining these parts together, and we may use `CodeLabel::fallback_for_completion` that uses `label` at least. /// All that may lead to a newline being inserted into resulting `CodeLabel.text`, which will force `UniformList` to bloat each entry to occupy more space, /// breaking the completions menu presentation. ///