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.rs466
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 @@
1mod reference_completion; 1mod completion_item;
2mod completion_context;
3
4mod complete_fn_param;
5mod complete_keyword;
6mod complete_snippet;
7mod complete_path;
8mod complete_scope;
2 9
3use ra_editor::find_node_at_offset;
4use ra_text_edit::AtomTextEdit;
5use ra_syntax::{
6 algo::visit::{visitor_ctx, VisitorCtx},
7 ast,
8 AstNode,
9 SyntaxNodeRef,
10};
11use ra_db::SyntaxDatabase; 10use ra_db::SyntaxDatabase;
12use rustc_hash::{FxHashMap};
13use hir::source_binder;
14 11
15use crate::{ 12use 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)] 21pub use crate::completion::completion_item::{CompletionItem, InsertText, CompletionItemKind};
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 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.
30pub(crate) fn completions( 31pub(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
66fn 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
106fn 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)]
114mod tests { 51fn 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}