diff options
Diffstat (limited to 'crates/ra_analysis/src/completion.rs')
-rw-r--r-- | crates/ra_analysis/src/completion.rs | 466 |
1 files changed, 41 insertions, 425 deletions
diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index f480af611..d742d6295 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs | |||
@@ -1,444 +1,60 @@ | |||
1 | mod reference_completion; | 1 | mod completion_item; |
2 | mod completion_context; | ||
3 | |||
4 | mod complete_fn_param; | ||
5 | mod complete_keyword; | ||
6 | mod complete_snippet; | ||
7 | mod complete_path; | ||
8 | mod complete_scope; | ||
2 | 9 | ||
3 | use ra_editor::find_node_at_offset; | ||
4 | use ra_text_edit::AtomTextEdit; | ||
5 | use ra_syntax::{ | ||
6 | algo::visit::{visitor_ctx, VisitorCtx}, | ||
7 | ast, | ||
8 | AstNode, | ||
9 | SyntaxNodeRef, | ||
10 | }; | ||
11 | use ra_db::SyntaxDatabase; | 10 | use ra_db::SyntaxDatabase; |
12 | use rustc_hash::{FxHashMap}; | ||
13 | use hir::source_binder; | ||
14 | 11 | ||
15 | use crate::{ | 12 | use crate::{ |
16 | db, | 13 | db, |
17 | Cancelable, FilePosition | 14 | Cancelable, FilePosition, |
15 | completion::{ | ||
16 | completion_item::{Completions, CompletionKind}, | ||
17 | completion_context::CompletionContext, | ||
18 | }, | ||
18 | }; | 19 | }; |
19 | 20 | ||
20 | #[derive(Debug)] | 21 | pub use crate::completion::completion_item::{CompletionItem, InsertText, CompletionItemKind}; |
21 | pub struct CompletionItem { | ||
22 | /// What user sees in pop-up | ||
23 | pub label: String, | ||
24 | /// What string is used for filtering, defaults to label | ||
25 | pub lookup: Option<String>, | ||
26 | /// What is inserted, defaults to label | ||
27 | pub snippet: Option<String>, | ||
28 | } | ||
29 | 22 | ||
23 | /// Main entry point for copmletion. We run comletion as a two-phase process. | ||
24 | /// | ||
25 | /// First, we look at the position and collect a so-called `CompletionContext. | ||
26 | /// This is a somewhat messy process, because, during completion, syntax tree is | ||
27 | /// incomplete and can look readlly weired. | ||
28 | /// | ||
29 | /// Once the context is collected, we run a series of completion routines whihc | ||
30 | /// look at the context and produce completion items. | ||
30 | pub(crate) fn completions( | 31 | pub(crate) fn completions( |
31 | db: &db::RootDatabase, | 32 | db: &db::RootDatabase, |
32 | position: FilePosition, | 33 | position: FilePosition, |
33 | ) -> Cancelable<Option<Vec<CompletionItem>>> { | 34 | ) -> Cancelable<Option<Completions>> { |
34 | let original_file = db.source_file(position.file_id); | 35 | let original_file = db.source_file(position.file_id); |
35 | // Insert a fake ident to get a valid parse tree | 36 | let ctx = ctry!(CompletionContext::new(db, &original_file, position)?); |
36 | let file = { | ||
37 | let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string()); | ||
38 | original_file.reparse(&edit) | ||
39 | }; | ||
40 | |||
41 | let module = ctry!(source_binder::module_from_position(db, position)?); | ||
42 | 37 | ||
43 | let mut res = Vec::new(); | 38 | let mut acc = Completions::default(); |
44 | let mut has_completions = false; | ||
45 | // First, let's try to complete a reference to some declaration. | ||
46 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) { | ||
47 | has_completions = true; | ||
48 | reference_completion::completions(&mut res, db, &module, &file, name_ref)?; | ||
49 | // special case, `trait T { fn foo(i_am_a_name_ref) {} }` | ||
50 | if is_node::<ast::Param>(name_ref.syntax()) { | ||
51 | param_completions(name_ref.syntax(), &mut res); | ||
52 | } | ||
53 | } | ||
54 | 39 | ||
55 | // Otherwise, if this is a declaration, use heuristics to suggest a name. | 40 | complete_fn_param::complete_fn_param(&mut acc, &ctx); |
56 | if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) { | 41 | complete_keyword::complete_expr_keyword(&mut acc, &ctx); |
57 | if is_node::<ast::Param>(name.syntax()) { | 42 | complete_snippet::complete_expr_snippet(&mut acc, &ctx); |
58 | has_completions = true; | 43 | complete_snippet::complete_item_snippet(&mut acc, &ctx); |
59 | param_completions(name.syntax(), &mut res); | 44 | complete_path::complete_path(&mut acc, &ctx)?; |
60 | } | 45 | complete_scope::complete_scope(&mut acc, &ctx)?; |
61 | } | ||
62 | let res = if has_completions { Some(res) } else { None }; | ||
63 | Ok(res) | ||
64 | } | ||
65 | |||
66 | fn param_completions(ctx: SyntaxNodeRef, acc: &mut Vec<CompletionItem>) { | ||
67 | let mut params = FxHashMap::default(); | ||
68 | for node in ctx.ancestors() { | ||
69 | let _ = visitor_ctx(&mut params) | ||
70 | .visit::<ast::SourceFile, _>(process) | ||
71 | .visit::<ast::ItemList, _>(process) | ||
72 | .accept(node); | ||
73 | } | ||
74 | params | ||
75 | .into_iter() | ||
76 | .filter_map(|(label, (count, param))| { | ||
77 | let lookup = param.pat()?.syntax().text().to_string(); | ||
78 | if count < 2 { | ||
79 | None | ||
80 | } else { | ||
81 | Some((label, lookup)) | ||
82 | } | ||
83 | }) | ||
84 | .for_each(|(label, lookup)| { | ||
85 | acc.push(CompletionItem { | ||
86 | label, | ||
87 | lookup: Some(lookup), | ||
88 | snippet: None, | ||
89 | }) | ||
90 | }); | ||
91 | |||
92 | fn process<'a, N: ast::FnDefOwner<'a>>( | ||
93 | node: N, | ||
94 | params: &mut FxHashMap<String, (u32, ast::Param<'a>)>, | ||
95 | ) { | ||
96 | node.functions() | ||
97 | .filter_map(|it| it.param_list()) | ||
98 | .flat_map(|it| it.params()) | ||
99 | .for_each(|param| { | ||
100 | let text = param.syntax().text().to_string(); | ||
101 | params.entry(text).or_insert((0, param)).0 += 1; | ||
102 | }) | ||
103 | } | ||
104 | } | ||
105 | 46 | ||
106 | fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool { | 47 | Ok(Some(acc)) |
107 | match node.ancestors().filter_map(N::cast).next() { | ||
108 | None => false, | ||
109 | Some(n) => n.syntax().range() == node.range(), | ||
110 | } | ||
111 | } | 48 | } |
112 | 49 | ||
113 | #[cfg(test)] | 50 | #[cfg(test)] |
114 | mod tests { | 51 | fn check_completion(code: &str, expected_completions: &str, kind: CompletionKind) { |
115 | use test_utils::assert_eq_dbg; | 52 | use crate::mock_analysis::{single_file_with_position, analysis_and_position}; |
116 | 53 | let (analysis, position) = if code.contains("//-") { | |
117 | use crate::mock_analysis::single_file_with_position; | 54 | analysis_and_position(code) |
118 | 55 | } else { | |
119 | use super::*; | 56 | single_file_with_position(code) |
120 | 57 | }; | |
121 | fn check_scope_completion(code: &str, expected_completions: &str) { | 58 | let completions = completions(&analysis.imp.db, position).unwrap().unwrap(); |
122 | let (analysis, position) = single_file_with_position(code); | 59 | completions.assert_match(expected_completions, kind); |
123 | let completions = completions(&analysis.imp.db, position) | ||
124 | .unwrap() | ||
125 | .unwrap() | ||
126 | .into_iter() | ||
127 | .filter(|c| c.snippet.is_none()) | ||
128 | .collect::<Vec<_>>(); | ||
129 | assert_eq_dbg(expected_completions, &completions); | ||
130 | } | ||
131 | |||
132 | fn check_snippet_completion(code: &str, expected_completions: &str) { | ||
133 | let (analysis, position) = single_file_with_position(code); | ||
134 | let completions = completions(&analysis.imp.db, position) | ||
135 | .unwrap() | ||
136 | .unwrap() | ||
137 | .into_iter() | ||
138 | .filter(|c| c.snippet.is_some()) | ||
139 | .collect::<Vec<_>>(); | ||
140 | assert_eq_dbg(expected_completions, &completions); | ||
141 | } | ||
142 | |||
143 | #[test] | ||
144 | fn test_completion_let_scope() { | ||
145 | check_scope_completion( | ||
146 | r" | ||
147 | fn quux(x: i32) { | ||
148 | let y = 92; | ||
149 | 1 + <|>; | ||
150 | let z = (); | ||
151 | } | ||
152 | ", | ||
153 | r#"[CompletionItem { label: "y", lookup: None, snippet: None }, | ||
154 | CompletionItem { label: "x", lookup: None, snippet: None }, | ||
155 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#, | ||
156 | ); | ||
157 | } | ||
158 | |||
159 | #[test] | ||
160 | fn test_completion_if_let_scope() { | ||
161 | check_scope_completion( | ||
162 | r" | ||
163 | fn quux() { | ||
164 | if let Some(x) = foo() { | ||
165 | let y = 92; | ||
166 | }; | ||
167 | if let Some(a) = bar() { | ||
168 | let b = 62; | ||
169 | 1 + <|> | ||
170 | } | ||
171 | } | ||
172 | ", | ||
173 | r#"[CompletionItem { label: "b", lookup: None, snippet: None }, | ||
174 | CompletionItem { label: "a", lookup: None, snippet: None }, | ||
175 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#, | ||
176 | ); | ||
177 | } | ||
178 | |||
179 | #[test] | ||
180 | fn test_completion_for_scope() { | ||
181 | check_scope_completion( | ||
182 | r" | ||
183 | fn quux() { | ||
184 | for x in &[1, 2, 3] { | ||
185 | <|> | ||
186 | } | ||
187 | } | ||
188 | ", | ||
189 | r#"[CompletionItem { label: "x", lookup: None, snippet: None }, | ||
190 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#, | ||
191 | ); | ||
192 | } | ||
193 | |||
194 | #[test] | ||
195 | fn test_completion_mod_scope() { | ||
196 | check_scope_completion( | ||
197 | r" | ||
198 | struct Foo; | ||
199 | enum Baz {} | ||
200 | fn quux() { | ||
201 | <|> | ||
202 | } | ||
203 | ", | ||
204 | r#"[CompletionItem { label: "quux", lookup: None, snippet: None }, | ||
205 | CompletionItem { label: "Foo", lookup: None, snippet: None }, | ||
206 | CompletionItem { label: "Baz", lookup: None, snippet: None }]"#, | ||
207 | ); | ||
208 | } | ||
209 | |||
210 | #[test] | ||
211 | fn test_completion_mod_scope_no_self_use() { | ||
212 | check_scope_completion( | ||
213 | r" | ||
214 | use foo<|>; | ||
215 | ", | ||
216 | r#"[]"#, | ||
217 | ); | ||
218 | } | ||
219 | |||
220 | #[test] | ||
221 | fn test_completion_self_path() { | ||
222 | check_scope_completion( | ||
223 | r" | ||
224 | use self::m::<|>; | ||
225 | |||
226 | mod m { | ||
227 | struct Bar; | ||
228 | } | ||
229 | ", | ||
230 | r#"[CompletionItem { label: "Bar", lookup: None, snippet: None }]"#, | ||
231 | ); | ||
232 | } | ||
233 | |||
234 | #[test] | ||
235 | fn test_completion_mod_scope_nested() { | ||
236 | check_scope_completion( | ||
237 | r" | ||
238 | struct Foo; | ||
239 | mod m { | ||
240 | struct Bar; | ||
241 | fn quux() { <|> } | ||
242 | } | ||
243 | ", | ||
244 | r#"[CompletionItem { label: "quux", lookup: None, snippet: None }, | ||
245 | CompletionItem { label: "Bar", lookup: None, snippet: None }]"#, | ||
246 | ); | ||
247 | } | ||
248 | |||
249 | #[test] | ||
250 | fn test_complete_type() { | ||
251 | check_scope_completion( | ||
252 | r" | ||
253 | struct Foo; | ||
254 | fn x() -> <|> | ||
255 | ", | ||
256 | r#"[CompletionItem { label: "Foo", lookup: None, snippet: None }, | ||
257 | CompletionItem { label: "x", lookup: None, snippet: None }]"#, | ||
258 | ) | ||
259 | } | ||
260 | |||
261 | #[test] | ||
262 | fn test_complete_shadowing() { | ||
263 | check_scope_completion( | ||
264 | r" | ||
265 | fn foo() -> { | ||
266 | let bar = 92; | ||
267 | { | ||
268 | let bar = 62; | ||
269 | <|> | ||
270 | } | ||
271 | } | ||
272 | ", | ||
273 | r#"[CompletionItem { label: "bar", lookup: None, snippet: None }, | ||
274 | CompletionItem { label: "foo", lookup: None, snippet: None }]"#, | ||
275 | ) | ||
276 | } | ||
277 | |||
278 | #[test] | ||
279 | fn test_complete_self() { | ||
280 | check_scope_completion( | ||
281 | r" | ||
282 | impl S { fn foo(&self) { <|> } } | ||
283 | ", | ||
284 | r#"[CompletionItem { label: "self", lookup: None, snippet: None }]"#, | ||
285 | ) | ||
286 | } | ||
287 | |||
288 | #[test] | ||
289 | fn test_completion_kewords() { | ||
290 | check_snippet_completion(r" | ||
291 | fn quux() { | ||
292 | <|> | ||
293 | } | ||
294 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
295 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
296 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
297 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
298 | CompletionItem { label: "return", lookup: None, snippet: Some("return") }, | ||
299 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
300 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
301 | } | ||
302 | |||
303 | #[test] | ||
304 | fn test_completion_else() { | ||
305 | check_snippet_completion(r" | ||
306 | fn quux() { | ||
307 | if true { | ||
308 | () | ||
309 | } <|> | ||
310 | } | ||
311 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
312 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
313 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
314 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
315 | CompletionItem { label: "else", lookup: None, snippet: Some("else {$0}") }, | ||
316 | CompletionItem { label: "else if", lookup: None, snippet: Some("else if $0 {}") }, | ||
317 | CompletionItem { label: "return", lookup: None, snippet: Some("return") }, | ||
318 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
319 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
320 | } | ||
321 | |||
322 | #[test] | ||
323 | fn test_completion_return_value() { | ||
324 | check_snippet_completion(r" | ||
325 | fn quux() -> i32 { | ||
326 | <|> | ||
327 | 92 | ||
328 | } | ||
329 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
330 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
331 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
332 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
333 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0;") }, | ||
334 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
335 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
336 | check_snippet_completion(r" | ||
337 | fn quux() { | ||
338 | <|> | ||
339 | 92 | ||
340 | } | ||
341 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
342 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
343 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
344 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
345 | CompletionItem { label: "return", lookup: None, snippet: Some("return;") }, | ||
346 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
347 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
348 | } | ||
349 | |||
350 | #[test] | ||
351 | fn test_completion_return_no_stmt() { | ||
352 | check_snippet_completion(r" | ||
353 | fn quux() -> i32 { | ||
354 | match () { | ||
355 | () => <|> | ||
356 | } | ||
357 | } | ||
358 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
359 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
360 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
361 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
362 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }, | ||
363 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
364 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
365 | } | ||
366 | |||
367 | #[test] | ||
368 | fn test_continue_break_completion() { | ||
369 | check_snippet_completion(r" | ||
370 | fn quux() -> i32 { | ||
371 | loop { <|> } | ||
372 | } | ||
373 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
374 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
375 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
376 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
377 | CompletionItem { label: "continue", lookup: None, snippet: Some("continue") }, | ||
378 | CompletionItem { label: "break", lookup: None, snippet: Some("break") }, | ||
379 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }, | ||
380 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
381 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
382 | check_snippet_completion(r" | ||
383 | fn quux() -> i32 { | ||
384 | loop { || { <|> } } | ||
385 | } | ||
386 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
387 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
388 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
389 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
390 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }, | ||
391 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
392 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
393 | } | ||
394 | |||
395 | #[test] | ||
396 | fn test_param_completion_last_param() { | ||
397 | check_scope_completion(r" | ||
398 | fn foo(file_id: FileId) {} | ||
399 | fn bar(file_id: FileId) {} | ||
400 | fn baz(file<|>) {} | ||
401 | ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); | ||
402 | } | ||
403 | |||
404 | #[test] | ||
405 | fn test_param_completion_nth_param() { | ||
406 | check_scope_completion(r" | ||
407 | fn foo(file_id: FileId) {} | ||
408 | fn bar(file_id: FileId) {} | ||
409 | fn baz(file<|>, x: i32) {} | ||
410 | ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); | ||
411 | } | ||
412 | |||
413 | #[test] | ||
414 | fn test_param_completion_trait_param() { | ||
415 | check_scope_completion(r" | ||
416 | pub(crate) trait SourceRoot { | ||
417 | pub fn contains(&self, file_id: FileId) -> bool; | ||
418 | pub fn module_map(&self) -> &ModuleMap; | ||
419 | pub fn lines(&self, file_id: FileId) -> &LineIndex; | ||
420 | pub fn syntax(&self, file<|>) | ||
421 | } | ||
422 | ", r#"[CompletionItem { label: "self", lookup: None, snippet: None }, | ||
423 | CompletionItem { label: "SourceRoot", lookup: None, snippet: None }, | ||
424 | CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); | ||
425 | } | ||
426 | |||
427 | #[test] | ||
428 | fn test_item_snippets() { | ||
429 | // check_snippet_completion(r" | ||
430 | // <|> | ||
431 | // ", | ||
432 | // r##"[CompletionItem { label: "Test function", lookup: None, snippet: Some("#[test]\nfn test_${1:feature}() {\n$0\n}"##, | ||
433 | // ); | ||
434 | check_snippet_completion(r" | ||
435 | #[cfg(test)] | ||
436 | mod tests { | ||
437 | <|> | ||
438 | } | ||
439 | ", | ||
440 | r##"[CompletionItem { label: "Test function", lookup: Some("tfn"), snippet: Some("#[test]\nfn ${1:feature}() {\n$0\n}") }, | ||
441 | CompletionItem { label: "pub(crate)", lookup: None, snippet: Some("pub(crate) $0") }]"##, | ||
442 | ); | ||
443 | } | ||
444 | } | 60 | } |