diff options
author | Kirill Bulatov <[email protected]> | 2020-05-01 01:46:17 +0100 |
---|---|---|
committer | Kirill Bulatov <[email protected]> | 2020-05-02 19:41:02 +0100 |
commit | 767bff89ededdff875b042bb37397b972e3a82f1 (patch) | |
tree | bc2d1cfa1211d471bfcb9abfb6ea0b0d0b57b300 | |
parent | c4b32d15340e1dae74d3eaa701ef92ae0f9e295e (diff) |
Complete standard derives
-rw-r--r-- | crates/ra_ide/src/completion/complete_attribute.rs | 255 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/completion_context.rs | 6 |
2 files changed, 245 insertions, 16 deletions
diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs index 8bf952798..8934b45fe 100644 --- a/crates/ra_ide/src/completion/complete_attribute.rs +++ b/crates/ra_ide/src/completion/complete_attribute.rs | |||
@@ -5,23 +5,26 @@ | |||
5 | 5 | ||
6 | use super::completion_context::CompletionContext; | 6 | use super::completion_context::CompletionContext; |
7 | use super::completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions}; | 7 | use super::completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions}; |
8 | use ast::AttrInput; | ||
8 | use ra_syntax::{ | 9 | use ra_syntax::{ |
9 | ast::{Attr, AttrKind}, | 10 | ast::{self, AttrKind}, |
10 | AstNode, | 11 | AstNode, SyntaxKind, |
11 | }; | 12 | }; |
13 | use rustc_hash::FxHashSet; | ||
12 | 14 | ||
13 | pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) { | 15 | pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { |
14 | if !ctx.is_attribute { | 16 | let attribute = ctx.attribute_under_caret.as_ref()?; |
15 | return; | ||
16 | } | ||
17 | 17 | ||
18 | let is_inner = ctx | 18 | match (attribute.path(), attribute.input()) { |
19 | .original_token | 19 | (Some(path), Some(AttrInput::TokenTree(token_tree))) if path.to_string() == "derive" => { |
20 | .ancestors() | 20 | complete_derive(acc, ctx, token_tree) |
21 | .find_map(Attr::cast) | 21 | } |
22 | .map(|attr| attr.kind() == AttrKind::Inner) | 22 | _ => complete_attribute_start(acc, ctx, attribute), |
23 | .unwrap_or(false); | 23 | } |
24 | Some(()) | ||
25 | } | ||
24 | 26 | ||
27 | fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) { | ||
25 | for attr_completion in ATTRIBUTES { | 28 | for attr_completion in ATTRIBUTES { |
26 | let mut item = CompletionItem::new( | 29 | let mut item = CompletionItem::new( |
27 | CompletionKind::Attribute, | 30 | CompletionKind::Attribute, |
@@ -37,7 +40,7 @@ pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) | |||
37 | _ => {} | 40 | _ => {} |
38 | } | 41 | } |
39 | 42 | ||
40 | if is_inner || !attr_completion.should_be_inner { | 43 | if attribute.kind() == AttrKind::Inner || !attr_completion.should_be_inner { |
41 | acc.add(item); | 44 | acc.add(item); |
42 | } | 45 | } |
43 | } | 46 | } |
@@ -126,6 +129,68 @@ const ATTRIBUTES: &[AttrCompletion] = &[ | |||
126 | }, | 129 | }, |
127 | ]; | 130 | ]; |
128 | 131 | ||
132 | fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) { | ||
133 | // TODO kb autodetect derive macros | ||
134 | // https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Find.20all.20possible.20derive.20macro.20values.3F/near/195955580 | ||
135 | |||
136 | if let Ok(existing_derives) = parse_derive_input(derive_input) { | ||
137 | for derive_completion in DERIVE_COMPLETIONS | ||
138 | .into_iter() | ||
139 | .filter(|completion| !existing_derives.contains(completion.label)) | ||
140 | { | ||
141 | let mut label = derive_completion.label.to_owned(); | ||
142 | for dependency in derive_completion | ||
143 | .dependencies | ||
144 | .into_iter() | ||
145 | .filter(|&&dependency| !existing_derives.contains(dependency)) | ||
146 | { | ||
147 | label.push_str(", "); | ||
148 | label.push_str(dependency); | ||
149 | } | ||
150 | let item = CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label) | ||
151 | .kind(CompletionItemKind::Attribute); | ||
152 | acc.add(item); | ||
153 | } | ||
154 | } | ||
155 | } | ||
156 | |||
157 | fn parse_derive_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> { | ||
158 | match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) { | ||
159 | (Some(left_paren), Some(right_paren)) | ||
160 | if left_paren.kind() == SyntaxKind::L_PAREN | ||
161 | && right_paren.kind() == SyntaxKind::R_PAREN => | ||
162 | { | ||
163 | Ok(derive_input | ||
164 | .syntax() | ||
165 | .children_with_tokens() | ||
166 | .filter_map(|child| child.into_token()) | ||
167 | .skip_while(|child| child != &left_paren) | ||
168 | .take_while(|child| child != &right_paren) | ||
169 | .filter(|child| child.kind() == SyntaxKind::IDENT) | ||
170 | .map(|child| child.to_string()) | ||
171 | .collect()) | ||
172 | } | ||
173 | _ => Err(()), | ||
174 | } | ||
175 | } | ||
176 | |||
177 | struct DeriveCompletion { | ||
178 | label: &'static str, | ||
179 | dependencies: &'static [&'static str], | ||
180 | } | ||
181 | |||
182 | const DERIVE_COMPLETIONS: &[DeriveCompletion] = &[ | ||
183 | DeriveCompletion { label: "Clone", dependencies: &[] }, | ||
184 | DeriveCompletion { label: "Copy", dependencies: &["Clone"] }, | ||
185 | DeriveCompletion { label: "Debug", dependencies: &[] }, | ||
186 | DeriveCompletion { label: "Default", dependencies: &[] }, | ||
187 | DeriveCompletion { label: "Hash", dependencies: &[] }, | ||
188 | DeriveCompletion { label: "PartialEq", dependencies: &[] }, | ||
189 | DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] }, | ||
190 | DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] }, | ||
191 | DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] }, | ||
192 | ]; | ||
193 | |||
129 | #[cfg(test)] | 194 | #[cfg(test)] |
130 | mod tests { | 195 | mod tests { |
131 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; | 196 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; |
@@ -136,6 +201,170 @@ mod tests { | |||
136 | } | 201 | } |
137 | 202 | ||
138 | #[test] | 203 | #[test] |
204 | fn empty_derive_completion() { | ||
205 | assert_debug_snapshot!( | ||
206 | do_attr_completion( | ||
207 | r" | ||
208 | #[derive(<|>)] | ||
209 | struct Test {} | ||
210 | ", | ||
211 | ), | ||
212 | @r###" | ||
213 | [ | ||
214 | CompletionItem { | ||
215 | label: "Clone", | ||
216 | source_range: 30..30, | ||
217 | delete: 30..30, | ||
218 | insert: "Clone", | ||
219 | kind: Attribute, | ||
220 | }, | ||
221 | CompletionItem { | ||
222 | label: "Copy, Clone", | ||
223 | source_range: 30..30, | ||
224 | delete: 30..30, | ||
225 | insert: "Copy, Clone", | ||
226 | kind: Attribute, | ||
227 | }, | ||
228 | CompletionItem { | ||
229 | label: "Debug", | ||
230 | source_range: 30..30, | ||
231 | delete: 30..30, | ||
232 | insert: "Debug", | ||
233 | kind: Attribute, | ||
234 | }, | ||
235 | CompletionItem { | ||
236 | label: "Default", | ||
237 | source_range: 30..30, | ||
238 | delete: 30..30, | ||
239 | insert: "Default", | ||
240 | kind: Attribute, | ||
241 | }, | ||
242 | CompletionItem { | ||
243 | label: "Eq, PartialEq", | ||
244 | source_range: 30..30, | ||
245 | delete: 30..30, | ||
246 | insert: "Eq, PartialEq", | ||
247 | kind: Attribute, | ||
248 | }, | ||
249 | CompletionItem { | ||
250 | label: "Hash", | ||
251 | source_range: 30..30, | ||
252 | delete: 30..30, | ||
253 | insert: "Hash", | ||
254 | kind: Attribute, | ||
255 | }, | ||
256 | CompletionItem { | ||
257 | label: "Ord, PartialOrd, Eq, PartialEq", | ||
258 | source_range: 30..30, | ||
259 | delete: 30..30, | ||
260 | insert: "Ord, PartialOrd, Eq, PartialEq", | ||
261 | kind: Attribute, | ||
262 | }, | ||
263 | CompletionItem { | ||
264 | label: "PartialEq", | ||
265 | source_range: 30..30, | ||
266 | delete: 30..30, | ||
267 | insert: "PartialEq", | ||
268 | kind: Attribute, | ||
269 | }, | ||
270 | CompletionItem { | ||
271 | label: "PartialOrd, PartialEq", | ||
272 | source_range: 30..30, | ||
273 | delete: 30..30, | ||
274 | insert: "PartialOrd, PartialEq", | ||
275 | kind: Attribute, | ||
276 | }, | ||
277 | ] | ||
278 | "### | ||
279 | ); | ||
280 | } | ||
281 | |||
282 | #[test] | ||
283 | fn no_completion_for_incorrect_derive() { | ||
284 | assert_debug_snapshot!( | ||
285 | do_attr_completion( | ||
286 | r" | ||
287 | #[derive{<|>)] | ||
288 | struct Test {} | ||
289 | ", | ||
290 | ), | ||
291 | @"[]" | ||
292 | ); | ||
293 | } | ||
294 | |||
295 | #[test] | ||
296 | fn derive_with_input_completion() { | ||
297 | assert_debug_snapshot!( | ||
298 | do_attr_completion( | ||
299 | r" | ||
300 | #[derive(Whatever, PartialEq, <|>)] | ||
301 | struct Test {} | ||
302 | ", | ||
303 | ), | ||
304 | @r###" | ||
305 | [ | ||
306 | CompletionItem { | ||
307 | label: "Clone", | ||
308 | source_range: 51..51, | ||
309 | delete: 51..51, | ||
310 | insert: "Clone", | ||
311 | kind: Attribute, | ||
312 | }, | ||
313 | CompletionItem { | ||
314 | label: "Copy, Clone", | ||
315 | source_range: 51..51, | ||
316 | delete: 51..51, | ||
317 | insert: "Copy, Clone", | ||
318 | kind: Attribute, | ||
319 | }, | ||
320 | CompletionItem { | ||
321 | label: "Debug", | ||
322 | source_range: 51..51, | ||
323 | delete: 51..51, | ||
324 | insert: "Debug", | ||
325 | kind: Attribute, | ||
326 | }, | ||
327 | CompletionItem { | ||
328 | label: "Default", | ||
329 | source_range: 51..51, | ||
330 | delete: 51..51, | ||
331 | insert: "Default", | ||
332 | kind: Attribute, | ||
333 | }, | ||
334 | CompletionItem { | ||
335 | label: "Eq", | ||
336 | source_range: 51..51, | ||
337 | delete: 51..51, | ||
338 | insert: "Eq", | ||
339 | kind: Attribute, | ||
340 | }, | ||
341 | CompletionItem { | ||
342 | label: "Hash", | ||
343 | source_range: 51..51, | ||
344 | delete: 51..51, | ||
345 | insert: "Hash", | ||
346 | kind: Attribute, | ||
347 | }, | ||
348 | CompletionItem { | ||
349 | label: "Ord, PartialOrd, Eq", | ||
350 | source_range: 51..51, | ||
351 | delete: 51..51, | ||
352 | insert: "Ord, PartialOrd, Eq", | ||
353 | kind: Attribute, | ||
354 | }, | ||
355 | CompletionItem { | ||
356 | label: "PartialOrd", | ||
357 | source_range: 51..51, | ||
358 | delete: 51..51, | ||
359 | insert: "PartialOrd", | ||
360 | kind: Attribute, | ||
361 | }, | ||
362 | ] | ||
363 | "### | ||
364 | ); | ||
365 | } | ||
366 | |||
367 | #[test] | ||
139 | fn test_attribute_completion() { | 368 | fn test_attribute_completion() { |
140 | assert_debug_snapshot!( | 369 | assert_debug_snapshot!( |
141 | do_attr_completion( | 370 | do_attr_completion( |
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index c529752d4..dd87bd119 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs | |||
@@ -58,7 +58,7 @@ pub(crate) struct CompletionContext<'a> { | |||
58 | pub(super) is_macro_call: bool, | 58 | pub(super) is_macro_call: bool, |
59 | pub(super) is_path_type: bool, | 59 | pub(super) is_path_type: bool, |
60 | pub(super) has_type_args: bool, | 60 | pub(super) has_type_args: bool, |
61 | pub(super) is_attribute: bool, | 61 | pub(super) attribute_under_caret: Option<ast::Attr>, |
62 | } | 62 | } |
63 | 63 | ||
64 | impl<'a> CompletionContext<'a> { | 64 | impl<'a> CompletionContext<'a> { |
@@ -116,7 +116,7 @@ impl<'a> CompletionContext<'a> { | |||
116 | is_path_type: false, | 116 | is_path_type: false, |
117 | has_type_args: false, | 117 | has_type_args: false, |
118 | dot_receiver_is_ambiguous_float_literal: false, | 118 | dot_receiver_is_ambiguous_float_literal: false, |
119 | is_attribute: false, | 119 | attribute_under_caret: None, |
120 | }; | 120 | }; |
121 | 121 | ||
122 | let mut original_file = original_file.syntax().clone(); | 122 | let mut original_file = original_file.syntax().clone(); |
@@ -200,6 +200,7 @@ impl<'a> CompletionContext<'a> { | |||
200 | Some(ty) | 200 | Some(ty) |
201 | }) | 201 | }) |
202 | .flatten(); | 202 | .flatten(); |
203 | self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); | ||
203 | 204 | ||
204 | // First, let's try to complete a reference to some declaration. | 205 | // First, let's try to complete a reference to some declaration. |
205 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { | 206 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { |
@@ -318,7 +319,6 @@ impl<'a> CompletionContext<'a> { | |||
318 | .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast)) | 319 | .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast)) |
319 | .is_some(); | 320 | .is_some(); |
320 | self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some(); | 321 | self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some(); |
321 | self.is_attribute = path.syntax().parent().and_then(ast::Attr::cast).is_some(); | ||
322 | 322 | ||
323 | self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); | 323 | self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); |
324 | self.has_type_args = segment.type_arg_list().is_some(); | 324 | self.has_type_args = segment.type_arg_list().is_some(); |