Still complete parentheses & method call arguments if there are existing parentheses, but they are after a newline

This commit is contained in:
Chayim Refael Friedman 2025-05-08 11:05:39 +03:00
parent 45f2d3c67a
commit db9c18e44e
2 changed files with 76 additions and 3 deletions

View file

@ -883,9 +883,10 @@ fn classify_name_ref(
},
ast::MethodCallExpr(method) => {
let receiver = find_opt_node_in_file(original_file, method.receiver());
let has_parens = has_parens(&method);
let kind = NameRefKind::DotAccess(DotAccess {
receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
kind: DotAccessKind::Method { has_parens: method.arg_list().is_some_and(|it| it.l_paren_token().is_some()) },
kind: DotAccessKind::Method { has_parens },
receiver,
ctx: DotAccessExprCtx { in_block_expr: is_in_block(method.syntax()), in_breakable: is_in_breakable(method.syntax()) }
});
@ -1372,7 +1373,7 @@ fn classify_name_ref(
}
}
path_ctx.has_call_parens = it.syntax().parent().is_some_and(|it| ast::CallExpr::can_cast(it.kind()));
path_ctx.has_call_parens = it.syntax().parent().is_some_and(|it| ast::CallExpr::cast(it).is_some_and(|it| has_parens(&it)));
make_path_kind_expr(it.into())
},
@ -1401,7 +1402,7 @@ fn classify_name_ref(
match parent {
ast::PathType(it) => make_path_kind_type(it.into()),
ast::PathExpr(it) => {
path_ctx.has_call_parens = it.syntax().parent().is_some_and(|it| ast::CallExpr::can_cast(it.kind()));
path_ctx.has_call_parens = it.syntax().parent().is_some_and(|it| ast::CallExpr::cast(it).is_some_and(|it| has_parens(&it)));
make_path_kind_expr(it.into())
},
@ -1559,6 +1560,30 @@ fn classify_name_ref(
Some((NameRefContext { nameref, kind: NameRefKind::Path(path_ctx) }, qualifier_ctx))
}
/// When writing in the middle of some code the following situation commonly occurs (`|` denotes the cursor):
/// ```ignore
/// value.method|
/// (1, 2, 3)
/// ```
/// Here, we want to complete the method parentheses & arguments (if the corresponding settings are on),
/// but the thing is parsed as a method call with parentheses. Therefore we use heuristics: if the parentheses
/// are on the next line, consider them non-existent.
fn has_parens(node: &dyn HasArgList) -> bool {
let Some(arg_list) = node.arg_list() else { return false };
if arg_list.l_paren_token().is_none() {
return false;
}
let prev_siblings = iter::successors(arg_list.syntax().prev_sibling_or_token(), |it| {
it.prev_sibling_or_token()
});
prev_siblings
.take_while(|syntax| syntax.kind().is_trivia())
.filter_map(|syntax| {
syntax.into_token().filter(|token| token.kind() == SyntaxKind::WHITESPACE)
})
.all(|whitespace| !whitespace.text().contains('\n'))
}
fn pattern_context_for(
sema: &Semantics<'_, RootDatabase>,
original_file: &SyntaxNode,

View file

@ -2126,3 +2126,51 @@ fn main() {
"#]],
);
}
#[test]
fn call_parens_with_newline() {
check_edit(
"foo",
r#"
fn foo(v: i32) {}
fn bar() {
foo$0
()
}
"#,
r#"
fn foo(v: i32) {}
fn bar() {
foo(${1:v});$0
()
}
"#,
);
check_edit(
"foo",
r#"
struct Foo;
impl Foo {
fn foo(&self, v: i32) {}
}
fn bar() {
Foo.foo$0
()
}
"#,
r#"
struct Foo;
impl Foo {
fn foo(&self, v: i32) {}
}
fn bar() {
Foo.foo(${1:v});$0
()
}
"#,
);
}