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