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