//! Completes lifetimes and labels.
+//!
+//! These completions work a bit differently in that they are only shown when what the user types
+//! has a `'` preceding it, as our fake syntax tree is invalid otherwise (due to us not inserting
+//! a lifetime but an ident for obvious reasons).
+//! Due to this all the tests for lifetimes and labels live in this module for the time being as
+//! there is no value in lifting these out into the outline module test since they will either not
+//! show up for normal completions, or they won't show completions other than lifetimes depending
+//! on the fixture input.
use hir::ScopeDef;
+use syntax::ast;
-use crate::{completions::Completions, context::CompletionContext};
+use crate::{
+ completions::Completions,
+ context::{CompletionContext, LifetimeContext},
+};
/// Completes lifetimes.
pub(crate) fn complete_lifetime(acc: &mut Completions, ctx: &CompletionContext) {
- if !ctx.lifetime_allowed {
- return;
- }
- let param_lifetime = match (
- &ctx.lifetime_syntax,
- ctx.lifetime_param_syntax.as_ref().and_then(|lp| lp.lifetime()),
- ) {
- (Some(lt), Some(lp)) if lp == lt.clone() => return,
- (Some(_), Some(lp)) => Some(lp.to_string()),
+ let lp = match &ctx.lifetime_ctx {
+ Some(LifetimeContext::Lifetime) => None,
+ Some(LifetimeContext::LifetimeParam(param)) => param.as_ref(),
+ _ => return,
+ };
+ let lp_string;
+ let param_lifetime = match (&ctx.name_syntax, lp.and_then(|lp| lp.lifetime())) {
+ (Some(ast::NameLike::Lifetime(lt)), Some(lp)) if lp == lt.clone() => return,
+ (Some(_), Some(lp)) => {
+ lp_string = lp.to_string();
+ Some(&*lp_string)
+ }
_ => None,
};
ctx.scope.process_all_names(&mut |name, res| {
if let ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) = res {
- if param_lifetime != Some(name.to_string()) {
- acc.add_resolution(ctx, name.to_string(), &res);
+ if param_lifetime != Some(&*name.to_string()) {
+ acc.add_resolution(ctx, name, &res);
}
}
});
/// Completes labels.
pub(crate) fn complete_label(acc: &mut Completions, ctx: &CompletionContext) {
- if !ctx.is_label_ref {
+ if !matches!(ctx.lifetime_ctx, Some(LifetimeContext::LabelRef)) {
return;
}
ctx.scope.process_all_names(&mut |name, res| {
if let ScopeDef::Label(_) = res {
- acc.add_resolution(ctx, name.to_string(), &res);
+ acc.add_resolution(ctx, name, &res);
}
});
}
mod tests {
use expect_test::{expect, Expect};
- use crate::{
- test_utils::{check_edit, completion_list_with_config, TEST_CONFIG},
- CompletionConfig, CompletionKind,
- };
+ use crate::tests::{check_edit, completion_list};
fn check(ra_fixture: &str, expect: Expect) {
- check_with_config(TEST_CONFIG, ra_fixture, expect);
- }
-
- fn check_with_config(config: CompletionConfig, ra_fixture: &str, expect: Expect) {
- let actual = completion_list_with_config(config, ra_fixture, CompletionKind::Reference);
- expect.assert_eq(&actual)
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual);
}
#[test]
"#,
r#"
fn func<'lifetime>(foo: &'lifetime) {}
+"#,
+ );
+ cov_mark::check!(completes_if_lifetime_without_idents);
+ check_edit(
+ "'lifetime",
+ r#"
+fn func<'lifetime>(foo: &'$0) {}
+"#,
+ r#"
+fn func<'lifetime>(foo: &'lifetime) {}
"#,
);
}
);
}
+ #[test]
+ fn check_label_edit() {
+ check_edit(
+ "'label",
+ r#"
+fn foo() {
+ 'label: loop {
+ break '$0
+ }
+}
+"#,
+ r#"
+fn foo() {
+ 'label: loop {
+ break 'label
+ }
+}
+"#,
+ );
+ }
+
#[test]
fn complete_label_in_loop() {
check(
"#]],
);
}
+
+ #[test]
+ fn complete_label_in_while_cond() {
+ check(
+ r#"
+fn foo() {
+ 'outer: while { 'inner: loop { break '$0 } } {}
+}
+"#,
+ expect![[r#"
+ lb 'inner
+ lb 'outer
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_label_in_for_iterable() {
+ check(
+ r#"
+fn foo() {
+ 'outer: for _ in [{ 'inner: loop { break '$0 } }] {}
+}
+"#,
+ expect![[r#"
+ lb 'inner
+ "#]],
+ );
+ }
}