diff options
Diffstat (limited to 'crates/ra_analysis/src/completion/completion_context.rs')
-rw-r--r-- | crates/ra_analysis/src/completion/completion_context.rs | 54 |
1 files changed, 44 insertions, 10 deletions
diff --git a/crates/ra_analysis/src/completion/completion_context.rs b/crates/ra_analysis/src/completion/completion_context.rs index 064fbc6f7..978772fd4 100644 --- a/crates/ra_analysis/src/completion/completion_context.rs +++ b/crates/ra_analysis/src/completion/completion_context.rs | |||
@@ -1,12 +1,13 @@ | |||
1 | use ra_editor::find_node_at_offset; | 1 | use ra_editor::find_node_at_offset; |
2 | use ra_text_edit::AtomTextEdit; | 2 | use ra_text_edit::AtomTextEdit; |
3 | use ra_syntax::{ | 3 | use ra_syntax::{ |
4 | algo::find_leaf_at_offset, | 4 | algo::{find_leaf_at_offset, find_covering_node}, |
5 | ast, | 5 | ast, |
6 | AstNode, | 6 | AstNode, |
7 | SyntaxNodeRef, | 7 | SyntaxNodeRef, |
8 | SourceFileNode, | 8 | SourceFileNode, |
9 | TextUnit, | 9 | TextUnit, |
10 | TextRange, | ||
10 | SyntaxKind::*, | 11 | SyntaxKind::*, |
11 | }; | 12 | }; |
12 | use hir::source_binder; | 13 | use hir::source_binder; |
@@ -31,6 +32,10 @@ pub(super) struct CompletionContext<'a> { | |||
31 | pub(super) is_stmt: bool, | 32 | pub(super) is_stmt: bool, |
32 | /// Something is typed at the "top" level, in module or impl/trait. | 33 | /// Something is typed at the "top" level, in module or impl/trait. |
33 | pub(super) is_new_item: bool, | 34 | pub(super) is_new_item: bool, |
35 | /// The receiver if this is a field or method access, i.e. writing something.<|> | ||
36 | pub(super) dot_receiver: Option<ast::Expr<'a>>, | ||
37 | /// If this is a method call in particular, i.e. the () are already there. | ||
38 | pub(super) is_method_call: bool, | ||
34 | } | 39 | } |
35 | 40 | ||
36 | impl<'a> CompletionContext<'a> { | 41 | impl<'a> CompletionContext<'a> { |
@@ -54,12 +59,14 @@ impl<'a> CompletionContext<'a> { | |||
54 | after_if: false, | 59 | after_if: false, |
55 | is_stmt: false, | 60 | is_stmt: false, |
56 | is_new_item: false, | 61 | is_new_item: false, |
62 | dot_receiver: None, | ||
63 | is_method_call: false, | ||
57 | }; | 64 | }; |
58 | ctx.fill(original_file, position.offset); | 65 | ctx.fill(original_file, position.offset); |
59 | Ok(Some(ctx)) | 66 | Ok(Some(ctx)) |
60 | } | 67 | } |
61 | 68 | ||
62 | fn fill(&mut self, original_file: &SourceFileNode, offset: TextUnit) { | 69 | fn fill(&mut self, original_file: &'a SourceFileNode, offset: TextUnit) { |
63 | // Insert a fake ident to get a valid parse tree. We will use this file | 70 | // Insert a fake ident to get a valid parse tree. We will use this file |
64 | // to determine context, though the original_file will be used for | 71 | // to determine context, though the original_file will be used for |
65 | // actual completion. | 72 | // actual completion. |
@@ -76,7 +83,7 @@ impl<'a> CompletionContext<'a> { | |||
76 | self.is_param = true; | 83 | self.is_param = true; |
77 | return; | 84 | return; |
78 | } | 85 | } |
79 | self.classify_name_ref(&file, name_ref); | 86 | self.classify_name_ref(original_file, name_ref); |
80 | } | 87 | } |
81 | 88 | ||
82 | // Otherwise, see if this is a declaration. We can use heuristics to | 89 | // Otherwise, see if this is a declaration. We can use heuristics to |
@@ -88,7 +95,7 @@ impl<'a> CompletionContext<'a> { | |||
88 | } | 95 | } |
89 | } | 96 | } |
90 | } | 97 | } |
91 | fn classify_name_ref(&mut self, file: &SourceFileNode, name_ref: ast::NameRef) { | 98 | fn classify_name_ref(&mut self, original_file: &'a SourceFileNode, name_ref: ast::NameRef) { |
92 | let name_range = name_ref.syntax().range(); | 99 | let name_range = name_ref.syntax().range(); |
93 | let top_node = name_ref | 100 | let top_node = name_ref |
94 | .syntax() | 101 | .syntax() |
@@ -105,6 +112,12 @@ impl<'a> CompletionContext<'a> { | |||
105 | _ => (), | 112 | _ => (), |
106 | } | 113 | } |
107 | 114 | ||
115 | self.enclosing_fn = self | ||
116 | .leaf | ||
117 | .ancestors() | ||
118 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) | ||
119 | .find_map(ast::FnDef::cast); | ||
120 | |||
108 | let parent = match name_ref.syntax().parent() { | 121 | let parent = match name_ref.syntax().parent() { |
109 | Some(it) => it, | 122 | Some(it) => it, |
110 | None => return, | 123 | None => return, |
@@ -120,11 +133,6 @@ impl<'a> CompletionContext<'a> { | |||
120 | } | 133 | } |
121 | if path.qualifier().is_none() { | 134 | if path.qualifier().is_none() { |
122 | self.is_trivial_path = true; | 135 | self.is_trivial_path = true; |
123 | self.enclosing_fn = self | ||
124 | .leaf | ||
125 | .ancestors() | ||
126 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) | ||
127 | .find_map(ast::FnDef::cast); | ||
128 | 136 | ||
129 | self.is_stmt = match name_ref | 137 | self.is_stmt = match name_ref |
130 | .syntax() | 138 | .syntax() |
@@ -137,7 +145,9 @@ impl<'a> CompletionContext<'a> { | |||
137 | }; | 145 | }; |
138 | 146 | ||
139 | if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) { | 147 | if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) { |
140 | if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) { | 148 | if let Some(if_expr) = |
149 | find_node_at_offset::<ast::IfExpr>(original_file.syntax(), off) | ||
150 | { | ||
141 | if if_expr.syntax().range().end() < name_ref.syntax().range().start() { | 151 | if if_expr.syntax().range().end() < name_ref.syntax().range().start() { |
142 | self.after_if = true; | 152 | self.after_if = true; |
143 | } | 153 | } |
@@ -145,9 +155,33 @@ impl<'a> CompletionContext<'a> { | |||
145 | } | 155 | } |
146 | } | 156 | } |
147 | } | 157 | } |
158 | if let Some(field_expr) = ast::FieldExpr::cast(parent) { | ||
159 | // The receiver comes before the point of insertion of the fake | ||
160 | // ident, so it should have the same range in the non-modified file | ||
161 | self.dot_receiver = field_expr | ||
162 | .expr() | ||
163 | .map(|e| e.syntax().range()) | ||
164 | .and_then(|r| find_node_with_range(original_file.syntax(), r)); | ||
165 | } | ||
166 | if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) { | ||
167 | // As above | ||
168 | self.dot_receiver = method_call_expr | ||
169 | .expr() | ||
170 | .map(|e| e.syntax().range()) | ||
171 | .and_then(|r| find_node_with_range(original_file.syntax(), r)); | ||
172 | self.is_method_call = true; | ||
173 | } | ||
148 | } | 174 | } |
149 | } | 175 | } |
150 | 176 | ||
177 | fn find_node_with_range<'a, N: AstNode<'a>>( | ||
178 | syntax: SyntaxNodeRef<'a>, | ||
179 | range: TextRange, | ||
180 | ) -> Option<N> { | ||
181 | let node = find_covering_node(syntax, range); | ||
182 | node.ancestors().find_map(N::cast) | ||
183 | } | ||
184 | |||
151 | fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool { | 185 | fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool { |
152 | match node.ancestors().filter_map(N::cast).next() { | 186 | match node.ancestors().filter_map(N::cast).next() { |
153 | None => false, | 187 | None => false, |