aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2018-12-21 22:04:32 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2018-12-21 22:04:32 +0000
commit184665ff9b7b64730ecf481c1914a74e7191a6dd (patch)
tree4f5e97a0832821a4b06b784591d6cb3a417f1198 /crates
parent2351308d92f4c785d98cc24026a818d28945513e (diff)
parent2ae87ffc9afe67945e2ad655c3577b589ff640ab (diff)
Merge #315
315: Split completion into manageable components r=matklad a=matklad The main idea here is to do completion in two phases: * first, we figure out surrounding context * then, we run a series of completers on the given context. Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_analysis/src/completion.rs164
-rw-r--r--crates/ra_analysis/src/completion/complete_fn_param.rs103
-rw-r--r--crates/ra_analysis/src/completion/complete_keyword.rs204
-rw-r--r--crates/ra_analysis/src/completion/complete_path.rs95
-rw-r--r--crates/ra_analysis/src/completion/complete_scope.rs171
-rw-r--r--crates/ra_analysis/src/completion/complete_snippet.rs76
-rw-r--r--crates/ra_analysis/src/completion/completion_context.rs156
-rw-r--r--crates/ra_analysis/src/completion/reference_completion.rs633
-rw-r--r--crates/ra_hir/src/function/scope.rs47
9 files changed, 878 insertions, 771 deletions
diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs
index a11e98ac0..2d61a3aef 100644
--- a/crates/ra_analysis/src/completion.rs
+++ b/crates/ra_analysis/src/completion.rs
@@ -1,112 +1,50 @@
1mod completion_item; 1mod completion_item;
2mod reference_completion; 2mod completion_context;
3
4mod complete_fn_param;
5mod complete_keyword;
6mod complete_snippet;
7mod complete_path;
8mod complete_scope;
3 9
4use ra_editor::find_node_at_offset;
5use ra_text_edit::AtomTextEdit;
6use ra_syntax::{
7 algo::visit::{visitor_ctx, VisitorCtx},
8 ast,
9 AstNode,
10 SyntaxNodeRef,
11};
12use ra_db::SyntaxDatabase; 10use ra_db::SyntaxDatabase;
13use rustc_hash::{FxHashMap};
14use hir::source_binder;
15 11
16use crate::{ 12use crate::{
17 db, 13 db,
18 Cancelable, FilePosition, 14 Cancelable, FilePosition,
19 completion::completion_item::{Completions, CompletionKind}, 15 completion::{
16 completion_item::{Completions, CompletionKind},
17 completion_context::CompletionContext,
18 },
20}; 19};
21 20
22pub use crate::completion::completion_item::{CompletionItem, InsertText}; 21pub use crate::completion::completion_item::{CompletionItem, InsertText};
23 22
23/// Main entry point for copmletion. We run comletion as a two-phase process.
24///
25/// First, we look at the position and collect a so-called `CompletionContext.
26/// This is a somewhat messy process, because, during completion, syntax tree is
27/// incomplete and can look readlly weired.
28///
29/// Once the context is collected, we run a series of completion routines whihc
30/// look at the context and produce completion items.
24pub(crate) fn completions( 31pub(crate) fn completions(
25 db: &db::RootDatabase, 32 db: &db::RootDatabase,
26 position: FilePosition, 33 position: FilePosition,
27) -> Cancelable<Option<Completions>> { 34) -> Cancelable<Option<Completions>> {
28 let original_file = db.source_file(position.file_id); 35 let original_file = db.source_file(position.file_id);
29 // Insert a fake ident to get a valid parse tree 36 let ctx = ctry!(CompletionContext::new(db, &original_file, position)?);
30 let file = {
31 let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string());
32 original_file.reparse(&edit)
33 };
34
35 let module = ctry!(source_binder::module_from_position(db, position)?);
36 37
37 let mut acc = Completions::default(); 38 let mut acc = Completions::default();
38 let mut has_completions = false;
39 // First, let's try to complete a reference to some declaration.
40 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) {
41 has_completions = true;
42 reference_completion::completions(&mut acc, db, &module, &file, name_ref)?;
43 // special case, `trait T { fn foo(i_am_a_name_ref) {} }`
44 if is_node::<ast::Param>(name_ref.syntax()) {
45 param_completions(&mut acc, name_ref.syntax());
46 }
47 }
48 39
49 // Otherwise, if this is a declaration, use heuristics to suggest a name. 40 complete_fn_param::complete_fn_param(&mut acc, &ctx);
50 if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) { 41 complete_keyword::complete_expr_keyword(&mut acc, &ctx);
51 if is_node::<ast::Param>(name.syntax()) { 42 complete_snippet::complete_expr_snippet(&mut acc, &ctx);
52 has_completions = true; 43 complete_snippet::complete_item_snippet(&mut acc, &ctx);
53 param_completions(&mut acc, name.syntax()); 44 complete_path::complete_path(&mut acc, &ctx)?;
54 } 45 complete_scope::complete_scope(&mut acc, &ctx)?;
55 }
56 if !has_completions {
57 return Ok(None);
58 }
59 Ok(Some(acc))
60}
61
62/// Complete repeated parametes, both name and type. For example, if all
63/// functions in a file have a `spam: &mut Spam` parameter, a completion with
64/// `spam: &mut Spam` insert text/label and `spam` lookup string will be
65/// suggested.
66fn param_completions(acc: &mut Completions, ctx: SyntaxNodeRef) {
67 let mut params = FxHashMap::default();
68 for node in ctx.ancestors() {
69 let _ = visitor_ctx(&mut params)
70 .visit::<ast::SourceFile, _>(process)
71 .visit::<ast::ItemList, _>(process)
72 .accept(node);
73 }
74 params
75 .into_iter()
76 .filter_map(|(label, (count, param))| {
77 let lookup = param.pat()?.syntax().text().to_string();
78 if count < 2 {
79 None
80 } else {
81 Some((label, lookup))
82 }
83 })
84 .for_each(|(label, lookup)| {
85 CompletionItem::new(label)
86 .lookup_by(lookup)
87 .kind(CompletionKind::Magic)
88 .add_to(acc)
89 });
90
91 fn process<'a, N: ast::FnDefOwner<'a>>(
92 node: N,
93 params: &mut FxHashMap<String, (u32, ast::Param<'a>)>,
94 ) {
95 node.functions()
96 .filter_map(|it| it.param_list())
97 .flat_map(|it| it.params())
98 .for_each(|param| {
99 let text = param.syntax().text().to_string();
100 params.entry(text).or_insert((0, param)).0 += 1;
101 })
102 }
103}
104 46
105fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool { 47 Ok(Some(acc))
106 match node.ancestors().filter_map(N::cast).next() {
107 None => false,
108 Some(n) => n.syntax().range() == node.range(),
109 }
110} 48}
111 49
112#[cfg(test)] 50#[cfg(test)]
@@ -120,51 +58,3 @@ fn check_completion(code: &str, expected_completions: &str, kind: CompletionKind
120 let completions = completions(&analysis.imp.db, position).unwrap().unwrap(); 58 let completions = completions(&analysis.imp.db, position).unwrap().unwrap();
121 completions.assert_match(expected_completions, kind); 59 completions.assert_match(expected_completions, kind);
122} 60}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 fn check_magic_completion(code: &str, expected_completions: &str) {
129 check_completion(code, expected_completions, CompletionKind::Magic);
130 }
131
132 #[test]
133 fn test_param_completion_last_param() {
134 check_magic_completion(
135 r"
136 fn foo(file_id: FileId) {}
137 fn bar(file_id: FileId) {}
138 fn baz(file<|>) {}
139 ",
140 r#"file_id "file_id: FileId""#,
141 );
142 }
143
144 #[test]
145 fn test_param_completion_nth_param() {
146 check_magic_completion(
147 r"
148 fn foo(file_id: FileId) {}
149 fn bar(file_id: FileId) {}
150 fn baz(file<|>, x: i32) {}
151 ",
152 r#"file_id "file_id: FileId""#,
153 );
154 }
155
156 #[test]
157 fn test_param_completion_trait_param() {
158 check_magic_completion(
159 r"
160 pub(crate) trait SourceRoot {
161 pub fn contains(&self, file_id: FileId) -> bool;
162 pub fn module_map(&self) -> &ModuleMap;
163 pub fn lines(&self, file_id: FileId) -> &LineIndex;
164 pub fn syntax(&self, file<|>)
165 }
166 ",
167 r#"file_id "file_id: FileId""#,
168 );
169 }
170}
diff --git a/crates/ra_analysis/src/completion/complete_fn_param.rs b/crates/ra_analysis/src/completion/complete_fn_param.rs
new file mode 100644
index 000000000..6a6213e67
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_fn_param.rs
@@ -0,0 +1,103 @@
1use ra_syntax::{
2 algo::visit::{visitor_ctx, VisitorCtx},
3 ast,
4 AstNode,
5};
6use rustc_hash::FxHashMap;
7
8use crate::completion::{CompletionContext, Completions, CompletionKind, CompletionItem};
9
10/// Complete repeated parametes, both name and type. For example, if all
11/// functions in a file have a `spam: &mut Spam` parameter, a completion with
12/// `spam: &mut Spam` insert text/label and `spam` lookup string will be
13/// suggested.
14pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) {
15 if !ctx.is_param {
16 return;
17 }
18
19 let mut params = FxHashMap::default();
20 for node in ctx.leaf.ancestors() {
21 let _ = visitor_ctx(&mut params)
22 .visit::<ast::SourceFile, _>(process)
23 .visit::<ast::ItemList, _>(process)
24 .accept(node);
25 }
26 params
27 .into_iter()
28 .filter_map(|(label, (count, param))| {
29 let lookup = param.pat()?.syntax().text().to_string();
30 if count < 2 {
31 None
32 } else {
33 Some((label, lookup))
34 }
35 })
36 .for_each(|(label, lookup)| {
37 CompletionItem::new(label)
38 .lookup_by(lookup)
39 .kind(CompletionKind::Magic)
40 .add_to(acc)
41 });
42
43 fn process<'a, N: ast::FnDefOwner<'a>>(
44 node: N,
45 params: &mut FxHashMap<String, (u32, ast::Param<'a>)>,
46 ) {
47 node.functions()
48 .filter_map(|it| it.param_list())
49 .flat_map(|it| it.params())
50 .for_each(|param| {
51 let text = param.syntax().text().to_string();
52 params.entry(text).or_insert((0, param)).0 += 1;
53 })
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use crate::completion::*;
60
61 fn check_magic_completion(code: &str, expected_completions: &str) {
62 check_completion(code, expected_completions, CompletionKind::Magic);
63 }
64
65 #[test]
66 fn test_param_completion_last_param() {
67 check_magic_completion(
68 r"
69 fn foo(file_id: FileId) {}
70 fn bar(file_id: FileId) {}
71 fn baz(file<|>) {}
72 ",
73 r#"file_id "file_id: FileId""#,
74 );
75 }
76
77 #[test]
78 fn test_param_completion_nth_param() {
79 check_magic_completion(
80 r"
81 fn foo(file_id: FileId) {}
82 fn bar(file_id: FileId) {}
83 fn baz(file<|>, x: i32) {}
84 ",
85 r#"file_id "file_id: FileId""#,
86 );
87 }
88
89 #[test]
90 fn test_param_completion_trait_param() {
91 check_magic_completion(
92 r"
93 pub(crate) trait SourceRoot {
94 pub fn contains(&self, file_id: FileId) -> bool;
95 pub fn module_map(&self) -> &ModuleMap;
96 pub fn lines(&self, file_id: FileId) -> &LineIndex;
97 pub fn syntax(&self, file<|>)
98 }
99 ",
100 r#"file_id "file_id: FileId""#,
101 );
102 }
103}
diff --git a/crates/ra_analysis/src/completion/complete_keyword.rs b/crates/ra_analysis/src/completion/complete_keyword.rs
new file mode 100644
index 000000000..dead15bb6
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_keyword.rs
@@ -0,0 +1,204 @@
1use ra_syntax::{
2 algo::visit::{visitor, Visitor},
3 AstNode,
4 ast::{self, LoopBodyOwner},
5 SyntaxKind::*, SyntaxNodeRef,
6};
7
8use crate::completion::{CompletionContext, CompletionItem, Completions, CompletionKind::*};
9
10pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
11 if !ctx.is_trivial_path {
12 return;
13 }
14 let fn_def = match ctx.enclosing_fn {
15 Some(it) => it,
16 None => return,
17 };
18 acc.add(keyword("if", "if $0 {}"));
19 acc.add(keyword("match", "match $0 {}"));
20 acc.add(keyword("while", "while $0 {}"));
21 acc.add(keyword("loop", "loop {$0}"));
22
23 if ctx.after_if {
24 acc.add(keyword("else", "else {$0}"));
25 acc.add(keyword("else if", "else if $0 {}"));
26 }
27 if is_in_loop_body(ctx.leaf) {
28 acc.add(keyword("continue", "continue"));
29 acc.add(keyword("break", "break"));
30 }
31 acc.add_all(complete_return(fn_def, ctx.is_stmt));
32}
33
34fn is_in_loop_body(leaf: SyntaxNodeRef) -> bool {
35 for node in leaf.ancestors() {
36 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
37 break;
38 }
39 let loop_body = visitor()
40 .visit::<ast::ForExpr, _>(LoopBodyOwner::loop_body)
41 .visit::<ast::WhileExpr, _>(LoopBodyOwner::loop_body)
42 .visit::<ast::LoopExpr, _>(LoopBodyOwner::loop_body)
43 .accept(node);
44 if let Some(Some(body)) = loop_body {
45 if leaf.range().is_subrange(&body.syntax().range()) {
46 return true;
47 }
48 }
49 }
50 false
51}
52
53fn complete_return(fn_def: ast::FnDef, is_stmt: bool) -> Option<CompletionItem> {
54 let snip = match (is_stmt, fn_def.ret_type().is_some()) {
55 (true, true) => "return $0;",
56 (true, false) => "return;",
57 (false, true) => "return $0",
58 (false, false) => "return",
59 };
60 Some(keyword("return", snip))
61}
62
63fn keyword(kw: &str, snippet: &str) -> CompletionItem {
64 CompletionItem::new(kw)
65 .kind(Keyword)
66 .snippet(snippet)
67 .build()
68}
69
70#[cfg(test)]
71mod tests {
72 use crate::completion::{CompletionKind, check_completion};
73 fn check_keyword_completion(code: &str, expected_completions: &str) {
74 check_completion(code, expected_completions, CompletionKind::Keyword);
75 }
76
77 #[test]
78 fn test_completion_kewords() {
79 check_keyword_completion(
80 r"
81 fn quux() {
82 <|>
83 }
84 ",
85 r#"
86 if "if $0 {}"
87 match "match $0 {}"
88 while "while $0 {}"
89 loop "loop {$0}"
90 return "return"
91 "#,
92 );
93 }
94
95 #[test]
96 fn test_completion_else() {
97 check_keyword_completion(
98 r"
99 fn quux() {
100 if true {
101 ()
102 } <|>
103 }
104 ",
105 r#"
106 if "if $0 {}"
107 match "match $0 {}"
108 while "while $0 {}"
109 loop "loop {$0}"
110 else "else {$0}"
111 else if "else if $0 {}"
112 return "return"
113 "#,
114 );
115 }
116
117 #[test]
118 fn test_completion_return_value() {
119 check_keyword_completion(
120 r"
121 fn quux() -> i32 {
122 <|>
123 92
124 }
125 ",
126 r#"
127 if "if $0 {}"
128 match "match $0 {}"
129 while "while $0 {}"
130 loop "loop {$0}"
131 return "return $0;"
132 "#,
133 );
134 check_keyword_completion(
135 r"
136 fn quux() {
137 <|>
138 92
139 }
140 ",
141 r#"
142 if "if $0 {}"
143 match "match $0 {}"
144 while "while $0 {}"
145 loop "loop {$0}"
146 return "return;"
147 "#,
148 );
149 }
150
151 #[test]
152 fn test_completion_return_no_stmt() {
153 check_keyword_completion(
154 r"
155 fn quux() -> i32 {
156 match () {
157 () => <|>
158 }
159 }
160 ",
161 r#"
162 if "if $0 {}"
163 match "match $0 {}"
164 while "while $0 {}"
165 loop "loop {$0}"
166 return "return $0"
167 "#,
168 );
169 }
170
171 #[test]
172 fn test_continue_break_completion() {
173 check_keyword_completion(
174 r"
175 fn quux() -> i32 {
176 loop { <|> }
177 }
178 ",
179 r#"
180 if "if $0 {}"
181 match "match $0 {}"
182 while "while $0 {}"
183 loop "loop {$0}"
184 continue "continue"
185 break "break"
186 return "return $0"
187 "#,
188 );
189 check_keyword_completion(
190 r"
191 fn quux() -> i32 {
192 loop { || { <|> } }
193 }
194 ",
195 r#"
196 if "if $0 {}"
197 match "match $0 {}"
198 while "while $0 {}"
199 loop "loop {$0}"
200 return "return $0"
201 "#,
202 );
203 }
204}
diff --git a/crates/ra_analysis/src/completion/complete_path.rs b/crates/ra_analysis/src/completion/complete_path.rs
new file mode 100644
index 000000000..5fc24af72
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_path.rs
@@ -0,0 +1,95 @@
1use crate::{
2 Cancelable,
3 completion::{CompletionItem, Completions, CompletionKind::*, CompletionContext},
4};
5
6pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> {
7 let (path, module) = match (&ctx.path_prefix, &ctx.module) {
8 (Some(path), Some(module)) => (path.clone(), module),
9 _ => return Ok(()),
10 };
11 let def_id = match module.resolve_path(ctx.db, path)? {
12 Some(it) => it,
13 None => return Ok(()),
14 };
15 let target_module = match def_id.resolve(ctx.db)? {
16 hir::Def::Module(it) => it,
17 _ => return Ok(()),
18 };
19 let module_scope = target_module.scope(ctx.db)?;
20 module_scope.entries().for_each(|(name, _res)| {
21 CompletionItem::new(name.to_string())
22 .kind(Reference)
23 .add_to(acc)
24 });
25 Ok(())
26}
27
28#[cfg(test)]
29mod tests {
30 use crate::completion::{CompletionKind, check_completion};
31
32 fn check_reference_completion(code: &str, expected_completions: &str) {
33 check_completion(code, expected_completions, CompletionKind::Reference);
34 }
35
36 #[test]
37 fn completes_use_item_starting_with_self() {
38 check_reference_completion(
39 r"
40 use self::m::<|>;
41
42 mod m {
43 struct Bar;
44 }
45 ",
46 "Bar",
47 );
48 }
49
50 #[test]
51 fn completes_use_item_starting_with_crate() {
52 check_reference_completion(
53 "
54 //- /lib.rs
55 mod foo;
56 struct Spam;
57 //- /foo.rs
58 use crate::Sp<|>
59 ",
60 "Spam;foo",
61 );
62 }
63
64 #[test]
65 fn completes_nested_use_tree() {
66 check_reference_completion(
67 "
68 //- /lib.rs
69 mod foo;
70 struct Spam;
71 //- /foo.rs
72 use crate::{Sp<|>};
73 ",
74 "Spam;foo",
75 );
76 }
77
78 #[test]
79 fn completes_deeply_nested_use_tree() {
80 check_reference_completion(
81 "
82 //- /lib.rs
83 mod foo;
84 pub mod bar {
85 pub mod baz {
86 pub struct Spam;
87 }
88 }
89 //- /foo.rs
90 use crate::{bar::{baz::Sp<|>}};
91 ",
92 "Spam",
93 );
94 }
95}
diff --git a/crates/ra_analysis/src/completion/complete_scope.rs b/crates/ra_analysis/src/completion/complete_scope.rs
new file mode 100644
index 000000000..d07c0e46d
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_scope.rs
@@ -0,0 +1,171 @@
1use rustc_hash::FxHashSet;
2use ra_syntax::TextUnit;
3
4use crate::{
5 Cancelable,
6 completion::{CompletionItem, Completions, CompletionKind::*, CompletionContext},
7};
8
9pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> {
10 if !ctx.is_trivial_path {
11 return Ok(());
12 }
13 if let Some(fn_def) = ctx.enclosing_fn {
14 let scopes = hir::FnScopes::new(fn_def);
15 complete_fn(acc, &scopes, ctx.offset);
16 }
17
18 if let Some(module) = &ctx.module {
19 let module_scope = module.scope(ctx.db)?;
20 module_scope
21 .entries()
22 .filter(|(_name, res)| {
23 // Don't expose this item
24 match res.import {
25 None => true,
26 Some(import) => {
27 let range = import.range(ctx.db, module.source().file_id());
28 !range.is_subrange(&ctx.leaf.range())
29 }
30 }
31 })
32 .for_each(|(name, _res)| {
33 CompletionItem::new(name.to_string())
34 .kind(Reference)
35 .add_to(acc)
36 });
37 }
38
39 Ok(())
40}
41
42fn complete_fn(acc: &mut Completions, scopes: &hir::FnScopes, offset: TextUnit) {
43 let mut shadowed = FxHashSet::default();
44 scopes
45 .scope_chain_for_offset(offset)
46 .flat_map(|scope| scopes.entries(scope).iter())
47 .filter(|entry| shadowed.insert(entry.name()))
48 .for_each(|entry| {
49 CompletionItem::new(entry.name().to_string())
50 .kind(Reference)
51 .add_to(acc)
52 });
53 if scopes.self_param.is_some() {
54 CompletionItem::new("self").kind(Reference).add_to(acc);
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use crate::completion::{CompletionKind, check_completion};
61
62 fn check_reference_completion(code: &str, expected_completions: &str) {
63 check_completion(code, expected_completions, CompletionKind::Reference);
64 }
65
66 #[test]
67 fn completes_bindings_from_let() {
68 check_reference_completion(
69 r"
70 fn quux(x: i32) {
71 let y = 92;
72 1 + <|>;
73 let z = ();
74 }
75 ",
76 "y;x;quux",
77 );
78 }
79
80 #[test]
81 fn completes_bindings_from_if_let() {
82 check_reference_completion(
83 r"
84 fn quux() {
85 if let Some(x) = foo() {
86 let y = 92;
87 };
88 if let Some(a) = bar() {
89 let b = 62;
90 1 + <|>
91 }
92 }
93 ",
94 "b;a;quux",
95 );
96 }
97
98 #[test]
99 fn completes_bindings_from_for() {
100 check_reference_completion(
101 r"
102 fn quux() {
103 for x in &[1, 2, 3] {
104 <|>
105 }
106 }
107 ",
108 "x;quux",
109 );
110 }
111
112 #[test]
113 fn completes_module_items() {
114 check_reference_completion(
115 r"
116 struct Foo;
117 enum Baz {}
118 fn quux() {
119 <|>
120 }
121 ",
122 "quux;Foo;Baz",
123 );
124 }
125
126 #[test]
127 fn completes_module_items_in_nested_modules() {
128 check_reference_completion(
129 r"
130 struct Foo;
131 mod m {
132 struct Bar;
133 fn quux() { <|> }
134 }
135 ",
136 "quux;Bar",
137 );
138 }
139
140 #[test]
141 fn completes_return_type() {
142 check_reference_completion(
143 r"
144 struct Foo;
145 fn x() -> <|>
146 ",
147 "Foo;x",
148 )
149 }
150
151 #[test]
152 fn dont_show_to_completions_for_shadowing() {
153 check_reference_completion(
154 r"
155 fn foo() -> {
156 let bar = 92;
157 {
158 let bar = 62;
159 <|>
160 }
161 }
162 ",
163 "bar;foo",
164 )
165 }
166
167 #[test]
168 fn completes_self_in_methods() {
169 check_reference_completion(r"impl S { fn foo(&self) { <|> } }", "self")
170 }
171}
diff --git a/crates/ra_analysis/src/completion/complete_snippet.rs b/crates/ra_analysis/src/completion/complete_snippet.rs
new file mode 100644
index 000000000..ccd68832b
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_snippet.rs
@@ -0,0 +1,76 @@
1use crate::completion::{CompletionItem, Completions, CompletionKind::*, CompletionContext};
2
3pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) {
4 if !(ctx.is_trivial_path && ctx.enclosing_fn.is_some()) {
5 return;
6 }
7 CompletionItem::new("pd")
8 .snippet("eprintln!(\"$0 = {:?}\", $0);")
9 .kind(Snippet)
10 .add_to(acc);
11 CompletionItem::new("ppd")
12 .snippet("eprintln!(\"$0 = {:#?}\", $0);")
13 .kind(Snippet)
14 .add_to(acc);
15}
16
17pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
18 if !ctx.is_new_item {
19 return;
20 }
21 CompletionItem::new("Test function")
22 .lookup_by("tfn")
23 .snippet(
24 "\
25#[test]
26fn ${1:feature}() {
27 $0
28}",
29 )
30 .kind(Snippet)
31 .add_to(acc);
32 CompletionItem::new("pub(crate)")
33 .snippet("pub(crate) $0")
34 .kind(Snippet)
35 .add_to(acc);
36}
37
38#[cfg(test)]
39mod tests {
40 use crate::completion::{CompletionKind, check_completion};
41 fn check_snippet_completion(code: &str, expected_completions: &str) {
42 check_completion(code, expected_completions, CompletionKind::Snippet);
43 }
44
45 #[test]
46 fn completes_snippets_in_expressions() {
47 check_snippet_completion(
48 r"fn foo(x: i32) { <|> }",
49 r##"
50 pd "eprintln!(\"$0 = {:?}\", $0);"
51 ppd "eprintln!(\"$0 = {:#?}\", $0);"
52 "##,
53 );
54 }
55
56 #[test]
57 fn completes_snippets_in_items() {
58 // check_snippet_completion(r"
59 // <|>
60 // ",
61 // r##"[CompletionItem { label: "Test function", lookup: None, snippet: Some("#[test]\nfn test_${1:feature}() {\n$0\n}"##,
62 // );
63 check_snippet_completion(
64 r"
65 #[cfg(test)]
66 mod tests {
67 <|>
68 }
69 ",
70 r##"
71 tfn "Test function" "#[test]\nfn ${1:feature}() {\n $0\n}"
72 pub(crate) "pub(crate) $0"
73 "##,
74 );
75 }
76}
diff --git a/crates/ra_analysis/src/completion/completion_context.rs b/crates/ra_analysis/src/completion/completion_context.rs
new file mode 100644
index 000000000..064fbc6f7
--- /dev/null
+++ b/crates/ra_analysis/src/completion/completion_context.rs
@@ -0,0 +1,156 @@
1use ra_editor::find_node_at_offset;
2use ra_text_edit::AtomTextEdit;
3use ra_syntax::{
4 algo::find_leaf_at_offset,
5 ast,
6 AstNode,
7 SyntaxNodeRef,
8 SourceFileNode,
9 TextUnit,
10 SyntaxKind::*,
11};
12use hir::source_binder;
13
14use crate::{db, FilePosition, Cancelable};
15
16/// `CompletionContext` is created early during completion to figure out, where
17/// exactly is the cursor, syntax-wise.
18#[derive(Debug)]
19pub(super) struct CompletionContext<'a> {
20 pub(super) db: &'a db::RootDatabase,
21 pub(super) offset: TextUnit,
22 pub(super) leaf: SyntaxNodeRef<'a>,
23 pub(super) module: Option<hir::Module>,
24 pub(super) enclosing_fn: Option<ast::FnDef<'a>>,
25 pub(super) is_param: bool,
26 /// A single-indent path, like `foo`.
27 pub(super) is_trivial_path: bool,
28 /// If not a trivial, path, the prefix (qualifier).
29 pub(super) path_prefix: Option<hir::Path>,
30 pub(super) after_if: bool,
31 pub(super) is_stmt: bool,
32 /// Something is typed at the "top" level, in module or impl/trait.
33 pub(super) is_new_item: bool,
34}
35
36impl<'a> CompletionContext<'a> {
37 pub(super) fn new(
38 db: &'a db::RootDatabase,
39 original_file: &'a SourceFileNode,
40 position: FilePosition,
41 ) -> Cancelable<Option<CompletionContext<'a>>> {
42 let module = source_binder::module_from_position(db, position)?;
43 let leaf =
44 ctry!(find_leaf_at_offset(original_file.syntax(), position.offset).left_biased());
45 let mut ctx = CompletionContext {
46 db,
47 leaf,
48 offset: position.offset,
49 module,
50 enclosing_fn: None,
51 is_param: false,
52 is_trivial_path: false,
53 path_prefix: None,
54 after_if: false,
55 is_stmt: false,
56 is_new_item: false,
57 };
58 ctx.fill(original_file, position.offset);
59 Ok(Some(ctx))
60 }
61
62 fn fill(&mut self, original_file: &SourceFileNode, offset: TextUnit) {
63 // Insert a fake ident to get a valid parse tree. We will use this file
64 // to determine context, though the original_file will be used for
65 // actual completion.
66 let file = {
67 let edit = AtomTextEdit::insert(offset, "intellijRulezz".to_string());
68 original_file.reparse(&edit)
69 };
70
71 // First, let's try to complete a reference to some declaration.
72 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), offset) {
73 // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
74 // See RFC#1685.
75 if is_node::<ast::Param>(name_ref.syntax()) {
76 self.is_param = true;
77 return;
78 }
79 self.classify_name_ref(&file, name_ref);
80 }
81
82 // Otherwise, see if this is a declaration. We can use heuristics to
83 // suggest declaration names, see `CompletionKind::Magic`.
84 if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), offset) {
85 if is_node::<ast::Param>(name.syntax()) {
86 self.is_param = true;
87 return;
88 }
89 }
90 }
91 fn classify_name_ref(&mut self, file: &SourceFileNode, name_ref: ast::NameRef) {
92 let name_range = name_ref.syntax().range();
93 let top_node = name_ref
94 .syntax()
95 .ancestors()
96 .take_while(|it| it.range() == name_range)
97 .last()
98 .unwrap();
99
100 match top_node.parent().map(|it| it.kind()) {
101 Some(SOURCE_FILE) | Some(ITEM_LIST) => {
102 self.is_new_item = true;
103 return;
104 }
105 _ => (),
106 }
107
108 let parent = match name_ref.syntax().parent() {
109 Some(it) => it,
110 None => return,
111 };
112 if let Some(segment) = ast::PathSegment::cast(parent) {
113 let path = segment.parent_path();
114 if let Some(mut path) = hir::Path::from_ast(path) {
115 if !path.is_ident() {
116 path.segments.pop().unwrap();
117 self.path_prefix = Some(path);
118 return;
119 }
120 }
121 if path.qualifier().is_none() {
122 self.is_trivial_path = true;
123 self.enclosing_fn = self
124 .leaf
125 .ancestors()
126 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
127 .find_map(ast::FnDef::cast);
128
129 self.is_stmt = match name_ref
130 .syntax()
131 .ancestors()
132 .filter_map(ast::ExprStmt::cast)
133 .next()
134 {
135 None => false,
136 Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(),
137 };
138
139 if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
140 if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) {
141 if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
142 self.after_if = true;
143 }
144 }
145 }
146 }
147 }
148 }
149}
150
151fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool {
152 match node.ancestors().filter_map(N::cast).next() {
153 None => false,
154 Some(n) => n.syntax().range() == node.range(),
155 }
156}
diff --git a/crates/ra_analysis/src/completion/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs
deleted file mode 100644
index c2ac95453..000000000
--- a/crates/ra_analysis/src/completion/reference_completion.rs
+++ /dev/null
@@ -1,633 +0,0 @@
1use rustc_hash::{FxHashSet};
2use ra_editor::find_node_at_offset;
3use ra_syntax::{
4 algo::visit::{visitor, Visitor},
5 SourceFileNode, AstNode,
6 ast::{self, LoopBodyOwner},
7 SyntaxKind::*,
8};
9use hir::{
10 self,
11 FnScopes, Def, Path
12};
13
14use crate::{
15 db::RootDatabase,
16 completion::{CompletionItem, Completions, CompletionKind::*},
17 Cancelable
18};
19
20pub(super) fn completions(
21 acc: &mut Completions,
22 db: &RootDatabase,
23 module: &hir::Module,
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
32 match kind {
33 NameRefKind::LocalRef { enclosing_fn } => {
34 if let Some(fn_def) = enclosing_fn {
35 let scopes = FnScopes::new(fn_def);
36 complete_fn(name_ref, &scopes, acc);
37 complete_expr_keywords(&file, fn_def, name_ref, acc);
38 complete_expr_snippets(acc);
39 }
40
41 let module_scope = module.scope(db)?;
42 module_scope
43 .entries()
44 .filter(|(_name, res)| {
45 // Don't expose this item
46 match res.import {
47 None => true,
48 Some(import) => {
49 let range = import.range(db, module.source().file_id());
50 !range.is_subrange(&name_ref.syntax().range())
51 }
52 }
53 })
54 .for_each(|(name, _res)| {
55 CompletionItem::new(name.to_string())
56 .kind(Reference)
57 .add_to(acc)
58 });
59 }
60 NameRefKind::Path(path) => complete_path(acc, db, module, path)?,
61 NameRefKind::BareIdentInMod => {
62 let name_range = name_ref.syntax().range();
63 let top_node = name_ref
64 .syntax()
65 .ancestors()
66 .take_while(|it| it.range() == name_range)
67 .last()
68 .unwrap();
69 match top_node.parent().map(|it| it.kind()) {
70 Some(SOURCE_FILE) | Some(ITEM_LIST) => complete_mod_item_snippets(acc),
71 _ => (),
72 }
73 }
74 }
75 Ok(())
76}
77
78enum NameRefKind<'a> {
79 /// NameRef is a part of single-segment path, for example, a refernece to a
80 /// local variable.
81 LocalRef {
82 enclosing_fn: Option<ast::FnDef<'a>>,
83 },
84 /// NameRef is the last segment in some path
85 Path(Path),
86 /// NameRef is bare identifier at the module's root.
87 /// Used for keyword completion
88 BareIdentInMod,
89}
90
91fn classify_name_ref(name_ref: ast::NameRef) -> Option<NameRefKind> {
92 let name_range = name_ref.syntax().range();
93 let top_node = name_ref
94 .syntax()
95 .ancestors()
96 .take_while(|it| it.range() == name_range)
97 .last()
98 .unwrap();
99 match top_node.parent().map(|it| it.kind()) {
100 Some(SOURCE_FILE) | Some(ITEM_LIST) => return Some(NameRefKind::BareIdentInMod),
101 _ => (),
102 }
103
104 let parent = name_ref.syntax().parent()?;
105 if let Some(segment) = ast::PathSegment::cast(parent) {
106 let path = segment.parent_path();
107 if let Some(path) = Path::from_ast(path) {
108 if !path.is_ident() {
109 return Some(NameRefKind::Path(path));
110 }
111 }
112 if path.qualifier().is_none() {
113 let enclosing_fn = name_ref
114 .syntax()
115 .ancestors()
116 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
117 .find_map(ast::FnDef::cast);
118 return Some(NameRefKind::LocalRef { enclosing_fn });
119 }
120 }
121 None
122}
123
124fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Completions) {
125 let mut shadowed = FxHashSet::default();
126 scopes
127 .scope_chain(name_ref.syntax())
128 .flat_map(|scope| scopes.entries(scope).iter())
129 .filter(|entry| shadowed.insert(entry.name()))
130 .for_each(|entry| {
131 CompletionItem::new(entry.name().to_string())
132 .kind(Reference)
133 .add_to(acc)
134 });
135 if scopes.self_param.is_some() {
136 CompletionItem::new("self").kind(Reference).add_to(acc);
137 }
138}
139
140fn complete_path(
141 acc: &mut Completions,
142 db: &RootDatabase,
143 module: &hir::Module,
144 mut path: Path,
145) -> Cancelable<()> {
146 if path.segments.is_empty() {
147 return Ok(());
148 }
149 path.segments.pop();
150 let def_id = match module.resolve_path(db, path)? {
151 None => return Ok(()),
152 Some(it) => it,
153 };
154 let target_module = match def_id.resolve(db)? {
155 Def::Module(it) => it,
156 _ => return Ok(()),
157 };
158 let module_scope = target_module.scope(db)?;
159 module_scope.entries().for_each(|(name, _res)| {
160 CompletionItem::new(name.to_string())
161 .kind(Reference)
162 .add_to(acc)
163 });
164 Ok(())
165}
166
167fn complete_mod_item_snippets(acc: &mut Completions) {
168 CompletionItem::new("Test function")
169 .lookup_by("tfn")
170 .snippet(
171 "\
172#[test]
173fn ${1:feature}() {
174 $0
175}",
176 )
177 .kind(Snippet)
178 .add_to(acc);
179 CompletionItem::new("pub(crate)")
180 .snippet("pub(crate) $0")
181 .kind(Snippet)
182 .add_to(acc);
183}
184
185fn complete_expr_keywords(
186 file: &SourceFileNode,
187 fn_def: ast::FnDef,
188 name_ref: ast::NameRef,
189 acc: &mut Completions,
190) {
191 acc.add(keyword("if", "if $0 {}"));
192 acc.add(keyword("match", "match $0 {}"));
193 acc.add(keyword("while", "while $0 {}"));
194 acc.add(keyword("loop", "loop {$0}"));
195
196 if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
197 if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) {
198 if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
199 acc.add(keyword("else", "else {$0}"));
200 acc.add(keyword("else if", "else if $0 {}"));
201 }
202 }
203 }
204 if is_in_loop_body(name_ref) {
205 acc.add(keyword("continue", "continue"));
206 acc.add(keyword("break", "break"));
207 }
208 acc.add_all(complete_return(fn_def, name_ref));
209}
210
211fn is_in_loop_body(name_ref: ast::NameRef) -> bool {
212 for node in name_ref.syntax().ancestors() {
213 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
214 break;
215 }
216 let loop_body = visitor()
217 .visit::<ast::ForExpr, _>(LoopBodyOwner::loop_body)
218 .visit::<ast::WhileExpr, _>(LoopBodyOwner::loop_body)
219 .visit::<ast::LoopExpr, _>(LoopBodyOwner::loop_body)
220 .accept(node);
221 if let Some(Some(body)) = loop_body {
222 if name_ref
223 .syntax()
224 .range()
225 .is_subrange(&body.syntax().range())
226 {
227 return true;
228 }
229 }
230 }
231 false
232}
233
234fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option<CompletionItem> {
235 // let is_last_in_block = name_ref.syntax().ancestors().filter_map(ast::Expr::cast)
236 // .next()
237 // .and_then(|it| it.syntax().parent())
238 // .and_then(ast::Block::cast)
239 // .is_some();
240
241 // if is_last_in_block {
242 // return None;
243 // }
244
245 let is_stmt = match name_ref
246 .syntax()
247 .ancestors()
248 .filter_map(ast::ExprStmt::cast)
249 .next()
250 {
251 None => false,
252 Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(),
253 };
254 let snip = match (is_stmt, fn_def.ret_type().is_some()) {
255 (true, true) => "return $0;",
256 (true, false) => "return;",
257 (false, true) => "return $0",
258 (false, false) => "return",
259 };
260 Some(keyword("return", snip))
261}
262
263fn keyword(kw: &str, snippet: &str) -> CompletionItem {
264 CompletionItem::new(kw)
265 .kind(Keyword)
266 .snippet(snippet)
267 .build()
268}
269
270fn complete_expr_snippets(acc: &mut Completions) {
271 CompletionItem::new("pd")
272 .snippet("eprintln!(\"$0 = {:?}\", $0);")
273 .kind(Snippet)
274 .add_to(acc);
275 CompletionItem::new("ppd")
276 .snippet("eprintln!(\"$0 = {:#?}\", $0);")
277 .kind(Snippet)
278 .add_to(acc);
279}
280
281#[cfg(test)]
282mod tests {
283 use crate::completion::{CompletionKind, check_completion};
284
285 fn check_reference_completion(code: &str, expected_completions: &str) {
286 check_completion(code, expected_completions, CompletionKind::Reference);
287 }
288
289 fn check_keyword_completion(code: &str, expected_completions: &str) {
290 check_completion(code, expected_completions, CompletionKind::Keyword);
291 }
292
293 fn check_snippet_completion(code: &str, expected_completions: &str) {
294 check_completion(code, expected_completions, CompletionKind::Snippet);
295 }
296
297 #[test]
298 fn test_completion_let_scope() {
299 check_reference_completion(
300 r"
301 fn quux(x: i32) {
302 let y = 92;
303 1 + <|>;
304 let z = ();
305 }
306 ",
307 "y;x;quux",
308 );
309 }
310
311 #[test]
312 fn test_completion_if_let_scope() {
313 check_reference_completion(
314 r"
315 fn quux() {
316 if let Some(x) = foo() {
317 let y = 92;
318 };
319 if let Some(a) = bar() {
320 let b = 62;
321 1 + <|>
322 }
323 }
324 ",
325 "b;a;quux",
326 );
327 }
328
329 #[test]
330 fn test_completion_for_scope() {
331 check_reference_completion(
332 r"
333 fn quux() {
334 for x in &[1, 2, 3] {
335 <|>
336 }
337 }
338 ",
339 "x;quux",
340 );
341 }
342
343 #[test]
344 fn test_completion_mod_scope() {
345 check_reference_completion(
346 r"
347 struct Foo;
348 enum Baz {}
349 fn quux() {
350 <|>
351 }
352 ",
353 "quux;Foo;Baz",
354 );
355 }
356
357 #[test]
358 fn test_completion_mod_scope_no_self_use() {
359 check_reference_completion(
360 r"
361 use foo<|>;
362 ",
363 "",
364 );
365 }
366
367 #[test]
368 fn test_completion_self_path() {
369 check_reference_completion(
370 r"
371 use self::m::<|>;
372
373 mod m {
374 struct Bar;
375 }
376 ",
377 "Bar",
378 );
379 }
380
381 #[test]
382 fn test_completion_mod_scope_nested() {
383 check_reference_completion(
384 r"
385 struct Foo;
386 mod m {
387 struct Bar;
388 fn quux() { <|> }
389 }
390 ",
391 "quux;Bar",
392 );
393 }
394
395 #[test]
396 fn test_complete_type() {
397 check_reference_completion(
398 r"
399 struct Foo;
400 fn x() -> <|>
401 ",
402 "Foo;x",
403 )
404 }
405
406 #[test]
407 fn test_complete_shadowing() {
408 check_reference_completion(
409 r"
410 fn foo() -> {
411 let bar = 92;
412 {
413 let bar = 62;
414 <|>
415 }
416 }
417 ",
418 "bar;foo",
419 )
420 }
421
422 #[test]
423 fn test_complete_self() {
424 check_reference_completion(r"impl S { fn foo(&self) { <|> } }", "self")
425 }
426
427 #[test]
428 fn test_complete_crate_path() {
429 check_reference_completion(
430 "
431 //- /lib.rs
432 mod foo;
433 struct Spam;
434 //- /foo.rs
435 use crate::Sp<|>
436 ",
437 "Spam;foo",
438 );
439 }
440
441 #[test]
442 fn test_complete_crate_path_with_braces() {
443 check_reference_completion(
444 "
445 //- /lib.rs
446 mod foo;
447 struct Spam;
448 //- /foo.rs
449 use crate::{Sp<|>};
450 ",
451 "Spam;foo",
452 );
453 }
454
455 #[test]
456 fn test_complete_crate_path_in_nested_tree() {
457 check_reference_completion(
458 "
459 //- /lib.rs
460 mod foo;
461 pub mod bar {
462 pub mod baz {
463 pub struct Spam;
464 }
465 }
466 //- /foo.rs
467 use crate::{bar::{baz::Sp<|>}};
468 ",
469 "Spam",
470 );
471 }
472
473 #[test]
474 fn test_completion_kewords() {
475 check_keyword_completion(
476 r"
477 fn quux() {
478 <|>
479 }
480 ",
481 r#"
482 if "if $0 {}"
483 match "match $0 {}"
484 while "while $0 {}"
485 loop "loop {$0}"
486 return "return"
487 "#,
488 );
489 }
490
491 #[test]
492 fn test_completion_else() {
493 check_keyword_completion(
494 r"
495 fn quux() {
496 if true {
497 ()
498 } <|>
499 }
500 ",
501 r#"
502 if "if $0 {}"
503 match "match $0 {}"
504 while "while $0 {}"
505 loop "loop {$0}"
506 else "else {$0}"
507 else if "else if $0 {}"
508 return "return"
509 "#,
510 );
511 }
512
513 #[test]
514 fn test_completion_return_value() {
515 check_keyword_completion(
516 r"
517 fn quux() -> i32 {
518 <|>
519 92
520 }
521 ",
522 r#"
523 if "if $0 {}"
524 match "match $0 {}"
525 while "while $0 {}"
526 loop "loop {$0}"
527 return "return $0;"
528 "#,
529 );
530 check_keyword_completion(
531 r"
532 fn quux() {
533 <|>
534 92
535 }
536 ",
537 r#"
538 if "if $0 {}"
539 match "match $0 {}"
540 while "while $0 {}"
541 loop "loop {$0}"
542 return "return;"
543 "#,
544 );
545 }
546
547 #[test]
548 fn test_completion_return_no_stmt() {
549 check_keyword_completion(
550 r"
551 fn quux() -> i32 {
552 match () {
553 () => <|>
554 }
555 }
556 ",
557 r#"
558 if "if $0 {}"
559 match "match $0 {}"
560 while "while $0 {}"
561 loop "loop {$0}"
562 return "return $0"
563 "#,
564 );
565 }
566
567 #[test]
568 fn test_continue_break_completion() {
569 check_keyword_completion(
570 r"
571 fn quux() -> i32 {
572 loop { <|> }
573 }
574 ",
575 r#"
576 if "if $0 {}"
577 match "match $0 {}"
578 while "while $0 {}"
579 loop "loop {$0}"
580 continue "continue"
581 break "break"
582 return "return $0"
583 "#,
584 );
585 check_keyword_completion(
586 r"
587 fn quux() -> i32 {
588 loop { || { <|> } }
589 }
590 ",
591 r#"
592 if "if $0 {}"
593 match "match $0 {}"
594 while "while $0 {}"
595 loop "loop {$0}"
596 return "return $0"
597 "#,
598 );
599 }
600
601 #[test]
602 fn completes_snippets_in_expressions() {
603 check_snippet_completion(
604 r"fn foo(x: i32) { <|> }",
605 r##"
606 pd "eprintln!(\"$0 = {:?}\", $0);"
607 ppd "eprintln!(\"$0 = {:#?}\", $0);"
608 "##,
609 );
610 }
611
612 #[test]
613 fn completes_snippets_in_items() {
614 // check_snippet_completion(r"
615 // <|>
616 // ",
617 // r##"[CompletionItem { label: "Test function", lookup: None, snippet: Some("#[test]\nfn test_${1:feature}() {\n$0\n}"##,
618 // );
619 check_snippet_completion(
620 r"
621 #[cfg(test)]
622 mod tests {
623 <|>
624 }
625 ",
626 r##"
627 tfn "Test function" "#[test]\nfn ${1:feature}() {\n $0\n}"
628 pub(crate) "pub(crate) $0"
629 "##,
630 );
631 }
632
633}
diff --git a/crates/ra_hir/src/function/scope.rs b/crates/ra_hir/src/function/scope.rs
index 863453291..9f1aa1ef2 100644
--- a/crates/ra_hir/src/function/scope.rs
+++ b/crates/ra_hir/src/function/scope.rs
@@ -1,7 +1,7 @@
1use rustc_hash::{FxHashMap, FxHashSet}; 1use rustc_hash::{FxHashMap, FxHashSet};
2 2
3use ra_syntax::{ 3use ra_syntax::{
4 AstNode, SmolStr, SyntaxNodeRef, TextRange, 4 AstNode, SmolStr, SyntaxNodeRef, TextUnit, TextRange,
5 algo::generate, 5 algo::generate,
6 ast::{self, ArgListOwner, LoopBodyOwner, NameOwner}, 6 ast::{self, ArgListOwner, LoopBodyOwner, NameOwner},
7}; 7};
@@ -57,6 +57,48 @@ impl FnScopes {
57 self.scopes[scope].parent 57 self.scopes[scope].parent
58 }) 58 })
59 } 59 }
60 pub fn scope_chain_for_offset<'a>(
61 &'a self,
62 offset: TextUnit,
63 ) -> impl Iterator<Item = ScopeId> + 'a {
64 let scope = self
65 .scope_for
66 .iter()
67 // find containin scope
68 .min_by_key(|(ptr, _scope)| {
69 (
70 !(ptr.range().start() <= offset && offset <= ptr.range().end()),
71 ptr.range().len(),
72 )
73 })
74 .map(|(ptr, scope)| self.adjust(*ptr, *scope, offset));
75
76 generate(scope, move |&scope| self.scopes[scope].parent)
77 }
78 // XXX: during completion, cursor might be outside of any particular
79 // expression. Try to figure out the correct scope...
80 fn adjust(&self, ptr: LocalSyntaxPtr, original_scope: ScopeId, offset: TextUnit) -> ScopeId {
81 let r = ptr.range();
82 let child_scopes = self
83 .scope_for
84 .iter()
85 .map(|(ptr, scope)| (ptr.range(), scope))
86 .filter(|(range, _)| range.start() <= offset && range.is_subrange(&r) && *range != r);
87
88 child_scopes
89 .max_by(|(r1, _), (r2, _)| {
90 if r2.is_subrange(&r1) {
91 std::cmp::Ordering::Greater
92 } else if r1.is_subrange(&r2) {
93 std::cmp::Ordering::Less
94 } else {
95 r1.start().cmp(&r2.start())
96 }
97 })
98 .map(|(ptr, scope)| *scope)
99 .unwrap_or(original_scope)
100 }
101
60 pub fn resolve_local_name<'a>(&'a self, name_ref: ast::NameRef) -> Option<&'a ScopeEntry> { 102 pub fn resolve_local_name<'a>(&'a self, name_ref: ast::NameRef) -> Option<&'a ScopeEntry> {
61 let mut shadowed = FxHashSet::default(); 103 let mut shadowed = FxHashSet::default();
62 let ret = self 104 let ret = self
@@ -144,6 +186,8 @@ impl ScopeEntry {
144} 186}
145 187
146fn compute_block_scopes(block: ast::Block, scopes: &mut FnScopes, mut scope: ScopeId) { 188fn compute_block_scopes(block: ast::Block, scopes: &mut FnScopes, mut scope: ScopeId) {
189 // A hack for completion :(
190 scopes.set_scope(block.syntax(), scope);
147 for stmt in block.statements() { 191 for stmt in block.statements() {
148 match stmt { 192 match stmt {
149 ast::Stmt::LetStmt(stmt) => { 193 ast::Stmt::LetStmt(stmt) => {
@@ -165,6 +209,7 @@ fn compute_block_scopes(block: ast::Block, scopes: &mut FnScopes, mut scope: Sco
165 } 209 }
166 } 210 }
167 if let Some(expr) = block.expr() { 211 if let Some(expr) = block.expr() {
212 eprintln!("{:?}", expr);
168 scopes.set_scope(expr.syntax(), scope); 213 scopes.set_scope(expr.syntax(), scope);
169 compute_expr_scopes(expr, scopes, scope); 214 compute_expr_scopes(expr, scopes, scope);
170 } 215 }