aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_analysis/src/completion/reference_completion.rs
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2018-11-07 17:34:16 +0000
committerAleksey Kladov <[email protected]>2018-11-07 17:34:16 +0000
commit9b88ec488b3f83ab718c8cb4d7dff95aff0113ed (patch)
treefcdb2d0922b1492df59e779b5cbcb6086c19c402 /crates/ra_analysis/src/completion/reference_completion.rs
parentaf17fc969742a36cee5199860789ed0b14123240 (diff)
split completion mod
Diffstat (limited to 'crates/ra_analysis/src/completion/reference_completion.rs')
-rw-r--r--crates/ra_analysis/src/completion/reference_completion.rs331
1 files changed, 331 insertions, 0 deletions
diff --git a/crates/ra_analysis/src/completion/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs
new file mode 100644
index 000000000..b08174968
--- /dev/null
+++ b/crates/ra_analysis/src/completion/reference_completion.rs
@@ -0,0 +1,331 @@
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, AstChildren, ModuleItemOwner, LoopBodyOwner},
7 SyntaxKind::*,
8};
9
10use crate::{
11 db::RootDatabase,
12 input::FilesDatabase,
13 completion::CompletionItem,
14 descriptors::module::{ModuleId, ModuleScope, ModuleTree, ModuleSource},
15 descriptors::function::FnScopes,
16 descriptors::DescriptorDatabase,
17 FileId, Cancelable
18};
19
20pub(super) fn completions(
21 acc: &mut Vec<CompletionItem>,
22 db: &RootDatabase,
23 file_id: FileId,
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 match kind {
32 NameRefKind::LocalRef => {
33 if let Some(fn_def) = complete_local_name(acc, &file, name_ref) {
34 complete_expr_keywords(&file, fn_def, name_ref, acc);
35 complete_expr_snippets(acc);
36 }
37 }
38 NameRefKind::CratePath(path) => complete_path(acc, db, file_id, path)?,
39 NameRefKind::BareIdentInMod => {
40 let name_range = name_ref.syntax().range();
41 let top_node = name_ref
42 .syntax()
43 .ancestors()
44 .take_while(|it| it.range() == name_range)
45 .last()
46 .unwrap();
47 match top_node.parent().map(|it| it.kind()) {
48 Some(SOURCE_FILE) | Some(ITEM_LIST) => complete_mod_item_snippets(acc),
49 _ => (),
50 }
51 }
52 }
53 Ok(())
54}
55
56enum NameRefKind<'a> {
57 /// NameRef is a part of single-segment path, for example, a refernece to a
58 /// local variable.
59 LocalRef,
60 /// NameRef is the last segment in crate:: path
61 CratePath(Vec<ast::NameRef<'a>>),
62 /// NameRef is bare identifier at the module's root.
63 /// Used for keyword completion
64 BareIdentInMod,
65}
66
67fn classify_name_ref(name_ref: ast::NameRef) -> Option<NameRefKind> {
68 let name_range = name_ref.syntax().range();
69 let top_node = name_ref
70 .syntax()
71 .ancestors()
72 .take_while(|it| it.range() == name_range)
73 .last()
74 .unwrap();
75 match top_node.parent().map(|it| it.kind()) {
76 Some(SOURCE_FILE) | Some(ITEM_LIST) => return Some(NameRefKind::BareIdentInMod),
77 _ => (),
78 }
79
80 let parent = name_ref.syntax().parent()?;
81 if let Some(segment) = ast::PathSegment::cast(parent) {
82 let path = segment.parent_path();
83 if path.qualifier().is_none() {
84 return Some(NameRefKind::LocalRef);
85 }
86 if let Some(crate_path) = crate_path(path) {
87 return Some(NameRefKind::CratePath(crate_path));
88 }
89 }
90 None
91}
92
93fn crate_path(mut path: ast::Path) -> Option<Vec<ast::NameRef>> {
94 let mut res = Vec::new();
95 loop {
96 let segment = path.segment()?;
97 match segment.kind()? {
98 ast::PathSegmentKind::Name(name) => res.push(name),
99 ast::PathSegmentKind::CrateKw => break,
100 ast::PathSegmentKind::SelfKw | ast::PathSegmentKind::SuperKw => return None,
101 }
102 path = path.qualifier()?;
103 }
104 res.reverse();
105 Some(res)
106}
107
108fn complete_local_name<'a>(
109 acc: &mut Vec<CompletionItem>,
110 file: &SourceFileNode,
111 name_ref: ast::NameRef<'a>,
112) -> Option<ast::FnDef<'a>> {
113 let mut enclosing_fn = None;
114 for node in name_ref.syntax().ancestors() {
115 if let Some(items) = visitor()
116 .visit::<ast::SourceFile, _>(|it| Some(it.items()))
117 .visit::<ast::Module, _>(|it| Some(it.item_list()?.items()))
118 .accept(node)
119 {
120 if let Some(items) = items {
121 complete_module_items(file, items, Some(name_ref), acc);
122 }
123 break;
124 } else if enclosing_fn.is_none() {
125 if let Some(fn_def) = ast::FnDef::cast(node) {
126 enclosing_fn = Some(fn_def);
127 let scopes = FnScopes::new(fn_def);
128 complete_fn(name_ref, &scopes, acc);
129 }
130 }
131 }
132 enclosing_fn
133}
134
135fn complete_module_items(
136 file: &SourceFileNode,
137 items: AstChildren<ast::ModuleItem>,
138 this_item: Option<ast::NameRef>,
139 acc: &mut Vec<CompletionItem>,
140) {
141 let scope = ModuleScope::new(items); // FIXME
142 acc.extend(
143 scope
144 .entries()
145 .iter()
146 .filter(|entry| {
147 let syntax = entry.ptr().resolve(file);
148 Some(syntax.borrowed()) != this_item.map(|it| it.syntax())
149 })
150 .map(|entry| CompletionItem {
151 label: entry.name().to_string(),
152 lookup: None,
153 snippet: None,
154 }),
155 );
156}
157
158fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<CompletionItem>) {
159 let mut shadowed = FxHashSet::default();
160 acc.extend(
161 scopes
162 .scope_chain(name_ref.syntax())
163 .flat_map(|scope| scopes.entries(scope).iter())
164 .filter(|entry| shadowed.insert(entry.name()))
165 .map(|entry| CompletionItem {
166 label: entry.name().to_string(),
167 lookup: None,
168 snippet: None,
169 }),
170 );
171 if scopes.self_param.is_some() {
172 acc.push(CompletionItem {
173 label: "self".to_string(),
174 lookup: None,
175 snippet: None,
176 })
177 }
178}
179
180fn complete_path(
181 acc: &mut Vec<CompletionItem>,
182 db: &RootDatabase,
183 file_id: FileId,
184 crate_path: Vec<ast::NameRef>,
185) -> Cancelable<()> {
186 let source_root_id = db.file_source_root(file_id);
187 let module_tree = db.module_tree(source_root_id)?;
188 let module_id = match module_tree.any_module_for_source(ModuleSource::SourceFile(file_id)) {
189 None => return Ok(()),
190 Some(it) => it,
191 };
192 let target_module_id = match find_target_module(&module_tree, module_id, crate_path) {
193 None => return Ok(()),
194 Some(it) => it,
195 };
196 let module_scope = db.module_scope(source_root_id, target_module_id)?;
197 let completions = module_scope.entries().iter().map(|entry| CompletionItem {
198 label: entry.name().to_string(),
199 lookup: None,
200 snippet: None,
201 });
202 acc.extend(completions);
203 Ok(())
204}
205
206fn find_target_module(
207 module_tree: &ModuleTree,
208 module_id: ModuleId,
209 mut crate_path: Vec<ast::NameRef>,
210) -> Option<ModuleId> {
211 crate_path.pop();
212 let mut target_module = module_id.root(&module_tree);
213 for name in crate_path {
214 target_module = target_module.child(module_tree, name.text().as_str())?;
215 }
216 Some(target_module)
217}
218
219fn complete_mod_item_snippets(acc: &mut Vec<CompletionItem>) {
220 acc.push(CompletionItem {
221 label: "tfn".to_string(),
222 lookup: None,
223 snippet: Some("#[test]\nfn $1() {\n $0\n}".to_string()),
224 });
225 acc.push(CompletionItem {
226 label: "pub(crate)".to_string(),
227 lookup: None,
228 snippet: Some("pub(crate) $0".to_string()),
229 })
230}
231
232fn complete_expr_keywords(
233 file: &SourceFileNode,
234 fn_def: ast::FnDef,
235 name_ref: ast::NameRef,
236 acc: &mut Vec<CompletionItem>,
237) {
238 acc.push(keyword("if", "if $0 {}"));
239 acc.push(keyword("match", "match $0 {}"));
240 acc.push(keyword("while", "while $0 {}"));
241 acc.push(keyword("loop", "loop {$0}"));
242
243 if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
244 if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) {
245 if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
246 acc.push(keyword("else", "else {$0}"));
247 acc.push(keyword("else if", "else if $0 {}"));
248 }
249 }
250 }
251 if is_in_loop_body(name_ref) {
252 acc.push(keyword("continue", "continue"));
253 acc.push(keyword("break", "break"));
254 }
255 acc.extend(complete_return(fn_def, name_ref));
256}
257
258fn is_in_loop_body(name_ref: ast::NameRef) -> bool {
259 for node in name_ref.syntax().ancestors() {
260 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
261 break;
262 }
263 let loop_body = visitor()
264 .visit::<ast::ForExpr, _>(LoopBodyOwner::loop_body)
265 .visit::<ast::WhileExpr, _>(LoopBodyOwner::loop_body)
266 .visit::<ast::LoopExpr, _>(LoopBodyOwner::loop_body)
267 .accept(node);
268 if let Some(Some(body)) = loop_body {
269 if name_ref
270 .syntax()
271 .range()
272 .is_subrange(&body.syntax().range())
273 {
274 return true;
275 }
276 }
277 }
278 false
279}
280
281fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option<CompletionItem> {
282 // let is_last_in_block = name_ref.syntax().ancestors().filter_map(ast::Expr::cast)
283 // .next()
284 // .and_then(|it| it.syntax().parent())
285 // .and_then(ast::Block::cast)
286 // .is_some();
287
288 // if is_last_in_block {
289 // return None;
290 // }
291
292 let is_stmt = match name_ref
293 .syntax()
294 .ancestors()
295 .filter_map(ast::ExprStmt::cast)
296 .next()
297 {
298 None => false,
299 Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(),
300 };
301 let snip = match (is_stmt, fn_def.ret_type().is_some()) {
302 (true, true) => "return $0;",
303 (true, false) => "return;",
304 (false, true) => "return $0",
305 (false, false) => "return",
306 };
307 Some(keyword("return", snip))
308}
309
310fn keyword(kw: &str, snip: &str) -> CompletionItem {
311 CompletionItem {
312 label: kw.to_string(),
313 lookup: None,
314 snippet: Some(snip.to_string()),
315 }
316}
317
318fn complete_expr_snippets(acc: &mut Vec<CompletionItem>) {
319 acc.push(CompletionItem {
320 label: "pd".to_string(),
321 lookup: None,
322 snippet: Some("eprintln!(\"$0 = {:?}\", $0);".to_string()),
323 });
324 acc.push(CompletionItem {
325 label: "ppd".to_string(),
326 lookup: None,
327 snippet: Some("eprintln!(\"$0 = {:#?}\", $0);".to_string()),
328 });
329}
330
331