diff options
-rw-r--r-- | crates/ra_ide/src/inlay_hints.rs | 235 |
1 files changed, 159 insertions, 76 deletions
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index 2353ad71f..293944206 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs | |||
@@ -70,6 +70,45 @@ pub(crate) fn inlay_hints( | |||
70 | res | 70 | res |
71 | } | 71 | } |
72 | 72 | ||
73 | fn get_chaining_hints( | ||
74 | acc: &mut Vec<InlayHint>, | ||
75 | sema: &Semantics<RootDatabase>, | ||
76 | options: &InlayHintsOptions, | ||
77 | expr: ast::Expr, | ||
78 | ) -> Option<()> { | ||
79 | if !options.chaining_hints { | ||
80 | return None; | ||
81 | } | ||
82 | |||
83 | let ty = sema.type_of_expr(&expr)?; | ||
84 | let label = ty.display_truncated(sema.db, options.max_length).to_string(); | ||
85 | if ty.is_unknown() { | ||
86 | return None; | ||
87 | } | ||
88 | |||
89 | let mut tokens = expr.syntax() | ||
90 | .siblings_with_tokens(Direction::Next) | ||
91 | .filter_map(NodeOrToken::into_token) | ||
92 | .filter(|t| match t.kind() { | ||
93 | SyntaxKind::WHITESPACE if !t.text().contains('\n') => false, | ||
94 | SyntaxKind::COMMENT => false, | ||
95 | _ => true, | ||
96 | }); | ||
97 | |||
98 | // Chaining can be defined as an expression whose next sibling tokens are newline and dot | ||
99 | // Ignoring extra whitespace and comments | ||
100 | let next = tokens.next()?.kind(); | ||
101 | let next_next = tokens.next()?.kind(); | ||
102 | if next == SyntaxKind::WHITESPACE && next_next == SyntaxKind::DOT { | ||
103 | acc.push(InlayHint { | ||
104 | range: expr.syntax().text_range(), | ||
105 | kind: InlayKind::ChainingHint, | ||
106 | label: label.into(), | ||
107 | }); | ||
108 | } | ||
109 | Some(()) | ||
110 | } | ||
111 | |||
73 | fn get_param_name_hints( | 112 | fn get_param_name_hints( |
74 | acc: &mut Vec<InlayHint>, | 113 | acc: &mut Vec<InlayHint>, |
75 | sema: &Semantics<RootDatabase>, | 114 | sema: &Semantics<RootDatabase>, |
@@ -233,45 +272,6 @@ fn get_fn_signature(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option< | |||
233 | } | 272 | } |
234 | } | 273 | } |
235 | 274 | ||
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 | |||
275 | #[cfg(test)] | 275 | #[cfg(test)] |
276 | mod tests { | 276 | mod tests { |
277 | use crate::inlay_hints::InlayHintsOptions; | 277 | use crate::inlay_hints::InlayHintsOptions; |
@@ -280,43 +280,6 @@ mod tests { | |||
280 | use crate::mock_analysis::single_file; | 280 | use crate::mock_analysis::single_file; |
281 | 281 | ||
282 | #[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] | ||
320 | fn param_hints_only() { | 283 | fn param_hints_only() { |
321 | let (analysis, file_id) = single_file( | 284 | let (analysis, file_id) = single_file( |
322 | r#" | 285 | r#" |
@@ -1139,4 +1102,124 @@ fn main() { | |||
1139 | "### | 1102 | "### |
1140 | ); | 1103 | ); |
1141 | } | 1104 | } |
1105 | |||
1106 | #[test] | ||
1107 | fn chaining_hints_ignore_comments() { | ||
1108 | let (analysis, file_id) = single_file( | ||
1109 | r#" | ||
1110 | struct A(B); | ||
1111 | impl A { fn into_b(self) -> B { self.0 } } | ||
1112 | struct B(C) | ||
1113 | impl B { fn into_c(self) -> C { self.0 } } | ||
1114 | struct C; | ||
1115 | |||
1116 | fn main() { | ||
1117 | let c = A(B(C)) | ||
1118 | .into_b() // This is a comment | ||
1119 | .into_c(); | ||
1120 | }"#, | ||
1121 | ); | ||
1122 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###" | ||
1123 | [ | ||
1124 | InlayHint { | ||
1125 | range: [231; 268), | ||
1126 | kind: ChainingHint, | ||
1127 | label: "B", | ||
1128 | }, | ||
1129 | InlayHint { | ||
1130 | range: [231; 238), | ||
1131 | kind: ChainingHint, | ||
1132 | label: "A", | ||
1133 | }, | ||
1134 | ]"###); | ||
1135 | } | ||
1136 | |||
1137 | #[test] | ||
1138 | fn chaining_hints_without_newlines() { | ||
1139 | let (analysis, file_id) = single_file( | ||
1140 | r#" | ||
1141 | struct A(B); | ||
1142 | impl A { fn into_b(self) -> B { self.0 } } | ||
1143 | struct B(C) | ||
1144 | impl B { fn into_c(self) -> C { self.0 } } | ||
1145 | struct C; | ||
1146 | |||
1147 | fn main() { | ||
1148 | let c = A(B(C)).into_b().into_c(); | ||
1149 | }"#, | ||
1150 | ); | ||
1151 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"[]"###); | ||
1152 | } | ||
1153 | |||
1154 | #[test] | ||
1155 | fn struct_access_chaining_hints() { | ||
1156 | let (analysis, file_id) = single_file( | ||
1157 | r#" | ||
1158 | struct A { pub b: B } | ||
1159 | struct B { pub c: C } | ||
1160 | struct C(pub bool); | ||
1161 | |||
1162 | fn main() { | ||
1163 | let x = A { b: B { c: C(true) } } | ||
1164 | .b | ||
1165 | .c | ||
1166 | .0; | ||
1167 | }"#, | ||
1168 | ); | ||
1169 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###" | ||
1170 | [ | ||
1171 | InlayHint { | ||
1172 | range: [150; 221), | ||
1173 | kind: ChainingHint, | ||
1174 | label: "C", | ||
1175 | }, | ||
1176 | InlayHint { | ||
1177 | range: [150; 198), | ||
1178 | kind: ChainingHint, | ||
1179 | label: "B", | ||
1180 | }, | ||
1181 | InlayHint { | ||
1182 | range: [150; 175), | ||
1183 | kind: ChainingHint, | ||
1184 | label: "A", | ||
1185 | }, | ||
1186 | ]"###); | ||
1187 | } | ||
1188 | |||
1189 | #[test] | ||
1190 | fn generic_chaining_hints() { | ||
1191 | let (analysis, file_id) = single_file( | ||
1192 | r#" | ||
1193 | struct A<T>(T); | ||
1194 | struct B<T>(T); | ||
1195 | struct C<T>(T); | ||
1196 | struct X<T,R>(T, R); | ||
1197 | |||
1198 | impl<T> A<T> { | ||
1199 | fn new(t: T) -> Self { A(t) } | ||
1200 | fn into_b(self) -> B<T> { B(self.0) } | ||
1201 | } | ||
1202 | impl<T> B<T> { | ||
1203 | fn into_c(self) -> C<T> { C(self.0) } | ||
1204 | } | ||
1205 | fn main() { | ||
1206 | let c = A::new(X(42, true)) | ||
1207 | .into_b() | ||
1208 | .into_c(); | ||
1209 | }"#, | ||
1210 | ); | ||
1211 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###" | ||
1212 | [ | ||
1213 | InlayHint { | ||
1214 | range: [416; 465), | ||
1215 | kind: ChainingHint, | ||
1216 | label: "B<X<i32, bool>>", | ||
1217 | }, | ||
1218 | InlayHint { | ||
1219 | range: [416; 435), | ||
1220 | kind: ChainingHint, | ||
1221 | label: "A<X<i32, bool>>", | ||
1222 | }, | ||
1223 | ]"###); | ||
1224 | } | ||
1142 | } | 1225 | } |