diff options
Diffstat (limited to 'crates/ra_analysis/src/completion/completion_context.rs')
-rw-r--r-- | crates/ra_analysis/src/completion/completion_context.rs | 205 |
1 files changed, 0 insertions, 205 deletions
diff --git a/crates/ra_analysis/src/completion/completion_context.rs b/crates/ra_analysis/src/completion/completion_context.rs deleted file mode 100644 index 01786bb69..000000000 --- a/crates/ra_analysis/src/completion/completion_context.rs +++ /dev/null | |||
@@ -1,205 +0,0 @@ | |||
1 | use ra_text_edit::AtomTextEdit; | ||
2 | use ra_syntax::{ | ||
3 | AstNode, SyntaxNode, SourceFile, TextUnit, TextRange, | ||
4 | ast, | ||
5 | algo::{find_leaf_at_offset, find_covering_node, find_node_at_offset}, | ||
6 | SyntaxKind::*, | ||
7 | }; | ||
8 | use hir::source_binder; | ||
9 | |||
10 | use crate::{db, FilePosition, Cancelable}; | ||
11 | |||
12 | /// `CompletionContext` is created early during completion to figure out, where | ||
13 | /// exactly is the cursor, syntax-wise. | ||
14 | #[derive(Debug)] | ||
15 | pub(super) struct CompletionContext<'a> { | ||
16 | pub(super) db: &'a db::RootDatabase, | ||
17 | pub(super) offset: TextUnit, | ||
18 | pub(super) leaf: &'a SyntaxNode, | ||
19 | pub(super) module: Option<hir::Module>, | ||
20 | pub(super) function: Option<hir::Function>, | ||
21 | pub(super) function_syntax: Option<&'a ast::FnDef>, | ||
22 | pub(super) use_item_syntax: Option<&'a ast::UseItem>, | ||
23 | pub(super) is_param: bool, | ||
24 | /// A single-indent path, like `foo`. | ||
25 | pub(super) is_trivial_path: bool, | ||
26 | /// If not a trivial, path, the prefix (qualifier). | ||
27 | pub(super) path_prefix: Option<hir::Path>, | ||
28 | pub(super) after_if: bool, | ||
29 | /// `true` if we are a statement or a last expr in the block. | ||
30 | pub(super) can_be_stmt: bool, | ||
31 | /// Something is typed at the "top" level, in module or impl/trait. | ||
32 | pub(super) is_new_item: bool, | ||
33 | /// The receiver if this is a field or method access, i.e. writing something.<|> | ||
34 | pub(super) dot_receiver: Option<&'a ast::Expr>, | ||
35 | /// If this is a method call in particular, i.e. the () are already there. | ||
36 | pub(super) is_method_call: bool, | ||
37 | } | ||
38 | |||
39 | impl<'a> CompletionContext<'a> { | ||
40 | pub(super) fn new( | ||
41 | db: &'a db::RootDatabase, | ||
42 | original_file: &'a SourceFile, | ||
43 | position: FilePosition, | ||
44 | ) -> Cancelable<Option<CompletionContext<'a>>> { | ||
45 | let module = source_binder::module_from_position(db, position)?; | ||
46 | let leaf = | ||
47 | ctry!(find_leaf_at_offset(original_file.syntax(), position.offset).left_biased()); | ||
48 | let mut ctx = CompletionContext { | ||
49 | db, | ||
50 | leaf, | ||
51 | offset: position.offset, | ||
52 | module, | ||
53 | function: None, | ||
54 | function_syntax: None, | ||
55 | use_item_syntax: None, | ||
56 | is_param: false, | ||
57 | is_trivial_path: false, | ||
58 | path_prefix: None, | ||
59 | after_if: false, | ||
60 | can_be_stmt: false, | ||
61 | is_new_item: false, | ||
62 | dot_receiver: None, | ||
63 | is_method_call: false, | ||
64 | }; | ||
65 | ctx.fill(original_file, position.offset); | ||
66 | Ok(Some(ctx)) | ||
67 | } | ||
68 | |||
69 | fn fill(&mut self, original_file: &'a SourceFile, offset: TextUnit) { | ||
70 | // Insert a fake ident to get a valid parse tree. We will use this file | ||
71 | // to determine context, though the original_file will be used for | ||
72 | // actual completion. | ||
73 | let file = { | ||
74 | let edit = AtomTextEdit::insert(offset, "intellijRulezz".to_string()); | ||
75 | original_file.reparse(&edit) | ||
76 | }; | ||
77 | |||
78 | // First, let's try to complete a reference to some declaration. | ||
79 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), offset) { | ||
80 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. | ||
81 | // See RFC#1685. | ||
82 | if is_node::<ast::Param>(name_ref.syntax()) { | ||
83 | self.is_param = true; | ||
84 | return; | ||
85 | } | ||
86 | self.classify_name_ref(original_file, name_ref); | ||
87 | } | ||
88 | |||
89 | // Otherwise, see if this is a declaration. We can use heuristics to | ||
90 | // suggest declaration names, see `CompletionKind::Magic`. | ||
91 | if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), offset) { | ||
92 | if is_node::<ast::Param>(name.syntax()) { | ||
93 | self.is_param = true; | ||
94 | return; | ||
95 | } | ||
96 | } | ||
97 | } | ||
98 | fn classify_name_ref(&mut self, original_file: &'a SourceFile, name_ref: &ast::NameRef) { | ||
99 | let name_range = name_ref.syntax().range(); | ||
100 | let top_node = name_ref | ||
101 | .syntax() | ||
102 | .ancestors() | ||
103 | .take_while(|it| it.range() == name_range) | ||
104 | .last() | ||
105 | .unwrap(); | ||
106 | |||
107 | match top_node.parent().map(|it| it.kind()) { | ||
108 | Some(SOURCE_FILE) | Some(ITEM_LIST) => { | ||
109 | self.is_new_item = true; | ||
110 | return; | ||
111 | } | ||
112 | _ => (), | ||
113 | } | ||
114 | |||
115 | self.use_item_syntax = self.leaf.ancestors().find_map(ast::UseItem::cast); | ||
116 | |||
117 | self.function_syntax = self | ||
118 | .leaf | ||
119 | .ancestors() | ||
120 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) | ||
121 | .find_map(ast::FnDef::cast); | ||
122 | match (&self.module, self.function_syntax) { | ||
123 | (Some(module), Some(fn_def)) => { | ||
124 | let function = source_binder::function_from_module(self.db, module, fn_def); | ||
125 | self.function = Some(function); | ||
126 | } | ||
127 | _ => (), | ||
128 | } | ||
129 | |||
130 | let parent = match name_ref.syntax().parent() { | ||
131 | Some(it) => it, | ||
132 | None => return, | ||
133 | }; | ||
134 | if let Some(segment) = ast::PathSegment::cast(parent) { | ||
135 | let path = segment.parent_path(); | ||
136 | if let Some(mut path) = hir::Path::from_ast(path) { | ||
137 | if !path.is_ident() { | ||
138 | path.segments.pop().unwrap(); | ||
139 | self.path_prefix = Some(path); | ||
140 | return; | ||
141 | } | ||
142 | } | ||
143 | if path.qualifier().is_none() { | ||
144 | self.is_trivial_path = true; | ||
145 | |||
146 | // Find either enclosing expr statement (thing with `;`) or a | ||
147 | // block. If block, check that we are the last expr. | ||
148 | self.can_be_stmt = name_ref | ||
149 | .syntax() | ||
150 | .ancestors() | ||
151 | .find_map(|node| { | ||
152 | if let Some(stmt) = ast::ExprStmt::cast(node) { | ||
153 | return Some(stmt.syntax().range() == name_ref.syntax().range()); | ||
154 | } | ||
155 | if let Some(block) = ast::Block::cast(node) { | ||
156 | return Some( | ||
157 | block.expr().map(|e| e.syntax().range()) | ||
158 | == Some(name_ref.syntax().range()), | ||
159 | ); | ||
160 | } | ||
161 | None | ||
162 | }) | ||
163 | .unwrap_or(false); | ||
164 | |||
165 | if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) { | ||
166 | if let Some(if_expr) = | ||
167 | find_node_at_offset::<ast::IfExpr>(original_file.syntax(), off) | ||
168 | { | ||
169 | if if_expr.syntax().range().end() < name_ref.syntax().range().start() { | ||
170 | self.after_if = true; | ||
171 | } | ||
172 | } | ||
173 | } | ||
174 | } | ||
175 | } | ||
176 | if let Some(field_expr) = ast::FieldExpr::cast(parent) { | ||
177 | // The receiver comes before the point of insertion of the fake | ||
178 | // ident, so it should have the same range in the non-modified file | ||
179 | self.dot_receiver = field_expr | ||
180 | .expr() | ||
181 | .map(|e| e.syntax().range()) | ||
182 | .and_then(|r| find_node_with_range(original_file.syntax(), r)); | ||
183 | } | ||
184 | if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) { | ||
185 | // As above | ||
186 | self.dot_receiver = method_call_expr | ||
187 | .expr() | ||
188 | .map(|e| e.syntax().range()) | ||
189 | .and_then(|r| find_node_with_range(original_file.syntax(), r)); | ||
190 | self.is_method_call = true; | ||
191 | } | ||
192 | } | ||
193 | } | ||
194 | |||
195 | fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<&N> { | ||
196 | let node = find_covering_node(syntax, range); | ||
197 | node.ancestors().find_map(N::cast) | ||
198 | } | ||
199 | |||
200 | fn is_node<N: AstNode>(node: &SyntaxNode) -> bool { | ||
201 | match node.ancestors().filter_map(N::cast).next() { | ||
202 | None => false, | ||
203 | Some(n) => n.syntax().range() == node.range(), | ||
204 | } | ||
205 | } | ||