Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/ide/src/signature_help.rs376
-rw-r--r--crates/ide_completion/src/render.rs1
-rw-r--r--crates/ide_db/src/active_parameter.rs60
3 files changed, 270 insertions, 167 deletions
diff --git a/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs
index e13abe2d87..f33b09ed57 100644
--- a/crates/ide/src/signature_help.rs
+++ b/crates/ide/src/signature_help.rs
@@ -2,13 +2,14 @@
//! a call or use-site.
use either::Either;
-use hir::{HasAttrs, HirDisplay, Semantics};
-use ide_db::{
- active_parameter::{callable_for_token, generics_for_token},
- base_db::FilePosition,
-};
+use hir::{GenericParam, HasAttrs, HirDisplay, Semantics};
+use ide_db::{active_parameter::callable_for_node, base_db::FilePosition};
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;
@@ -65,25 +66,50 @@ pub(crate) fn signature_help(db: &RootDatabase, position: FilePosition) -> Optio
.and_then(|tok| algo::skip_trivia_token(tok, Direction::Prev))?;
let token = sema.descend_into_macros_single(token);
- if let Some((callable, active_parameter)) = callable_for_token(&sema, token.clone()) {
- return Some(signature_help_for_callable(db, callable, active_parameter));
+ if let Some(help) = signature_help_for_call(&sema, &token) {
+ return Some(help);
}
- if let Some((generic_def, active_parameter)) = generics_for_token(&sema, token.clone()) {
- return signature_help_for_generics(db, generic_def, active_parameter);
+ if let Some(help) = signature_help_for_generics(&sema, &token) {
+ return Some(help);
}
None
}
-fn signature_help_for_callable(
- db: &RootDatabase,
- callable: hir::Callable,
- active_parameter: Option<usize>,
-) -> SignatureHelp {
+fn signature_help_for_call(
+ sema: &Semantics<RootDatabase>,
+ token: &SyntaxToken,
+) -> Option<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 =
SignatureHelp { doc: None, signature: String::new(), parameters: vec![], active_parameter };
+ let db = sema.db;
match callable.kind() {
hir::CallableKind::Function(func) => {
res.doc = func.docs(db).map(|it| it.into());
@@ -134,21 +160,73 @@ fn signature_help_for_callable(
}
hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_) => {}
}
- res
+ Some(res)
}
fn signature_help_for_generics(
- db: &RootDatabase,
- mut generics_def: hir::GenericDef,
- active_parameter: usize,
+ sema: &Semantics<RootDatabase>,
+ token: &SyntaxToken,
) -> Option<SignatureHelp> {
+ let parent = token.parent()?;
+ let arg_list = parent
+ .ancestors()
+ .filter_map(ast::GenericArgList::cast)
+ .find(|list| list.syntax().text_range().contains(token.text_range().start()))?;
+
+ let mut active_parameter = arg_list
+ .generic_args()
+ .take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start())
+ .count();
+
+ let first_arg_is_non_lifetime = arg_list
+ .generic_args()
+ .next()
+ .map_or(false, |arg| !matches!(arg, ast::GenericArg::LifetimeArg(_)));
+
+ let mut generics_def = if let Some(path) =
+ arg_list.syntax().ancestors().find_map(ast::Path::cast)
+ {
+ let res = sema.resolve_path(&path)?;
+ let generic_def: hir::GenericDef = match res {
+ hir::PathResolution::Def(hir::ModuleDef::Adt(it)) => it.into(),
+ hir::PathResolution::Def(hir::ModuleDef::Function(it)) => it.into(),
+ hir::PathResolution::Def(hir::ModuleDef::Trait(it)) => it.into(),
+ hir::PathResolution::Def(hir::ModuleDef::TypeAlias(it)) => it.into(),
+ hir::PathResolution::Def(hir::ModuleDef::Variant(it)) => it.into(),
+ hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_))
+ | hir::PathResolution::Def(hir::ModuleDef::Const(_))
+ | hir::PathResolution::Def(hir::ModuleDef::Macro(_))
+ | hir::PathResolution::Def(hir::ModuleDef::Module(_))
+ | hir::PathResolution::Def(hir::ModuleDef::Static(_)) => return None,
+ hir::PathResolution::AssocItem(hir::AssocItem::Function(it)) => it.into(),
+ hir::PathResolution::AssocItem(hir::AssocItem::TypeAlias(it)) => it.into(),
+ hir::PathResolution::AssocItem(hir::AssocItem::Const(_)) => return None,
+ hir::PathResolution::BuiltinAttr(_)
+ | hir::PathResolution::ToolModule(_)
+ | hir::PathResolution::Local(_)
+ | hir::PathResolution::TypeParam(_)
+ | hir::PathResolution::ConstParam(_)
+ | hir::PathResolution::SelfType(_) => return None,
+ };
+
+ generic_def
+ } else if let Some(method_call) = arg_list.syntax().parent().and_then(ast::MethodCallExpr::cast)
+ {
+ // recv.method::<$0>()
+ let method = sema.resolve_method_call(&method_call)?;
+ method.into()
+ } else {
+ return None;
+ };
+
let mut res = SignatureHelp {
doc: None,
signature: String::new(),
parameters: vec![],
- active_parameter: Some(active_parameter),
+ active_parameter: None,
};
+ let db = sema.db;
match generics_def {
hir::GenericDef::Function(it) => {
res.doc = it.docs(db).map(|it| it.into());
@@ -187,8 +265,16 @@ fn signature_help_for_generics(
hir::GenericDef::Impl(_) | hir::GenericDef::Const(_) => return None,
}
+ let params = generics_def.params(sema.db);
+ let num_lifetime_params =
+ params.iter().take_while(|param| matches!(param, GenericParam::LifetimeParam(_))).count();
+ if first_arg_is_non_lifetime {
+ // Lifetime parameters were omitted.
+ active_parameter += num_lifetime_params;
+ }
+ res.active_parameter = Some(active_parameter);
+
res.signature.push('<');
- let params = generics_def.params(db);
let mut buf = String::new();
for param in params {
if let hir::GenericParam::TypeParam(ty) = param {
@@ -208,8 +294,11 @@ fn signature_help_for_generics(
#[cfg(test)]
mod tests {
+ use std::iter;
+
use expect_test::{expect, Expect};
use ide_db::base_db::{fixture::ChangeFixture, FilePosition};
+ use stdx::format_to;
use crate::RootDatabase;
@@ -233,26 +322,32 @@ mod tests {
"#
);
let (db, position) = position(&fixture);
- let call_info = crate::signature_help::signature_help(&db, position);
- let actual = match call_info {
- Some(call_info) => {
- let docs = match &call_info.doc {
- None => "".to_string(),
- Some(docs) => format!("{}\n------\n", docs.as_str()),
- };
- let params = call_info
- .parameter_labels()
- .enumerate()
- .map(|(i, param)| {
- if Some(i) == call_info.active_parameter {
- format!("<{}>", param)
- } else {
- param.to_string()
- }
- })
- .collect::<Vec<_>>()
- .join(", ");
- format!("{}{}\n({})\n", docs, call_info.signature, params)
+ let sig_help = crate::signature_help::signature_help(&db, position);
+ let actual = match sig_help {
+ Some(sig_help) => {
+ let mut rendered = String::new();
+ if let Some(docs) = &sig_help.doc {
+ format_to!(rendered, "{}\n------\n", docs.as_str());
+ }
+ format_to!(rendered, "{}\n", sig_help.signature);
+ let mut offset = 0;
+ for (i, range) in sig_help.parameter_ranges().iter().enumerate() {
+ let is_active = sig_help.active_parameter == Some(i);
+
+ let start = u32::from(range.start());
+ let gap = start.checked_sub(offset).unwrap_or_else(|| {
+ panic!("parameter ranges out of order: {:?}", sig_help.parameter_ranges())
+ });
+ rendered.extend(iter::repeat(' ').take(gap as usize));
+ let width = u32::from(range.end() - range.start());
+ let marker = if is_active { '^' } else { '-' };
+ rendered.extend(iter::repeat(marker).take(width as usize));
+ offset += gap + width;
+ }
+ if !sig_help.parameter_ranges().is_empty() {
+ format_to!(rendered, "\n");
+ }
+ rendered
}
None => String::new(),
};
@@ -268,7 +363,7 @@ fn bar() { foo($03, ); }
"#,
expect![[r#"
fn foo(x: u32, y: u32) -> u32
- (<x: u32>, y: u32)
+ ^^^^^^ ------
"#]],
);
check(
@@ -278,7 +373,7 @@ fn bar() { foo(3$0, ); }
"#,
expect![[r#"
fn foo(x: u32, y: u32) -> u32
- (<x: u32>, y: u32)
+ ^^^^^^ ------
"#]],
);
check(
@@ -288,7 +383,7 @@ fn bar() { foo(3,$0 ); }
"#,
expect![[r#"
fn foo(x: u32, y: u32) -> u32
- (x: u32, <y: u32>)
+ ------ ^^^^^^
"#]],
);
check(
@@ -298,7 +393,7 @@ fn bar() { foo(3, $0); }
"#,
expect![[r#"
fn foo(x: u32, y: u32) -> u32
- (x: u32, <y: u32>)
+ ------ ^^^^^^
"#]],
);
}
@@ -312,7 +407,7 @@ fn bar() { foo($0); }
"#,
expect![[r#"
fn foo(x: u32, y: u32) -> u32
- (<x: u32>, y: u32)
+ ^^^^^^ ------
"#]],
);
}
@@ -329,7 +424,7 @@ fn bar() { foo($03, ); }
"#,
expect![[r#"
fn foo(x: i32, y: {unknown}) -> u32
- (<x: i32>, y: {unknown})
+ ^^^^^^ ------------
"#]],
);
}
@@ -343,7 +438,6 @@ fn bar() { foo($0); }
"#,
expect![[r#"
fn foo() -> {unknown}
- ()
"#]],
);
}
@@ -360,7 +454,6 @@ fn bar() {
"#,
expect![[r#"
fn new()
- ()
"#]],
);
}
@@ -379,7 +472,6 @@ fn bar() {
"#,
expect![[r#"
fn do_it(&self)
- ()
"#]],
);
}
@@ -397,7 +489,7 @@ fn main() { S.foo($0); }
"#,
expect![[r#"
fn foo(&self, x: i32)
- (<x: i32>)
+ ^^^^^^
"#]],
);
}
@@ -415,7 +507,7 @@ fn main() { S(1u32).foo($0); }
"#,
expect![[r#"
fn foo(&self, x: u32)
- (<x: u32>)
+ ^^^^^^
"#]],
);
}
@@ -433,7 +525,7 @@ fn main() { S::foo($0); }
"#,
expect![[r#"
fn foo(self: &S, x: i32)
- (<self: &S>, x: i32)
+ ^^^^^^^^ ------
"#]],
);
}
@@ -453,11 +545,11 @@ fn bar() {
}
"#,
expect![[r#"
- test
- ------
- fn foo(j: u32) -> u32
- (<j: u32>)
- "#]],
+ test
+ ------
+ fn foo(j: u32) -> u32
+ ^^^^^^
+ "#]],
);
}
@@ -482,19 +574,19 @@ pub fn do() {
add_one($0
}"#,
expect![[r##"
- Adds one to the number given.
+ Adds one to the number given.
- # Examples
+ # Examples
- ```
- let five = 5;
+ ```
+ let five = 5;
- assert_eq!(6, my_crate::add_one(5));
- ```
- ------
- fn add_one(x: i32) -> i32
- (<x: i32>)
- "##]],
+ assert_eq!(6, my_crate::add_one(5));
+ ```
+ ------
+ fn add_one(x: i32) -> i32
+ ^^^^^^
+ "##]],
);
}
@@ -524,19 +616,19 @@ pub fn do_it() {
}
"#,
expect![[r##"
- Adds one to the number given.
+ Adds one to the number given.
- # Examples
+ # Examples
- ```
- let five = 5;
+ ```
+ let five = 5;
- assert_eq!(6, my_crate::add_one(5));
- ```
- ------
- fn add_one(x: i32) -> i32
- (<x: i32>)
- "##]],
+ assert_eq!(6, my_crate::add_one(5));
+ ```
+ ------
+ fn add_one(x: i32) -> i32
+ ^^^^^^
+ "##]],
);
}
@@ -568,13 +660,13 @@ pub fn foo(mut r: WriteHandler<()>) {
}
"#,
expect![[r#"
- Method is called when writer finishes.
+ Method is called when writer finishes.
- By default this method stops actor's `Context`.
- ------
- fn finished(&mut self, ctx: &mut {unknown})
- (<ctx: &mut {unknown}>)
- "#]],
+ By default this method stops actor's `Context`.
+ ------
+ fn finished(&mut self, ctx: &mut {unknown})
+ ^^^^^^^^^^^^^^^^^^^
+ "#]],
);
}
@@ -605,7 +697,7 @@ fn main() {
"#,
expect![[r#"
fn bar(&self, _: u32)
- (<_: u32>)
+ ^^^^^^
"#]],
);
}
@@ -621,11 +713,11 @@ fn main() {
}
"#,
expect![[r#"
- A cool tuple struct
- ------
- struct S(u32, i32)
- (u32, <i32>)
- "#]],
+ A cool tuple struct
+ ------
+ struct S(u32, i32)
+ --- ^^^
+ "#]],
);
}
@@ -640,7 +732,7 @@ fn main() {
"#,
expect![[r#"
struct S({unknown})
- (<{unknown}>)
+ ^^^^^^^^^
"#]],
);
}
@@ -663,11 +755,11 @@ fn main() {
}
"#,
expect![[r#"
- A Variant
- ------
- enum E::A(i32)
- (<i32>)
- "#]],
+ A Variant
+ ------
+ enum E::A(i32)
+ ^^^
+ "#]],
);
}
@@ -717,7 +809,6 @@ id! {
"#,
expect![[r#"
fn foo()
- ()
"#]],
);
}
@@ -734,7 +825,7 @@ fn main() {
"#,
expect![[r#"
(S) -> i32
- (<S>)
+ ^
"#]],
)
}
@@ -749,7 +840,7 @@ fn main(f: fn(i32, f64) -> char) {
"#,
expect![[r#"
(i32, f64) -> char
- (i32, <f64>)
+ --- ^^^
"#]],
)
}
@@ -763,9 +854,9 @@ fn main() {
foo($0
}"#,
expect![[r#"
- fn foo(foo: u32, bar: u32)
- (<foo: u32>, bar: u32)
- "#]],
+ fn foo(foo: u32, bar: u32)
+ ^^^^^^^^ --------
+ "#]],
);
// check with surrounding space
check(
@@ -775,13 +866,53 @@ fn main() {
foo( $0
}"#,
expect![[r#"
- fn foo(foo: u32, bar: u32)
- (<foo: u32>, bar: u32)
- "#]],
+ fn foo(foo: u32, bar: u32)
+ ^^^^^^^^ --------
+ "#]],
)
}
#[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]
fn test_generics_simple() {
check(
r#"
@@ -799,7 +930,7 @@ fn f() {
Option docs.
------
enum Option<T>
- (<T>)
+ ^
"#]],
);
}
@@ -826,7 +957,7 @@ fn f() {
None docs.
------
enum Option<T>
- (<T>)
+ ^
"#]],
);
}
@@ -849,7 +980,7 @@ fn f() {
"#,
expect![[r#"
fn f<G: Tr<()>, H>
- (G: Tr<()>, <H>)
+ --------- ^
"#]],
);
}
@@ -872,7 +1003,7 @@ fn f() {
"#,
expect![[r#"
fn f<T: Tr, U>
- (<T: Tr>, U)
+ ^^^^^ -
"#]],
);
}
@@ -892,9 +1023,9 @@ fn f() {
}
"#,
expect![[r#"
- fn f<T>
- (<T>)
- "#]],
+ fn f<T>
+ ^
+ "#]],
);
}
@@ -902,16 +1033,29 @@ fn f() {
fn test_generic_kinds() {
check(
r#"
-fn callee<'a, const A: (), T, const C: u8>() {}
+fn callee<'a, const A: u8, T, const C: u8>() {}
fn f() {
callee::<'static, $0
}
"#,
expect![[r#"
- fn callee<'a, const A: (), T, const C: u8>
- ('a, <const A: ()>, T, const C: u8)
- "#]],
+ fn callee<'a, const A: u8, T, const C: u8>
+ -- ^^^^^^^^^^^ - -----------
+ "#]],
+ );
+ check(
+ r#"
+fn callee<'a, const A: u8, T, const C: u8>() {}
+
+fn f() {
+ callee::<NON_LIFETIME$0
+}
+ "#,
+ expect![[r#"
+ fn callee<'a, const A: u8, T, const C: u8>
+ -- ^^^^^^^^^^^ - -----------
+ "#]],
);
}
}
diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs
index 10211cd971..1850fd1aa8 100644
--- a/crates/ide_completion/src/render.rs
+++ b/crates/ide_completion/src/render.rs
@@ -272,6 +272,7 @@ fn render_resolution_simple_(
cov_mark::hit!(inserts_angle_brackets_for_generics);
item.lookup_by(local_name.clone())
.label(SmolStr::from_iter([&local_name, "<…>"]))
+ .trigger_call_info()
.insert_snippet(cap, format!("{}<$0>", local_name));
}
}
diff --git a/crates/ide_db/src/active_parameter.rs b/crates/ide_db/src/active_parameter.rs
index 67b819c5a5..788a5ed17e 100644
--- a/crates/ide_db/src/active_parameter.rs
+++ b/crates/ide_db/src/active_parameter.rs
@@ -43,13 +43,21 @@ pub fn callable_for_token(
sema: &Semantics<RootDatabase>,
token: SyntaxToken,
) -> 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 calling_node = parent.ancestors().filter_map(ast::CallableExpr::cast).find(|it| {
it.arg_list()
.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 {
ast::CallableExpr::Call(call) => {
let expr = call.expr()?;
@@ -68,53 +76,3 @@ pub fn callable_for_token(
};
Some((callable, active_param))
}
-
-pub fn generics_for_token(
- sema: &Semantics<RootDatabase>,
- token: SyntaxToken,
-) -> Option<(hir::GenericDef, usize)> {
- let parent = token.parent()?;
- let arg_list = parent
- .ancestors()
- .filter_map(ast::GenericArgList::cast)
- .find(|list| list.syntax().text_range().contains(token.text_range().start()))?;
-
- let active_param = arg_list
- .generic_args()
- .take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start())
- .count();
-
- if let Some(path) = arg_list.syntax().ancestors().find_map(ast::Path::cast) {
- let res = sema.resolve_path(&path)?;
- let generic_def: hir::GenericDef = match res {
- hir::PathResolution::Def(hir::ModuleDef::Adt(it)) => it.into(),
- hir::PathResolution::Def(hir::ModuleDef::Function(it)) => it.into(),
- hir::PathResolution::Def(hir::ModuleDef::Trait(it)) => it.into(),
- hir::PathResolution::Def(hir::ModuleDef::TypeAlias(it)) => it.into(),
- hir::PathResolution::Def(hir::ModuleDef::Variant(it)) => it.into(),
- hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_))
- | hir::PathResolution::Def(hir::ModuleDef::Const(_))
- | hir::PathResolution::Def(hir::ModuleDef::Macro(_))
- | hir::PathResolution::Def(hir::ModuleDef::Module(_))
- | hir::PathResolution::Def(hir::ModuleDef::Static(_)) => return None,
- hir::PathResolution::AssocItem(hir::AssocItem::Function(it)) => it.into(),
- hir::PathResolution::AssocItem(hir::AssocItem::TypeAlias(it)) => it.into(),
- hir::PathResolution::AssocItem(hir::AssocItem::Const(_)) => return None,
- hir::PathResolution::BuiltinAttr(_)
- | hir::PathResolution::ToolModule(_)
- | hir::PathResolution::Local(_)
- | hir::PathResolution::TypeParam(_)
- | hir::PathResolution::ConstParam(_)
- | hir::PathResolution::SelfType(_) => return None,
- };
-
- Some((generic_def, active_param))
- } else if let Some(method_call) = arg_list.syntax().parent().and_then(ast::MethodCallExpr::cast)
- {
- // recv.method::<$0>()
- let method = sema.resolve_method_call(&method_call)?;
- Some((method.into(), active_param))
- } else {
- None
- }
-}