diff options
Diffstat (limited to 'crates/ide_completion/src/context.rs')
-rw-r--r-- | crates/ide_completion/src/context.rs | 90 |
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}; | |||
4 | use ide_db::base_db::{FilePosition, SourceDatabase}; | 4 | use ide_db::base_db::{FilePosition, SourceDatabase}; |
5 | use ide_db::{call_info::ActiveParameter, RootDatabase}; | 5 | use ide_db::{call_info::ActiveParameter, RootDatabase}; |
6 | use syntax::{ | 6 | use 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 | ||
11 | use text_edit::Indel; | 14 | use 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 | } |