aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_completion/src/context.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_completion/src/context.rs')
-rw-r--r--crates/ide_completion/src/context.rs90
1 files changed, 71 insertions, 19 deletions
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index 6d57da06a..32f81aec1 100644
--- a/crates/ide_completion/src/context.rs
+++ b/crates/ide_completion/src/context.rs
@@ -4,8 +4,11 @@ use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type};
4use ide_db::base_db::{FilePosition, SourceDatabase}; 4use ide_db::base_db::{FilePosition, SourceDatabase};
5use ide_db::{call_info::ActiveParameter, RootDatabase}; 5use ide_db::{call_info::ActiveParameter, RootDatabase};
6use syntax::{ 6use syntax::{
7 algo::find_node_at_offset, ast, match_ast, AstNode, NodeOrToken, SyntaxKind::*, SyntaxNode, 7 algo::find_node_at_offset,
8 SyntaxToken, TextRange, TextSize, 8 ast::{self, NameOrNameRef, NameOwner},
9 match_ast, AstNode, NodeOrToken,
10 SyntaxKind::*,
11 SyntaxNode, SyntaxToken, TextRange, TextSize,
9}; 12};
10 13
11use text_edit::Indel; 14use text_edit::Indel;
@@ -35,18 +38,22 @@ pub(crate) struct CompletionContext<'a> {
35 /// The token before the cursor, in the macro-expanded file. 38 /// The token before the cursor, in the macro-expanded file.
36 pub(super) token: SyntaxToken, 39 pub(super) token: SyntaxToken,
37 pub(super) krate: Option<hir::Crate>, 40 pub(super) krate: Option<hir::Crate>,
38 pub(super) expected_name: Option<String>, 41 pub(super) expected_name: Option<NameOrNameRef>,
39 pub(super) expected_type: Option<Type>, 42 pub(super) expected_type: Option<Type>,
40 pub(super) name_ref_syntax: Option<ast::NameRef>, 43 pub(super) name_ref_syntax: Option<ast::NameRef>,
44 pub(super) lifetime_syntax: Option<ast::Lifetime>,
45 pub(super) lifetime_param_syntax: Option<ast::LifetimeParam>,
41 pub(super) function_syntax: Option<ast::Fn>, 46 pub(super) function_syntax: Option<ast::Fn>,
42 pub(super) use_item_syntax: Option<ast::Use>, 47 pub(super) use_item_syntax: Option<ast::Use>,
43 pub(super) record_lit_syntax: Option<ast::RecordExpr>, 48 pub(super) record_lit_syntax: Option<ast::RecordExpr>,
44 pub(super) record_pat_syntax: Option<ast::RecordPat>, 49 pub(super) record_pat_syntax: Option<ast::RecordPat>,
45 pub(super) record_field_syntax: Option<ast::RecordExprField>, 50 pub(super) record_field_syntax: Option<ast::RecordExprField>,
46 pub(super) impl_def: Option<ast::Impl>, 51 pub(super) impl_def: Option<ast::Impl>,
52 pub(super) lifetime_allowed: bool,
47 /// FIXME: `ActiveParameter` is string-based, which is very very wrong 53 /// FIXME: `ActiveParameter` is string-based, which is very very wrong
48 pub(super) active_parameter: Option<ActiveParameter>, 54 pub(super) active_parameter: Option<ActiveParameter>,
49 pub(super) is_param: bool, 55 pub(super) is_param: bool,
56 pub(super) is_label_ref: bool,
50 /// If a name-binding or reference to a const in a pattern. 57 /// If a name-binding or reference to a const in a pattern.
51 /// Irrefutable patterns (like let) are excluded. 58 /// Irrefutable patterns (like let) are excluded.
52 pub(super) is_pat_binding_or_const: bool, 59 pub(super) is_pat_binding_or_const: bool,
@@ -136,9 +143,12 @@ impl<'a> CompletionContext<'a> {
136 original_token, 143 original_token,
137 token, 144 token,
138 krate, 145 krate,
146 lifetime_allowed: false,
139 expected_name: None, 147 expected_name: None,
140 expected_type: None, 148 expected_type: None,
141 name_ref_syntax: None, 149 name_ref_syntax: None,
150 lifetime_syntax: None,
151 lifetime_param_syntax: None,
142 function_syntax: None, 152 function_syntax: None,
143 use_item_syntax: None, 153 use_item_syntax: None,
144 record_lit_syntax: None, 154 record_lit_syntax: None,
@@ -146,6 +156,7 @@ impl<'a> CompletionContext<'a> {
146 record_field_syntax: None, 156 record_field_syntax: None,
147 impl_def: None, 157 impl_def: None,
148 active_parameter: ActiveParameter::at(db, position), 158 active_parameter: ActiveParameter::at(db, position),
159 is_label_ref: false,
149 is_param: false, 160 is_param: false,
150 is_pat_binding_or_const: false, 161 is_pat_binding_or_const: false,
151 is_irrefutable_pat_binding: false, 162 is_irrefutable_pat_binding: false,
@@ -241,9 +252,13 @@ impl<'a> CompletionContext<'a> {
241 pub(crate) fn source_range(&self) -> TextRange { 252 pub(crate) fn source_range(&self) -> TextRange {
242 // check kind of macro-expanded token, but use range of original token 253 // check kind of macro-expanded token, but use range of original token
243 let kind = self.token.kind(); 254 let kind = self.token.kind();
244 if kind == IDENT || kind == UNDERSCORE || kind.is_keyword() { 255 if kind == IDENT || kind == LIFETIME_IDENT || kind == UNDERSCORE || kind.is_keyword() {
245 cov_mark::hit!(completes_if_prefix_is_keyword); 256 cov_mark::hit!(completes_if_prefix_is_keyword);
246 self.original_token.text_range() 257 self.original_token.text_range()
258 } else if kind == CHAR {
259 // assume we are completing a lifetime but the user has only typed the '
260 cov_mark::hit!(completes_if_lifetime_without_idents);
261 TextRange::at(self.original_token.text_range().start(), TextSize::from(1))
247 } else { 262 } else {
248 TextRange::empty(self.position.offset) 263 TextRange::empty(self.position.offset)
249 } 264 }
@@ -292,13 +307,13 @@ impl<'a> CompletionContext<'a> {
292 file_with_fake_ident: SyntaxNode, 307 file_with_fake_ident: SyntaxNode,
293 offset: TextSize, 308 offset: TextSize,
294 ) { 309 ) {
295 let expected = { 310 let (expected_type, expected_name) = {
296 let mut node = match self.token.parent() { 311 let mut node = match self.token.parent() {
297 Some(it) => it, 312 Some(it) => it,
298 None => return, 313 None => return,
299 }; 314 };
300 loop { 315 loop {
301 let ret = match_ast! { 316 break match_ast! {
302 match node { 317 match node {
303 ast::LetStmt(it) => { 318 ast::LetStmt(it) => {
304 cov_mark::hit!(expected_type_let_with_leading_char); 319 cov_mark::hit!(expected_type_let_with_leading_char);
@@ -306,7 +321,7 @@ impl<'a> CompletionContext<'a> {
306 let ty = it.pat() 321 let ty = it.pat()
307 .and_then(|pat| self.sema.type_of_pat(&pat)); 322 .and_then(|pat| self.sema.type_of_pat(&pat));
308 let name = if let Some(ast::Pat::IdentPat(ident)) = it.pat() { 323 let name = if let Some(ast::Pat::IdentPat(ident)) = it.pat() {
309 Some(ident.syntax().text().to_string()) 324 ident.name().map(NameOrNameRef::Name)
310 } else { 325 } else {
311 None 326 None
312 }; 327 };
@@ -319,7 +334,10 @@ impl<'a> CompletionContext<'a> {
319 ActiveParameter::at_token( 334 ActiveParameter::at_token(
320 &self.sema, 335 &self.sema,
321 self.token.clone(), 336 self.token.clone(),
322 ).map(|ap| (Some(ap.ty), Some(ap.name))) 337 ).map(|ap| {
338 let name = ap.ident().map(NameOrNameRef::Name);
339 (Some(ap.ty), name)
340 })
323 .unwrap_or((None, None)) 341 .unwrap_or((None, None))
324 }, 342 },
325 ast::RecordExprFieldList(_it) => { 343 ast::RecordExprFieldList(_it) => {
@@ -327,10 +345,10 @@ impl<'a> CompletionContext<'a> {
327 self.token.prev_sibling_or_token() 345 self.token.prev_sibling_or_token()
328 .and_then(|se| se.into_node()) 346 .and_then(|se| se.into_node())
329 .and_then(|node| ast::RecordExprField::cast(node)) 347 .and_then(|node| ast::RecordExprField::cast(node))
330 .and_then(|rf| self.sema.resolve_record_field(&rf)) 348 .and_then(|rf| self.sema.resolve_record_field(&rf).zip(Some(rf)))
331 .map(|f|( 349 .map(|(f, rf)|(
332 Some(f.0.signature_ty(self.db)), 350 Some(f.0.signature_ty(self.db)),
333 Some(f.0.name(self.db).to_string()), 351 rf.field_name().map(NameOrNameRef::NameRef),
334 )) 352 ))
335 .unwrap_or((None, None)) 353 .unwrap_or((None, None))
336 }, 354 },
@@ -340,7 +358,7 @@ impl<'a> CompletionContext<'a> {
340 .resolve_record_field(&it) 358 .resolve_record_field(&it)
341 .map(|f|( 359 .map(|f|(
342 Some(f.0.signature_ty(self.db)), 360 Some(f.0.signature_ty(self.db)),
343 Some(f.0.name(self.db).to_string()), 361 it.field_name().map(NameOrNameRef::NameRef),
344 )) 362 ))
345 .unwrap_or((None, None)) 363 .unwrap_or((None, None))
346 }, 364 },
@@ -378,14 +396,17 @@ impl<'a> CompletionContext<'a> {
378 }, 396 },
379 } 397 }
380 }; 398 };
381
382 break ret;
383 } 399 }
384 }; 400 };
385 self.expected_type = expected.0; 401 self.expected_type = expected_type;
386 self.expected_name = expected.1; 402 self.expected_name = expected_name;
387 self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); 403 self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset);
388 404
405 if let Some(lifetime) = find_node_at_offset::<ast::Lifetime>(&file_with_fake_ident, offset)
406 {
407 self.classify_lifetime(original_file, lifetime, offset);
408 }
409
389 // First, let's try to complete a reference to some declaration. 410 // First, let's try to complete a reference to some declaration.
390 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { 411 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) {
391 // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. 412 // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
@@ -445,6 +466,35 @@ impl<'a> CompletionContext<'a> {
445 } 466 }
446 } 467 }
447 468
469 fn classify_lifetime(
470 &mut self,
471 original_file: &SyntaxNode,
472 lifetime: ast::Lifetime,
473 offset: TextSize,
474 ) {
475 self.lifetime_syntax =
476 find_node_at_offset(original_file, lifetime.syntax().text_range().start());
477 if let Some(parent) = lifetime.syntax().parent() {
478 if parent.kind() == ERROR {
479 return;
480 }
481
482 match_ast! {
483 match parent {
484 ast::LifetimeParam(_it) => {
485 self.lifetime_allowed = true;
486 self.lifetime_param_syntax =
487 self.sema.find_node_at_offset_with_macros(original_file, offset);
488 },
489 ast::BreakExpr(_it) => self.is_label_ref = true,
490 ast::ContinueExpr(_it) => self.is_label_ref = true,
491 ast::Label(_it) => (),
492 _ => self.lifetime_allowed = true,
493 }
494 }
495 }
496 }
497
448 fn classify_name_ref( 498 fn classify_name_ref(
449 &mut self, 499 &mut self,
450 original_file: &SyntaxNode, 500 original_file: &SyntaxNode,
@@ -452,11 +502,11 @@ impl<'a> CompletionContext<'a> {
452 offset: TextSize, 502 offset: TextSize,
453 ) { 503 ) {
454 self.name_ref_syntax = 504 self.name_ref_syntax =
455 find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); 505 find_node_at_offset(original_file, name_ref.syntax().text_range().start());
456 let name_range = name_ref.syntax().text_range(); 506 let name_range = name_ref.syntax().text_range();
457 if ast::RecordExprField::for_field_name(&name_ref).is_some() { 507 if ast::RecordExprField::for_field_name(&name_ref).is_some() {
458 self.record_lit_syntax = 508 self.record_lit_syntax =
459 self.sema.find_node_at_offset_with_macros(&original_file, offset); 509 self.sema.find_node_at_offset_with_macros(original_file, offset);
460 } 510 }
461 511
462 self.fill_impl_def(); 512 self.fill_impl_def();
@@ -631,7 +681,9 @@ mod tests {
631 .map(|t| t.display_test(&db).to_string()) 681 .map(|t| t.display_test(&db).to_string())
632 .unwrap_or("?".to_owned()); 682 .unwrap_or("?".to_owned());
633 683
634 let name = completion_context.expected_name.unwrap_or("?".to_owned()); 684 let name = completion_context
685 .expected_name
686 .map_or_else(|| "?".to_owned(), |name| name.to_string());
635 687
636 expect.assert_eq(&format!("ty: {}, name: {}", ty, name)); 688 expect.assert_eq(&format!("ty: {}, name: {}", ty, name));
637 } 689 }