diff options
Diffstat (limited to 'crates/ra_ide/src/completion/complete_attribute.rs')
-rw-r--r-- | crates/ra_ide/src/completion/complete_attribute.rs | 306 |
1 files changed, 287 insertions, 19 deletions
diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs index 8bf952798..fb3f0b743 100644 --- a/crates/ra_ide/src/completion/complete_attribute.rs +++ b/crates/ra_ide/src/completion/complete_attribute.rs | |||
@@ -3,25 +3,29 @@ | |||
3 | //! This module uses a bit of static metadata to provide completions | 3 | //! This module uses a bit of static metadata to provide completions |
4 | //! for built-in attributes. | 4 | //! for built-in attributes. |
5 | 5 | ||
6 | use super::completion_context::CompletionContext; | 6 | use ra_syntax::{ast, AstNode, SyntaxKind}; |
7 | use super::completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions}; | 7 | use rustc_hash::FxHashSet; |
8 | use ra_syntax::{ | 8 | |
9 | ast::{Attr, AttrKind}, | 9 | use crate::completion::{ |
10 | AstNode, | 10 | completion_context::CompletionContext, |
11 | completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions}, | ||
11 | }; | 12 | }; |
12 | 13 | ||
13 | pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) { | 14 | pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { |
14 | if !ctx.is_attribute { | 15 | let attribute = ctx.attribute_under_caret.as_ref()?; |
15 | return; | ||
16 | } | ||
17 | 16 | ||
18 | let is_inner = ctx | 17 | match (attribute.path(), attribute.input()) { |
19 | .original_token | 18 | (Some(path), Some(ast::AttrInput::TokenTree(token_tree))) |
20 | .ancestors() | 19 | if path.to_string() == "derive" => |
21 | .find_map(Attr::cast) | 20 | { |
22 | .map(|attr| attr.kind() == AttrKind::Inner) | 21 | complete_derive(acc, ctx, token_tree) |
23 | .unwrap_or(false); | 22 | } |
23 | _ => complete_attribute_start(acc, ctx, attribute), | ||
24 | } | ||
25 | Some(()) | ||
26 | } | ||
24 | 27 | ||
28 | fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) { | ||
25 | for attr_completion in ATTRIBUTES { | 29 | for attr_completion in ATTRIBUTES { |
26 | let mut item = CompletionItem::new( | 30 | let mut item = CompletionItem::new( |
27 | CompletionKind::Attribute, | 31 | CompletionKind::Attribute, |
@@ -37,7 +41,7 @@ pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) | |||
37 | _ => {} | 41 | _ => {} |
38 | } | 42 | } |
39 | 43 | ||
40 | if is_inner || !attr_completion.should_be_inner { | 44 | if attribute.kind() == ast::AttrKind::Inner || !attr_completion.should_be_inner { |
41 | acc.add(item); | 45 | acc.add(item); |
42 | } | 46 | } |
43 | } | 47 | } |
@@ -108,7 +112,7 @@ const ATTRIBUTES: &[AttrCompletion] = &[ | |||
108 | AttrCompletion { label: "repr", snippet: Some("repr(${0:C})"), should_be_inner: false }, | 112 | AttrCompletion { label: "repr", snippet: Some("repr(${0:C})"), should_be_inner: false }, |
109 | AttrCompletion { | 113 | AttrCompletion { |
110 | label: "should_panic", | 114 | label: "should_panic", |
111 | snippet: Some(r#"expected = "${0:reason}""#), | 115 | snippet: Some(r#"should_panic(expected = "${0:reason}")"#), |
112 | should_be_inner: false, | 116 | should_be_inner: false, |
113 | }, | 117 | }, |
114 | AttrCompletion { | 118 | AttrCompletion { |
@@ -126,6 +130,106 @@ const ATTRIBUTES: &[AttrCompletion] = &[ | |||
126 | }, | 130 | }, |
127 | ]; | 131 | ]; |
128 | 132 | ||
133 | fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) { | ||
134 | if let Ok(existing_derives) = parse_derive_input(derive_input) { | ||
135 | for derive_completion in DEFAULT_DERIVE_COMPLETIONS | ||
136 | .into_iter() | ||
137 | .filter(|completion| !existing_derives.contains(completion.label)) | ||
138 | { | ||
139 | let mut label = derive_completion.label.to_owned(); | ||
140 | for dependency in derive_completion | ||
141 | .dependencies | ||
142 | .into_iter() | ||
143 | .filter(|&&dependency| !existing_derives.contains(dependency)) | ||
144 | { | ||
145 | label.push_str(", "); | ||
146 | label.push_str(dependency); | ||
147 | } | ||
148 | acc.add( | ||
149 | CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label) | ||
150 | .kind(CompletionItemKind::Attribute), | ||
151 | ); | ||
152 | } | ||
153 | |||
154 | for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) { | ||
155 | acc.add( | ||
156 | CompletionItem::new( | ||
157 | CompletionKind::Attribute, | ||
158 | ctx.source_range(), | ||
159 | custom_derive_name, | ||
160 | ) | ||
161 | .kind(CompletionItemKind::Attribute), | ||
162 | ); | ||
163 | } | ||
164 | } | ||
165 | } | ||
166 | |||
167 | fn parse_derive_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> { | ||
168 | match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) { | ||
169 | (Some(left_paren), Some(right_paren)) | ||
170 | if left_paren.kind() == SyntaxKind::L_PAREN | ||
171 | && right_paren.kind() == SyntaxKind::R_PAREN => | ||
172 | { | ||
173 | let mut input_derives = FxHashSet::default(); | ||
174 | let mut current_derive = String::new(); | ||
175 | for token in derive_input | ||
176 | .syntax() | ||
177 | .children_with_tokens() | ||
178 | .filter_map(|token| token.into_token()) | ||
179 | .skip_while(|token| token != &left_paren) | ||
180 | .skip(1) | ||
181 | .take_while(|token| token != &right_paren) | ||
182 | { | ||
183 | if SyntaxKind::COMMA == token.kind() { | ||
184 | if !current_derive.is_empty() { | ||
185 | input_derives.insert(current_derive); | ||
186 | current_derive = String::new(); | ||
187 | } | ||
188 | } else { | ||
189 | current_derive.push_str(token.to_string().trim()); | ||
190 | } | ||
191 | } | ||
192 | |||
193 | if !current_derive.is_empty() { | ||
194 | input_derives.insert(current_derive); | ||
195 | } | ||
196 | Ok(input_derives) | ||
197 | } | ||
198 | _ => Err(()), | ||
199 | } | ||
200 | } | ||
201 | |||
202 | fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet<String> { | ||
203 | let mut result = FxHashSet::default(); | ||
204 | ctx.scope().process_all_names(&mut |name, scope_def| { | ||
205 | if let hir::ScopeDef::MacroDef(mac) = scope_def { | ||
206 | if mac.is_derive_macro() { | ||
207 | result.insert(name.to_string()); | ||
208 | } | ||
209 | } | ||
210 | }); | ||
211 | result | ||
212 | } | ||
213 | |||
214 | struct DeriveCompletion { | ||
215 | label: &'static str, | ||
216 | dependencies: &'static [&'static str], | ||
217 | } | ||
218 | |||
219 | /// Standard Rust derives and the information about their dependencies | ||
220 | /// (the dependencies are needed so that the main derive don't break the compilation when added) | ||
221 | const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[ | ||
222 | DeriveCompletion { label: "Clone", dependencies: &[] }, | ||
223 | DeriveCompletion { label: "Copy", dependencies: &["Clone"] }, | ||
224 | DeriveCompletion { label: "Debug", dependencies: &[] }, | ||
225 | DeriveCompletion { label: "Default", dependencies: &[] }, | ||
226 | DeriveCompletion { label: "Hash", dependencies: &[] }, | ||
227 | DeriveCompletion { label: "PartialEq", dependencies: &[] }, | ||
228 | DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] }, | ||
229 | DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] }, | ||
230 | DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] }, | ||
231 | ]; | ||
232 | |||
129 | #[cfg(test)] | 233 | #[cfg(test)] |
130 | mod tests { | 234 | mod tests { |
131 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; | 235 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; |
@@ -136,6 +240,170 @@ mod tests { | |||
136 | } | 240 | } |
137 | 241 | ||
138 | #[test] | 242 | #[test] |
243 | fn empty_derive_completion() { | ||
244 | assert_debug_snapshot!( | ||
245 | do_attr_completion( | ||
246 | r" | ||
247 | #[derive(<|>)] | ||
248 | struct Test {} | ||
249 | ", | ||
250 | ), | ||
251 | @r###" | ||
252 | [ | ||
253 | CompletionItem { | ||
254 | label: "Clone", | ||
255 | source_range: 30..30, | ||
256 | delete: 30..30, | ||
257 | insert: "Clone", | ||
258 | kind: Attribute, | ||
259 | }, | ||
260 | CompletionItem { | ||
261 | label: "Copy, Clone", | ||
262 | source_range: 30..30, | ||
263 | delete: 30..30, | ||
264 | insert: "Copy, Clone", | ||
265 | kind: Attribute, | ||
266 | }, | ||
267 | CompletionItem { | ||
268 | label: "Debug", | ||
269 | source_range: 30..30, | ||
270 | delete: 30..30, | ||
271 | insert: "Debug", | ||
272 | kind: Attribute, | ||
273 | }, | ||
274 | CompletionItem { | ||
275 | label: "Default", | ||
276 | source_range: 30..30, | ||
277 | delete: 30..30, | ||
278 | insert: "Default", | ||
279 | kind: Attribute, | ||
280 | }, | ||
281 | CompletionItem { | ||
282 | label: "Eq, PartialEq", | ||
283 | source_range: 30..30, | ||
284 | delete: 30..30, | ||
285 | insert: "Eq, PartialEq", | ||
286 | kind: Attribute, | ||
287 | }, | ||
288 | CompletionItem { | ||
289 | label: "Hash", | ||
290 | source_range: 30..30, | ||
291 | delete: 30..30, | ||
292 | insert: "Hash", | ||
293 | kind: Attribute, | ||
294 | }, | ||
295 | CompletionItem { | ||
296 | label: "Ord, PartialOrd, Eq, PartialEq", | ||
297 | source_range: 30..30, | ||
298 | delete: 30..30, | ||
299 | insert: "Ord, PartialOrd, Eq, PartialEq", | ||
300 | kind: Attribute, | ||
301 | }, | ||
302 | CompletionItem { | ||
303 | label: "PartialEq", | ||
304 | source_range: 30..30, | ||
305 | delete: 30..30, | ||
306 | insert: "PartialEq", | ||
307 | kind: Attribute, | ||
308 | }, | ||
309 | CompletionItem { | ||
310 | label: "PartialOrd, PartialEq", | ||
311 | source_range: 30..30, | ||
312 | delete: 30..30, | ||
313 | insert: "PartialOrd, PartialEq", | ||
314 | kind: Attribute, | ||
315 | }, | ||
316 | ] | ||
317 | "### | ||
318 | ); | ||
319 | } | ||
320 | |||
321 | #[test] | ||
322 | fn no_completion_for_incorrect_derive() { | ||
323 | assert_debug_snapshot!( | ||
324 | do_attr_completion( | ||
325 | r" | ||
326 | #[derive{<|>)] | ||
327 | struct Test {} | ||
328 | ", | ||
329 | ), | ||
330 | @"[]" | ||
331 | ); | ||
332 | } | ||
333 | |||
334 | #[test] | ||
335 | fn derive_with_input_completion() { | ||
336 | assert_debug_snapshot!( | ||
337 | do_attr_completion( | ||
338 | r" | ||
339 | #[derive(serde::Serialize, PartialEq, <|>)] | ||
340 | struct Test {} | ||
341 | ", | ||
342 | ), | ||
343 | @r###" | ||
344 | [ | ||
345 | CompletionItem { | ||
346 | label: "Clone", | ||
347 | source_range: 59..59, | ||
348 | delete: 59..59, | ||
349 | insert: "Clone", | ||
350 | kind: Attribute, | ||
351 | }, | ||
352 | CompletionItem { | ||
353 | label: "Copy, Clone", | ||
354 | source_range: 59..59, | ||
355 | delete: 59..59, | ||
356 | insert: "Copy, Clone", | ||
357 | kind: Attribute, | ||
358 | }, | ||
359 | CompletionItem { | ||
360 | label: "Debug", | ||
361 | source_range: 59..59, | ||
362 | delete: 59..59, | ||
363 | insert: "Debug", | ||
364 | kind: Attribute, | ||
365 | }, | ||
366 | CompletionItem { | ||
367 | label: "Default", | ||
368 | source_range: 59..59, | ||
369 | delete: 59..59, | ||
370 | insert: "Default", | ||
371 | kind: Attribute, | ||
372 | }, | ||
373 | CompletionItem { | ||
374 | label: "Eq", | ||
375 | source_range: 59..59, | ||
376 | delete: 59..59, | ||
377 | insert: "Eq", | ||
378 | kind: Attribute, | ||
379 | }, | ||
380 | CompletionItem { | ||
381 | label: "Hash", | ||
382 | source_range: 59..59, | ||
383 | delete: 59..59, | ||
384 | insert: "Hash", | ||
385 | kind: Attribute, | ||
386 | }, | ||
387 | CompletionItem { | ||
388 | label: "Ord, PartialOrd, Eq", | ||
389 | source_range: 59..59, | ||
390 | delete: 59..59, | ||
391 | insert: "Ord, PartialOrd, Eq", | ||
392 | kind: Attribute, | ||
393 | }, | ||
394 | CompletionItem { | ||
395 | label: "PartialOrd", | ||
396 | source_range: 59..59, | ||
397 | delete: 59..59, | ||
398 | insert: "PartialOrd", | ||
399 | kind: Attribute, | ||
400 | }, | ||
401 | ] | ||
402 | "### | ||
403 | ); | ||
404 | } | ||
405 | |||
406 | #[test] | ||
139 | fn test_attribute_completion() { | 407 | fn test_attribute_completion() { |
140 | assert_debug_snapshot!( | 408 | assert_debug_snapshot!( |
141 | do_attr_completion( | 409 | do_attr_completion( |
@@ -303,7 +571,7 @@ mod tests { | |||
303 | label: "should_panic", | 571 | label: "should_panic", |
304 | source_range: 19..19, | 572 | source_range: 19..19, |
305 | delete: 19..19, | 573 | delete: 19..19, |
306 | insert: "expected = \"${0:reason}\"", | 574 | insert: "should_panic(expected = \"${0:reason}\")", |
307 | kind: Attribute, | 575 | kind: Attribute, |
308 | }, | 576 | }, |
309 | CompletionItem { | 577 | CompletionItem { |
@@ -542,7 +810,7 @@ mod tests { | |||
542 | label: "should_panic", | 810 | label: "should_panic", |
543 | source_range: 20..20, | 811 | source_range: 20..20, |
544 | delete: 20..20, | 812 | delete: 20..20, |
545 | insert: "expected = \"${0:reason}\"", | 813 | insert: "should_panic(expected = \"${0:reason}\")", |
546 | kind: Attribute, | 814 | kind: Attribute, |
547 | }, | 815 | }, |
548 | CompletionItem { | 816 | CompletionItem { |