diff options
-rw-r--r-- | crates/ra_analysis/src/completion.rs | 148 | ||||
-rw-r--r-- | crates/ra_analysis/src/imp.rs | 15 |
2 files changed, 75 insertions, 88 deletions
diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index 6fd30aaee..27566a8a1 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs | |||
@@ -4,7 +4,7 @@ use ra_syntax::{ | |||
4 | ast::{self, AstChildren, LoopBodyOwner, ModuleItemOwner}, | 4 | ast::{self, AstChildren, LoopBodyOwner, ModuleItemOwner}, |
5 | AstNode, AtomEdit, SourceFileNode, | 5 | AstNode, AtomEdit, SourceFileNode, |
6 | SyntaxKind::*, | 6 | SyntaxKind::*, |
7 | SyntaxNodeRef, TextUnit, | 7 | SyntaxNodeRef, |
8 | }; | 8 | }; |
9 | use rustc_hash::{FxHashMap, FxHashSet}; | 9 | use rustc_hash::{FxHashMap, FxHashSet}; |
10 | 10 | ||
@@ -14,7 +14,7 @@ use crate::{ | |||
14 | descriptors::module::{ModuleId, ModuleScope, ModuleTree, ModuleSource}, | 14 | descriptors::module::{ModuleId, ModuleScope, ModuleTree, ModuleSource}, |
15 | descriptors::DescriptorDatabase, | 15 | descriptors::DescriptorDatabase, |
16 | input::FilesDatabase, | 16 | input::FilesDatabase, |
17 | Cancelable, FilePosition, | 17 | Cancelable, FilePosition, FileId, |
18 | }; | 18 | }; |
19 | 19 | ||
20 | #[derive(Debug)] | 20 | #[derive(Debug)] |
@@ -27,47 +27,87 @@ pub struct CompletionItem { | |||
27 | pub snippet: Option<String>, | 27 | pub snippet: Option<String>, |
28 | } | 28 | } |
29 | 29 | ||
30 | pub(crate) fn resolve_based_completion( | 30 | pub(crate) fn completions( |
31 | db: &db::RootDatabase, | 31 | db: &db::RootDatabase, |
32 | position: FilePosition, | 32 | position: FilePosition, |
33 | ) -> Cancelable<Option<Vec<CompletionItem>>> { | 33 | ) -> Cancelable<Option<Vec<CompletionItem>>> { |
34 | let source_root_id = db.file_source_root(position.file_id); | 34 | let original_file = db.file_syntax(position.file_id); |
35 | let file = db.file_syntax(position.file_id); | 35 | // Insert a fake ident to get a valid parse tree |
36 | let module_tree = db.module_tree(source_root_id)?; | ||
37 | let module_id = | ||
38 | match module_tree.any_module_for_source(ModuleSource::SourceFile(position.file_id)) { | ||
39 | None => return Ok(None), | ||
40 | Some(it) => it, | ||
41 | }; | ||
42 | let file = { | 36 | let file = { |
43 | let edit = AtomEdit::insert(position.offset, "intellijRulezz".to_string()); | 37 | let edit = AtomEdit::insert(position.offset, "intellijRulezz".to_string()); |
44 | file.reparse(&edit) | 38 | original_file.reparse(&edit) |
45 | }; | 39 | }; |
46 | let target_module_id = match find_target_module(&module_tree, module_id, &file, position.offset) | 40 | |
47 | { | 41 | let mut res = Vec::new(); |
48 | None => return Ok(None), | 42 | let mut has_completions = false; |
43 | // First, let's try to complete a reference to some declaration. | ||
44 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) { | ||
45 | has_completions = true; | ||
46 | // completion from lexical scope | ||
47 | complete_name_ref(&file, name_ref, &mut res); | ||
48 | // special case, `trait T { fn foo(i_am_a_name_ref) {} }` | ||
49 | if is_node::<ast::Param>(name_ref.syntax()) { | ||
50 | param_completions(name_ref.syntax(), &mut res); | ||
51 | } | ||
52 | // snippet completions | ||
53 | { | ||
54 | let name_range = name_ref.syntax().range(); | ||
55 | let top_node = name_ref | ||
56 | .syntax() | ||
57 | .ancestors() | ||
58 | .take_while(|it| it.range() == name_range) | ||
59 | .last() | ||
60 | .unwrap(); | ||
61 | match top_node.parent().map(|it| it.kind()) { | ||
62 | Some(SOURCE_FILE) | Some(ITEM_LIST) => complete_mod_item_snippets(&mut res), | ||
63 | _ => (), | ||
64 | } | ||
65 | } | ||
66 | complete_path(db, position.file_id, name_ref, &mut res)?; | ||
67 | } | ||
68 | |||
69 | // Otherwise, if this is a declaration, use heuristics to suggest a name. | ||
70 | if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) { | ||
71 | if is_node::<ast::Param>(name.syntax()) { | ||
72 | has_completions = true; | ||
73 | param_completions(name.syntax(), &mut res); | ||
74 | } | ||
75 | } | ||
76 | let res = if has_completions { Some(res) } else { None }; | ||
77 | Ok(res) | ||
78 | } | ||
79 | |||
80 | fn complete_path( | ||
81 | db: &db::RootDatabase, | ||
82 | file_id: FileId, | ||
83 | name_ref: ast::NameRef, | ||
84 | acc: &mut Vec<CompletionItem>, | ||
85 | ) -> Cancelable<()> { | ||
86 | let source_root_id = db.file_source_root(file_id); | ||
87 | let module_tree = db.module_tree(source_root_id)?; | ||
88 | let module_id = match module_tree.any_module_for_source(ModuleSource::SourceFile(file_id)) { | ||
89 | None => return Ok(()), | ||
90 | Some(it) => it, | ||
91 | }; | ||
92 | let target_module_id = match find_target_module(&module_tree, module_id, name_ref) { | ||
93 | None => return Ok(()), | ||
49 | Some(it) => it, | 94 | Some(it) => it, |
50 | }; | 95 | }; |
51 | let module_scope = db.module_scope(source_root_id, target_module_id)?; | 96 | let module_scope = db.module_scope(source_root_id, target_module_id)?; |
52 | let res: Vec<_> = module_scope | 97 | let completions = module_scope.entries().iter().map(|entry| CompletionItem { |
53 | .entries() | 98 | label: entry.name().to_string(), |
54 | .iter() | 99 | lookup: None, |
55 | .map(|entry| CompletionItem { | 100 | snippet: None, |
56 | label: entry.name().to_string(), | 101 | }); |
57 | lookup: None, | 102 | acc.extend(completions); |
58 | snippet: None, | 103 | Ok(()) |
59 | }) | ||
60 | .collect(); | ||
61 | Ok(Some(res)) | ||
62 | } | 104 | } |
63 | 105 | ||
64 | pub(crate) fn find_target_module( | 106 | fn find_target_module( |
65 | module_tree: &ModuleTree, | 107 | module_tree: &ModuleTree, |
66 | module_id: ModuleId, | 108 | module_id: ModuleId, |
67 | file: &SourceFileNode, | 109 | name_ref: ast::NameRef, |
68 | offset: TextUnit, | ||
69 | ) -> Option<ModuleId> { | 110 | ) -> Option<ModuleId> { |
70 | let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), offset)?; | ||
71 | let mut crate_path = crate_path(name_ref)?; | 111 | let mut crate_path = crate_path(name_ref)?; |
72 | 112 | ||
73 | crate_path.pop(); | 113 | crate_path.pop(); |
@@ -98,50 +138,6 @@ fn crate_path(name_ref: ast::NameRef) -> Option<Vec<ast::NameRef>> { | |||
98 | Some(res) | 138 | Some(res) |
99 | } | 139 | } |
100 | 140 | ||
101 | pub(crate) fn scope_completion( | ||
102 | db: &db::RootDatabase, | ||
103 | position: FilePosition, | ||
104 | ) -> Option<Vec<CompletionItem>> { | ||
105 | let original_file = db.file_syntax(position.file_id); | ||
106 | // Insert a fake ident to get a valid parse tree | ||
107 | let file = { | ||
108 | let edit = AtomEdit::insert(position.offset, "intellijRulezz".to_string()); | ||
109 | original_file.reparse(&edit) | ||
110 | }; | ||
111 | let mut has_completions = false; | ||
112 | let mut res = Vec::new(); | ||
113 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) { | ||
114 | has_completions = true; | ||
115 | complete_name_ref(&file, name_ref, &mut res); | ||
116 | // special case, `trait T { fn foo(i_am_a_name_ref) {} }` | ||
117 | if is_node::<ast::Param>(name_ref.syntax()) { | ||
118 | param_completions(name_ref.syntax(), &mut res); | ||
119 | } | ||
120 | let name_range = name_ref.syntax().range(); | ||
121 | let top_node = name_ref | ||
122 | .syntax() | ||
123 | .ancestors() | ||
124 | .take_while(|it| it.range() == name_range) | ||
125 | .last() | ||
126 | .unwrap(); | ||
127 | match top_node.parent().map(|it| it.kind()) { | ||
128 | Some(SOURCE_FILE) | Some(ITEM_LIST) => complete_mod_item_snippets(&mut res), | ||
129 | _ => (), | ||
130 | } | ||
131 | } | ||
132 | if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) { | ||
133 | if is_node::<ast::Param>(name.syntax()) { | ||
134 | has_completions = true; | ||
135 | param_completions(name.syntax(), &mut res); | ||
136 | } | ||
137 | } | ||
138 | if has_completions { | ||
139 | Some(res) | ||
140 | } else { | ||
141 | None | ||
142 | } | ||
143 | } | ||
144 | |||
145 | fn complete_module_items( | 141 | fn complete_module_items( |
146 | file: &SourceFileNode, | 142 | file: &SourceFileNode, |
147 | items: AstChildren<ast::ModuleItem>, | 143 | items: AstChildren<ast::ModuleItem>, |
@@ -383,7 +379,8 @@ mod tests { | |||
383 | 379 | ||
384 | fn check_scope_completion(code: &str, expected_completions: &str) { | 380 | fn check_scope_completion(code: &str, expected_completions: &str) { |
385 | let (analysis, position) = single_file_with_position(code); | 381 | let (analysis, position) = single_file_with_position(code); |
386 | let completions = scope_completion(&analysis.imp.db, position) | 382 | let completions = completions(&analysis.imp.db, position) |
383 | .unwrap() | ||
387 | .unwrap() | 384 | .unwrap() |
388 | .into_iter() | 385 | .into_iter() |
389 | .filter(|c| c.snippet.is_none()) | 386 | .filter(|c| c.snippet.is_none()) |
@@ -393,7 +390,8 @@ mod tests { | |||
393 | 390 | ||
394 | fn check_snippet_completion(code: &str, expected_completions: &str) { | 391 | fn check_snippet_completion(code: &str, expected_completions: &str) { |
395 | let (analysis, position) = single_file_with_position(code); | 392 | let (analysis, position) = single_file_with_position(code); |
396 | let completions = scope_completion(&analysis.imp.db, position) | 393 | let completions = completions(&analysis.imp.db, position) |
394 | .unwrap() | ||
397 | .unwrap() | 395 | .unwrap() |
398 | .into_iter() | 396 | .into_iter() |
399 | .filter(|c| c.snippet.is_some()) | 397 | .filter(|c| c.snippet.is_some()) |
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs index 614a6e9be..74c248a96 100644 --- a/crates/ra_analysis/src/imp.rs +++ b/crates/ra_analysis/src/imp.rs | |||
@@ -17,7 +17,7 @@ use rustc_hash::FxHashSet; | |||
17 | use salsa::{Database, ParallelDatabase}; | 17 | use salsa::{Database, ParallelDatabase}; |
18 | 18 | ||
19 | use crate::{ | 19 | use crate::{ |
20 | completion::{resolve_based_completion, scope_completion, CompletionItem}, | 20 | completion::{completions, CompletionItem}, |
21 | db::{self, FileSyntaxQuery, SyntaxDatabase}, | 21 | db::{self, FileSyntaxQuery, SyntaxDatabase}, |
22 | descriptors::{ | 22 | descriptors::{ |
23 | function::{FnDescriptor, FnId}, | 23 | function::{FnDescriptor, FnId}, |
@@ -267,18 +267,7 @@ impl AnalysisImpl { | |||
267 | self.db.crate_graph().crate_roots[&crate_id] | 267 | self.db.crate_graph().crate_roots[&crate_id] |
268 | } | 268 | } |
269 | pub fn completions(&self, position: FilePosition) -> Cancelable<Option<Vec<CompletionItem>>> { | 269 | pub fn completions(&self, position: FilePosition) -> Cancelable<Option<Vec<CompletionItem>>> { |
270 | let mut res = Vec::new(); | 270 | completions(&self.db, position) |
271 | let mut has_completions = false; | ||
272 | if let Some(scope_based) = scope_completion(&self.db, position) { | ||
273 | res.extend(scope_based); | ||
274 | has_completions = true; | ||
275 | } | ||
276 | if let Some(scope_based) = resolve_based_completion(&self.db, position)? { | ||
277 | res.extend(scope_based); | ||
278 | has_completions = true; | ||
279 | } | ||
280 | let res = if has_completions { Some(res) } else { None }; | ||
281 | Ok(res) | ||
282 | } | 271 | } |
283 | pub fn approximately_resolve_symbol( | 272 | pub fn approximately_resolve_symbol( |
284 | &self, | 273 | &self, |