diff options
Diffstat (limited to 'crates/ra_ide/src')
-rw-r--r-- | crates/ra_ide/src/inlay_hints.rs | 97 |
1 files changed, 92 insertions, 5 deletions
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index ecd615cf4..2353ad71f 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs | |||
@@ -5,7 +5,7 @@ use ra_ide_db::RootDatabase; | |||
5 | use ra_prof::profile; | 5 | use ra_prof::profile; |
6 | use ra_syntax::{ | 6 | use ra_syntax::{ |
7 | ast::{self, ArgListOwner, AstNode, TypeAscriptionOwner}, | 7 | ast::{self, ArgListOwner, AstNode, TypeAscriptionOwner}, |
8 | match_ast, SmolStr, TextRange, | 8 | match_ast, SmolStr, TextRange, NodeOrToken, SyntaxKind, Direction |
9 | }; | 9 | }; |
10 | 10 | ||
11 | use crate::{FileId, FunctionSignature}; | 11 | use crate::{FileId, FunctionSignature}; |
@@ -14,12 +14,18 @@ use crate::{FileId, FunctionSignature}; | |||
14 | pub struct InlayHintsOptions { | 14 | pub struct InlayHintsOptions { |
15 | pub type_hints: bool, | 15 | pub type_hints: bool, |
16 | pub parameter_hints: bool, | 16 | pub parameter_hints: bool, |
17 | pub chaining_hints: bool, | ||
17 | pub max_length: Option<usize>, | 18 | pub max_length: Option<usize>, |
18 | } | 19 | } |
19 | 20 | ||
20 | impl Default for InlayHintsOptions { | 21 | impl Default for InlayHintsOptions { |
21 | fn default() -> Self { | 22 | fn default() -> Self { |
22 | Self { type_hints: true, parameter_hints: true, max_length: None } | 23 | Self { |
24 | type_hints: true, | ||
25 | parameter_hints: true, | ||
26 | chaining_hints: true, | ||
27 | max_length: None | ||
28 | } | ||
23 | } | 29 | } |
24 | } | 30 | } |
25 | 31 | ||
@@ -27,6 +33,7 @@ impl Default for InlayHintsOptions { | |||
27 | pub enum InlayKind { | 33 | pub enum InlayKind { |
28 | TypeHint, | 34 | TypeHint, |
29 | ParameterHint, | 35 | ParameterHint, |
36 | ChainingHint, | ||
30 | } | 37 | } |
31 | 38 | ||
32 | #[derive(Debug)] | 39 | #[derive(Debug)] |
@@ -47,6 +54,10 @@ pub(crate) fn inlay_hints( | |||
47 | 54 | ||
48 | let mut res = Vec::new(); | 55 | let mut res = Vec::new(); |
49 | for node in file.syntax().descendants() { | 56 | for node in file.syntax().descendants() { |
57 | if let Some(expr) = ast::Expr::cast(node.clone()) { | ||
58 | get_chaining_hints(&mut res, &sema, options, expr); | ||
59 | } | ||
60 | |||
50 | match_ast! { | 61 | match_ast! { |
51 | match node { | 62 | match node { |
52 | ast::CallExpr(it) => { get_param_name_hints(&mut res, &sema, options, ast::Expr::from(it)); }, | 63 | ast::CallExpr(it) => { get_param_name_hints(&mut res, &sema, options, ast::Expr::from(it)); }, |
@@ -222,6 +233,45 @@ fn get_fn_signature(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option< | |||
222 | } | 233 | } |
223 | } | 234 | } |
224 | 235 | ||
236 | fn get_chaining_hints( | ||
237 | acc: &mut Vec<InlayHint>, | ||
238 | sema: &Semantics<RootDatabase>, | ||
239 | options: &InlayHintsOptions, | ||
240 | expr: ast::Expr, | ||
241 | ) -> Option<()> { | ||
242 | if !options.chaining_hints { | ||
243 | return None; | ||
244 | } | ||
245 | |||
246 | let ty = sema.type_of_expr(&expr)?; | ||
247 | let label = ty.display_truncated(sema.db, options.max_length).to_string(); | ||
248 | if ty.is_unknown() { | ||
249 | return None; | ||
250 | } | ||
251 | |||
252 | let mut tokens = expr.syntax() | ||
253 | .siblings_with_tokens(Direction::Next) | ||
254 | .filter_map(NodeOrToken::into_token) | ||
255 | .filter(|t| match t.kind() { | ||
256 | SyntaxKind::WHITESPACE if !t.text().contains('\n') => false, | ||
257 | SyntaxKind::COMMENT => false, | ||
258 | _ => true, | ||
259 | }); | ||
260 | |||
261 | // Chaining can be defined as an expression whose next sibling tokens are newline and dot | ||
262 | // Ignoring extra whitespace and comments | ||
263 | let next = tokens.next()?.kind(); | ||
264 | let next_next = tokens.next()?.kind(); | ||
265 | if next == SyntaxKind::WHITESPACE && next_next == SyntaxKind::DOT { | ||
266 | acc.push(InlayHint { | ||
267 | range: expr.syntax().text_range(), | ||
268 | kind: InlayKind::ChainingHint, | ||
269 | label: label.into(), | ||
270 | }); | ||
271 | } | ||
272 | Some(()) | ||
273 | } | ||
274 | |||
225 | #[cfg(test)] | 275 | #[cfg(test)] |
226 | mod tests { | 276 | mod tests { |
227 | use crate::inlay_hints::InlayHintsOptions; | 277 | use crate::inlay_hints::InlayHintsOptions; |
@@ -230,6 +280,43 @@ mod tests { | |||
230 | use crate::mock_analysis::single_file; | 280 | use crate::mock_analysis::single_file; |
231 | 281 | ||
232 | #[test] | 282 | #[test] |
283 | fn generic_chaining_hints() { | ||
284 | let (analysis, file_id) = single_file( | ||
285 | r#" | ||
286 | struct A<T>(T); | ||
287 | struct B<T>(T); | ||
288 | struct C<T>(T); | ||
289 | struct X<T,R>(T, R); | ||
290 | |||
291 | impl<T> A<T> { | ||
292 | fn new(t: T) -> Self { A(t) } | ||
293 | fn into_b(self) -> B<T> { B(self.0) } | ||
294 | } | ||
295 | impl<T> B<T> { | ||
296 | fn into_c(self) -> C<T> { C(self.0) } | ||
297 | } | ||
298 | fn test() { | ||
299 | let c = A::new(X(42, true)) | ||
300 | .into_b() // All the from A -> B -> C | ||
301 | .into_c(); | ||
302 | }"#, | ||
303 | ); | ||
304 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###" | ||
305 | [ | ||
306 | InlayHint { | ||
307 | range: [416; 465), | ||
308 | kind: ChainingHint, | ||
309 | label: "B<X<i32, bool>>", | ||
310 | }, | ||
311 | InlayHint { | ||
312 | range: [416; 435), | ||
313 | kind: ChainingHint, | ||
314 | label: "A<X<i32, bool>>", | ||
315 | }, | ||
316 | ]"###); | ||
317 | } | ||
318 | |||
319 | #[test] | ||
233 | fn param_hints_only() { | 320 | fn param_hints_only() { |
234 | let (analysis, file_id) = single_file( | 321 | let (analysis, file_id) = single_file( |
235 | r#" | 322 | r#" |
@@ -238,7 +325,7 @@ mod tests { | |||
238 | let _x = foo(4, 4); | 325 | let _x = foo(4, 4); |
239 | }"#, | 326 | }"#, |
240 | ); | 327 | ); |
241 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: true, type_hints: false, max_length: None}).unwrap(), @r###" | 328 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: true, type_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###" |
242 | [ | 329 | [ |
243 | InlayHint { | 330 | InlayHint { |
244 | range: [106; 107), | 331 | range: [106; 107), |
@@ -262,7 +349,7 @@ mod tests { | |||
262 | let _x = foo(4, 4); | 349 | let _x = foo(4, 4); |
263 | }"#, | 350 | }"#, |
264 | ); | 351 | ); |
265 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: false, parameter_hints: false, max_length: None}).unwrap(), @r###"[]"###); | 352 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: false, parameter_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###"[]"###); |
266 | } | 353 | } |
267 | 354 | ||
268 | #[test] | 355 | #[test] |
@@ -274,7 +361,7 @@ mod tests { | |||
274 | let _x = foo(4, 4); | 361 | let _x = foo(4, 4); |
275 | }"#, | 362 | }"#, |
276 | ); | 363 | ); |
277 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: true, parameter_hints: false, max_length: None}).unwrap(), @r###" | 364 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: true, parameter_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###" |
278 | [ | 365 | [ |
279 | InlayHint { | 366 | InlayHint { |
280 | range: [97; 99), | 367 | range: [97; 99), |