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