editor: Fix function completion expansion in string contexts and call expressions (#30351)

Closes #27582

Now, when accepting function completion, it doesn't expand with
parentheses and arguments in the following cases:
1. If it's in a string (like `type Foo = MyClass["sayHello"]` instead of
`type Foo = MyClass["sayHello(name)"]`)
2. If it's in a call expression (like `useRef<HTMLDivElement>(null)`
over `useRef(initialValue)<HTMLDivElement>(null)`)

This is a follow-up to https://github.com/zed-industries/zed/pull/30312,
more like cleaner version of it.

Release Notes:

- Fixed an issue where accepting a method as an object string in
JavaScript would incorrectly expand. E.g. `MyClass["sayHello(name)"]`
instead of `MyClass["sayHello"]`.
This commit is contained in:
Smit Barmase 2025-05-09 02:22:52 -07:00 committed by GitHub
parent 851ab13f94
commit b88ba44b32
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 39 additions and 21 deletions

View file

@ -5051,27 +5051,19 @@ impl Editor {
let snippet; let snippet;
let new_text; let new_text;
if completion.is_snippet() { if completion.is_snippet() {
// lsp returns function definition with placeholders in "new_text" let mut snippet_source = completion.new_text.clone();
// when configured from language server, even when renaming a function if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
// if scope.prefers_label_for_snippet_in_completion() {
// in such cases, we use the label instead if let Some(label) = completion.label() {
// https://github.com/zed-industries/zed/issues/29982 if matches!(
let snippet_source = completion completion.kind(),
.label() Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
.filter(|label| { ) {
completion.kind() == Some(CompletionItemKind::FUNCTION) snippet_source = label;
&& label != &completion.new_text }
}) }
.and_then(|label| { }
let cursor_offset = newest_anchor.head().to_offset(&snapshot); }
let next_char_is_not_whitespace = snapshot
.chars_at(cursor_offset)
.next()
.map_or(true, |ch| !ch.is_whitespace());
next_char_is_not_whitespace.then_some(label)
})
.unwrap_or(completion.new_text.clone());
snippet = Some(Snippet::parse(&snippet_source).log_err()?); snippet = Some(Snippet::parse(&snippet_source).log_err()?);
new_text = snippet.as_ref().unwrap().text.clone(); new_text = snippet.as_ref().unwrap().text.clone();
} else { } else {

View file

@ -828,6 +828,8 @@ pub struct LanguageConfigOverride {
pub completion_query_characters: Override<HashSet<char>>, pub completion_query_characters: Override<HashSet<char>>,
#[serde(default)] #[serde(default)]
pub opt_into_language_servers: Vec<LanguageServerName>, pub opt_into_language_servers: Vec<LanguageServerName>,
#[serde(default)]
pub prefer_label_for_snippet: Option<bool>,
} }
#[derive(Clone, Deserialize, Debug, Serialize, JsonSchema)] #[derive(Clone, Deserialize, Debug, Serialize, JsonSchema)]
@ -1788,6 +1790,18 @@ impl LanguageScope {
) )
} }
/// Returns whether to prefer snippet `label` over `new_text` to replace text when
/// completion is accepted.
///
/// In cases like when cursor is in string or renaming existing function,
/// you don't want to expand function signature instead just want function name
/// to replace existing one.
pub fn prefers_label_for_snippet_in_completion(&self) -> bool {
self.config_override()
.and_then(|o| o.prefer_label_for_snippet)
.unwrap_or(false)
}
/// Returns a list of bracket pairs for a given language with an additional /// Returns a list of bracket pairs for a given language with an additional
/// piece of information about whether the particular bracket pair is currently active for a given language. /// piece of information about whether the particular bracket pair is currently active for a given language.
pub fn brackets(&self) -> impl Iterator<Item = (&BracketPair, bool)> { pub fn brackets(&self) -> impl Iterator<Item = (&BracketPair, bool)> {

View file

@ -34,3 +34,7 @@ opt_into_language_servers = ["emmet-language-server"]
[overrides.string] [overrides.string]
completion_query_characters = ["-", "."] completion_query_characters = ["-", "."]
opt_into_language_servers = ["tailwindcss-language-server"] opt_into_language_servers = ["tailwindcss-language-server"]
prefer_label_for_snippet = true
[overrides.call_expression]
prefer_label_for_snippet = true

View file

@ -13,3 +13,5 @@
(jsx_self_closing_element) (jsx_self_closing_element)
(jsx_expression) (jsx_expression)
] @default ] @default
(_ value: (call_expression) @call_expression)

View file

@ -21,3 +21,7 @@ debuggers = ["JavaScript"]
[overrides.string] [overrides.string]
completion_query_characters = ["."] completion_query_characters = ["."]
prefer_label_for_snippet = true
[overrides.call_expression]
prefer_label_for_snippet = true

View file

@ -1,2 +1,4 @@
(comment) @comment.inclusive (comment) @comment.inclusive
(string) @string (string) @string
(_ value: (call_expression) @call_expression)