aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/completion/completion_context.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/completion/completion_context.rs')
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs111
1 files changed, 82 insertions, 29 deletions
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs
index 9aa5a705d..40535c09e 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;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use 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,8 +20,11 @@ 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) krate: Option<hir::Crate>,
25 pub(super) name_ref_syntax: Option<ast::NameRef>, 28 pub(super) name_ref_syntax: Option<ast::NameRef>,
26 pub(super) function_syntax: Option<ast::FnDef>, 29 pub(super) function_syntax: Option<ast::FnDef>,
27 pub(super) use_item_syntax: Option<ast::UseItem>, 30 pub(super) use_item_syntax: Option<ast::UseItem>,
@@ -67,15 +70,20 @@ 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
71 let module = sema.to_module_def(position.file_id); 76 let krate = sema.to_module_def(position.file_id).map(|m| m.krate());
72 let token = original_file.syntax().token_at_offset(position.offset).left_biased()?; 77 let original_token =
78 original_file.syntax().token_at_offset(position.offset).left_biased()?;
79 let token = sema.descend_into_macros(original_token.clone());
73 let mut ctx = CompletionContext { 80 let mut ctx = CompletionContext {
74 sema, 81 sema,
75 db, 82 db,
83 original_token,
76 token, 84 token,
77 offset: position.offset, 85 offset: position.offset,
78 module, 86 krate,
79 name_ref_syntax: None, 87 name_ref_syntax: None,
80 function_syntax: None, 88 function_syntax: None,
81 use_item_syntax: None, 89 use_item_syntax: None,
@@ -95,15 +103,57 @@ impl<'a> CompletionContext<'a> {
95 has_type_args: false, 103 has_type_args: false,
96 dot_receiver_is_ambiguous_float_literal: false, 104 dot_receiver_is_ambiguous_float_literal: false,
97 }; 105 };
98 ctx.fill(&original_file, file_with_fake_ident, position.offset); 106
107 let mut original_file = original_file.syntax().clone();
108 let mut hypothetical_file = file_with_fake_ident.syntax().clone();
109 let mut offset = position.offset;
110 let mut fake_ident_token = fake_ident_token;
111
112 // Are we inside a macro call?
113 while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
114 find_node_at_offset::<ast::MacroCall>(&original_file, offset),
115 find_node_at_offset::<ast::MacroCall>(&hypothetical_file, offset),
116 ) {
117 if actual_macro_call.path().as_ref().map(|s| s.syntax().text())
118 != macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text())
119 {
120 break;
121 }
122 let hypothetical_args = match macro_call_with_fake_ident.token_tree() {
123 Some(tt) => tt,
124 None => break,
125 };
126 if let (Some(actual_expansion), Some(hypothetical_expansion)) = (
127 ctx.sema.expand(&actual_macro_call),
128 ctx.sema.expand_hypothetical(
129 &actual_macro_call,
130 &hypothetical_args,
131 fake_ident_token,
132 ),
133 ) {
134 let new_offset = hypothetical_expansion.1.text_range().start();
135 if new_offset >= actual_expansion.text_range().end() {
136 break;
137 }
138 original_file = actual_expansion;
139 hypothetical_file = hypothetical_expansion.0;
140 fake_ident_token = hypothetical_expansion.1;
141 offset = new_offset;
142 } else {
143 break;
144 }
145 }
146
147 ctx.fill(&original_file, hypothetical_file, offset);
99 Some(ctx) 148 Some(ctx)
100 } 149 }
101 150
102 // The range of the identifier that is being completed. 151 // The range of the identifier that is being completed.
103 pub(crate) fn source_range(&self) -> TextRange { 152 pub(crate) fn source_range(&self) -> TextRange {
153 // check kind of macro-expanded token, but use range of original token
104 match self.token.kind() { 154 match self.token.kind() {
105 // workaroud when completion is triggered by trigger characters. 155 // workaroud when completion is triggered by trigger characters.
106 IDENT => self.token.text_range(), 156 IDENT => self.original_token.text_range(),
107 _ => TextRange::offset_len(self.offset, 0.into()), 157 _ => TextRange::offset_len(self.offset, 0.into()),
108 } 158 }
109 } 159 }
@@ -114,27 +164,24 @@ impl<'a> CompletionContext<'a> {
114 164
115 fn fill( 165 fn fill(
116 &mut self, 166 &mut self,
117 original_file: &ast::SourceFile, 167 original_file: &SyntaxNode,
118 file_with_fake_ident: ast::SourceFile, 168 file_with_fake_ident: SyntaxNode,
119 offset: TextUnit, 169 offset: TextUnit,
120 ) { 170 ) {
121 // First, let's try to complete a reference to some declaration. 171 // First, let's try to complete a reference to some declaration.
122 if let Some(name_ref) = 172 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) {} }`. 173 // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
126 // See RFC#1685. 174 // See RFC#1685.
127 if is_node::<ast::Param>(name_ref.syntax()) { 175 if is_node::<ast::Param>(name_ref.syntax()) {
128 self.is_param = true; 176 self.is_param = true;
129 return; 177 return;
130 } 178 }
131 self.classify_name_ref(original_file, name_ref); 179 self.classify_name_ref(original_file, name_ref, offset);
132 } 180 }
133 181
134 // Otherwise, see if this is a declaration. We can use heuristics to 182 // Otherwise, see if this is a declaration. We can use heuristics to
135 // suggest declaration names, see `CompletionKind::Magic`. 183 // suggest declaration names, see `CompletionKind::Magic`.
136 if let Some(name) = find_node_at_offset::<ast::Name>(file_with_fake_ident.syntax(), offset) 184 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) { 185 if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::BindPat::cast) {
139 let parent = bind_pat.syntax().parent(); 186 let parent = bind_pat.syntax().parent();
140 if parent.clone().and_then(ast::MatchArm::cast).is_some() 187 if parent.clone().and_then(ast::MatchArm::cast).is_some()
@@ -148,23 +195,29 @@ impl<'a> CompletionContext<'a> {
148 return; 195 return;
149 } 196 }
150 if name.syntax().ancestors().find_map(ast::RecordFieldPatList::cast).is_some() { 197 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); 198 self.record_lit_pat =
199 self.sema.find_node_at_offset_with_macros(&original_file, offset);
152 } 200 }
153 } 201 }
154 } 202 }
155 203
156 fn classify_name_ref(&mut self, original_file: &SourceFile, name_ref: ast::NameRef) { 204 fn classify_name_ref(
205 &mut self,
206 original_file: &SyntaxNode,
207 name_ref: ast::NameRef,
208 offset: TextUnit,
209 ) {
157 self.name_ref_syntax = 210 self.name_ref_syntax =
158 find_node_at_offset(original_file.syntax(), name_ref.syntax().text_range().start()); 211 find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
159 let name_range = name_ref.syntax().text_range(); 212 let name_range = name_ref.syntax().text_range();
160 if name_ref.syntax().parent().and_then(ast::RecordField::cast).is_some() { 213 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); 214 self.record_lit_syntax =
215 self.sema.find_node_at_offset_with_macros(&original_file, offset);
162 } 216 }
163 217
164 self.impl_def = self 218 self.impl_def = self
165 .token 219 .sema
166 .parent() 220 .ancestors_with_macros(self.token.parent())
167 .ancestors()
168 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) 221 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
169 .find_map(ast::ImplDef::cast); 222 .find_map(ast::ImplDef::cast);
170 223
@@ -183,12 +236,12 @@ impl<'a> CompletionContext<'a> {
183 _ => (), 236 _ => (),
184 } 237 }
185 238
186 self.use_item_syntax = self.token.parent().ancestors().find_map(ast::UseItem::cast); 239 self.use_item_syntax =
240 self.sema.ancestors_with_macros(self.token.parent()).find_map(ast::UseItem::cast);
187 241
188 self.function_syntax = self 242 self.function_syntax = self
189 .token 243 .sema
190 .parent() 244 .ancestors_with_macros(self.token.parent())
191 .ancestors()
192 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) 245 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
193 .find_map(ast::FnDef::cast); 246 .find_map(ast::FnDef::cast);
194 247
@@ -242,7 +295,7 @@ impl<'a> CompletionContext<'a> {
242 295
243 if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) { 296 if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) {
244 if let Some(if_expr) = 297 if let Some(if_expr) =
245 find_node_at_offset::<ast::IfExpr>(original_file.syntax(), off) 298 self.sema.find_node_at_offset_with_macros::<ast::IfExpr>(original_file, off)
246 { 299 {
247 if if_expr.syntax().text_range().end() 300 if if_expr.syntax().text_range().end()
248 < name_ref.syntax().text_range().start() 301 < name_ref.syntax().text_range().start()
@@ -259,7 +312,7 @@ impl<'a> CompletionContext<'a> {
259 self.dot_receiver = field_expr 312 self.dot_receiver = field_expr
260 .expr() 313 .expr()
261 .map(|e| e.syntax().text_range()) 314 .map(|e| e.syntax().text_range())
262 .and_then(|r| find_node_with_range(original_file.syntax(), r)); 315 .and_then(|r| find_node_with_range(original_file, r));
263 self.dot_receiver_is_ambiguous_float_literal = 316 self.dot_receiver_is_ambiguous_float_literal =
264 if let Some(ast::Expr::Literal(l)) = &self.dot_receiver { 317 if let Some(ast::Expr::Literal(l)) = &self.dot_receiver {
265 match l.kind() { 318 match l.kind() {
@@ -275,7 +328,7 @@ impl<'a> CompletionContext<'a> {
275 self.dot_receiver = method_call_expr 328 self.dot_receiver = method_call_expr
276 .expr() 329 .expr()
277 .map(|e| e.syntax().text_range()) 330 .map(|e| e.syntax().text_range())
278 .and_then(|r| find_node_with_range(original_file.syntax(), r)); 331 .and_then(|r| find_node_with_range(original_file, r));
279 self.is_call = true; 332 self.is_call = true;
280 } 333 }
281 } 334 }