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