aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_editor/src/completion.rs
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2018-09-16 10:54:24 +0100
committerAleksey Kladov <[email protected]>2018-09-16 11:07:39 +0100
commitb5021411a84822cb3f1e3aeffad9550dd15bdeb6 (patch)
tree9dca564f8e51b298dced01c4ce669c756dce3142 /crates/ra_editor/src/completion.rs
parentba0bfeee12e19da40b5eabc8d0408639af10e96f (diff)
rename all things
Diffstat (limited to 'crates/ra_editor/src/completion.rs')
-rw-r--r--crates/ra_editor/src/completion.rs480
1 files changed, 480 insertions, 0 deletions
diff --git a/crates/ra_editor/src/completion.rs b/crates/ra_editor/src/completion.rs
new file mode 100644
index 000000000..5000b32a0
--- /dev/null
+++ b/crates/ra_editor/src/completion.rs
@@ -0,0 +1,480 @@
1use std::collections::{HashSet, HashMap};
2
3use ra_syntax::{
4 File, TextUnit, AstNode, SyntaxNodeRef, SyntaxKind::*,
5 ast::{self, LoopBodyOwner, ModuleItemOwner},
6 algo::{
7 ancestors,
8 visit::{visitor, Visitor, visitor_ctx, VisitorCtx},
9 },
10 text_utils::is_subrange,
11};
12
13use {
14 AtomEdit, find_node_at_offset,
15 scope::{FnScopes, ModuleScope},
16};
17
18#[derive(Debug)]
19pub struct CompletionItem {
20 /// What user sees in pop-up
21 pub label: String,
22 /// What string is used for filtering, defaults to label
23 pub lookup: Option<String>,
24 /// What is inserted, defaults to label
25 pub snippet: Option<String>
26}
27
28pub fn scope_completion(file: &File, offset: TextUnit) -> Option<Vec<CompletionItem>> {
29 // Insert a fake ident to get a valid parse tree
30 let file = {
31 let edit = AtomEdit::insert(offset, "intellijRulezz".to_string());
32 file.reparse(&edit)
33 };
34 let mut has_completions = false;
35 let mut res = Vec::new();
36 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), offset) {
37 has_completions = true;
38 complete_name_ref(&file, name_ref, &mut res);
39 // special case, `trait T { fn foo(i_am_a_name_ref) {} }`
40 if is_node::<ast::Param>(name_ref.syntax()) {
41 param_completions(name_ref.syntax(), &mut res);
42 }
43 }
44 if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), offset) {
45 if is_node::<ast::Param>(name.syntax()) {
46 has_completions = true;
47 param_completions(name.syntax(), &mut res);
48 }
49 }
50 if has_completions {
51 Some(res)
52 } else {
53 None
54 }
55}
56
57fn complete_name_ref(file: &File, name_ref: ast::NameRef, acc: &mut Vec<CompletionItem>) {
58 if !is_node::<ast::Path>(name_ref.syntax()) {
59 return;
60 }
61 let mut visited_fn = false;
62 for node in ancestors(name_ref.syntax()) {
63 if let Some(items) = visitor()
64 .visit::<ast::Root, _>(|it| Some(it.items()))
65 .visit::<ast::Module, _>(|it| Some(it.item_list()?.items()))
66 .accept(node) {
67 if let Some(items) = items {
68 let scope = ModuleScope::new(items);
69 acc.extend(
70 scope.entries().iter()
71 .filter(|entry| entry.syntax() != name_ref.syntax())
72 .map(|entry| CompletionItem {
73 label: entry.name().to_string(),
74 lookup: None,
75 snippet: None,
76 })
77 );
78 }
79 break;
80
81 } else if !visited_fn {
82 if let Some(fn_def) = ast::FnDef::cast(node) {
83 visited_fn = true;
84 complete_expr_keywords(&file, fn_def, name_ref, acc);
85 let scopes = FnScopes::new(fn_def);
86 complete_fn(name_ref, &scopes, acc);
87 }
88 }
89 }
90}
91
92fn param_completions(ctx: SyntaxNodeRef, acc: &mut Vec<CompletionItem>) {
93 let mut params = HashMap::new();
94 for node in ancestors(ctx) {
95 let _ = visitor_ctx(&mut params)
96 .visit::<ast::Root, _>(process)
97 .visit::<ast::ItemList, _>(process)
98 .accept(node);
99 }
100 params.into_iter()
101 .filter_map(|(label, (count, param))| {
102 let lookup = param.pat()?.syntax().text().to_string();
103 if count < 2 { None } else { Some((label, lookup)) }
104 })
105 .for_each(|(label, lookup)| {
106 acc.push(CompletionItem {
107 label, lookup: Some(lookup), snippet: None
108 })
109 });
110
111 fn process<'a, N: ast::FnDefOwner<'a>>(node: N, params: &mut HashMap<String, (u32, ast::Param<'a>)>) {
112 node.functions()
113 .filter_map(|it| it.param_list())
114 .flat_map(|it| it.params())
115 .for_each(|param| {
116 let text = param.syntax().text().to_string();
117 params.entry(text)
118 .or_insert((0, param))
119 .0 += 1;
120 })
121 }
122}
123
124fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool {
125 match ancestors(node).filter_map(N::cast).next() {
126 None => false,
127 Some(n) => n.syntax().range() == node.range(),
128 }
129}
130
131
132fn complete_expr_keywords(file: &File, fn_def: ast::FnDef, name_ref: ast::NameRef, acc: &mut Vec<CompletionItem>) {
133 acc.push(keyword("if", "if $0 {}"));
134 acc.push(keyword("match", "match $0 {}"));
135 acc.push(keyword("while", "while $0 {}"));
136 acc.push(keyword("loop", "loop {$0}"));
137
138 if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
139 if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) {
140 if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
141 acc.push(keyword("else", "else {$0}"));
142 acc.push(keyword("else if", "else if $0 {}"));
143 }
144 }
145 }
146 if is_in_loop_body(name_ref) {
147 acc.push(keyword("continue", "continue"));
148 acc.push(keyword("break", "break"));
149 }
150 acc.extend(complete_return(fn_def, name_ref));
151}
152
153fn is_in_loop_body(name_ref: ast::NameRef) -> bool {
154 for node in ancestors(name_ref.syntax()) {
155 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
156 break;
157 }
158 let loop_body = visitor()
159 .visit::<ast::ForExpr, _>(LoopBodyOwner::loop_body)
160 .visit::<ast::WhileExpr, _>(LoopBodyOwner::loop_body)
161 .visit::<ast::LoopExpr, _>(LoopBodyOwner::loop_body)
162 .accept(node);
163 if let Some(Some(body)) = loop_body {
164 if is_subrange(body.syntax().range(), name_ref.syntax().range()) {
165 return true;
166 }
167 }
168 }
169 false
170}
171
172fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option<CompletionItem> {
173 // let is_last_in_block = ancestors(name_ref.syntax()).filter_map(ast::Expr::cast)
174 // .next()
175 // .and_then(|it| it.syntax().parent())
176 // .and_then(ast::Block::cast)
177 // .is_some();
178
179 // if is_last_in_block {
180 // return None;
181 // }
182
183 let is_stmt = match ancestors(name_ref.syntax()).filter_map(ast::ExprStmt::cast).next() {
184 None => false,
185 Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range()
186 };
187 let snip = match (is_stmt, fn_def.ret_type().is_some()) {
188 (true, true) => "return $0;",
189 (true, false) => "return;",
190 (false, true) => "return $0",
191 (false, false) => "return",
192 };
193 Some(keyword("return", snip))
194}
195
196fn keyword(kw: &str, snip: &str) -> CompletionItem {
197 CompletionItem {
198 label: kw.to_string(),
199 lookup: None,
200 snippet: Some(snip.to_string()),
201 }
202}
203
204fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<CompletionItem>) {
205 let mut shadowed = HashSet::new();
206 acc.extend(
207 scopes.scope_chain(name_ref.syntax())
208 .flat_map(|scope| scopes.entries(scope).iter())
209 .filter(|entry| shadowed.insert(entry.name()))
210 .map(|entry| CompletionItem {
211 label: entry.name().to_string(),
212 lookup: None,
213 snippet: None,
214 })
215 );
216 if scopes.self_param.is_some() {
217 acc.push(CompletionItem {
218 label: "self".to_string(),
219 lookup: None,
220 snippet: None,
221 })
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use test_utils::{assert_eq_dbg, extract_offset};
229
230 fn check_scope_completion(code: &str, expected_completions: &str) {
231 let (off, code) = extract_offset(&code);
232 let file = File::parse(&code);
233 let completions = scope_completion(&file, off)
234 .unwrap()
235 .into_iter()
236 .filter(|c| c.snippet.is_none())
237 .collect::<Vec<_>>();
238 assert_eq_dbg(expected_completions, &completions);
239 }
240
241 fn check_snippet_completion(code: &str, expected_completions: &str) {
242 let (off, code) = extract_offset(&code);
243 let file = File::parse(&code);
244 let completions = scope_completion(&file, off)
245 .unwrap()
246 .into_iter()
247 .filter(|c| c.snippet.is_some())
248 .collect::<Vec<_>>();
249 assert_eq_dbg(expected_completions, &completions);
250 }
251
252 #[test]
253 fn test_completion_let_scope() {
254 check_scope_completion(r"
255 fn quux(x: i32) {
256 let y = 92;
257 1 + <|>;
258 let z = ();
259 }
260 ", r#"[CompletionItem { label: "y", lookup: None, snippet: None },
261 CompletionItem { label: "x", lookup: None, snippet: None },
262 CompletionItem { label: "quux", lookup: None, snippet: None }]"#);
263 }
264
265 #[test]
266 fn test_completion_if_let_scope() {
267 check_scope_completion(r"
268 fn quux() {
269 if let Some(x) = foo() {
270 let y = 92;
271 };
272 if let Some(a) = bar() {
273 let b = 62;
274 1 + <|>
275 }
276 }
277 ", r#"[CompletionItem { label: "b", lookup: None, snippet: None },
278 CompletionItem { label: "a", lookup: None, snippet: None },
279 CompletionItem { label: "quux", lookup: None, snippet: None }]"#);
280 }
281
282 #[test]
283 fn test_completion_for_scope() {
284 check_scope_completion(r"
285 fn quux() {
286 for x in &[1, 2, 3] {
287 <|>
288 }
289 }
290 ", r#"[CompletionItem { label: "x", lookup: None, snippet: None },
291 CompletionItem { label: "quux", lookup: None, snippet: None }]"#);
292 }
293
294 #[test]
295 fn test_completion_mod_scope() {
296 check_scope_completion(r"
297 struct Foo;
298 enum Baz {}
299 fn quux() {
300 <|>
301 }
302 ", r#"[CompletionItem { label: "Foo", lookup: None, snippet: None },
303 CompletionItem { label: "Baz", lookup: None, snippet: None },
304 CompletionItem { label: "quux", lookup: None, snippet: None }]"#);
305 }
306
307 #[test]
308 fn test_completion_mod_scope_no_self_use() {
309 check_scope_completion(r"
310 use foo<|>;
311 ", r#"[]"#);
312 }
313
314 #[test]
315 fn test_completion_mod_scope_nested() {
316 check_scope_completion(r"
317 struct Foo;
318 mod m {
319 struct Bar;
320 fn quux() { <|> }
321 }
322 ", r#"[CompletionItem { label: "Bar", lookup: None, snippet: None },
323 CompletionItem { label: "quux", lookup: None, snippet: None }]"#);
324 }
325
326 #[test]
327 fn test_complete_type() {
328 check_scope_completion(r"
329 struct Foo;
330 fn x() -> <|>
331 ", r#"[CompletionItem { label: "Foo", lookup: None, snippet: None },
332 CompletionItem { label: "x", lookup: None, snippet: None }]"#)
333 }
334
335 #[test]
336 fn test_complete_shadowing() {
337 check_scope_completion(r"
338 fn foo() -> {
339 let bar = 92;
340 {
341 let bar = 62;
342 <|>
343 }
344 }
345 ", r#"[CompletionItem { label: "bar", lookup: None, snippet: None },
346 CompletionItem { label: "foo", lookup: None, snippet: None }]"#)
347 }
348
349 #[test]
350 fn test_complete_self() {
351 check_scope_completion(r"
352 impl S { fn foo(&self) { <|> } }
353 ", r#"[CompletionItem { label: "self", lookup: None, snippet: None }]"#)
354 }
355
356 #[test]
357 fn test_completion_kewords() {
358 check_snippet_completion(r"
359 fn quux() {
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") }]"#);
367 }
368
369 #[test]
370 fn test_completion_else() {
371 check_snippet_completion(r"
372 fn quux() {
373 if true {
374 ()
375 } <|>
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: "else", lookup: None, snippet: Some("else {$0}") },
382 CompletionItem { label: "else if", lookup: None, snippet: Some("else if $0 {}") },
383 CompletionItem { label: "return", lookup: None, snippet: Some("return") }]"#);
384 }
385
386 #[test]
387 fn test_completion_return_value() {
388 check_snippet_completion(r"
389 fn quux() -> i32 {
390 <|>
391 92
392 }
393 ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
394 CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
395 CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
396 CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
397 CompletionItem { label: "return", lookup: None, snippet: Some("return $0;") }]"#);
398 check_snippet_completion(r"
399 fn quux() {
400 <|>
401 92
402 }
403 ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
404 CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
405 CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
406 CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
407 CompletionItem { label: "return", lookup: None, snippet: Some("return;") }]"#);
408 }
409
410 #[test]
411 fn test_completion_return_no_stmt() {
412 check_snippet_completion(r"
413 fn quux() -> i32 {
414 match () {
415 () => <|>
416 }
417 }
418 ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
419 CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
420 CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
421 CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
422 CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }]"#);
423 }
424
425 #[test]
426 fn test_continue_break_completion() {
427 check_snippet_completion(r"
428 fn quux() -> i32 {
429 loop { <|> }
430 }
431 ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
432 CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
433 CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
434 CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
435 CompletionItem { label: "continue", lookup: None, snippet: Some("continue") },
436 CompletionItem { label: "break", lookup: None, snippet: Some("break") },
437 CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }]"#);
438 check_snippet_completion(r"
439 fn quux() -> i32 {
440 loop { || { <|> } }
441 }
442 ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
443 CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
444 CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
445 CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
446 CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }]"#);
447 }
448
449 #[test]
450 fn test_param_completion_last_param() {
451 check_scope_completion(r"
452 fn foo(file_id: FileId) {}
453 fn bar(file_id: FileId) {}
454 fn baz(file<|>) {}
455 ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#);
456 }
457
458 #[test]
459 fn test_param_completion_nth_param() {
460 check_scope_completion(r"
461 fn foo(file_id: FileId) {}
462 fn bar(file_id: FileId) {}
463 fn baz(file<|>, x: i32) {}
464 ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#);
465 }
466
467 #[test]
468 fn test_param_completion_trait_param() {
469 check_scope_completion(r"
470 pub(crate) trait SourceRoot {
471 pub fn contains(&self, file_id: FileId) -> bool;
472 pub fn module_map(&self) -> &ModuleMap;
473 pub fn lines(&self, file_id: FileId) -> &LineIndex;
474 pub fn syntax(&self, file<|>)
475 }
476 ", r#"[CompletionItem { label: "self", lookup: None, snippet: None },
477 CompletionItem { label: "SourceRoot", lookup: None, snippet: None },
478 CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#);
479 }
480}