diff --git a/crates/ide_completion/src/completions/lifetime.rs b/crates/ide_completion/src/completions/lifetime.rs index 74eb233602a..07be28e9c41 100644 --- a/crates/ide_completion/src/completions/lifetime.rs +++ b/crates/ide_completion/src/completions/lifetime.rs @@ -1,4 +1,4 @@ -//! Completes lifetimes. +//! Completes lifetimes and labels. use hir::ScopeDef; use crate::{completions::Completions, context::CompletionContext}; @@ -29,6 +29,18 @@ pub(crate) fn complete_lifetime(acc: &mut Completions, ctx: &CompletionContext) } } +/// Completes labels. +pub(crate) fn complete_label(acc: &mut Completions, ctx: &CompletionContext) { + if !ctx.is_label_ref { + return; + } + ctx.scope.process_all_names(&mut |name, res| { + if let ScopeDef::Label(_) = res { + acc.add_resolution(ctx, name.to_string(), &res); + } + }); +} + #[cfg(test)] mod tests { use expect_test::{expect, Expect}; @@ -178,4 +190,67 @@ fn foo<'footime, 'lifetime: 'a$0>() {} "#]], ); } + + #[test] + fn complete_label_in_loop() { + check( + r#" +fn foo() { + 'foop: loop { + break '$0 + } +} +"#, + expect![[r#" + lb 'foop + "#]], + ); + check( + r#" +fn foo() { + 'foop: loop { + continue '$0 + } +} +"#, + expect![[r#" + lb 'foop + "#]], + ); + } + + #[test] + fn complete_label_in_block_nested() { + check( + r#" +fn foo() { + 'foop: { + 'baap: { + break '$0 + } + } +} +"#, + expect![[r#" + lb 'baap + lb 'foop + "#]], + ); + } + + #[test] + fn complete_label_in_loop_with_value() { + check( + r#" +fn foo() { + 'foop: loop { + break '$0 i32; + } +} +"#, + expect![[r#" + lb 'foop + "#]], + ); + } } diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index 4c2b31084d6..6cb7e526454 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs @@ -53,6 +53,7 @@ pub(crate) struct CompletionContext<'a> { /// FIXME: `ActiveParameter` is string-based, which is very very wrong pub(super) active_parameter: Option, pub(super) is_param: bool, + pub(super) is_label_ref: bool, /// If a name-binding or reference to a const in a pattern. /// Irrefutable patterns (like let) are excluded. pub(super) is_pat_binding_or_const: bool, @@ -155,6 +156,7 @@ impl<'a> CompletionContext<'a> { record_field_syntax: None, impl_def: None, active_parameter: ActiveParameter::at(db, position), + is_label_ref: false, is_param: false, is_pat_binding_or_const: false, is_irrefutable_pat_binding: false, @@ -468,12 +470,26 @@ impl<'a> CompletionContext<'a> { ) { self.lifetime_syntax = find_node_at_offset(original_file, lifetime.syntax().text_range().start()); - if lifetime.syntax().parent().map_or(false, |p| p.kind() != syntax::SyntaxKind::ERROR) { - self.lifetime_allowed = true; - } - if let Some(_) = lifetime.syntax().parent().and_then(ast::LifetimeParam::cast) { - self.lifetime_param_syntax = - self.sema.find_node_at_offset_with_macros(original_file, offset); + if let Some(parent) = lifetime.syntax().parent() { + if parent.kind() == syntax::SyntaxKind::ERROR { + return; + } + + if parent.kind() != syntax::SyntaxKind::LABEL { + match_ast! { + match parent { + ast::LifetimeParam(_it) => { + self.lifetime_allowed = true; + self.lifetime_param_syntax = + self.sema.find_node_at_offset_with_macros(original_file, offset); + }, + ast::BreakExpr(_it) => self.is_label_ref = true, + ast::ContinueExpr(_it) => self.is_label_ref = true, + ast::Label(_it) => (), + _ => self.lifetime_allowed = true, + } + } + } } } diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs index 7a0eb6a9662..995970fcab8 100644 --- a/crates/ide_completion/src/lib.rs +++ b/crates/ide_completion/src/lib.rs @@ -131,6 +131,7 @@ pub fn completions( completions::mod_::complete_mod(&mut acc, &ctx); completions::flyimport::import_on_the_fly(&mut acc, &ctx); completions::lifetime::complete_lifetime(&mut acc, &ctx); + completions::lifetime::complete_label(&mut acc, &ctx); Some(acc) } diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index 2b6e9ebd1f8..23e00aa471a 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs @@ -219,6 +219,7 @@ impl<'a> Render<'a> { hir::GenericParam::ConstParam(_) => SymbolKind::ConstParam, }), ScopeDef::Local(..) => CompletionItemKind::SymbolKind(SymbolKind::Local), + ScopeDef::Label(..) => CompletionItemKind::SymbolKind(SymbolKind::Label), ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => { CompletionItemKind::SymbolKind(SymbolKind::SelfParam) }