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.rs372
1 files changed, 49 insertions, 323 deletions
diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs
index f480af611..a11e98ac0 100644
--- a/crates/ra_analysis/src/completion.rs
+++ b/crates/ra_analysis/src/completion.rs
@@ -1,3 +1,4 @@
1mod completion_item;
1mod reference_completion; 2mod reference_completion;
2 3
3use ra_editor::find_node_at_offset; 4use ra_editor::find_node_at_offset;
@@ -14,23 +15,16 @@ use hir::source_binder;
14 15
15use crate::{ 16use crate::{
16 db, 17 db,
17 Cancelable, FilePosition 18 Cancelable, FilePosition,
19 completion::completion_item::{Completions, CompletionKind},
18}; 20};
19 21
20#[derive(Debug)] 22pub use crate::completion::completion_item::{CompletionItem, InsertText};
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 23
30pub(crate) fn completions( 24pub(crate) fn completions(
31 db: &db::RootDatabase, 25 db: &db::RootDatabase,
32 position: FilePosition, 26 position: FilePosition,
33) -> Cancelable<Option<Vec<CompletionItem>>> { 27) -> Cancelable<Option<Completions>> {
34 let original_file = db.source_file(position.file_id); 28 let original_file = db.source_file(position.file_id);
35 // Insert a fake ident to get a valid parse tree 29 // Insert a fake ident to get a valid parse tree
36 let file = { 30 let file = {
@@ -40,15 +34,15 @@ pub(crate) fn completions(
40 34
41 let module = ctry!(source_binder::module_from_position(db, position)?); 35 let module = ctry!(source_binder::module_from_position(db, position)?);
42 36
43 let mut res = Vec::new(); 37 let mut acc = Completions::default();
44 let mut has_completions = false; 38 let mut has_completions = false;
45 // First, let's try to complete a reference to some declaration. 39 // 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) { 40 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) {
47 has_completions = true; 41 has_completions = true;
48 reference_completion::completions(&mut res, db, &module, &file, name_ref)?; 42 reference_completion::completions(&mut acc, db, &module, &file, name_ref)?;
49 // special case, `trait T { fn foo(i_am_a_name_ref) {} }` 43 // special case, `trait T { fn foo(i_am_a_name_ref) {} }`
50 if is_node::<ast::Param>(name_ref.syntax()) { 44 if is_node::<ast::Param>(name_ref.syntax()) {
51 param_completions(name_ref.syntax(), &mut res); 45 param_completions(&mut acc, name_ref.syntax());
52 } 46 }
53 } 47 }
54 48
@@ -56,14 +50,20 @@ pub(crate) fn completions(
56 if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) { 50 if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) {
57 if is_node::<ast::Param>(name.syntax()) { 51 if is_node::<ast::Param>(name.syntax()) {
58 has_completions = true; 52 has_completions = true;
59 param_completions(name.syntax(), &mut res); 53 param_completions(&mut acc, name.syntax());
60 } 54 }
61 } 55 }
62 let res = if has_completions { Some(res) } else { None }; 56 if !has_completions {
63 Ok(res) 57 return Ok(None);
58 }
59 Ok(Some(acc))
64} 60}
65 61
66fn param_completions(ctx: SyntaxNodeRef, acc: &mut Vec<CompletionItem>) { 62/// Complete repeated parametes, both name and type. For example, if all
63/// functions in a file have a `spam: &mut Spam` parameter, a completion with
64/// `spam: &mut Spam` insert text/label and `spam` lookup string will be
65/// suggested.
66fn param_completions(acc: &mut Completions, ctx: SyntaxNodeRef) {
67 let mut params = FxHashMap::default(); 67 let mut params = FxHashMap::default();
68 for node in ctx.ancestors() { 68 for node in ctx.ancestors() {
69 let _ = visitor_ctx(&mut params) 69 let _ = visitor_ctx(&mut params)
@@ -82,11 +82,10 @@ fn param_completions(ctx: SyntaxNodeRef, acc: &mut Vec<CompletionItem>) {
82 } 82 }
83 }) 83 })
84 .for_each(|(label, lookup)| { 84 .for_each(|(label, lookup)| {
85 acc.push(CompletionItem { 85 CompletionItem::new(label)
86 label, 86 .lookup_by(lookup)
87 lookup: Some(lookup), 87 .kind(CompletionKind::Magic)
88 snippet: None, 88 .add_to(acc)
89 })
90 }); 89 });
91 90
92 fn process<'a, N: ast::FnDefOwner<'a>>( 91 fn process<'a, N: ast::FnDefOwner<'a>>(
@@ -111,334 +110,61 @@ fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool {
111} 110}
112 111
113#[cfg(test)] 112#[cfg(test)]
114mod tests { 113fn check_completion(code: &str, expected_completions: &str, kind: CompletionKind) {
115 use test_utils::assert_eq_dbg; 114 use crate::mock_analysis::{single_file_with_position, analysis_and_position};
116 115 let (analysis, position) = if code.contains("//-") {
117 use crate::mock_analysis::single_file_with_position; 116 analysis_and_position(code)
117 } else {
118 single_file_with_position(code)
119 };
120 let completions = completions(&analysis.imp.db, position).unwrap().unwrap();
121 completions.assert_match(expected_completions, kind);
122}
118 123
124#[cfg(test)]
125mod tests {
119 use super::*; 126 use super::*;
120 127
121 fn check_scope_completion(code: &str, expected_completions: &str) { 128 fn check_magic_completion(code: &str, expected_completions: &str) {
122 let (analysis, position) = single_file_with_position(code); 129 check_completion(code, expected_completions, CompletionKind::Magic);
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 } 130 }
394 131
395 #[test] 132 #[test]
396 fn test_param_completion_last_param() { 133 fn test_param_completion_last_param() {
397 check_scope_completion(r" 134 check_magic_completion(
135 r"
398 fn foo(file_id: FileId) {} 136 fn foo(file_id: FileId) {}
399 fn bar(file_id: FileId) {} 137 fn bar(file_id: FileId) {}
400 fn baz(file<|>) {} 138 fn baz(file<|>) {}
401 ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); 139 ",
140 r#"file_id "file_id: FileId""#,
141 );
402 } 142 }
403 143
404 #[test] 144 #[test]
405 fn test_param_completion_nth_param() { 145 fn test_param_completion_nth_param() {
406 check_scope_completion(r" 146 check_magic_completion(
147 r"
407 fn foo(file_id: FileId) {} 148 fn foo(file_id: FileId) {}
408 fn bar(file_id: FileId) {} 149 fn bar(file_id: FileId) {}
409 fn baz(file<|>, x: i32) {} 150 fn baz(file<|>, x: i32) {}
410 ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); 151 ",
152 r#"file_id "file_id: FileId""#,
153 );
411 } 154 }
412 155
413 #[test] 156 #[test]
414 fn test_param_completion_trait_param() { 157 fn test_param_completion_trait_param() {
415 check_scope_completion(r" 158 check_magic_completion(
159 r"
416 pub(crate) trait SourceRoot { 160 pub(crate) trait SourceRoot {
417 pub fn contains(&self, file_id: FileId) -> bool; 161 pub fn contains(&self, file_id: FileId) -> bool;
418 pub fn module_map(&self) -> &ModuleMap; 162 pub fn module_map(&self) -> &ModuleMap;
419 pub fn lines(&self, file_id: FileId) -> &LineIndex; 163 pub fn lines(&self, file_id: FileId) -> &LineIndex;
420 pub fn syntax(&self, file<|>) 164 pub fn syntax(&self, file<|>)
421 } 165 }
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 ", 166 ",
440 r##"[CompletionItem { label: "Test function", lookup: Some("tfn"), snippet: Some("#[test]\nfn ${1:feature}() {\n$0\n}") }, 167 r#"file_id "file_id: FileId""#,
441 CompletionItem { label: "pub(crate)", lookup: None, snippet: Some("pub(crate) $0") }]"##,
442 ); 168 );
443 } 169 }
444} 170}