aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2018-12-21 21:52:02 +0000
committerAleksey Kladov <[email protected]>2018-12-21 21:52:02 +0000
commitccca5aae43a27c94180bb099bcc09bb6c29c2ea3 (patch)
tree3d6ce58a0e87cb89ef4ca2df29e5f25bac86d0b8
parent2136e75c0bce090d104bb5b5006e48e42fb22a0a (diff)
scope-based copmletions on original file
-rw-r--r--crates/ra_analysis/src/completion.rs18
-rw-r--r--crates/ra_analysis/src/completion/complete_path.rs2
-rw-r--r--crates/ra_analysis/src/completion/complete_scope.rs171
-rw-r--r--crates/ra_analysis/src/completion/reference_completion.rs307
-rw-r--r--crates/ra_hir/src/function/scope.rs47
5 files changed, 223 insertions, 322 deletions
diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs
index d91304bc2..93edcc4c2 100644
--- a/crates/ra_analysis/src/completion.rs
+++ b/crates/ra_analysis/src/completion.rs
@@ -1,10 +1,10 @@
1mod completion_item; 1mod completion_item;
2mod reference_completion;
3 2
4mod complete_fn_param; 3mod complete_fn_param;
5mod complete_keyword; 4mod complete_keyword;
6mod complete_snippet; 5mod complete_snippet;
7mod complete_path; 6mod complete_path;
7mod complete_scope;
8 8
9use ra_editor::find_node_at_offset; 9use ra_editor::find_node_at_offset;
10use ra_text_edit::AtomTextEdit; 10use ra_text_edit::AtomTextEdit;
@@ -33,26 +33,16 @@ pub(crate) fn completions(
33 position: FilePosition, 33 position: FilePosition,
34) -> Cancelable<Option<Completions>> { 34) -> Cancelable<Option<Completions>> {
35 let original_file = db.source_file(position.file_id); 35 let original_file = db.source_file(position.file_id);
36 // Insert a fake ident to get a valid parse tree 36 let ctx = ctry!(SyntaxContext::new(db, &original_file, position)?);
37 let file = {
38 let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string());
39 original_file.reparse(&edit)
40 };
41 let module = ctry!(source_binder::module_from_position(db, position)?);
42 37
43 let mut acc = Completions::default(); 38 let mut acc = Completions::default();
44 39
45 // First, let's try to complete a reference to some declaration.
46 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) {
47 reference_completion::completions(&mut acc, db, &module, &file, name_ref)?;
48 }
49
50 let ctx = ctry!(SyntaxContext::new(db, &original_file, position)?);
51 complete_fn_param::complete_fn_param(&mut acc, &ctx); 40 complete_fn_param::complete_fn_param(&mut acc, &ctx);
52 complete_keyword::complete_expr_keyword(&mut acc, &ctx); 41 complete_keyword::complete_expr_keyword(&mut acc, &ctx);
53 complete_snippet::complete_expr_snippet(&mut acc, &ctx); 42 complete_snippet::complete_expr_snippet(&mut acc, &ctx);
54 complete_snippet::complete_item_snippet(&mut acc, &ctx); 43 complete_snippet::complete_item_snippet(&mut acc, &ctx);
55 complete_path::complete_path(&mut acc, &ctx)?; 44 complete_path::complete_path(&mut acc, &ctx)?;
45 complete_scope::complete_scope(&mut acc, &ctx)?;
56 46
57 Ok(Some(acc)) 47 Ok(Some(acc))
58} 48}
@@ -62,6 +52,7 @@ pub(crate) fn completions(
62#[derive(Debug)] 52#[derive(Debug)]
63pub(super) struct SyntaxContext<'a> { 53pub(super) struct SyntaxContext<'a> {
64 db: &'a db::RootDatabase, 54 db: &'a db::RootDatabase,
55 offset: TextUnit,
65 leaf: SyntaxNodeRef<'a>, 56 leaf: SyntaxNodeRef<'a>,
66 module: Option<hir::Module>, 57 module: Option<hir::Module>,
67 enclosing_fn: Option<ast::FnDef<'a>>, 58 enclosing_fn: Option<ast::FnDef<'a>>,
@@ -88,6 +79,7 @@ impl<'a> SyntaxContext<'a> {
88 let mut ctx = SyntaxContext { 79 let mut ctx = SyntaxContext {
89 db, 80 db,
90 leaf, 81 leaf,
82 offset: position.offset,
91 module, 83 module,
92 enclosing_fn: None, 84 enclosing_fn: None,
93 is_param: false, 85 is_param: false,
diff --git a/crates/ra_analysis/src/completion/complete_path.rs b/crates/ra_analysis/src/completion/complete_path.rs
index d04503e46..8374ec346 100644
--- a/crates/ra_analysis/src/completion/complete_path.rs
+++ b/crates/ra_analysis/src/completion/complete_path.rs
@@ -9,8 +9,8 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &SyntaxContext) -> Cance
9 _ => return Ok(()), 9 _ => return Ok(()),
10 }; 10 };
11 let def_id = match module.resolve_path(ctx.db, path)? { 11 let def_id = match module.resolve_path(ctx.db, path)? {
12 None => return Ok(()),
13 Some(it) => it, 12 Some(it) => it,
13 None => return Ok(()),
14 }; 14 };
15 let target_module = match def_id.resolve(ctx.db)? { 15 let target_module = match def_id.resolve(ctx.db)? {
16 hir::Def::Module(it) => it, 16 hir::Def::Module(it) => it,
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..4ffd63016
--- /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 completion::{CompletionItem, Completions, CompletionKind::*, SyntaxContext},
6 Cancelable
7};
8
9pub(super) fn complete_scope(acc: &mut Completions, ctx: &SyntaxContext) -> 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 test_completion_let_scope() {
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 test_completion_if_let_scope() {
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 test_completion_for_scope() {
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 test_completion_mod_scope() {
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 test_completion_mod_scope_nested() {
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 test_complete_type() {
142 check_reference_completion(
143 r"
144 struct Foo;
145 fn x() -> <|>
146 ",
147 "Foo;x",
148 )
149 }
150
151 #[test]
152 fn test_complete_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 test_complete_self() {
169 check_reference_completion(r"impl S { fn foo(&self) { <|> } }", "self")
170 }
171}
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 459ed8f6f..000000000
--- a/crates/ra_analysis/src/completion/reference_completion.rs
+++ /dev/null
@@ -1,307 +0,0 @@
1use rustc_hash::FxHashSet;
2use ra_syntax::{
3 SourceFileNode, AstNode,
4 ast,
5 SyntaxKind::*,
6};
7use hir::{
8 self,
9 FnScopes, Path
10};
11
12use crate::{
13 db::RootDatabase,
14 completion::{CompletionItem, Completions, CompletionKind::*},
15 Cancelable
16};
17
18pub(super) fn completions(
19 acc: &mut Completions,
20 db: &RootDatabase,
21 module: &hir::Module,
22 _file: &SourceFileNode,
23 name_ref: ast::NameRef,
24) -> Cancelable<()> {
25 let kind = match classify_name_ref(name_ref) {
26 Some(it) => it,
27 None => return Ok(()),
28 };
29
30 match kind {
31 NameRefKind::LocalRef { enclosing_fn } => {
32 if let Some(fn_def) = enclosing_fn {
33 let scopes = FnScopes::new(fn_def);
34 complete_fn(name_ref, &scopes, acc);
35 }
36
37 let module_scope = module.scope(db)?;
38 module_scope
39 .entries()
40 .filter(|(_name, res)| {
41 // Don't expose this item
42 match res.import {
43 None => true,
44 Some(import) => {
45 let range = import.range(db, module.source().file_id());
46 !range.is_subrange(&name_ref.syntax().range())
47 }
48 }
49 })
50 .for_each(|(name, _res)| {
51 CompletionItem::new(name.to_string())
52 .kind(Reference)
53 .add_to(acc)
54 });
55 }
56 NameRefKind::Path(_) => (),
57 NameRefKind::BareIdentInMod => (),
58 }
59 Ok(())
60}
61
62enum NameRefKind<'a> {
63 /// NameRef is a part of single-segment path, for example, a refernece to a
64 /// local variable.
65 LocalRef {
66 enclosing_fn: Option<ast::FnDef<'a>>,
67 },
68 /// NameRef is the last segment in some path
69 Path(Path),
70 /// NameRef is bare identifier at the module's root.
71 /// Used for keyword completion
72 BareIdentInMod,
73}
74
75fn classify_name_ref(name_ref: ast::NameRef) -> Option<NameRefKind> {
76 let name_range = name_ref.syntax().range();
77 let top_node = name_ref
78 .syntax()
79 .ancestors()
80 .take_while(|it| it.range() == name_range)
81 .last()
82 .unwrap();
83 match top_node.parent().map(|it| it.kind()) {
84 Some(SOURCE_FILE) | Some(ITEM_LIST) => return Some(NameRefKind::BareIdentInMod),
85 _ => (),
86 }
87
88 let parent = name_ref.syntax().parent()?;
89 if let Some(segment) = ast::PathSegment::cast(parent) {
90 let path = segment.parent_path();
91 if let Some(path) = Path::from_ast(path) {
92 if !path.is_ident() {
93 return Some(NameRefKind::Path(path));
94 }
95 }
96 if path.qualifier().is_none() {
97 let enclosing_fn = name_ref
98 .syntax()
99 .ancestors()
100 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
101 .find_map(ast::FnDef::cast);
102 return Some(NameRefKind::LocalRef { enclosing_fn });
103 }
104 }
105 None
106}
107
108fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Completions) {
109 let mut shadowed = FxHashSet::default();
110 scopes
111 .scope_chain(name_ref.syntax())
112 .flat_map(|scope| scopes.entries(scope).iter())
113 .filter(|entry| shadowed.insert(entry.name()))
114 .for_each(|entry| {
115 CompletionItem::new(entry.name().to_string())
116 .kind(Reference)
117 .add_to(acc)
118 });
119 if scopes.self_param.is_some() {
120 CompletionItem::new("self").kind(Reference).add_to(acc);
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use crate::completion::{CompletionKind, check_completion};
127
128 fn check_reference_completion(code: &str, expected_completions: &str) {
129 check_completion(code, expected_completions, CompletionKind::Reference);
130 }
131
132 #[test]
133 fn test_completion_let_scope() {
134 check_reference_completion(
135 r"
136 fn quux(x: i32) {
137 let y = 92;
138 1 + <|>;
139 let z = ();
140 }
141 ",
142 "y;x;quux",
143 );
144 }
145
146 #[test]
147 fn test_completion_if_let_scope() {
148 check_reference_completion(
149 r"
150 fn quux() {
151 if let Some(x) = foo() {
152 let y = 92;
153 };
154 if let Some(a) = bar() {
155 let b = 62;
156 1 + <|>
157 }
158 }
159 ",
160 "b;a;quux",
161 );
162 }
163
164 #[test]
165 fn test_completion_for_scope() {
166 check_reference_completion(
167 r"
168 fn quux() {
169 for x in &[1, 2, 3] {
170 <|>
171 }
172 }
173 ",
174 "x;quux",
175 );
176 }
177
178 #[test]
179 fn test_completion_mod_scope() {
180 check_reference_completion(
181 r"
182 struct Foo;
183 enum Baz {}
184 fn quux() {
185 <|>
186 }
187 ",
188 "quux;Foo;Baz",
189 );
190 }
191
192 #[test]
193 fn test_completion_mod_scope_no_self_use() {
194 check_reference_completion(
195 r"
196 use foo<|>;
197 ",
198 "",
199 );
200 }
201
202 #[test]
203 fn test_completion_self_path() {
204 check_reference_completion(
205 r"
206 use self::m::<|>;
207
208 mod m {
209 struct Bar;
210 }
211 ",
212 "Bar",
213 );
214 }
215
216 #[test]
217 fn test_completion_mod_scope_nested() {
218 check_reference_completion(
219 r"
220 struct Foo;
221 mod m {
222 struct Bar;
223 fn quux() { <|> }
224 }
225 ",
226 "quux;Bar",
227 );
228 }
229
230 #[test]
231 fn test_complete_type() {
232 check_reference_completion(
233 r"
234 struct Foo;
235 fn x() -> <|>
236 ",
237 "Foo;x",
238 )
239 }
240
241 #[test]
242 fn test_complete_shadowing() {
243 check_reference_completion(
244 r"
245 fn foo() -> {
246 let bar = 92;
247 {
248 let bar = 62;
249 <|>
250 }
251 }
252 ",
253 "bar;foo",
254 )
255 }
256
257 #[test]
258 fn test_complete_self() {
259 check_reference_completion(r"impl S { fn foo(&self) { <|> } }", "self")
260 }
261
262 #[test]
263 fn test_complete_crate_path() {
264 check_reference_completion(
265 "
266 //- /lib.rs
267 mod foo;
268 struct Spam;
269 //- /foo.rs
270 use crate::Sp<|>
271 ",
272 "Spam;foo",
273 );
274 }
275
276 #[test]
277 fn test_complete_crate_path_with_braces() {
278 check_reference_completion(
279 "
280 //- /lib.rs
281 mod foo;
282 struct Spam;
283 //- /foo.rs
284 use crate::{Sp<|>};
285 ",
286 "Spam;foo",
287 );
288 }
289
290 #[test]
291 fn test_complete_crate_path_in_nested_tree() {
292 check_reference_completion(
293 "
294 //- /lib.rs
295 mod foo;
296 pub mod bar {
297 pub mod baz {
298 pub struct Spam;
299 }
300 }
301 //- /foo.rs
302 use crate::{bar::{baz::Sp<|>}};
303 ",
304 "Spam",
305 );
306 }
307}
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 }