aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_editor/src
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2018-10-31 18:05:12 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2018-10-31 18:05:12 +0000
commit1dc5608d0bb6bf2eee5a1b9190fcb2f8cdfa2ef3 (patch)
treeb3c8a97880c625a81a814f4afa4e461ce5a58b82 /crates/ra_editor/src
parente60ef6260f49b2b0438f8649ca71034fbafef631 (diff)
parentc09e14a4ff02f774460a70472e1aeb3c598e01dc (diff)
Merge #176
176: Move completio to ra_analysis r=matklad a=matklad While we should handle completion for isolated file, it's better achieved by using empty Analysis, rather than working only with &File: we need memoization for type inference even inside a single file. Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_editor/src')
-rw-r--r--crates/ra_editor/src/completion.rs602
-rw-r--r--crates/ra_editor/src/lib.rs15
-rw-r--r--crates/ra_editor/src/scope/fn_scope.rs435
-rw-r--r--crates/ra_editor/src/scope/mod.rs7
-rw-r--r--crates/ra_editor/src/scope/mod_scope.rs124
5 files changed, 2 insertions, 1181 deletions
diff --git a/crates/ra_editor/src/completion.rs b/crates/ra_editor/src/completion.rs
deleted file mode 100644
index 20c8546a4..000000000
--- a/crates/ra_editor/src/completion.rs
+++ /dev/null
@@ -1,602 +0,0 @@
1/// FIXME: move completion from ra_editor to ra_analysis
2
3use rustc_hash::{FxHashMap, FxHashSet};
4
5use ra_syntax::{
6 algo::visit::{visitor, visitor_ctx, Visitor, VisitorCtx},
7 ast::{self, AstChildren, LoopBodyOwner, ModuleItemOwner},
8 AstNode, File,
9 SyntaxKind::*,
10 SyntaxNodeRef, TextUnit,
11};
12
13use crate::{
14 find_node_at_offset,
15 scope::{FnScopes, ModuleScope},
16 AtomEdit,
17};
18
19#[derive(Debug)]
20pub struct CompletionItem {
21 /// What user sees in pop-up
22 pub label: String,
23 /// What string is used for filtering, defaults to label
24 pub lookup: Option<String>,
25 /// What is inserted, defaults to label
26 pub snippet: Option<String>,
27}
28
29pub fn scope_completion(file: &File, offset: TextUnit) -> Option<Vec<CompletionItem>> {
30 // Insert a fake ident to get a valid parse tree
31 let file = {
32 let edit = AtomEdit::insert(offset, "intellijRulezz".to_string());
33 file.reparse(&edit)
34 };
35 let mut has_completions = false;
36 let mut res = Vec::new();
37 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), offset) {
38 has_completions = true;
39 complete_name_ref(&file, name_ref, &mut res);
40 // special case, `trait T { fn foo(i_am_a_name_ref) {} }`
41 if is_node::<ast::Param>(name_ref.syntax()) {
42 param_completions(name_ref.syntax(), &mut res);
43 }
44 let name_range = name_ref.syntax().range();
45 let top_node = name_ref
46 .syntax()
47 .ancestors()
48 .take_while(|it| it.range() == name_range)
49 .last()
50 .unwrap();
51 match top_node.parent().map(|it| it.kind()) {
52 Some(ROOT) | Some(ITEM_LIST) => complete_mod_item_snippets(&mut res),
53 _ => (),
54 }
55 }
56 if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), offset) {
57 if is_node::<ast::Param>(name.syntax()) {
58 has_completions = true;
59 param_completions(name.syntax(), &mut res);
60 }
61 }
62 if has_completions {
63 Some(res)
64 } else {
65 None
66 }
67}
68
69pub fn complete_module_items(items: AstChildren<ast::ModuleItem>, this_item: Option<ast::NameRef>, acc: &mut Vec<CompletionItem>) {
70 let scope = ModuleScope::new(items);
71 acc.extend(
72 scope
73 .entries()
74 .iter()
75 .filter(|entry| Some(entry.syntax()) != this_item.map(|it| it.syntax()))
76 .map(|entry| CompletionItem {
77 label: entry.name().to_string(),
78 lookup: None,
79 snippet: None,
80 }),
81 );
82}
83
84fn complete_name_ref(file: &File, name_ref: ast::NameRef, acc: &mut Vec<CompletionItem>) {
85 if !is_node::<ast::Path>(name_ref.syntax()) {
86 return;
87 }
88 let mut visited_fn = false;
89 for node in name_ref.syntax().ancestors() {
90 if let Some(items) = visitor()
91 .visit::<ast::Root, _>(|it| Some(it.items()))
92 .visit::<ast::Module, _>(|it| Some(it.item_list()?.items()))
93 .accept(node)
94 {
95 if let Some(items) = items {
96 complete_module_items(items, Some(name_ref), acc);
97 }
98 break;
99 } else if !visited_fn {
100 if let Some(fn_def) = ast::FnDef::cast(node) {
101 visited_fn = true;
102 complete_expr_keywords(&file, fn_def, name_ref, acc);
103 complete_expr_snippets(acc);
104 let scopes = FnScopes::new(fn_def);
105 complete_fn(name_ref, &scopes, acc);
106 }
107 }
108 }
109}
110
111fn param_completions(ctx: SyntaxNodeRef, acc: &mut Vec<CompletionItem>) {
112 let mut params = FxHashMap::default();
113 for node in ctx.ancestors() {
114 let _ = visitor_ctx(&mut params)
115 .visit::<ast::Root, _>(process)
116 .visit::<ast::ItemList, _>(process)
117 .accept(node);
118 }
119 params
120 .into_iter()
121 .filter_map(|(label, (count, param))| {
122 let lookup = param.pat()?.syntax().text().to_string();
123 if count < 2 {
124 None
125 } else {
126 Some((label, lookup))
127 }
128 })
129 .for_each(|(label, lookup)| {
130 acc.push(CompletionItem {
131 label,
132 lookup: Some(lookup),
133 snippet: None,
134 })
135 });
136
137 fn process<'a, N: ast::FnDefOwner<'a>>(
138 node: N,
139 params: &mut FxHashMap<String, (u32, ast::Param<'a>)>,
140 ) {
141 node.functions()
142 .filter_map(|it| it.param_list())
143 .flat_map(|it| it.params())
144 .for_each(|param| {
145 let text = param.syntax().text().to_string();
146 params.entry(text).or_insert((0, param)).0 += 1;
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}
157
158fn complete_expr_keywords(
159 file: &File,
160 fn_def: ast::FnDef,
161 name_ref: ast::NameRef,
162 acc: &mut Vec<CompletionItem>,
163) {
164 acc.push(keyword("if", "if $0 {}"));
165 acc.push(keyword("match", "match $0 {}"));
166 acc.push(keyword("while", "while $0 {}"));
167 acc.push(keyword("loop", "loop {$0}"));
168
169 if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
170 if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) {
171 if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
172 acc.push(keyword("else", "else {$0}"));
173 acc.push(keyword("else if", "else if $0 {}"));
174 }
175 }
176 }
177 if is_in_loop_body(name_ref) {
178 acc.push(keyword("continue", "continue"));
179 acc.push(keyword("break", "break"));
180 }
181 acc.extend(complete_return(fn_def, name_ref));
182}
183
184fn is_in_loop_body(name_ref: ast::NameRef) -> bool {
185 for node in name_ref.syntax().ancestors() {
186 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
187 break;
188 }
189 let loop_body = visitor()
190 .visit::<ast::ForExpr, _>(LoopBodyOwner::loop_body)
191 .visit::<ast::WhileExpr, _>(LoopBodyOwner::loop_body)
192 .visit::<ast::LoopExpr, _>(LoopBodyOwner::loop_body)
193 .accept(node);
194 if let Some(Some(body)) = loop_body {
195 if name_ref.syntax().range().is_subrange(&body.syntax().range()) {
196 return true;
197 }
198 }
199 }
200 false
201}
202
203fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option<CompletionItem> {
204 // let is_last_in_block = name_ref.syntax().ancestors().filter_map(ast::Expr::cast)
205 // .next()
206 // .and_then(|it| it.syntax().parent())
207 // .and_then(ast::Block::cast)
208 // .is_some();
209
210 // if is_last_in_block {
211 // return None;
212 // }
213
214 let is_stmt = match name_ref
215 .syntax()
216 .ancestors()
217 .filter_map(ast::ExprStmt::cast)
218 .next()
219 {
220 None => false,
221 Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(),
222 };
223 let snip = match (is_stmt, fn_def.ret_type().is_some()) {
224 (true, true) => "return $0;",
225 (true, false) => "return;",
226 (false, true) => "return $0",
227 (false, false) => "return",
228 };
229 Some(keyword("return", snip))
230}
231
232fn keyword(kw: &str, snip: &str) -> CompletionItem {
233 CompletionItem {
234 label: kw.to_string(),
235 lookup: None,
236 snippet: Some(snip.to_string()),
237 }
238}
239
240fn complete_expr_snippets(acc: &mut Vec<CompletionItem>) {
241 acc.push(CompletionItem {
242 label: "pd".to_string(),
243 lookup: None,
244 snippet: Some("eprintln!(\"$0 = {:?}\", $0);".to_string()),
245 });
246 acc.push(CompletionItem {
247 label: "ppd".to_string(),
248 lookup: None,
249 snippet: Some("eprintln!(\"$0 = {:#?}\", $0);".to_string()),
250 });
251}
252
253fn complete_mod_item_snippets(acc: &mut Vec<CompletionItem>) {
254 acc.push(CompletionItem {
255 label: "tfn".to_string(),
256 lookup: None,
257 snippet: Some("#[test]\nfn $1() {\n $0\n}".to_string()),
258 });
259 acc.push(CompletionItem {
260 label: "pub(crate)".to_string(),
261 lookup: None,
262 snippet: Some("pub(crate) $0".to_string()),
263 })
264}
265
266fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<CompletionItem>) {
267 let mut shadowed = FxHashSet::default();
268 acc.extend(
269 scopes
270 .scope_chain(name_ref.syntax())
271 .flat_map(|scope| scopes.entries(scope).iter())
272 .filter(|entry| shadowed.insert(entry.name()))
273 .map(|entry| CompletionItem {
274 label: entry.name().to_string(),
275 lookup: None,
276 snippet: None,
277 }),
278 );
279 if scopes.self_param.is_some() {
280 acc.push(CompletionItem {
281 label: "self".to_string(),
282 lookup: None,
283 snippet: None,
284 })
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use test_utils::{assert_eq_dbg, extract_offset};
292
293 fn check_scope_completion(code: &str, expected_completions: &str) {
294 let (off, code) = extract_offset(&code);
295 let file = File::parse(&code);
296 let completions = scope_completion(&file, off)
297 .unwrap()
298 .into_iter()
299 .filter(|c| c.snippet.is_none())
300 .collect::<Vec<_>>();
301 assert_eq_dbg(expected_completions, &completions);
302 }
303
304 fn check_snippet_completion(code: &str, expected_completions: &str) {
305 let (off, code) = extract_offset(&code);
306 let file = File::parse(&code);
307 let completions = scope_completion(&file, off)
308 .unwrap()
309 .into_iter()
310 .filter(|c| c.snippet.is_some())
311 .collect::<Vec<_>>();
312 assert_eq_dbg(expected_completions, &completions);
313 }
314
315 #[test]
316 fn test_completion_let_scope() {
317 check_scope_completion(
318 r"
319 fn quux(x: i32) {
320 let y = 92;
321 1 + <|>;
322 let z = ();
323 }
324 ",
325 r#"[CompletionItem { label: "y", lookup: None, snippet: None },
326 CompletionItem { label: "x", lookup: None, snippet: None },
327 CompletionItem { label: "quux", lookup: None, snippet: None }]"#,
328 );
329 }
330
331 #[test]
332 fn test_completion_if_let_scope() {
333 check_scope_completion(
334 r"
335 fn quux() {
336 if let Some(x) = foo() {
337 let y = 92;
338 };
339 if let Some(a) = bar() {
340 let b = 62;
341 1 + <|>
342 }
343 }
344 ",
345 r#"[CompletionItem { label: "b", lookup: None, snippet: None },
346 CompletionItem { label: "a", lookup: None, snippet: None },
347 CompletionItem { label: "quux", lookup: None, snippet: None }]"#,
348 );
349 }
350
351 #[test]
352 fn test_completion_for_scope() {
353 check_scope_completion(
354 r"
355 fn quux() {
356 for x in &[1, 2, 3] {
357 <|>
358 }
359 }
360 ",
361 r#"[CompletionItem { label: "x", lookup: None, snippet: None },
362 CompletionItem { label: "quux", lookup: None, snippet: None }]"#,
363 );
364 }
365
366 #[test]
367 fn test_completion_mod_scope() {
368 check_scope_completion(
369 r"
370 struct Foo;
371 enum Baz {}
372 fn quux() {
373 <|>
374 }
375 ",
376 r#"[CompletionItem { label: "Foo", lookup: None, snippet: None },
377 CompletionItem { label: "Baz", lookup: None, snippet: None },
378 CompletionItem { label: "quux", lookup: None, snippet: None }]"#,
379 );
380 }
381
382 #[test]
383 fn test_completion_mod_scope_no_self_use() {
384 check_scope_completion(
385 r"
386 use foo<|>;
387 ",
388 r#"[]"#,
389 );
390 }
391
392 #[test]
393 fn test_completion_mod_scope_nested() {
394 check_scope_completion(
395 r"
396 struct Foo;
397 mod m {
398 struct Bar;
399 fn quux() { <|> }
400 }
401 ",
402 r#"[CompletionItem { label: "Bar", lookup: None, snippet: None },
403 CompletionItem { label: "quux", lookup: None, snippet: None }]"#,
404 );
405 }
406
407 #[test]
408 fn test_complete_type() {
409 check_scope_completion(
410 r"
411 struct Foo;
412 fn x() -> <|>
413 ",
414 r#"[CompletionItem { label: "Foo", lookup: None, snippet: None },
415 CompletionItem { label: "x", lookup: None, snippet: None }]"#,
416 )
417 }
418
419 #[test]
420 fn test_complete_shadowing() {
421 check_scope_completion(
422 r"
423 fn foo() -> {
424 let bar = 92;
425 {
426 let bar = 62;
427 <|>
428 }
429 }
430 ",
431 r#"[CompletionItem { label: "bar", lookup: None, snippet: None },
432 CompletionItem { label: "foo", lookup: None, snippet: None }]"#,
433 )
434 }
435
436 #[test]
437 fn test_complete_self() {
438 check_scope_completion(
439 r"
440 impl S { fn foo(&self) { <|> } }
441 ",
442 r#"[CompletionItem { label: "self", lookup: None, snippet: None }]"#,
443 )
444 }
445
446 #[test]
447 fn test_completion_kewords() {
448 check_snippet_completion(r"
449 fn quux() {
450 <|>
451 }
452 ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
453 CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
454 CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
455 CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
456 CompletionItem { label: "return", lookup: None, snippet: Some("return") },
457 CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
458 CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
459 }
460
461 #[test]
462 fn test_completion_else() {
463 check_snippet_completion(r"
464 fn quux() {
465 if true {
466 ()
467 } <|>
468 }
469 ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
470 CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
471 CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
472 CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
473 CompletionItem { label: "else", lookup: None, snippet: Some("else {$0}") },
474 CompletionItem { label: "else if", lookup: None, snippet: Some("else if $0 {}") },
475 CompletionItem { label: "return", lookup: None, snippet: Some("return") },
476 CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
477 CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
478 }
479
480 #[test]
481 fn test_completion_return_value() {
482 check_snippet_completion(r"
483 fn quux() -> i32 {
484 <|>
485 92
486 }
487 ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
488 CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
489 CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
490 CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
491 CompletionItem { label: "return", lookup: None, snippet: Some("return $0;") },
492 CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
493 CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
494 check_snippet_completion(r"
495 fn quux() {
496 <|>
497 92
498 }
499 ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
500 CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
501 CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
502 CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
503 CompletionItem { label: "return", lookup: None, snippet: Some("return;") },
504 CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
505 CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
506 }
507
508 #[test]
509 fn test_completion_return_no_stmt() {
510 check_snippet_completion(r"
511 fn quux() -> i32 {
512 match () {
513 () => <|>
514 }
515 }
516 ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
517 CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
518 CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
519 CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
520 CompletionItem { label: "return", lookup: None, snippet: Some("return $0") },
521 CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
522 CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
523 }
524
525 #[test]
526 fn test_continue_break_completion() {
527 check_snippet_completion(r"
528 fn quux() -> i32 {
529 loop { <|> }
530 }
531 ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
532 CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
533 CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
534 CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
535 CompletionItem { label: "continue", lookup: None, snippet: Some("continue") },
536 CompletionItem { label: "break", lookup: None, snippet: Some("break") },
537 CompletionItem { label: "return", lookup: None, snippet: Some("return $0") },
538 CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
539 CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
540 check_snippet_completion(r"
541 fn quux() -> i32 {
542 loop { || { <|> } }
543 }
544 ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
545 CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
546 CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
547 CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
548 CompletionItem { label: "return", lookup: None, snippet: Some("return $0") },
549 CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
550 CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
551 }
552
553 #[test]
554 fn test_param_completion_last_param() {
555 check_scope_completion(r"
556 fn foo(file_id: FileId) {}
557 fn bar(file_id: FileId) {}
558 fn baz(file<|>) {}
559 ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#);
560 }
561
562 #[test]
563 fn test_param_completion_nth_param() {
564 check_scope_completion(r"
565 fn foo(file_id: FileId) {}
566 fn bar(file_id: FileId) {}
567 fn baz(file<|>, x: i32) {}
568 ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#);
569 }
570
571 #[test]
572 fn test_param_completion_trait_param() {
573 check_scope_completion(r"
574 pub(crate) trait SourceRoot {
575 pub fn contains(&self, file_id: FileId) -> bool;
576 pub fn module_map(&self) -> &ModuleMap;
577 pub fn lines(&self, file_id: FileId) -> &LineIndex;
578 pub fn syntax(&self, file<|>)
579 }
580 ", r#"[CompletionItem { label: "self", lookup: None, snippet: None },
581 CompletionItem { label: "SourceRoot", lookup: None, snippet: None },
582 CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#);
583 }
584
585 #[test]
586 fn test_item_snippets() {
587 // check_snippet_completion(r"
588 // <|>
589 // ",
590 // r##"[CompletionItem { label: "tfn", lookup: None, snippet: Some("#[test]\nfn $1() {\n $0\n}") }]"##,
591 // );
592 check_snippet_completion(r"
593 #[cfg(test)]
594 mod tests {
595 <|>
596 }
597 ",
598 r##"[CompletionItem { label: "tfn", lookup: None, snippet: Some("#[test]\nfn $1() {\n $0\n}") },
599 CompletionItem { label: "pub(crate)", lookup: None, snippet: Some("pub(crate) $0") }]"##,
600 );
601 }
602}
diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs
index b73eb4ac7..02a1b2d45 100644
--- a/crates/ra_editor/src/lib.rs
+++ b/crates/ra_editor/src/lib.rs
@@ -8,12 +8,10 @@ extern crate superslice;
8extern crate test_utils as _test_utils; 8extern crate test_utils as _test_utils;
9 9
10mod code_actions; 10mod code_actions;
11mod completion;
12mod edit; 11mod edit;
13mod extend_selection; 12mod extend_selection;
14mod folding_ranges; 13mod folding_ranges;
15mod line_index; 14mod line_index;
16mod scope;
17mod symbols; 15mod symbols;
18#[cfg(test)] 16#[cfg(test)]
19mod test_utils; 17mod test_utils;
@@ -21,7 +19,6 @@ mod typing;
21 19
22pub use self::{ 20pub use self::{
23 code_actions::{add_derive, add_impl, flip_comma, introduce_variable, LocalEdit}, 21 code_actions::{add_derive, add_impl, flip_comma, introduce_variable, LocalEdit},
24 completion::{scope_completion, complete_module_items, CompletionItem},
25 edit::{Edit, EditBuilder}, 22 edit::{Edit, EditBuilder},
26 extend_selection::extend_selection, 23 extend_selection::extend_selection,
27 folding_ranges::{folding_ranges, Fold, FoldKind}, 24 folding_ranges::{folding_ranges, Fold, FoldKind},
@@ -33,7 +30,7 @@ pub use ra_syntax::AtomEdit;
33use ra_syntax::{ 30use ra_syntax::{
34 algo::find_leaf_at_offset, 31 algo::find_leaf_at_offset,
35 ast::{self, AstNode, NameOwner}, 32 ast::{self, AstNode, NameOwner},
36 File, SmolStr, 33 File,
37 SyntaxKind::{self, *}, 34 SyntaxKind::{self, *},
38 SyntaxNodeRef, TextRange, TextUnit, 35 SyntaxNodeRef, TextRange, TextUnit,
39}; 36};
@@ -151,15 +148,7 @@ pub fn find_node_at_offset<'a, N: AstNode<'a>>(
151 leaf.ancestors().filter_map(N::cast).next() 148 leaf.ancestors().filter_map(N::cast).next()
152} 149}
153 150
154pub fn resolve_local_name( 151
155 name_ref: ast::NameRef,
156) -> Option<(SmolStr, TextRange)> {
157 let fn_def = name_ref.syntax().ancestors().find_map(ast::FnDef::cast)?;
158 let scopes = scope::FnScopes::new(fn_def);
159 let scope_entry = scope::resolve_local_name(name_ref, &scopes)?;
160 let name = scope_entry.ast().name()?;
161 Some((scope_entry.name(), name.syntax().range()))
162}
163 152
164#[cfg(test)] 153#[cfg(test)]
165mod tests { 154mod tests {
diff --git a/crates/ra_editor/src/scope/fn_scope.rs b/crates/ra_editor/src/scope/fn_scope.rs
deleted file mode 100644
index f10bdf657..000000000
--- a/crates/ra_editor/src/scope/fn_scope.rs
+++ /dev/null
@@ -1,435 +0,0 @@
1use std::fmt;
2
3use rustc_hash::FxHashMap;
4
5use ra_syntax::{
6 algo::generate,
7 ast::{self, ArgListOwner, LoopBodyOwner, NameOwner},
8 AstNode, SmolStr, SyntaxNode, SyntaxNodeRef,
9};
10
11type ScopeId = usize;
12
13#[derive(Debug)]
14pub struct FnScopes {
15 pub self_param: Option<SyntaxNode>,
16 scopes: Vec<ScopeData>,
17 scope_for: FxHashMap<SyntaxNode, ScopeId>,
18}
19
20impl FnScopes {
21 pub fn new(fn_def: ast::FnDef) -> FnScopes {
22 let mut scopes = FnScopes {
23 self_param: fn_def
24 .param_list()
25 .and_then(|it| it.self_param())
26 .map(|it| it.syntax().owned()),
27 scopes: Vec::new(),
28 scope_for: FxHashMap::default(),
29 };
30 let root = scopes.root_scope();
31 scopes.add_params_bindings(root, fn_def.param_list());
32 if let Some(body) = fn_def.body() {
33 compute_block_scopes(body, &mut scopes, root)
34 }
35 scopes
36 }
37 pub fn entries(&self, scope: ScopeId) -> &[ScopeEntry] {
38 &self.scopes[scope].entries
39 }
40 pub fn scope_chain<'a>(&'a self, node: SyntaxNodeRef) -> impl Iterator<Item = ScopeId> + 'a {
41 generate(self.scope_for(node), move |&scope| {
42 self.scopes[scope].parent
43 })
44 }
45 fn root_scope(&mut self) -> ScopeId {
46 let res = self.scopes.len();
47 self.scopes.push(ScopeData {
48 parent: None,
49 entries: vec![],
50 });
51 res
52 }
53 fn new_scope(&mut self, parent: ScopeId) -> ScopeId {
54 let res = self.scopes.len();
55 self.scopes.push(ScopeData {
56 parent: Some(parent),
57 entries: vec![],
58 });
59 res
60 }
61 fn add_bindings(&mut self, scope: ScopeId, pat: ast::Pat) {
62 let entries = pat
63 .syntax()
64 .descendants()
65 .filter_map(ast::BindPat::cast)
66 .filter_map(ScopeEntry::new);
67 self.scopes[scope].entries.extend(entries);
68 }
69 fn add_params_bindings(&mut self, scope: ScopeId, params: Option<ast::ParamList>) {
70 params
71 .into_iter()
72 .flat_map(|it| it.params())
73 .filter_map(|it| it.pat())
74 .for_each(|it| self.add_bindings(scope, it));
75 }
76 fn set_scope(&mut self, node: SyntaxNodeRef, scope: ScopeId) {
77 self.scope_for.insert(node.owned(), scope);
78 }
79 fn scope_for(&self, node: SyntaxNodeRef) -> Option<ScopeId> {
80 node.ancestors()
81 .filter_map(|it| self.scope_for.get(&it.owned()).map(|&scope| scope))
82 .next()
83 }
84}
85
86pub struct ScopeEntry {
87 syntax: SyntaxNode,
88}
89
90impl ScopeEntry {
91 fn new(pat: ast::BindPat) -> Option<ScopeEntry> {
92 if pat.name().is_some() {
93 Some(ScopeEntry {
94 syntax: pat.syntax().owned(),
95 })
96 } else {
97 None
98 }
99 }
100 pub fn name(&self) -> SmolStr {
101 self.ast().name().unwrap().text()
102 }
103 pub fn ast(&self) -> ast::BindPat {
104 ast::BindPat::cast(self.syntax.borrowed()).unwrap()
105 }
106}
107
108impl fmt::Debug for ScopeEntry {
109 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
110 f.debug_struct("ScopeEntry")
111 .field("name", &self.name())
112 .field("syntax", &self.syntax)
113 .finish()
114 }
115}
116
117fn compute_block_scopes(block: ast::Block, scopes: &mut FnScopes, mut scope: ScopeId) {
118 for stmt in block.statements() {
119 match stmt {
120 ast::Stmt::LetStmt(stmt) => {
121 if let Some(expr) = stmt.initializer() {
122 scopes.set_scope(expr.syntax(), scope);
123 compute_expr_scopes(expr, scopes, scope);
124 }
125 scope = scopes.new_scope(scope);
126 if let Some(pat) = stmt.pat() {
127 scopes.add_bindings(scope, pat);
128 }
129 }
130 ast::Stmt::ExprStmt(expr_stmt) => {
131 if let Some(expr) = expr_stmt.expr() {
132 scopes.set_scope(expr.syntax(), scope);
133 compute_expr_scopes(expr, scopes, scope);
134 }
135 }
136 }
137 }
138 if let Some(expr) = block.expr() {
139 scopes.set_scope(expr.syntax(), scope);
140 compute_expr_scopes(expr, scopes, scope);
141 }
142}
143
144fn compute_expr_scopes(expr: ast::Expr, scopes: &mut FnScopes, scope: ScopeId) {
145 match expr {
146 ast::Expr::IfExpr(e) => {
147 let cond_scope = e
148 .condition()
149 .and_then(|cond| compute_cond_scopes(cond, scopes, scope));
150 if let Some(block) = e.then_branch() {
151 compute_block_scopes(block, scopes, cond_scope.unwrap_or(scope));
152 }
153 if let Some(block) = e.else_branch() {
154 compute_block_scopes(block, scopes, scope);
155 }
156 }
157 ast::Expr::BlockExpr(e) => {
158 if let Some(block) = e.block() {
159 compute_block_scopes(block, scopes, scope);
160 }
161 }
162 ast::Expr::LoopExpr(e) => {
163 if let Some(block) = e.loop_body() {
164 compute_block_scopes(block, scopes, scope);
165 }
166 }
167 ast::Expr::WhileExpr(e) => {
168 let cond_scope = e
169 .condition()
170 .and_then(|cond| compute_cond_scopes(cond, scopes, scope));
171 if let Some(block) = e.loop_body() {
172 compute_block_scopes(block, scopes, cond_scope.unwrap_or(scope));
173 }
174 }
175 ast::Expr::ForExpr(e) => {
176 if let Some(expr) = e.iterable() {
177 compute_expr_scopes(expr, scopes, scope);
178 }
179 let mut scope = scope;
180 if let Some(pat) = e.pat() {
181 scope = scopes.new_scope(scope);
182 scopes.add_bindings(scope, pat);
183 }
184 if let Some(block) = e.loop_body() {
185 compute_block_scopes(block, scopes, scope);
186 }
187 }
188 ast::Expr::LambdaExpr(e) => {
189 let scope = scopes.new_scope(scope);
190 scopes.add_params_bindings(scope, e.param_list());
191 if let Some(body) = e.body() {
192 scopes.set_scope(body.syntax(), scope);
193 compute_expr_scopes(body, scopes, scope);
194 }
195 }
196 ast::Expr::CallExpr(e) => {
197 compute_call_scopes(e.expr(), e.arg_list(), scopes, scope);
198 }
199 ast::Expr::MethodCallExpr(e) => {
200 compute_call_scopes(e.expr(), e.arg_list(), scopes, scope);
201 }
202 ast::Expr::MatchExpr(e) => {
203 if let Some(expr) = e.expr() {
204 compute_expr_scopes(expr, scopes, scope);
205 }
206 for arm in e.match_arm_list().into_iter().flat_map(|it| it.arms()) {
207 let scope = scopes.new_scope(scope);
208 for pat in arm.pats() {
209 scopes.add_bindings(scope, pat);
210 }
211 if let Some(expr) = arm.expr() {
212 compute_expr_scopes(expr, scopes, scope);
213 }
214 }
215 }
216 _ => expr
217 .syntax()
218 .children()
219 .filter_map(ast::Expr::cast)
220 .for_each(|expr| compute_expr_scopes(expr, scopes, scope)),
221 };
222
223 fn compute_call_scopes(
224 receiver: Option<ast::Expr>,
225 arg_list: Option<ast::ArgList>,
226 scopes: &mut FnScopes,
227 scope: ScopeId,
228 ) {
229 arg_list
230 .into_iter()
231 .flat_map(|it| it.args())
232 .chain(receiver)
233 .for_each(|expr| compute_expr_scopes(expr, scopes, scope));
234 }
235
236 fn compute_cond_scopes(
237 cond: ast::Condition,
238 scopes: &mut FnScopes,
239 scope: ScopeId,
240 ) -> Option<ScopeId> {
241 if let Some(expr) = cond.expr() {
242 compute_expr_scopes(expr, scopes, scope);
243 }
244 if let Some(pat) = cond.pat() {
245 let s = scopes.new_scope(scope);
246 scopes.add_bindings(s, pat);
247 Some(s)
248 } else {
249 None
250 }
251 }
252}
253
254#[derive(Debug)]
255struct ScopeData {
256 parent: Option<ScopeId>,
257 entries: Vec<ScopeEntry>,
258}
259
260pub fn resolve_local_name<'a>(
261 name_ref: ast::NameRef,
262 scopes: &'a FnScopes,
263) -> Option<&'a ScopeEntry> {
264 use rustc_hash::FxHashSet;
265
266 let mut shadowed = FxHashSet::default();
267 let ret = scopes
268 .scope_chain(name_ref.syntax())
269 .flat_map(|scope| scopes.entries(scope).iter())
270 .filter(|entry| shadowed.insert(entry.name()))
271 .filter(|entry| entry.name() == name_ref.text())
272 .nth(0);
273 ret
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279 use crate::{find_node_at_offset, test_utils::extract_offset};
280 use ra_syntax::File;
281
282 fn do_check(code: &str, expected: &[&str]) {
283 let (off, code) = extract_offset(code);
284 let code = {
285 let mut buf = String::new();
286 let off = u32::from(off) as usize;
287 buf.push_str(&code[..off]);
288 buf.push_str("marker");
289 buf.push_str(&code[off..]);
290 buf
291 };
292 let file = File::parse(&code);
293 let marker: ast::PathExpr = find_node_at_offset(file.syntax(), off).unwrap();
294 let fn_def: ast::FnDef = find_node_at_offset(file.syntax(), off).unwrap();
295 let scopes = FnScopes::new(fn_def);
296 let actual = scopes
297 .scope_chain(marker.syntax())
298 .flat_map(|scope| scopes.entries(scope))
299 .map(|it| it.name())
300 .collect::<Vec<_>>();
301 assert_eq!(actual.as_slice(), expected);
302 }
303
304 #[test]
305 fn test_lambda_scope() {
306 do_check(
307 r"
308 fn quux(foo: i32) {
309 let f = |bar, baz: i32| {
310 <|>
311 };
312 }",
313 &["bar", "baz", "foo"],
314 );
315 }
316
317 #[test]
318 fn test_call_scope() {
319 do_check(
320 r"
321 fn quux() {
322 f(|x| <|> );
323 }",
324 &["x"],
325 );
326 }
327
328 #[test]
329 fn test_metod_call_scope() {
330 do_check(
331 r"
332 fn quux() {
333 z.f(|x| <|> );
334 }",
335 &["x"],
336 );
337 }
338
339 #[test]
340 fn test_loop_scope() {
341 do_check(
342 r"
343 fn quux() {
344 loop {
345 let x = ();
346 <|>
347 };
348 }",
349 &["x"],
350 );
351 }
352
353 #[test]
354 fn test_match() {
355 do_check(
356 r"
357 fn quux() {
358 match () {
359 Some(x) => {
360 <|>
361 }
362 };
363 }",
364 &["x"],
365 );
366 }
367
368 #[test]
369 fn test_shadow_variable() {
370 do_check(
371 r"
372 fn foo(x: String) {
373 let x : &str = &x<|>;
374 }",
375 &["x"],
376 );
377 }
378
379 fn do_check_local_name(code: &str, expected_offset: u32) {
380 let (off, code) = extract_offset(code);
381 let file = File::parse(&code);
382 let fn_def: ast::FnDef = find_node_at_offset(file.syntax(), off).unwrap();
383 let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), off).unwrap();
384
385 let scopes = FnScopes::new(fn_def);
386
387 let local_name = resolve_local_name(name_ref, &scopes)
388 .unwrap()
389 .ast()
390 .name()
391 .unwrap();
392 let expected_name =
393 find_node_at_offset::<ast::Name>(file.syntax(), expected_offset.into()).unwrap();
394 assert_eq!(local_name.syntax().range(), expected_name.syntax().range());
395 }
396
397 #[test]
398 fn test_resolve_local_name() {
399 do_check_local_name(
400 r#"
401 fn foo(x: i32, y: u32) {
402 {
403 let z = x * 2;
404 }
405 {
406 let t = x<|> * 3;
407 }
408 }"#,
409 21,
410 );
411 }
412
413 #[test]
414 fn test_resolve_local_name_declaration() {
415 do_check_local_name(
416 r#"
417 fn foo(x: String) {
418 let x : &str = &x<|>;
419 }"#,
420 21,
421 );
422 }
423
424 #[test]
425 fn test_resolve_local_name_shadow() {
426 do_check_local_name(
427 r"
428 fn foo(x: String) {
429 let x : &str = &x;
430 x<|>
431 }",
432 46,
433 );
434 }
435}
diff --git a/crates/ra_editor/src/scope/mod.rs b/crates/ra_editor/src/scope/mod.rs
deleted file mode 100644
index cc2d49392..000000000
--- a/crates/ra_editor/src/scope/mod.rs
+++ /dev/null
@@ -1,7 +0,0 @@
1mod fn_scope;
2mod mod_scope;
3
4pub use self::{
5 fn_scope::{resolve_local_name, FnScopes},
6 mod_scope::ModuleScope,
7};
diff --git a/crates/ra_editor/src/scope/mod_scope.rs b/crates/ra_editor/src/scope/mod_scope.rs
deleted file mode 100644
index 818749a12..000000000
--- a/crates/ra_editor/src/scope/mod_scope.rs
+++ /dev/null
@@ -1,124 +0,0 @@
1/// FIXME: this is now moved to ra_analysis::descriptors::module::scope.
2///
3/// Current copy will be deleted as soon as we move the rest of the completion
4/// to the analyezer.
5
6
7use ra_syntax::{
8 ast::{self, AstChildren},
9 AstNode, SmolStr, SyntaxNode, SyntaxNodeRef,
10};
11
12pub struct ModuleScope {
13 entries: Vec<Entry>,
14}
15
16pub struct Entry {
17 node: SyntaxNode,
18 kind: EntryKind,
19}
20
21enum EntryKind {
22 Item,
23 Import,
24}
25
26impl ModuleScope {
27 pub fn new(items: AstChildren<ast::ModuleItem>) -> ModuleScope {
28 let mut entries = Vec::new();
29 for item in items {
30 let entry = match item {
31 ast::ModuleItem::StructDef(item) => Entry::new_item(item),
32 ast::ModuleItem::EnumDef(item) => Entry::new_item(item),
33 ast::ModuleItem::FnDef(item) => Entry::new_item(item),
34 ast::ModuleItem::ConstDef(item) => Entry::new_item(item),
35 ast::ModuleItem::StaticDef(item) => Entry::new_item(item),
36 ast::ModuleItem::TraitDef(item) => Entry::new_item(item),
37 ast::ModuleItem::TypeDef(item) => Entry::new_item(item),
38 ast::ModuleItem::Module(item) => Entry::new_item(item),
39 ast::ModuleItem::UseItem(item) => {
40 if let Some(tree) = item.use_tree() {
41 collect_imports(tree, &mut entries);
42 }
43 continue;
44 }
45 ast::ModuleItem::ExternCrateItem(_) | ast::ModuleItem::ImplItem(_) => continue,
46 };
47 entries.extend(entry)
48 }
49
50 ModuleScope { entries }
51 }
52
53 pub fn entries(&self) -> &[Entry] {
54 self.entries.as_slice()
55 }
56}
57
58impl Entry {
59 fn new_item<'a>(item: impl ast::NameOwner<'a>) -> Option<Entry> {
60 let name = item.name()?;
61 Some(Entry {
62 node: name.syntax().owned(),
63 kind: EntryKind::Item,
64 })
65 }
66 fn new_import(path: ast::Path) -> Option<Entry> {
67 let name_ref = path.segment()?.name_ref()?;
68 Some(Entry {
69 node: name_ref.syntax().owned(),
70 kind: EntryKind::Import,
71 })
72 }
73 pub fn name(&self) -> SmolStr {
74 match self.kind {
75 EntryKind::Item => ast::Name::cast(self.node.borrowed()).unwrap().text(),
76 EntryKind::Import => ast::NameRef::cast(self.node.borrowed()).unwrap().text(),
77 }
78 }
79 pub fn syntax(&self) -> SyntaxNodeRef {
80 self.node.borrowed()
81 }
82}
83
84fn collect_imports(tree: ast::UseTree, acc: &mut Vec<Entry>) {
85 if let Some(use_tree_list) = tree.use_tree_list() {
86 return use_tree_list
87 .use_trees()
88 .for_each(|it| collect_imports(it, acc));
89 }
90 if let Some(path) = tree.path() {
91 acc.extend(Entry::new_import(path));
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use ra_syntax::{ast::ModuleItemOwner, File};
99
100 fn do_check(code: &str, expected: &[&str]) {
101 let file = File::parse(&code);
102 let scope = ModuleScope::new(file.ast().items());
103 let actual = scope.entries.iter().map(|it| it.name()).collect::<Vec<_>>();
104 assert_eq!(expected, actual.as_slice());
105 }
106
107 #[test]
108 fn test_module_scope() {
109 do_check(
110 "
111 struct Foo;
112 enum Bar {}
113 mod baz {}
114 fn quux() {}
115 use x::{
116 y::z,
117 t,
118 };
119 type T = ();
120 ",
121 &["Foo", "Bar", "baz", "quux", "z", "t", "T"],
122 )
123 }
124}