Avoid signature help inside multiline expressions

Fixes #11768
This commit is contained in:
Jonas Schievink 2022-03-22 16:05:24 +01:00
parent c9cefb9916
commit 4bb5df0ce5
2 changed files with 88 additions and 11 deletions

View file

@ -4,11 +4,15 @@
use either::Either; use either::Either;
use hir::{HasAttrs, HirDisplay, Semantics}; use hir::{HasAttrs, HirDisplay, Semantics};
use ide_db::{ use ide_db::{
active_parameter::{callable_for_token, generics_for_token}, active_parameter::{callable_for_node, generics_for_token},
base_db::FilePosition, base_db::FilePosition,
}; };
use stdx::format_to; use stdx::format_to;
use syntax::{algo, AstNode, Direction, TextRange, TextSize}; use syntax::{
algo,
ast::{self, HasArgList},
AstNode, Direction, SyntaxToken, TextRange, TextSize,
};
use crate::RootDatabase; use crate::RootDatabase;
@ -65,8 +69,8 @@ pub(crate) fn signature_help(db: &RootDatabase, position: FilePosition) -> Optio
.and_then(|tok| algo::skip_trivia_token(tok, Direction::Prev))?; .and_then(|tok| algo::skip_trivia_token(tok, Direction::Prev))?;
let token = sema.descend_into_macros_single(token); let token = sema.descend_into_macros_single(token);
if let Some((callable, active_parameter)) = callable_for_token(&sema, token.clone()) { if let Some(help) = signature_help_for_call(&sema, &token) {
return Some(signature_help_for_callable(db, callable, active_parameter)); return Some(help);
} }
if let Some((generic_def, active_parameter)) = generics_for_token(&sema, token.clone()) { if let Some((generic_def, active_parameter)) = generics_for_token(&sema, token.clone()) {
@ -76,14 +80,39 @@ pub(crate) fn signature_help(db: &RootDatabase, position: FilePosition) -> Optio
None None
} }
fn signature_help_for_callable( fn signature_help_for_call(
db: &RootDatabase, sema: &Semantics<RootDatabase>,
callable: hir::Callable, token: &SyntaxToken,
active_parameter: Option<usize>, ) -> Option<SignatureHelp> {
) -> SignatureHelp { // Find the calling expression and its NameRef
let mut node = token.parent()?;
let calling_node = loop {
if let Some(callable) = ast::CallableExpr::cast(node.clone()) {
if callable
.arg_list()
.map_or(false, |it| it.syntax().text_range().contains(token.text_range().start()))
{
break callable;
}
}
// Stop at multi-line expressions, since the signature of the outer call is not very
// helpful inside them.
if let Some(expr) = ast::Expr::cast(node.clone()) {
if expr.syntax().text().contains_char('\n') {
return None;
}
}
node = node.parent()?;
};
let (callable, active_parameter) = callable_for_node(sema, &calling_node, token)?;
let mut res = let mut res =
SignatureHelp { doc: None, signature: String::new(), parameters: vec![], active_parameter }; SignatureHelp { doc: None, signature: String::new(), parameters: vec![], active_parameter };
let db = sema.db;
match callable.kind() { match callable.kind() {
hir::CallableKind::Function(func) => { hir::CallableKind::Function(func) => {
res.doc = func.docs(db).map(|it| it.into()); res.doc = func.docs(db).map(|it| it.into());
@ -134,7 +163,7 @@ fn signature_help_for_callable(
} }
hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_) => {} hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_) => {}
} }
res Some(res)
} }
fn signature_help_for_generics( fn signature_help_for_generics(
@ -786,6 +815,46 @@ fn main() {
) )
} }
#[test]
fn test_multiline_argument() {
check(
r#"
fn callee(a: u8, b: u8) {}
fn main() {
callee(match 0 {
0 => 1,$0
})
}"#,
expect![[r#""#]],
);
check(
r#"
fn callee(a: u8, b: u8) {}
fn main() {
callee(match 0 {
0 => 1,
},$0)
}"#,
expect![[r#"
fn callee(a: u8, b: u8)
----- ^^^^^
"#]],
);
check(
r#"
fn callee(a: u8, b: u8) {}
fn main() {
callee($0match 0 {
0 => 1,
})
}"#,
expect![[r#"
fn callee(a: u8, b: u8)
^^^^^ -----
"#]],
);
}
#[test] #[test]
fn test_generics_simple() { fn test_generics_simple() {
check( check(

View file

@ -43,13 +43,21 @@ pub fn callable_for_token(
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
token: SyntaxToken, token: SyntaxToken,
) -> Option<(hir::Callable, Option<usize>)> { ) -> Option<(hir::Callable, Option<usize>)> {
// Find the calling expression and it's NameRef // Find the calling expression and its NameRef
let parent = token.parent()?; let parent = token.parent()?;
let calling_node = parent.ancestors().filter_map(ast::CallableExpr::cast).find(|it| { let calling_node = parent.ancestors().filter_map(ast::CallableExpr::cast).find(|it| {
it.arg_list() it.arg_list()
.map_or(false, |it| it.syntax().text_range().contains(token.text_range().start())) .map_or(false, |it| it.syntax().text_range().contains(token.text_range().start()))
})?; })?;
callable_for_node(sema, &calling_node, &token)
}
pub fn callable_for_node(
sema: &Semantics<RootDatabase>,
calling_node: &ast::CallableExpr,
token: &SyntaxToken,
) -> Option<(hir::Callable, Option<usize>)> {
let callable = match &calling_node { let callable = match &calling_node {
ast::CallableExpr::Call(call) => { ast::CallableExpr::Call(call) => {
let expr = call.expr()?; let expr = call.expr()?;