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