diff options
author | Florian Diebold <[email protected]> | 2020-03-07 14:27:03 +0000 |
---|---|---|
committer | Florian Diebold <[email protected]> | 2020-03-07 14:48:06 +0000 |
commit | 24e98121d81b75bafcd9c6005548776c00de8401 (patch) | |
tree | 976841b2501ab4501613a5f66b1004cd67f7e369 /crates/ra_ide/src/completion/completion_context.rs | |
parent | aff82cf7ac172f213cb5dcca637cb2c5332294c1 (diff) |
Try to complete within macros
Diffstat (limited to 'crates/ra_ide/src/completion/completion_context.rs')
-rw-r--r-- | crates/ra_ide/src/completion/completion_context.rs | 87 |
1 files changed, 62 insertions, 25 deletions
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index 9aa5a705d..81a033fcb 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs | |||
@@ -5,7 +5,7 @@ use ra_db::SourceDatabase; | |||
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{ | 6 | use ra_syntax::{ |
7 | algo::{find_covering_element, find_node_at_offset}, | 7 | algo::{find_covering_element, find_node_at_offset}, |
8 | ast, AstNode, SourceFile, | 8 | ast, AstNode, |
9 | SyntaxKind::*, | 9 | SyntaxKind::*, |
10 | SyntaxNode, SyntaxToken, TextRange, TextUnit, | 10 | SyntaxNode, SyntaxToken, TextRange, TextUnit, |
11 | }; | 11 | }; |
@@ -20,6 +20,9 @@ pub(crate) struct CompletionContext<'a> { | |||
20 | pub(super) sema: Semantics<'a, RootDatabase>, | 20 | pub(super) sema: Semantics<'a, RootDatabase>, |
21 | pub(super) db: &'a RootDatabase, | 21 | pub(super) db: &'a RootDatabase, |
22 | pub(super) offset: TextUnit, | 22 | pub(super) offset: TextUnit, |
23 | /// The token before the cursor, in the original file. | ||
24 | pub(super) original_token: SyntaxToken, | ||
25 | /// The token before the cursor, in the macro-expanded file. | ||
23 | pub(super) token: SyntaxToken, | 26 | pub(super) token: SyntaxToken, |
24 | pub(super) module: Option<hir::Module>, | 27 | pub(super) module: Option<hir::Module>, |
25 | pub(super) name_ref_syntax: Option<ast::NameRef>, | 28 | pub(super) name_ref_syntax: Option<ast::NameRef>, |
@@ -67,12 +70,18 @@ impl<'a> CompletionContext<'a> { | |||
67 | let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string()); | 70 | let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string()); |
68 | parse.reparse(&edit).tree() | 71 | parse.reparse(&edit).tree() |
69 | }; | 72 | }; |
73 | let fake_ident_token = | ||
74 | file_with_fake_ident.syntax().token_at_offset(position.offset).right_biased().unwrap(); | ||
70 | 75 | ||
76 | // TODO: shouldn't this take the position into account? (in case we're inside a mod {}) | ||
71 | let module = sema.to_module_def(position.file_id); | 77 | let module = sema.to_module_def(position.file_id); |
72 | let token = original_file.syntax().token_at_offset(position.offset).left_biased()?; | 78 | let original_token = |
79 | original_file.syntax().token_at_offset(position.offset).left_biased()?; | ||
80 | let token = sema.descend_into_macros(original_token.clone()); | ||
73 | let mut ctx = CompletionContext { | 81 | let mut ctx = CompletionContext { |
74 | sema, | 82 | sema, |
75 | db, | 83 | db, |
84 | original_token, | ||
76 | token, | 85 | token, |
77 | offset: position.offset, | 86 | offset: position.offset, |
78 | module, | 87 | module, |
@@ -95,15 +104,45 @@ impl<'a> CompletionContext<'a> { | |||
95 | has_type_args: false, | 104 | has_type_args: false, |
96 | dot_receiver_is_ambiguous_float_literal: false, | 105 | dot_receiver_is_ambiguous_float_literal: false, |
97 | }; | 106 | }; |
98 | ctx.fill(&original_file, file_with_fake_ident, position.offset); | 107 | |
108 | let mut original_file = original_file.syntax().clone(); | ||
109 | let mut hypothetical_file = file_with_fake_ident.syntax().clone(); | ||
110 | let mut offset = position.offset; | ||
111 | let mut fake_ident_token = fake_ident_token; | ||
112 | |||
113 | // Are we inside a macro call? | ||
114 | while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = ( | ||
115 | find_node_at_offset::<ast::MacroCall>(&original_file, offset), | ||
116 | find_node_at_offset::<ast::MacroCall>(&hypothetical_file, offset), | ||
117 | ) { | ||
118 | if let (Some(actual_expansion), Some(hypothetical_expansion)) = ( | ||
119 | ctx.sema.expand(&actual_macro_call), | ||
120 | ctx.sema.expand_hypothetical( | ||
121 | &actual_macro_call, | ||
122 | ¯o_call_with_fake_ident, | ||
123 | fake_ident_token, | ||
124 | ), | ||
125 | ) { | ||
126 | // TODO check that the expansions 'look the same' up to the inserted token? | ||
127 | original_file = actual_expansion; | ||
128 | hypothetical_file = hypothetical_expansion.0; | ||
129 | fake_ident_token = hypothetical_expansion.1; | ||
130 | offset = fake_ident_token.text_range().start(); | ||
131 | } else { | ||
132 | break; | ||
133 | } | ||
134 | } | ||
135 | |||
136 | ctx.fill(&original_file, hypothetical_file, offset); | ||
99 | Some(ctx) | 137 | Some(ctx) |
100 | } | 138 | } |
101 | 139 | ||
102 | // The range of the identifier that is being completed. | 140 | // The range of the identifier that is being completed. |
103 | pub(crate) fn source_range(&self) -> TextRange { | 141 | pub(crate) fn source_range(&self) -> TextRange { |
142 | // check kind of macro-expanded token, but use range of original token | ||
104 | match self.token.kind() { | 143 | match self.token.kind() { |
105 | // workaroud when completion is triggered by trigger characters. | 144 | // workaroud when completion is triggered by trigger characters. |
106 | IDENT => self.token.text_range(), | 145 | IDENT => self.original_token.text_range(), |
107 | _ => TextRange::offset_len(self.offset, 0.into()), | 146 | _ => TextRange::offset_len(self.offset, 0.into()), |
108 | } | 147 | } |
109 | } | 148 | } |
@@ -114,14 +153,12 @@ impl<'a> CompletionContext<'a> { | |||
114 | 153 | ||
115 | fn fill( | 154 | fn fill( |
116 | &mut self, | 155 | &mut self, |
117 | original_file: &ast::SourceFile, | 156 | original_file: &SyntaxNode, |
118 | file_with_fake_ident: ast::SourceFile, | 157 | file_with_fake_ident: SyntaxNode, |
119 | offset: TextUnit, | 158 | offset: TextUnit, |
120 | ) { | 159 | ) { |
121 | // First, let's try to complete a reference to some declaration. | 160 | // First, let's try to complete a reference to some declaration. |
122 | if let Some(name_ref) = | 161 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { |
123 | find_node_at_offset::<ast::NameRef>(file_with_fake_ident.syntax(), offset) | ||
124 | { | ||
125 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. | 162 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. |
126 | // See RFC#1685. | 163 | // See RFC#1685. |
127 | if is_node::<ast::Param>(name_ref.syntax()) { | 164 | if is_node::<ast::Param>(name_ref.syntax()) { |
@@ -133,8 +170,7 @@ impl<'a> CompletionContext<'a> { | |||
133 | 170 | ||
134 | // Otherwise, see if this is a declaration. We can use heuristics to | 171 | // Otherwise, see if this is a declaration. We can use heuristics to |
135 | // suggest declaration names, see `CompletionKind::Magic`. | 172 | // suggest declaration names, see `CompletionKind::Magic`. |
136 | if let Some(name) = find_node_at_offset::<ast::Name>(file_with_fake_ident.syntax(), offset) | 173 | if let Some(name) = find_node_at_offset::<ast::Name>(&file_with_fake_ident, offset) { |
137 | { | ||
138 | if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::BindPat::cast) { | 174 | if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::BindPat::cast) { |
139 | let parent = bind_pat.syntax().parent(); | 175 | let parent = bind_pat.syntax().parent(); |
140 | if parent.clone().and_then(ast::MatchArm::cast).is_some() | 176 | if parent.clone().and_then(ast::MatchArm::cast).is_some() |
@@ -148,23 +184,24 @@ impl<'a> CompletionContext<'a> { | |||
148 | return; | 184 | return; |
149 | } | 185 | } |
150 | if name.syntax().ancestors().find_map(ast::RecordFieldPatList::cast).is_some() { | 186 | if name.syntax().ancestors().find_map(ast::RecordFieldPatList::cast).is_some() { |
151 | self.record_lit_pat = find_node_at_offset(original_file.syntax(), self.offset); | 187 | self.record_lit_pat = |
188 | self.sema.find_node_at_offset_with_macros(&original_file, self.offset); | ||
152 | } | 189 | } |
153 | } | 190 | } |
154 | } | 191 | } |
155 | 192 | ||
156 | fn classify_name_ref(&mut self, original_file: &SourceFile, name_ref: ast::NameRef) { | 193 | fn classify_name_ref(&mut self, original_file: &SyntaxNode, name_ref: ast::NameRef) { |
157 | self.name_ref_syntax = | 194 | self.name_ref_syntax = |
158 | find_node_at_offset(original_file.syntax(), name_ref.syntax().text_range().start()); | 195 | find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); |
159 | let name_range = name_ref.syntax().text_range(); | 196 | let name_range = name_ref.syntax().text_range(); |
160 | if name_ref.syntax().parent().and_then(ast::RecordField::cast).is_some() { | 197 | if name_ref.syntax().parent().and_then(ast::RecordField::cast).is_some() { |
161 | self.record_lit_syntax = find_node_at_offset(original_file.syntax(), self.offset); | 198 | self.record_lit_syntax = |
199 | self.sema.find_node_at_offset_with_macros(&original_file, self.offset); | ||
162 | } | 200 | } |
163 | 201 | ||
164 | self.impl_def = self | 202 | self.impl_def = self |
165 | .token | 203 | .sema |
166 | .parent() | 204 | .ancestors_with_macros(self.token.parent()) |
167 | .ancestors() | ||
168 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) | 205 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) |
169 | .find_map(ast::ImplDef::cast); | 206 | .find_map(ast::ImplDef::cast); |
170 | 207 | ||
@@ -183,12 +220,12 @@ impl<'a> CompletionContext<'a> { | |||
183 | _ => (), | 220 | _ => (), |
184 | } | 221 | } |
185 | 222 | ||
186 | self.use_item_syntax = self.token.parent().ancestors().find_map(ast::UseItem::cast); | 223 | self.use_item_syntax = |
224 | self.sema.ancestors_with_macros(self.token.parent()).find_map(ast::UseItem::cast); | ||
187 | 225 | ||
188 | self.function_syntax = self | 226 | self.function_syntax = self |
189 | .token | 227 | .sema |
190 | .parent() | 228 | .ancestors_with_macros(self.token.parent()) |
191 | .ancestors() | ||
192 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) | 229 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) |
193 | .find_map(ast::FnDef::cast); | 230 | .find_map(ast::FnDef::cast); |
194 | 231 | ||
@@ -242,7 +279,7 @@ impl<'a> CompletionContext<'a> { | |||
242 | 279 | ||
243 | if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) { | 280 | if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) { |
244 | if let Some(if_expr) = | 281 | if let Some(if_expr) = |
245 | find_node_at_offset::<ast::IfExpr>(original_file.syntax(), off) | 282 | self.sema.find_node_at_offset_with_macros::<ast::IfExpr>(original_file, off) |
246 | { | 283 | { |
247 | if if_expr.syntax().text_range().end() | 284 | if if_expr.syntax().text_range().end() |
248 | < name_ref.syntax().text_range().start() | 285 | < name_ref.syntax().text_range().start() |
@@ -259,7 +296,7 @@ impl<'a> CompletionContext<'a> { | |||
259 | self.dot_receiver = field_expr | 296 | self.dot_receiver = field_expr |
260 | .expr() | 297 | .expr() |
261 | .map(|e| e.syntax().text_range()) | 298 | .map(|e| e.syntax().text_range()) |
262 | .and_then(|r| find_node_with_range(original_file.syntax(), r)); | 299 | .and_then(|r| find_node_with_range(original_file, r)); |
263 | self.dot_receiver_is_ambiguous_float_literal = | 300 | self.dot_receiver_is_ambiguous_float_literal = |
264 | if let Some(ast::Expr::Literal(l)) = &self.dot_receiver { | 301 | if let Some(ast::Expr::Literal(l)) = &self.dot_receiver { |
265 | match l.kind() { | 302 | match l.kind() { |
@@ -275,7 +312,7 @@ impl<'a> CompletionContext<'a> { | |||
275 | self.dot_receiver = method_call_expr | 312 | self.dot_receiver = method_call_expr |
276 | .expr() | 313 | .expr() |
277 | .map(|e| e.syntax().text_range()) | 314 | .map(|e| e.syntax().text_range()) |
278 | .and_then(|r| find_node_with_range(original_file.syntax(), r)); | 315 | .and_then(|r| find_node_with_range(original_file, r)); |
279 | self.is_call = true; | 316 | self.is_call = true; |
280 | } | 317 | } |
281 | } | 318 | } |