diff options
Diffstat (limited to 'crates/ra_ide/src/completion')
-rw-r--r-- | crates/ra_ide/src/completion/complete_attribute.rs | 293 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/completion_context.rs | 8 |
2 files changed, 284 insertions, 17 deletions
diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs index 8bf952798..20e6edc17 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,106 @@ const ATTRIBUTES: &[AttrCompletion] = &[ | |||
126 | }, | 129 | }, |
127 | ]; | 130 | ]; |
128 | 131 | ||
132 | fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) { | ||
133 | if let Ok(existing_derives) = parse_derive_input(derive_input) { | ||
134 | for derive_completion in DEFAULT_DERIVE_COMPLETIONS | ||
135 | .into_iter() | ||
136 | .filter(|completion| !existing_derives.contains(completion.label)) | ||
137 | { | ||
138 | let mut label = derive_completion.label.to_owned(); | ||
139 | for dependency in derive_completion | ||
140 | .dependencies | ||
141 | .into_iter() | ||
142 | .filter(|&&dependency| !existing_derives.contains(dependency)) | ||
143 | { | ||
144 | label.push_str(", "); | ||
145 | label.push_str(dependency); | ||
146 | } | ||
147 | acc.add( | ||
148 | CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label) | ||
149 | .kind(CompletionItemKind::Attribute), | ||
150 | ); | ||
151 | } | ||
152 | |||
153 | for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) { | ||
154 | acc.add( | ||
155 | CompletionItem::new( | ||
156 | CompletionKind::Attribute, | ||
157 | ctx.source_range(), | ||
158 | custom_derive_name, | ||
159 | ) | ||
160 | .kind(CompletionItemKind::Attribute), | ||
161 | ); | ||
162 | } | ||
163 | } | ||
164 | } | ||
165 | |||
166 | fn parse_derive_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> { | ||
167 | match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) { | ||
168 | (Some(left_paren), Some(right_paren)) | ||
169 | if left_paren.kind() == SyntaxKind::L_PAREN | ||
170 | && right_paren.kind() == SyntaxKind::R_PAREN => | ||
171 | { | ||
172 | let mut input_derives = FxHashSet::default(); | ||
173 | let mut current_derive = String::new(); | ||
174 | for token in derive_input | ||
175 | .syntax() | ||
176 | .children_with_tokens() | ||
177 | .filter_map(|token| token.into_token()) | ||
178 | .skip_while(|token| token != &left_paren) | ||
179 | .skip(1) | ||
180 | .take_while(|token| token != &right_paren) | ||
181 | { | ||
182 | if SyntaxKind::COMMA == token.kind() { | ||
183 | if !current_derive.is_empty() { | ||
184 | input_derives.insert(current_derive); | ||
185 | current_derive = String::new(); | ||
186 | } | ||
187 | } else { | ||
188 | current_derive.push_str(token.to_string().trim()); | ||
189 | } | ||
190 | } | ||
191 | |||
192 | if !current_derive.is_empty() { | ||
193 | input_derives.insert(current_derive); | ||
194 | } | ||
195 | Ok(input_derives) | ||
196 | } | ||
197 | _ => Err(()), | ||
198 | } | ||
199 | } | ||
200 | |||
201 | fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet<String> { | ||
202 | let mut result = FxHashSet::default(); | ||
203 | ctx.scope().process_all_names(&mut |name, scope_def| { | ||
204 | if let hir::ScopeDef::MacroDef(mac) = scope_def { | ||
205 | if mac.is_derive_macro() { | ||
206 | result.insert(name.to_string()); | ||
207 | } | ||
208 | } | ||
209 | }); | ||
210 | result | ||
211 | } | ||
212 | |||
213 | struct DeriveCompletion { | ||
214 | label: &'static str, | ||
215 | dependencies: &'static [&'static str], | ||
216 | } | ||
217 | |||
218 | /// Standard Rust derives and the information about their dependencies | ||
219 | /// (the dependencies are needed so that the main derive don't break the compilation when added) | ||
220 | const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[ | ||
221 | DeriveCompletion { label: "Clone", dependencies: &[] }, | ||
222 | DeriveCompletion { label: "Copy", dependencies: &["Clone"] }, | ||
223 | DeriveCompletion { label: "Debug", dependencies: &[] }, | ||
224 | DeriveCompletion { label: "Default", dependencies: &[] }, | ||
225 | DeriveCompletion { label: "Hash", dependencies: &[] }, | ||
226 | DeriveCompletion { label: "PartialEq", dependencies: &[] }, | ||
227 | DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] }, | ||
228 | DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] }, | ||
229 | DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] }, | ||
230 | ]; | ||
231 | |||
129 | #[cfg(test)] | 232 | #[cfg(test)] |
130 | mod tests { | 233 | mod tests { |
131 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; | 234 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; |
@@ -136,6 +239,170 @@ mod tests { | |||
136 | } | 239 | } |
137 | 240 | ||
138 | #[test] | 241 | #[test] |
242 | fn empty_derive_completion() { | ||
243 | assert_debug_snapshot!( | ||
244 | do_attr_completion( | ||
245 | r" | ||
246 | #[derive(<|>)] | ||
247 | struct Test {} | ||
248 | ", | ||
249 | ), | ||
250 | @r###" | ||
251 | [ | ||
252 | CompletionItem { | ||
253 | label: "Clone", | ||
254 | source_range: 30..30, | ||
255 | delete: 30..30, | ||
256 | insert: "Clone", | ||
257 | kind: Attribute, | ||
258 | }, | ||
259 | CompletionItem { | ||
260 | label: "Copy, Clone", | ||
261 | source_range: 30..30, | ||
262 | delete: 30..30, | ||
263 | insert: "Copy, Clone", | ||
264 | kind: Attribute, | ||
265 | }, | ||
266 | CompletionItem { | ||
267 | label: "Debug", | ||
268 | source_range: 30..30, | ||
269 | delete: 30..30, | ||
270 | insert: "Debug", | ||
271 | kind: Attribute, | ||
272 | }, | ||
273 | CompletionItem { | ||
274 | label: "Default", | ||
275 | source_range: 30..30, | ||
276 | delete: 30..30, | ||
277 | insert: "Default", | ||
278 | kind: Attribute, | ||
279 | }, | ||
280 | CompletionItem { | ||
281 | label: "Eq, PartialEq", | ||
282 | source_range: 30..30, | ||
283 | delete: 30..30, | ||
284 | insert: "Eq, PartialEq", | ||
285 | kind: Attribute, | ||
286 | }, | ||
287 | CompletionItem { | ||
288 | label: "Hash", | ||
289 | source_range: 30..30, | ||
290 | delete: 30..30, | ||
291 | insert: "Hash", | ||
292 | kind: Attribute, | ||
293 | }, | ||
294 | CompletionItem { | ||
295 | label: "Ord, PartialOrd, Eq, PartialEq", | ||
296 | source_range: 30..30, | ||
297 | delete: 30..30, | ||
298 | insert: "Ord, PartialOrd, Eq, PartialEq", | ||
299 | kind: Attribute, | ||
300 | }, | ||
301 | CompletionItem { | ||
302 | label: "PartialEq", | ||
303 | source_range: 30..30, | ||
304 | delete: 30..30, | ||
305 | insert: "PartialEq", | ||
306 | kind: Attribute, | ||
307 | }, | ||
308 | CompletionItem { | ||
309 | label: "PartialOrd, PartialEq", | ||
310 | source_range: 30..30, | ||
311 | delete: 30..30, | ||
312 | insert: "PartialOrd, PartialEq", | ||
313 | kind: Attribute, | ||
314 | }, | ||
315 | ] | ||
316 | "### | ||
317 | ); | ||
318 | } | ||
319 | |||
320 | #[test] | ||
321 | fn no_completion_for_incorrect_derive() { | ||
322 | assert_debug_snapshot!( | ||
323 | do_attr_completion( | ||
324 | r" | ||
325 | #[derive{<|>)] | ||
326 | struct Test {} | ||
327 | ", | ||
328 | ), | ||
329 | @"[]" | ||
330 | ); | ||
331 | } | ||
332 | |||
333 | #[test] | ||
334 | fn derive_with_input_completion() { | ||
335 | assert_debug_snapshot!( | ||
336 | do_attr_completion( | ||
337 | r" | ||
338 | #[derive(serde::Serialize, PartialEq, <|>)] | ||
339 | struct Test {} | ||
340 | ", | ||
341 | ), | ||
342 | @r###" | ||
343 | [ | ||
344 | CompletionItem { | ||
345 | label: "Clone", | ||
346 | source_range: 59..59, | ||
347 | delete: 59..59, | ||
348 | insert: "Clone", | ||
349 | kind: Attribute, | ||
350 | }, | ||
351 | CompletionItem { | ||
352 | label: "Copy, Clone", | ||
353 | source_range: 59..59, | ||
354 | delete: 59..59, | ||
355 | insert: "Copy, Clone", | ||
356 | kind: Attribute, | ||
357 | }, | ||
358 | CompletionItem { | ||
359 | label: "Debug", | ||
360 | source_range: 59..59, | ||
361 | delete: 59..59, | ||
362 | insert: "Debug", | ||
363 | kind: Attribute, | ||
364 | }, | ||
365 | CompletionItem { | ||
366 | label: "Default", | ||
367 | source_range: 59..59, | ||
368 | delete: 59..59, | ||
369 | insert: "Default", | ||
370 | kind: Attribute, | ||
371 | }, | ||
372 | CompletionItem { | ||
373 | label: "Eq", | ||
374 | source_range: 59..59, | ||
375 | delete: 59..59, | ||
376 | insert: "Eq", | ||
377 | kind: Attribute, | ||
378 | }, | ||
379 | CompletionItem { | ||
380 | label: "Hash", | ||
381 | source_range: 59..59, | ||
382 | delete: 59..59, | ||
383 | insert: "Hash", | ||
384 | kind: Attribute, | ||
385 | }, | ||
386 | CompletionItem { | ||
387 | label: "Ord, PartialOrd, Eq", | ||
388 | source_range: 59..59, | ||
389 | delete: 59..59, | ||
390 | insert: "Ord, PartialOrd, Eq", | ||
391 | kind: Attribute, | ||
392 | }, | ||
393 | CompletionItem { | ||
394 | label: "PartialOrd", | ||
395 | source_range: 59..59, | ||
396 | delete: 59..59, | ||
397 | insert: "PartialOrd", | ||
398 | kind: Attribute, | ||
399 | }, | ||
400 | ] | ||
401 | "### | ||
402 | ); | ||
403 | } | ||
404 | |||
405 | #[test] | ||
139 | fn test_attribute_completion() { | 406 | fn test_attribute_completion() { |
140 | assert_debug_snapshot!( | 407 | assert_debug_snapshot!( |
141 | do_attr_completion( | 408 | do_attr_completion( |
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index 118fceb2e..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(); |
@@ -344,7 +344,7 @@ impl<'a> CompletionContext<'a> { | |||
344 | stmt.syntax().text_range() == name_ref.syntax().text_range(), | 344 | stmt.syntax().text_range() == name_ref.syntax().text_range(), |
345 | ); | 345 | ); |
346 | } | 346 | } |
347 | if let Some(block) = ast::Block::cast(node) { | 347 | if let Some(block) = ast::BlockExpr::cast(node) { |
348 | return Some( | 348 | return Some( |
349 | block.expr().map(|e| e.syntax().text_range()) | 349 | block.expr().map(|e| e.syntax().text_range()) |
350 | == Some(name_ref.syntax().text_range()), | 350 | == Some(name_ref.syntax().text_range()), |