aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_analysis
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_analysis')
-rw-r--r--crates/ra_analysis/src/completion.rs466
-rw-r--r--crates/ra_analysis/src/completion/complete_fn_param.rs102
-rw-r--r--crates/ra_analysis/src/completion/complete_keyword.rs204
-rw-r--r--crates/ra_analysis/src/completion/complete_path.rs95
-rw-r--r--crates/ra_analysis/src/completion/complete_scope.rs173
-rw-r--r--crates/ra_analysis/src/completion/complete_snippet.rs73
-rw-r--r--crates/ra_analysis/src/completion/completion_context.rs156
-rw-r--r--crates/ra_analysis/src/completion/completion_item.rs214
-rw-r--r--crates/ra_analysis/src/completion/reference_completion.rs294
-rw-r--r--crates/ra_analysis/src/imp.rs9
-rw-r--r--crates/ra_analysis/src/lib.rs37
-rw-r--r--crates/ra_analysis/src/symbol_index.rs4
-rw-r--r--crates/ra_analysis/tests/tests.rs60
13 files changed, 1095 insertions, 792 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}
diff --git a/crates/ra_analysis/src/completion/complete_fn_param.rs b/crates/ra_analysis/src/completion/complete_fn_param.rs
new file mode 100644
index 000000000..bb5fdfda0
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_fn_param.rs
@@ -0,0 +1,102 @@
1use ra_syntax::{
2 algo::visit::{visitor_ctx, VisitorCtx},
3 ast,
4 AstNode,
5};
6use rustc_hash::FxHashMap;
7
8use crate::completion::{CompletionContext, Completions, CompletionKind, CompletionItem};
9
10/// Complete repeated parametes, both name and type. For example, if all
11/// functions in a file have a `spam: &mut Spam` parameter, a completion with
12/// `spam: &mut Spam` insert text/label and `spam` lookup string will be
13/// suggested.
14pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) {
15 if !ctx.is_param {
16 return;
17 }
18
19 let mut params = FxHashMap::default();
20 for node in ctx.leaf.ancestors() {
21 let _ = visitor_ctx(&mut params)
22 .visit::<ast::SourceFile, _>(process)
23 .visit::<ast::ItemList, _>(process)
24 .accept(node);
25 }
26 params
27 .into_iter()
28 .filter_map(|(label, (count, param))| {
29 let lookup = param.pat()?.syntax().text().to_string();
30 if count < 2 {
31 None
32 } else {
33 Some((label, lookup))
34 }
35 })
36 .for_each(|(label, lookup)| {
37 CompletionItem::new(CompletionKind::Magic, label)
38 .lookup_by(lookup)
39 .add_to(acc)
40 });
41
42 fn process<'a, N: ast::FnDefOwner<'a>>(
43 node: N,
44 params: &mut FxHashMap<String, (u32, ast::Param<'a>)>,
45 ) {
46 node.functions()
47 .filter_map(|it| it.param_list())
48 .flat_map(|it| it.params())
49 .for_each(|param| {
50 let text = param.syntax().text().to_string();
51 params.entry(text).or_insert((0, param)).0 += 1;
52 })
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use crate::completion::*;
59
60 fn check_magic_completion(code: &str, expected_completions: &str) {
61 check_completion(code, expected_completions, CompletionKind::Magic);
62 }
63
64 #[test]
65 fn test_param_completion_last_param() {
66 check_magic_completion(
67 r"
68 fn foo(file_id: FileId) {}
69 fn bar(file_id: FileId) {}
70 fn baz(file<|>) {}
71 ",
72 r#"file_id "file_id: FileId""#,
73 );
74 }
75
76 #[test]
77 fn test_param_completion_nth_param() {
78 check_magic_completion(
79 r"
80 fn foo(file_id: FileId) {}
81 fn bar(file_id: FileId) {}
82 fn baz(file<|>, x: i32) {}
83 ",
84 r#"file_id "file_id: FileId""#,
85 );
86 }
87
88 #[test]
89 fn test_param_completion_trait_param() {
90 check_magic_completion(
91 r"
92 pub(crate) trait SourceRoot {
93 pub fn contains(&self, file_id: FileId) -> bool;
94 pub fn module_map(&self) -> &ModuleMap;
95 pub fn lines(&self, file_id: FileId) -> &LineIndex;
96 pub fn syntax(&self, file<|>)
97 }
98 ",
99 r#"file_id "file_id: FileId""#,
100 );
101 }
102}
diff --git a/crates/ra_analysis/src/completion/complete_keyword.rs b/crates/ra_analysis/src/completion/complete_keyword.rs
new file mode 100644
index 000000000..5427fcb11
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_keyword.rs
@@ -0,0 +1,204 @@
1use ra_syntax::{
2 algo::visit::{visitor, Visitor},
3 AstNode,
4 ast::{self, LoopBodyOwner},
5 SyntaxKind::*, SyntaxNodeRef,
6};
7
8use crate::completion::{CompletionContext, CompletionItem, Completions, CompletionKind, CompletionItemKind};
9
10fn keyword(kw: &str, snippet: &str) -> CompletionItem {
11 CompletionItem::new(CompletionKind::Keyword, kw)
12 .kind(CompletionItemKind::Keyword)
13 .snippet(snippet)
14 .build()
15}
16
17pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
18 if !ctx.is_trivial_path {
19 return;
20 }
21 let fn_def = match ctx.enclosing_fn {
22 Some(it) => it,
23 None => return,
24 };
25 acc.add(keyword("if", "if $0 {}"));
26 acc.add(keyword("match", "match $0 {}"));
27 acc.add(keyword("while", "while $0 {}"));
28 acc.add(keyword("loop", "loop {$0}"));
29
30 if ctx.after_if {
31 acc.add(keyword("else", "else {$0}"));
32 acc.add(keyword("else if", "else if $0 {}"));
33 }
34 if is_in_loop_body(ctx.leaf) {
35 acc.add(keyword("continue", "continue"));
36 acc.add(keyword("break", "break"));
37 }
38 acc.add_all(complete_return(fn_def, ctx.is_stmt));
39}
40
41fn is_in_loop_body(leaf: SyntaxNodeRef) -> bool {
42 for node in leaf.ancestors() {
43 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
44 break;
45 }
46 let loop_body = visitor()
47 .visit::<ast::ForExpr, _>(LoopBodyOwner::loop_body)
48 .visit::<ast::WhileExpr, _>(LoopBodyOwner::loop_body)
49 .visit::<ast::LoopExpr, _>(LoopBodyOwner::loop_body)
50 .accept(node);
51 if let Some(Some(body)) = loop_body {
52 if leaf.range().is_subrange(&body.syntax().range()) {
53 return true;
54 }
55 }
56 }
57 false
58}
59
60fn complete_return(fn_def: ast::FnDef, is_stmt: bool) -> Option<CompletionItem> {
61 let snip = match (is_stmt, fn_def.ret_type().is_some()) {
62 (true, true) => "return $0;",
63 (true, false) => "return;",
64 (false, true) => "return $0",
65 (false, false) => "return",
66 };
67 Some(keyword("return", snip))
68}
69
70#[cfg(test)]
71mod tests {
72 use crate::completion::{CompletionKind, check_completion};
73 fn check_keyword_completion(code: &str, expected_completions: &str) {
74 check_completion(code, expected_completions, CompletionKind::Keyword);
75 }
76
77 #[test]
78 fn test_completion_kewords() {
79 check_keyword_completion(
80 r"
81 fn quux() {
82 <|>
83 }
84 ",
85 r#"
86 if "if $0 {}"
87 match "match $0 {}"
88 while "while $0 {}"
89 loop "loop {$0}"
90 return "return"
91 "#,
92 );
93 }
94
95 #[test]
96 fn test_completion_else() {
97 check_keyword_completion(
98 r"
99 fn quux() {
100 if true {
101 ()
102 } <|>
103 }
104 ",
105 r#"
106 if "if $0 {}"
107 match "match $0 {}"
108 while "while $0 {}"
109 loop "loop {$0}"
110 else "else {$0}"
111 else if "else if $0 {}"
112 return "return"
113 "#,
114 );
115 }
116
117 #[test]
118 fn test_completion_return_value() {
119 check_keyword_completion(
120 r"
121 fn quux() -> i32 {
122 <|>
123 92
124 }
125 ",
126 r#"
127 if "if $0 {}"
128 match "match $0 {}"
129 while "while $0 {}"
130 loop "loop {$0}"
131 return "return $0;"
132 "#,
133 );
134 check_keyword_completion(
135 r"
136 fn quux() {
137 <|>
138 92
139 }
140 ",
141 r#"
142 if "if $0 {}"
143 match "match $0 {}"
144 while "while $0 {}"
145 loop "loop {$0}"
146 return "return;"
147 "#,
148 );
149 }
150
151 #[test]
152 fn test_completion_return_no_stmt() {
153 check_keyword_completion(
154 r"
155 fn quux() -> i32 {
156 match () {
157 () => <|>
158 }
159 }
160 ",
161 r#"
162 if "if $0 {}"
163 match "match $0 {}"
164 while "while $0 {}"
165 loop "loop {$0}"
166 return "return $0"
167 "#,
168 );
169 }
170
171 #[test]
172 fn test_continue_break_completion() {
173 check_keyword_completion(
174 r"
175 fn quux() -> i32 {
176 loop { <|> }
177 }
178 ",
179 r#"
180 if "if $0 {}"
181 match "match $0 {}"
182 while "while $0 {}"
183 loop "loop {$0}"
184 continue "continue"
185 break "break"
186 return "return $0"
187 "#,
188 );
189 check_keyword_completion(
190 r"
191 fn quux() -> i32 {
192 loop { || { <|> } }
193 }
194 ",
195 r#"
196 if "if $0 {}"
197 match "match $0 {}"
198 while "while $0 {}"
199 loop "loop {$0}"
200 return "return $0"
201 "#,
202 );
203 }
204}
diff --git a/crates/ra_analysis/src/completion/complete_path.rs b/crates/ra_analysis/src/completion/complete_path.rs
new file mode 100644
index 000000000..ad4d68a33
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_path.rs
@@ -0,0 +1,95 @@
1use crate::{
2 Cancelable,
3 completion::{CompletionItem, Completions, CompletionKind, CompletionContext},
4};
5
6pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> {
7 let (path, module) = match (&ctx.path_prefix, &ctx.module) {
8 (Some(path), Some(module)) => (path.clone(), module),
9 _ => return Ok(()),
10 };
11 let def_id = match module.resolve_path(ctx.db, path)? {
12 Some(it) => it,
13 None => return Ok(()),
14 };
15 let target_module = match def_id.resolve(ctx.db)? {
16 hir::Def::Module(it) => it,
17 _ => return Ok(()),
18 };
19 let module_scope = target_module.scope(ctx.db)?;
20 module_scope.entries().for_each(|(name, res)| {
21 CompletionItem::new(CompletionKind::Reference, name.to_string())
22 .from_resolution(ctx.db, res)
23 .add_to(acc)
24 });
25 Ok(())
26}
27
28#[cfg(test)]
29mod tests {
30 use crate::completion::{CompletionKind, check_completion};
31
32 fn check_reference_completion(code: &str, expected_completions: &str) {
33 check_completion(code, expected_completions, CompletionKind::Reference);
34 }
35
36 #[test]
37 fn completes_use_item_starting_with_self() {
38 check_reference_completion(
39 r"
40 use self::m::<|>;
41
42 mod m {
43 struct Bar;
44 }
45 ",
46 "Bar",
47 );
48 }
49
50 #[test]
51 fn completes_use_item_starting_with_crate() {
52 check_reference_completion(
53 "
54 //- /lib.rs
55 mod foo;
56 struct Spam;
57 //- /foo.rs
58 use crate::Sp<|>
59 ",
60 "Spam;foo",
61 );
62 }
63
64 #[test]
65 fn completes_nested_use_tree() {
66 check_reference_completion(
67 "
68 //- /lib.rs
69 mod foo;
70 struct Spam;
71 //- /foo.rs
72 use crate::{Sp<|>};
73 ",
74 "Spam;foo",
75 );
76 }
77
78 #[test]
79 fn completes_deeply_nested_use_tree() {
80 check_reference_completion(
81 "
82 //- /lib.rs
83 mod foo;
84 pub mod bar {
85 pub mod baz {
86 pub struct Spam;
87 }
88 }
89 //- /foo.rs
90 use crate::{bar::{baz::Sp<|>}};
91 ",
92 "Spam",
93 );
94 }
95}
diff --git a/crates/ra_analysis/src/completion/complete_scope.rs b/crates/ra_analysis/src/completion/complete_scope.rs
new file mode 100644
index 000000000..a57670e3b
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_scope.rs
@@ -0,0 +1,173 @@
1use rustc_hash::FxHashSet;
2use ra_syntax::TextUnit;
3
4use crate::{
5 Cancelable,
6 completion::{CompletionItem, CompletionItemKind, Completions, CompletionKind, CompletionContext},
7};
8
9pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> {
10 if !ctx.is_trivial_path {
11 return Ok(());
12 }
13 let module = match &ctx.module {
14 Some(it) => it,
15 None => return Ok(()),
16 };
17 if let Some(fn_def) = ctx.enclosing_fn {
18 let function = hir::source_binder::function_from_module(ctx.db, module, fn_def);
19 let scopes = function.scopes(ctx.db);
20 complete_fn(acc, &scopes, ctx.offset);
21 }
22
23 let module_scope = module.scope(ctx.db)?;
24 module_scope
25 .entries()
26 .filter(|(_name, res)| {
27 // Don't expose this item
28 match res.import {
29 None => true,
30 Some(import) => {
31 let range = import.range(ctx.db, module.source().file_id());
32 !range.is_subrange(&ctx.leaf.range())
33 }
34 }
35 })
36 .for_each(|(name, res)| {
37 CompletionItem::new(CompletionKind::Reference, name.to_string())
38 .from_resolution(ctx.db, res)
39 .add_to(acc)
40 });
41 Ok(())
42}
43
44fn complete_fn(acc: &mut Completions, scopes: &hir::FnScopes, offset: TextUnit) {
45 let mut shadowed = FxHashSet::default();
46 scopes
47 .scope_chain_for_offset(offset)
48 .flat_map(|scope| scopes.entries(scope).iter())
49 .filter(|entry| shadowed.insert(entry.name()))
50 .for_each(|entry| {
51 CompletionItem::new(CompletionKind::Reference, entry.name().to_string())
52 .kind(CompletionItemKind::Binding)
53 .add_to(acc)
54 });
55 if scopes.self_param.is_some() {
56 CompletionItem::new(CompletionKind::Reference, "self").add_to(acc);
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use crate::completion::{CompletionKind, check_completion};
63
64 fn check_reference_completion(code: &str, expected_completions: &str) {
65 check_completion(code, expected_completions, CompletionKind::Reference);
66 }
67
68 #[test]
69 fn completes_bindings_from_let() {
70 check_reference_completion(
71 r"
72 fn quux(x: i32) {
73 let y = 92;
74 1 + <|>;
75 let z = ();
76 }
77 ",
78 "y;x;quux",
79 );
80 }
81
82 #[test]
83 fn completes_bindings_from_if_let() {
84 check_reference_completion(
85 r"
86 fn quux() {
87 if let Some(x) = foo() {
88 let y = 92;
89 };
90 if let Some(a) = bar() {
91 let b = 62;
92 1 + <|>
93 }
94 }
95 ",
96 "b;a;quux",
97 );
98 }
99
100 #[test]
101 fn completes_bindings_from_for() {
102 check_reference_completion(
103 r"
104 fn quux() {
105 for x in &[1, 2, 3] {
106 <|>
107 }
108 }
109 ",
110 "x;quux",
111 );
112 }
113
114 #[test]
115 fn completes_module_items() {
116 check_reference_completion(
117 r"
118 struct Foo;
119 enum Baz {}
120 fn quux() {
121 <|>
122 }
123 ",
124 "quux;Foo;Baz",
125 );
126 }
127
128 #[test]
129 fn completes_module_items_in_nested_modules() {
130 check_reference_completion(
131 r"
132 struct Foo;
133 mod m {
134 struct Bar;
135 fn quux() { <|> }
136 }
137 ",
138 "quux;Bar",
139 );
140 }
141
142 #[test]
143 fn completes_return_type() {
144 check_reference_completion(
145 r"
146 struct Foo;
147 fn x() -> <|>
148 ",
149 "Foo;x",
150 )
151 }
152
153 #[test]
154 fn dont_show_to_completions_for_shadowing() {
155 check_reference_completion(
156 r"
157 fn foo() -> {
158 let bar = 92;
159 {
160 let bar = 62;
161 <|>
162 }
163 }
164 ",
165 "bar;foo",
166 )
167 }
168
169 #[test]
170 fn completes_self_in_methods() {
171 check_reference_completion(r"impl S { fn foo(&self) { <|> } }", "self")
172 }
173}
diff --git a/crates/ra_analysis/src/completion/complete_snippet.rs b/crates/ra_analysis/src/completion/complete_snippet.rs
new file mode 100644
index 000000000..fb9da0a4f
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_snippet.rs
@@ -0,0 +1,73 @@
1use crate::completion::{CompletionItem, Completions, CompletionKind, CompletionItemKind, CompletionContext, completion_item::Builder};
2
3fn snippet(label: &str, snippet: &str) -> Builder {
4 CompletionItem::new(CompletionKind::Snippet, label)
5 .snippet(snippet)
6 .kind(CompletionItemKind::Snippet)
7}
8
9pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) {
10 if !(ctx.is_trivial_path && ctx.enclosing_fn.is_some()) {
11 return;
12 }
13 snippet("pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc);
14 snippet("ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
15}
16
17pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
18 if !ctx.is_new_item {
19 return;
20 }
21 snippet(
22 "Test function",
23 "\
24#[test]
25fn ${1:feature}() {
26 $0
27}",
28 )
29 .lookup_by("tfn")
30 .add_to(acc);
31
32 snippet("pub(crate)", "pub(crate) $0").add_to(acc);
33}
34
35#[cfg(test)]
36mod tests {
37 use crate::completion::{CompletionKind, check_completion};
38 fn check_snippet_completion(code: &str, expected_completions: &str) {
39 check_completion(code, expected_completions, CompletionKind::Snippet);
40 }
41
42 #[test]
43 fn completes_snippets_in_expressions() {
44 check_snippet_completion(
45 r"fn foo(x: i32) { <|> }",
46 r##"
47 pd "eprintln!(\"$0 = {:?}\", $0);"
48 ppd "eprintln!(\"$0 = {:#?}\", $0);"
49 "##,
50 );
51 }
52
53 #[test]
54 fn completes_snippets_in_items() {
55 // check_snippet_completion(r"
56 // <|>
57 // ",
58 // r##"[CompletionItem { label: "Test function", lookup: None, snippet: Some("#[test]\nfn test_${1:feature}() {\n$0\n}"##,
59 // );
60 check_snippet_completion(
61 r"
62 #[cfg(test)]
63 mod tests {
64 <|>
65 }
66 ",
67 r##"
68 tfn "Test function" "#[test]\nfn ${1:feature}() {\n $0\n}"
69 pub(crate) "pub(crate) $0"
70 "##,
71 );
72 }
73}
diff --git a/crates/ra_analysis/src/completion/completion_context.rs b/crates/ra_analysis/src/completion/completion_context.rs
new file mode 100644
index 000000000..064fbc6f7
--- /dev/null
+++ b/crates/ra_analysis/src/completion/completion_context.rs
@@ -0,0 +1,156 @@
1use ra_editor::find_node_at_offset;
2use ra_text_edit::AtomTextEdit;
3use ra_syntax::{
4 algo::find_leaf_at_offset,
5 ast,
6 AstNode,
7 SyntaxNodeRef,
8 SourceFileNode,
9 TextUnit,
10 SyntaxKind::*,
11};
12use hir::source_binder;
13
14use crate::{db, FilePosition, Cancelable};
15
16/// `CompletionContext` is created early during completion to figure out, where
17/// exactly is the cursor, syntax-wise.
18#[derive(Debug)]
19pub(super) struct CompletionContext<'a> {
20 pub(super) db: &'a db::RootDatabase,
21 pub(super) offset: TextUnit,
22 pub(super) leaf: SyntaxNodeRef<'a>,
23 pub(super) module: Option<hir::Module>,
24 pub(super) enclosing_fn: Option<ast::FnDef<'a>>,
25 pub(super) is_param: bool,
26 /// A single-indent path, like `foo`.
27 pub(super) is_trivial_path: bool,
28 /// If not a trivial, path, the prefix (qualifier).
29 pub(super) path_prefix: Option<hir::Path>,
30 pub(super) after_if: bool,
31 pub(super) is_stmt: bool,
32 /// Something is typed at the "top" level, in module or impl/trait.
33 pub(super) is_new_item: bool,
34}
35
36impl<'a> CompletionContext<'a> {
37 pub(super) fn new(
38 db: &'a db::RootDatabase,
39 original_file: &'a SourceFileNode,
40 position: FilePosition,
41 ) -> Cancelable<Option<CompletionContext<'a>>> {
42 let module = source_binder::module_from_position(db, position)?;
43 let leaf =
44 ctry!(find_leaf_at_offset(original_file.syntax(), position.offset).left_biased());
45 let mut ctx = CompletionContext {
46 db,
47 leaf,
48 offset: position.offset,
49 module,
50 enclosing_fn: None,
51 is_param: false,
52 is_trivial_path: false,
53 path_prefix: None,
54 after_if: false,
55 is_stmt: false,
56 is_new_item: false,
57 };
58 ctx.fill(original_file, position.offset);
59 Ok(Some(ctx))
60 }
61
62 fn fill(&mut self, original_file: &SourceFileNode, offset: TextUnit) {
63 // Insert a fake ident to get a valid parse tree. We will use this file
64 // to determine context, though the original_file will be used for
65 // actual completion.
66 let file = {
67 let edit = AtomTextEdit::insert(offset, "intellijRulezz".to_string());
68 original_file.reparse(&edit)
69 };
70
71 // First, let's try to complete a reference to some declaration.
72 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), offset) {
73 // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
74 // See RFC#1685.
75 if is_node::<ast::Param>(name_ref.syntax()) {
76 self.is_param = true;
77 return;
78 }
79 self.classify_name_ref(&file, name_ref);
80 }
81
82 // Otherwise, see if this is a declaration. We can use heuristics to
83 // suggest declaration names, see `CompletionKind::Magic`.
84 if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), offset) {
85 if is_node::<ast::Param>(name.syntax()) {
86 self.is_param = true;
87 return;
88 }
89 }
90 }
91 fn classify_name_ref(&mut self, file: &SourceFileNode, name_ref: ast::NameRef) {
92 let name_range = name_ref.syntax().range();
93 let top_node = name_ref
94 .syntax()
95 .ancestors()
96 .take_while(|it| it.range() == name_range)
97 .last()
98 .unwrap();
99
100 match top_node.parent().map(|it| it.kind()) {
101 Some(SOURCE_FILE) | Some(ITEM_LIST) => {
102 self.is_new_item = true;
103 return;
104 }
105 _ => (),
106 }
107
108 let parent = match name_ref.syntax().parent() {
109 Some(it) => it,
110 None => return,
111 };
112 if let Some(segment) = ast::PathSegment::cast(parent) {
113 let path = segment.parent_path();
114 if let Some(mut path) = hir::Path::from_ast(path) {
115 if !path.is_ident() {
116 path.segments.pop().unwrap();
117 self.path_prefix = Some(path);
118 return;
119 }
120 }
121 if path.qualifier().is_none() {
122 self.is_trivial_path = true;
123 self.enclosing_fn = self
124 .leaf
125 .ancestors()
126 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
127 .find_map(ast::FnDef::cast);
128
129 self.is_stmt = match name_ref
130 .syntax()
131 .ancestors()
132 .filter_map(ast::ExprStmt::cast)
133 .next()
134 {
135 None => false,
136 Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(),
137 };
138
139 if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
140 if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) {
141 if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
142 self.after_if = true;
143 }
144 }
145 }
146 }
147 }
148 }
149}
150
151fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool {
152 match node.ancestors().filter_map(N::cast).next() {
153 None => false,
154 Some(n) => n.syntax().range() == node.range(),
155 }
156}
diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs
new file mode 100644
index 000000000..911f08468
--- /dev/null
+++ b/crates/ra_analysis/src/completion/completion_item.rs
@@ -0,0 +1,214 @@
1use crate::db;
2
3/// `CompletionItem` describes a single completion variant in the editor pop-up.
4/// It is basically a POD with various properties. To construct a
5/// `CompletionItem`, use `new` method and the `Builder` struct.
6#[derive(Debug)]
7pub struct CompletionItem {
8 /// Used only internally in tests, to check only specific kind of
9 /// completion.
10 completion_kind: CompletionKind,
11 label: String,
12 lookup: Option<String>,
13 snippet: Option<String>,
14 kind: Option<CompletionItemKind>,
15}
16
17pub enum InsertText {
18 PlainText { text: String },
19 Snippet { text: String },
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum CompletionItemKind {
24 Snippet,
25 Keyword,
26 Module,
27 Function,
28 Binding,
29}
30
31#[derive(Debug, PartialEq, Eq)]
32pub(crate) enum CompletionKind {
33 /// Parser-based keyword completion.
34 Keyword,
35 /// Your usual "complete all valid identifiers".
36 Reference,
37 /// "Secret sauce" completions.
38 Magic,
39 Snippet,
40}
41
42impl CompletionItem {
43 pub(crate) fn new(completion_kind: CompletionKind, label: impl Into<String>) -> Builder {
44 let label = label.into();
45 Builder {
46 completion_kind,
47 label,
48 lookup: None,
49 snippet: None,
50 kind: None,
51 }
52 }
53 /// What user sees in pop-up in the UI.
54 pub fn label(&self) -> &str {
55 &self.label
56 }
57 /// What string is used for filtering.
58 pub fn lookup(&self) -> &str {
59 self.lookup
60 .as_ref()
61 .map(|it| it.as_str())
62 .unwrap_or(self.label())
63 }
64 /// What is inserted.
65 pub fn insert_text(&self) -> InsertText {
66 match &self.snippet {
67 None => InsertText::PlainText {
68 text: self.label.clone(),
69 },
70 Some(it) => InsertText::Snippet { text: it.clone() },
71 }
72 }
73
74 pub fn kind(&self) -> Option<CompletionItemKind> {
75 self.kind
76 }
77}
78
79/// A helper to make `CompletionItem`s.
80#[must_use]
81pub(crate) struct Builder {
82 completion_kind: CompletionKind,
83 label: String,
84 lookup: Option<String>,
85 snippet: Option<String>,
86 kind: Option<CompletionItemKind>,
87}
88
89impl Builder {
90 pub(crate) fn add_to(self, acc: &mut Completions) {
91 acc.add(self.build())
92 }
93
94 pub(crate) fn build(self) -> CompletionItem {
95 CompletionItem {
96 label: self.label,
97 lookup: self.lookup,
98 snippet: self.snippet,
99 kind: self.kind,
100 completion_kind: self.completion_kind,
101 }
102 }
103 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
104 self.lookup = Some(lookup.into());
105 self
106 }
107 pub(crate) fn snippet(mut self, snippet: impl Into<String>) -> Builder {
108 self.snippet = Some(snippet.into());
109 self
110 }
111 pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder {
112 self.kind = Some(kind);
113 self
114 }
115 pub(crate) fn from_resolution(
116 mut self,
117 db: &db::RootDatabase,
118 resolution: &hir::Resolution,
119 ) -> Builder {
120 if let Some(def_id) = resolution.def_id {
121 if let Ok(def) = def_id.resolve(db) {
122 let kind = match def {
123 hir::Def::Module(..) => CompletionItemKind::Module,
124 hir::Def::Function(..) => CompletionItemKind::Function,
125 _ => return self,
126 };
127 self.kind = Some(kind);
128 }
129 }
130 self
131 }
132}
133
134impl Into<CompletionItem> for Builder {
135 fn into(self) -> CompletionItem {
136 self.build()
137 }
138}
139
140/// Represents an in-progress set of completions being built.
141#[derive(Debug, Default)]
142pub(crate) struct Completions {
143 buf: Vec<CompletionItem>,
144}
145
146impl Completions {
147 pub(crate) fn add(&mut self, item: impl Into<CompletionItem>) {
148 self.buf.push(item.into())
149 }
150 pub(crate) fn add_all<I>(&mut self, items: I)
151 where
152 I: IntoIterator,
153 I::Item: Into<CompletionItem>,
154 {
155 items.into_iter().for_each(|item| self.add(item.into()))
156 }
157
158 #[cfg(test)]
159 pub(crate) fn assert_match(&self, expected: &str, kind: CompletionKind) {
160 let expected = normalize(expected);
161 let actual = self.debug_render(kind);
162 test_utils::assert_eq_text!(expected.as_str(), actual.as_str(),);
163
164 /// Normalize the textual representation of `Completions`:
165 /// replace `;` with newlines, normalize whitespace
166 fn normalize(expected: &str) -> String {
167 use ra_syntax::{tokenize, TextUnit, TextRange, SyntaxKind::SEMI};
168 let mut res = String::new();
169 for line in expected.trim().lines() {
170 let line = line.trim();
171 let mut start_offset: TextUnit = 0.into();
172 // Yep, we use rust tokenize in completion tests :-)
173 for token in tokenize(line) {
174 let range = TextRange::offset_len(start_offset, token.len);
175 start_offset += token.len;
176 if token.kind == SEMI {
177 res.push('\n');
178 } else {
179 res.push_str(&line[range]);
180 }
181 }
182
183 res.push('\n');
184 }
185 res
186 }
187 }
188
189 #[cfg(test)]
190 fn debug_render(&self, kind: CompletionKind) -> String {
191 let mut res = String::new();
192 for c in self.buf.iter() {
193 if c.completion_kind == kind {
194 if let Some(lookup) = &c.lookup {
195 res.push_str(lookup);
196 res.push_str(&format!(" {:?}", c.label));
197 } else {
198 res.push_str(&c.label);
199 }
200 if let Some(snippet) = &c.snippet {
201 res.push_str(&format!(" {:?}", snippet));
202 }
203 res.push('\n');
204 }
205 }
206 res
207 }
208}
209
210impl Into<Vec<CompletionItem>> for Completions {
211 fn into(self) -> Vec<CompletionItem> {
212 self.buf
213 }
214}
diff --git a/crates/ra_analysis/src/completion/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs
deleted file mode 100644
index f483ed045..000000000
--- a/crates/ra_analysis/src/completion/reference_completion.rs
+++ /dev/null
@@ -1,294 +0,0 @@
1use rustc_hash::{FxHashSet};
2use ra_editor::find_node_at_offset;
3use ra_syntax::{
4 algo::visit::{visitor, Visitor},
5 SourceFileNode, AstNode,
6 ast::{self, LoopBodyOwner},
7 SyntaxKind::*,
8};
9use hir::{
10 self,
11 FnScopes,
12 Def,
13 Path,
14};
15
16use crate::{
17 db::RootDatabase,
18 completion::CompletionItem,
19 Cancelable
20};
21
22pub(super) fn completions(
23 acc: &mut Vec<CompletionItem>,
24 db: &RootDatabase,
25 module: &hir::Module,
26 file: &SourceFileNode,
27 name_ref: ast::NameRef,
28) -> Cancelable<()> {
29 let kind = match classify_name_ref(name_ref) {
30 Some(it) => it,
31 None => return Ok(()),
32 };
33
34 match kind {
35 NameRefKind::LocalRef { enclosing_fn } => {
36 if let Some(fn_def) = enclosing_fn {
37 let scopes = FnScopes::new(fn_def);
38 complete_fn(name_ref, &scopes, acc);
39 complete_expr_keywords(&file, fn_def, name_ref, acc);
40 complete_expr_snippets(acc);
41 }
42
43 let module_scope = module.scope(db)?;
44 acc.extend(
45 module_scope
46 .entries()
47 .filter(|(_name, res)| {
48 // Don't expose this item
49 match res.import {
50 None => true,
51 Some(import) => {
52 let range = import.range(db, module.source().file_id());
53 !range.is_subrange(&name_ref.syntax().range())
54 }
55 }
56 })
57 .map(|(name, _res)| CompletionItem {
58 label: name.to_string(),
59 lookup: None,
60 snippet: None,
61 }),
62 );
63 }
64 NameRefKind::Path(path) => complete_path(acc, db, module, path)?,
65 NameRefKind::BareIdentInMod => {
66 let name_range = name_ref.syntax().range();
67 let top_node = name_ref
68 .syntax()
69 .ancestors()
70 .take_while(|it| it.range() == name_range)
71 .last()
72 .unwrap();
73 match top_node.parent().map(|it| it.kind()) {
74 Some(SOURCE_FILE) | Some(ITEM_LIST) => complete_mod_item_snippets(acc),
75 _ => (),
76 }
77 }
78 }
79 Ok(())
80}
81
82enum NameRefKind<'a> {
83 /// NameRef is a part of single-segment path, for example, a refernece to a
84 /// local variable.
85 LocalRef {
86 enclosing_fn: Option<ast::FnDef<'a>>,
87 },
88 /// NameRef is the last segment in some path
89 Path(Path),
90 /// NameRef is bare identifier at the module's root.
91 /// Used for keyword completion
92 BareIdentInMod,
93}
94
95fn classify_name_ref(name_ref: ast::NameRef) -> Option<NameRefKind> {
96 let name_range = name_ref.syntax().range();
97 let top_node = name_ref
98 .syntax()
99 .ancestors()
100 .take_while(|it| it.range() == name_range)
101 .last()
102 .unwrap();
103 match top_node.parent().map(|it| it.kind()) {
104 Some(SOURCE_FILE) | Some(ITEM_LIST) => return Some(NameRefKind::BareIdentInMod),
105 _ => (),
106 }
107
108 let parent = name_ref.syntax().parent()?;
109 if let Some(segment) = ast::PathSegment::cast(parent) {
110 let path = segment.parent_path();
111 if let Some(path) = Path::from_ast(path) {
112 if !path.is_ident() {
113 return Some(NameRefKind::Path(path));
114 }
115 }
116 if path.qualifier().is_none() {
117 let enclosing_fn = name_ref
118 .syntax()
119 .ancestors()
120 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
121 .find_map(ast::FnDef::cast);
122 return Some(NameRefKind::LocalRef { enclosing_fn });
123 }
124 }
125 None
126}
127
128fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<CompletionItem>) {
129 let mut shadowed = FxHashSet::default();
130 acc.extend(
131 scopes
132 .scope_chain(name_ref.syntax())
133 .flat_map(|scope| scopes.entries(scope).iter())
134 .filter(|entry| shadowed.insert(entry.name()))
135 .map(|entry| CompletionItem {
136 label: entry.name().to_string(),
137 lookup: None,
138 snippet: None,
139 }),
140 );
141 if scopes.self_param.is_some() {
142 acc.push(CompletionItem {
143 label: "self".to_string(),
144 lookup: None,
145 snippet: None,
146 })
147 }
148}
149
150fn complete_path(
151 acc: &mut Vec<CompletionItem>,
152 db: &RootDatabase,
153 module: &hir::Module,
154 mut path: Path,
155) -> Cancelable<()> {
156 if path.segments.is_empty() {
157 return Ok(());
158 }
159 path.segments.pop();
160 let def_id = match module.resolve_path(db, path)? {
161 None => return Ok(()),
162 Some(it) => it,
163 };
164 let target_module = match def_id.resolve(db)? {
165 Def::Module(it) => it,
166 _ => return Ok(()),
167 };
168 let module_scope = target_module.scope(db)?;
169 let completions = module_scope.entries().map(|(name, _res)| CompletionItem {
170 label: name.to_string(),
171 lookup: None,
172 snippet: None,
173 });
174 acc.extend(completions);
175 Ok(())
176}
177
178fn complete_mod_item_snippets(acc: &mut Vec<CompletionItem>) {
179 acc.push(CompletionItem {
180 label: "Test function".to_string(),
181 lookup: Some("tfn".to_string()),
182 snippet: Some(
183 "#[test]\n\
184 fn ${1:feature}() {\n\
185 $0\n\
186 }"
187 .to_string(),
188 ),
189 });
190 acc.push(CompletionItem {
191 label: "pub(crate)".to_string(),
192 lookup: None,
193 snippet: Some("pub(crate) $0".to_string()),
194 })
195}
196
197fn complete_expr_keywords(
198 file: &SourceFileNode,
199 fn_def: ast::FnDef,
200 name_ref: ast::NameRef,
201 acc: &mut Vec<CompletionItem>,
202) {
203 acc.push(keyword("if", "if $0 {}"));
204 acc.push(keyword("match", "match $0 {}"));
205 acc.push(keyword("while", "while $0 {}"));
206 acc.push(keyword("loop", "loop {$0}"));
207
208 if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
209 if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) {
210 if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
211 acc.push(keyword("else", "else {$0}"));
212 acc.push(keyword("else if", "else if $0 {}"));
213 }
214 }
215 }
216 if is_in_loop_body(name_ref) {
217 acc.push(keyword("continue", "continue"));
218 acc.push(keyword("break", "break"));
219 }
220 acc.extend(complete_return(fn_def, name_ref));
221}
222
223fn is_in_loop_body(name_ref: ast::NameRef) -> bool {
224 for node in name_ref.syntax().ancestors() {
225 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
226 break;
227 }
228 let loop_body = visitor()
229 .visit::<ast::ForExpr, _>(LoopBodyOwner::loop_body)
230 .visit::<ast::WhileExpr, _>(LoopBodyOwner::loop_body)
231 .visit::<ast::LoopExpr, _>(LoopBodyOwner::loop_body)
232 .accept(node);
233 if let Some(Some(body)) = loop_body {
234 if name_ref
235 .syntax()
236 .range()
237 .is_subrange(&body.syntax().range())
238 {
239 return true;
240 }
241 }
242 }
243 false
244}
245
246fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option<CompletionItem> {
247 // let is_last_in_block = name_ref.syntax().ancestors().filter_map(ast::Expr::cast)
248 // .next()
249 // .and_then(|it| it.syntax().parent())
250 // .and_then(ast::Block::cast)
251 // .is_some();
252
253 // if is_last_in_block {
254 // return None;
255 // }
256
257 let is_stmt = match name_ref
258 .syntax()
259 .ancestors()
260 .filter_map(ast::ExprStmt::cast)
261 .next()
262 {
263 None => false,
264 Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(),
265 };
266 let snip = match (is_stmt, fn_def.ret_type().is_some()) {
267 (true, true) => "return $0;",
268 (true, false) => "return;",
269 (false, true) => "return $0",
270 (false, false) => "return",
271 };
272 Some(keyword("return", snip))
273}
274
275fn keyword(kw: &str, snip: &str) -> CompletionItem {
276 CompletionItem {
277 label: kw.to_string(),
278 lookup: None,
279 snippet: Some(snip.to_string()),
280 }
281}
282
283fn complete_expr_snippets(acc: &mut Vec<CompletionItem>) {
284 acc.push(CompletionItem {
285 label: "pd".to_string(),
286 lookup: None,
287 snippet: Some("eprintln!(\"$0 = {:?}\", $0);".to_string()),
288 });
289 acc.push(CompletionItem {
290 label: "ppd".to_string(),
291 lookup: None,
292 snippet: Some("eprintln!(\"$0 = {:#?}\", $0);".to_string()),
293 });
294}
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs
index 5701e1ae2..b01382808 100644
--- a/crates/ra_analysis/src/imp.rs
+++ b/crates/ra_analysis/src/imp.rs
@@ -219,7 +219,8 @@ impl AnalysisImpl {
219 self.db.crate_graph().crate_root(crate_id) 219 self.db.crate_graph().crate_root(crate_id)
220 } 220 }
221 pub fn completions(&self, position: FilePosition) -> Cancelable<Option<Vec<CompletionItem>>> { 221 pub fn completions(&self, position: FilePosition) -> Cancelable<Option<Vec<CompletionItem>>> {
222 completions(&self.db, position) 222 let completions = completions(&self.db, position)?;
223 Ok(completions.map(|it| it.into()))
223 } 224 }
224 pub fn approximately_resolve_symbol( 225 pub fn approximately_resolve_symbol(
225 &self, 226 &self,
@@ -234,7 +235,7 @@ impl AnalysisImpl {
234 position.file_id, 235 position.file_id,
235 name_ref.syntax(), 236 name_ref.syntax(),
236 )? { 237 )? {
237 let scope = fn_descr.scope(&*self.db); 238 let scope = fn_descr.scopes(&*self.db);
238 // First try to resolve the symbol locally 239 // First try to resolve the symbol locally
239 if let Some(entry) = scope.resolve_local_name(name_ref) { 240 if let Some(entry) = scope.resolve_local_name(name_ref) {
240 rr.add_resolution( 241 rr.add_resolution(
@@ -293,7 +294,7 @@ impl AnalysisImpl {
293 let mut ret = vec![(position.file_id, binding.syntax().range())]; 294 let mut ret = vec![(position.file_id, binding.syntax().range())];
294 ret.extend( 295 ret.extend(
295 descr 296 descr
296 .scope(&*self.db) 297 .scopes(&*self.db)
297 .find_all_refs(binding) 298 .find_all_refs(binding)
298 .into_iter() 299 .into_iter()
299 .map(|ref_desc| (position.file_id, ref_desc.range)), 300 .map(|ref_desc| (position.file_id, ref_desc.range)),
@@ -321,7 +322,7 @@ impl AnalysisImpl {
321 position.file_id, 322 position.file_id,
322 name_ref.syntax(), 323 name_ref.syntax(),
323 )?); 324 )?);
324 let scope = descr.scope(db); 325 let scope = descr.scopes(db);
325 let resolved = ctry!(scope.resolve_local_name(name_ref)); 326 let resolved = ctry!(scope.resolve_local_name(name_ref));
326 let resolved = resolved.ptr().resolve(source_file); 327 let resolved = resolved.ptr().resolve(source_file);
327 let binding = ctry!(find_node_at_offset::<ast::BindPat>( 328 let binding = ctry!(find_node_at_offset::<ast::BindPat>(
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs
index c7e7dc1c0..85df9c089 100644
--- a/crates/ra_analysis/src/lib.rs
+++ b/crates/ra_analysis/src/lib.rs
@@ -30,7 +30,7 @@ use crate::{
30}; 30};
31 31
32pub use crate::{ 32pub use crate::{
33 completion::CompletionItem, 33 completion::{CompletionItem, CompletionItemKind, InsertText},
34}; 34};
35pub use ra_editor::{ 35pub use ra_editor::{
36 FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, Runnable, RunnableKind, StructureNode, 36 FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, Runnable, RunnableKind, StructureNode,
@@ -72,13 +72,23 @@ struct RemoveFile {
72 72
73impl fmt::Debug for AnalysisChange { 73impl fmt::Debug for AnalysisChange {
74 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 74 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
75 fmt.debug_struct("AnalysisChange") 75 let mut d = fmt.debug_struct("AnalysisChange");
76 .field("new_roots", &self.new_roots) 76 if !self.new_roots.is_empty() {
77 .field("roots_changed", &self.roots_changed) 77 d.field("new_roots", &self.new_roots);
78 .field("files_changed", &self.files_changed.len()) 78 }
79 .field("libraries_added", &self.libraries_added.len()) 79 if !self.roots_changed.is_empty() {
80 .field("crate_graph", &self.crate_graph) 80 d.field("roots_changed", &self.roots_changed);
81 .finish() 81 }
82 if !self.files_changed.is_empty() {
83 d.field("files_changed", &self.files_changed.len());
84 }
85 if !self.libraries_added.is_empty() {
86 d.field("libraries_added", &self.libraries_added.len());
87 }
88 if !self.crate_graph.is_some() {
89 d.field("crate_graph", &self.crate_graph);
90 }
91 d.finish()
82 } 92 }
83} 93}
84 94
@@ -358,13 +368,22 @@ impl Analysis {
358 } 368 }
359} 369}
360 370
361#[derive(Debug)]
362pub struct LibraryData { 371pub struct LibraryData {
363 root_id: SourceRootId, 372 root_id: SourceRootId,
364 root_change: RootChange, 373 root_change: RootChange,
365 symbol_index: SymbolIndex, 374 symbol_index: SymbolIndex,
366} 375}
367 376
377impl fmt::Debug for LibraryData {
378 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
379 f.debug_struct("LibraryData")
380 .field("root_id", &self.root_id)
381 .field("root_change", &self.root_change)
382 .field("n_symbols", &self.symbol_index.len())
383 .finish()
384 }
385}
386
368impl LibraryData { 387impl LibraryData {
369 pub fn prepare( 388 pub fn prepare(
370 root_id: SourceRootId, 389 root_id: SourceRootId,
diff --git a/crates/ra_analysis/src/symbol_index.rs b/crates/ra_analysis/src/symbol_index.rs
index b48a37229..e5bdf0aa1 100644
--- a/crates/ra_analysis/src/symbol_index.rs
+++ b/crates/ra_analysis/src/symbol_index.rs
@@ -56,6 +56,10 @@ impl Hash for SymbolIndex {
56} 56}
57 57
58impl SymbolIndex { 58impl SymbolIndex {
59 pub(crate) fn len(&self) -> usize {
60 self.symbols.len()
61 }
62
59 pub(crate) fn for_files( 63 pub(crate) fn for_files(
60 files: impl ParallelIterator<Item = (FileId, SourceFileNode)>, 64 files: impl ParallelIterator<Item = (FileId, SourceFileNode)>,
61 ) -> SymbolIndex { 65 ) -> SymbolIndex {
diff --git a/crates/ra_analysis/tests/tests.rs b/crates/ra_analysis/tests/tests.rs
index 67738da48..938ca797a 100644
--- a/crates/ra_analysis/tests/tests.rs
+++ b/crates/ra_analysis/tests/tests.rs
@@ -452,63 +452,3 @@ fn test_find_all_refs_for_fn_param() {
452 let refs = get_all_refs(code); 452 let refs = get_all_refs(code);
453 assert_eq!(refs.len(), 2); 453 assert_eq!(refs.len(), 2);
454} 454}
455
456#[test]
457fn test_complete_crate_path() {
458 let (analysis, position) = analysis_and_position(
459 "
460 //- /lib.rs
461 mod foo;
462 struct Spam;
463 //- /foo.rs
464 use crate::Sp<|>
465 ",
466 );
467 let completions = analysis.completions(position).unwrap().unwrap();
468 assert_eq_dbg(
469 r#"[CompletionItem { label: "Spam", lookup: None, snippet: None },
470 CompletionItem { label: "foo", lookup: None, snippet: None }]"#,
471 &completions,
472 );
473}
474
475#[test]
476fn test_complete_crate_path_with_braces() {
477 let (analysis, position) = analysis_and_position(
478 "
479 //- /lib.rs
480 mod foo;
481 struct Spam;
482 //- /foo.rs
483 use crate::{Sp<|>};
484 ",
485 );
486 let completions = analysis.completions(position).unwrap().unwrap();
487 assert_eq_dbg(
488 r#"[CompletionItem { label: "Spam", lookup: None, snippet: None },
489 CompletionItem { label: "foo", lookup: None, snippet: None }]"#,
490 &completions,
491 );
492}
493
494#[test]
495fn test_complete_crate_path_in_nested_tree() {
496 let (analysis, position) = analysis_and_position(
497 "
498 //- /lib.rs
499 mod foo;
500 pub mod bar {
501 pub mod baz {
502 pub struct Spam;
503 }
504 }
505 //- /foo.rs
506 use crate::{bar::{baz::Sp<|>}};
507 ",
508 );
509 let completions = analysis.completions(position).unwrap().unwrap();
510 assert_eq_dbg(
511 r#"[CompletionItem { label: "Spam", lookup: None, snippet: None }]"#,
512 &completions,
513 );
514}