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