diff --git a/crates/ide-completion/src/completions/item_list.rs b/crates/ide-completion/src/completions/item_list.rs index edff146d8d7..b78ed26ec3f 100644 --- a/crates/ide-completion/src/completions/item_list.rs +++ b/crates/ide-completion/src/completions/item_list.rs @@ -2,22 +2,98 @@ use crate::{ completions::module_or_fn_macro, - context::{PathCompletionCtx, PathKind, PathQualifierCtx}, - CompletionContext, Completions, + context::{ItemListKind, PathCompletionCtx, PathKind, PathQualifierCtx}, + CompletionContext, CompletionItem, CompletionItemKind, Completions, }; pub(crate) fn complete_item_list(acc: &mut Completions, ctx: &CompletionContext) { let _p = profile::span("complete_item_list"); - let (&is_absolute_path, path_qualifier, _kind) = match ctx.path_context() { + let (&is_absolute_path, path_qualifier, kind) = match ctx.path_context() { Some(PathCompletionCtx { kind: PathKind::Item { kind }, is_absolute_path, qualifier, .. - }) => (is_absolute_path, qualifier, kind), + }) => (is_absolute_path, qualifier, Some(kind)), + Some(PathCompletionCtx { + kind: PathKind::Expr { in_block_expr: true, .. }, + is_absolute_path, + qualifier, + .. + }) => (is_absolute_path, qualifier, None), _ => return, }; + let mut add_keyword = |kw, snippet| add_keyword(acc, ctx, kw, snippet); + + let in_item_list = matches!(kind, Some(ItemListKind::SourceFile | ItemListKind::Module) | None); + let in_assoc_non_trait_impl = matches!(kind, Some(ItemListKind::Impl | ItemListKind::Trait)); + let in_extern_block = matches!(kind, Some(ItemListKind::ExternBlock)); + let in_trait = matches!(kind, Some(ItemListKind::Trait)); + let in_trait_impl = matches!(kind, Some(ItemListKind::TraitImpl)); + let in_inherent_impl = matches!(kind, Some(ItemListKind::Impl)); + let no_qualifiers = ctx.qualifier_ctx.vis_node.is_none(); + let in_block = matches!(kind, None); + + 'block: loop { + if path_qualifier.is_some() { + break 'block; + } + if !in_trait_impl { + if ctx.qualifier_ctx.unsafe_tok.is_some() { + if in_item_list || in_assoc_non_trait_impl { + add_keyword("fn", "fn $1($2) {\n $0\n}"); + } + if in_item_list { + add_keyword("trait", "trait $1 {\n $0\n}"); + if no_qualifiers { + add_keyword("impl", "impl $1 {\n $0\n}"); + } + } + break 'block; + } + + if in_item_list { + add_keyword("enum", "enum $1 {\n $0\n}"); + add_keyword("mod", "mod $0"); + add_keyword("static", "static $0"); + add_keyword("struct", "struct $0"); + add_keyword("trait", "trait $1 {\n $0\n}"); + add_keyword("union", "union $1 {\n $0\n}"); + add_keyword("use", "use $0"); + if no_qualifiers { + add_keyword("impl", "impl $1 {\n $0\n}"); + } + } + + if !in_trait && !in_block && no_qualifiers { + add_keyword("pub(crate)", "pub(crate)"); + add_keyword("pub(super)", "pub(super)"); + add_keyword("pub", "pub"); + } + + if in_extern_block { + add_keyword("fn", "fn $1($2);"); + } else { + if !in_inherent_impl { + if !in_trait { + add_keyword("extern", "extern $0"); + } + add_keyword("type", "type $0"); + } + + add_keyword("fn", "fn $1($2) {\n $0\n}"); + add_keyword("unsafe", "unsafe"); + add_keyword("const", "const $0"); + } + } + break 'block; + } + + if kind.is_none() { + // this is already handled by expression + return; + } match path_qualifier { Some(PathQualifierCtx { resolution, is_super_chain, .. }) => { @@ -33,9 +109,7 @@ pub(crate) fn complete_item_list(acc: &mut Completions, ctx: &CompletionContext) acc.add_keyword(ctx, "super::"); } } - None if is_absolute_path => { - acc.add_crate_roots(ctx); - } + None if is_absolute_path => acc.add_crate_roots(ctx), None if ctx.qualifier_ctx.none() => { ctx.process_all_names(&mut |name, def| { if let Some(def) = module_or_fn_macro(ctx.db, def) { @@ -47,3 +121,23 @@ pub(crate) fn complete_item_list(acc: &mut Completions, ctx: &CompletionContext) None => {} } } + +pub(super) fn add_keyword(acc: &mut Completions, ctx: &CompletionContext, kw: &str, snippet: &str) { + let mut item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw); + + match ctx.config.snippet_cap { + Some(cap) => { + if snippet.ends_with('}') && ctx.incomplete_let { + // complete block expression snippets with a trailing semicolon, if inside an incomplete let + cov_mark::hit!(let_semi); + item.insert_snippet(cap, format!("{};", snippet)); + } else { + item.insert_snippet(cap, snippet); + } + } + None => { + item.insert_text(if snippet.contains('$') { kw } else { snippet }); + } + }; + item.add_to(acc); +} diff --git a/crates/ide-completion/src/completions/keyword.rs b/crates/ide-completion/src/completions/keyword.rs index 281e6e9783c..d55046e7107 100644 --- a/crates/ide-completion/src/completions/keyword.rs +++ b/crates/ide-completion/src/completions/keyword.rs @@ -2,8 +2,6 @@ //! - `self`, `super` and `crate`, as these are considered part of path completions. //! - `await`, as this is a postfix completion we handle this in the postfix completions. -use syntax::T; - use crate::{ context::{NameRefContext, PathKind}, CompletionContext, CompletionItem, CompletionItemKind, Completions, @@ -24,10 +22,6 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte let mut add_keyword = |kw, snippet| add_keyword(acc, ctx, kw, snippet); - let expects_assoc_item = ctx.expects_assoc_item(); - let has_block_expr_parent = ctx.has_block_expr_parent(); - let expects_item = ctx.expects_item(); - if let Some(PathKind::Vis { .. }) = ctx.path_kind() { return; } @@ -38,50 +32,6 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte } return; } - if ctx.previous_token_is(T![unsafe]) { - if expects_item || expects_assoc_item || has_block_expr_parent { - add_keyword("fn", "fn $1($2) {\n $0\n}") - } - - if expects_item || has_block_expr_parent { - add_keyword("trait", "trait $1 {\n $0\n}"); - add_keyword("impl", "impl $1 {\n $0\n}"); - } - - return; - } - - if ctx.qualifier_ctx.vis_node.is_none() - && (expects_item || ctx.expects_non_trait_assoc_item() || ctx.expect_field()) - { - add_keyword("pub(crate)", "pub(crate)"); - add_keyword("pub(super)", "pub(super)"); - add_keyword("pub", "pub"); - } - - if expects_item || expects_assoc_item || has_block_expr_parent { - add_keyword("unsafe", "unsafe"); - add_keyword("fn", "fn $1($2) {\n $0\n}"); - add_keyword("const", "const $0"); - add_keyword("type", "type $0"); - } - - if expects_item || has_block_expr_parent { - if ctx.qualifier_ctx.vis_node.is_none() { - add_keyword("impl", "impl $1 {\n $0\n}"); - add_keyword("extern", "extern $0"); - } - add_keyword("use", "use $0"); - add_keyword("trait", "trait $1 {\n $0\n}"); - add_keyword("static", "static $0"); - add_keyword("mod", "mod $0"); - } - - if expects_item || has_block_expr_parent { - add_keyword("enum", "enum $1 {\n $0\n}"); - add_keyword("struct", "struct $0"); - add_keyword("union", "union $1 {\n $0\n}"); - } } pub(super) fn add_keyword(acc: &mut Completions, ctx: &CompletionContext, kw: &str, snippet: &str) { diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index f3e316ff3c1..4eac86162a3 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -71,6 +71,7 @@ pub(super) enum ItemListKind { SourceFile, Module, Impl, + TraitImpl, Trait, ExternBlock, } @@ -335,10 +336,6 @@ impl<'a> CompletionContext<'a> { matches!(self.completion_location, Some(ImmediateLocation::Trait | ImmediateLocation::Impl)) } - pub(crate) fn expects_non_trait_assoc_item(&self) -> bool { - matches!(self.completion_location, Some(ImmediateLocation::Impl)) - } - pub(crate) fn expects_item(&self) -> bool { matches!(self.completion_location, Some(ImmediateLocation::ItemList)) } @@ -348,19 +345,10 @@ impl<'a> CompletionContext<'a> { matches!(self.completion_location, Some(ImmediateLocation::GenericArgList(_))) } - pub(crate) fn has_block_expr_parent(&self) -> bool { - matches!(self.completion_location, Some(ImmediateLocation::StmtList)) - } - pub(crate) fn expects_ident_ref_expr(&self) -> bool { matches!(self.completion_location, Some(ImmediateLocation::RefExpr)) } - pub(crate) fn expect_field(&self) -> bool { - matches!(self.completion_location, Some(ImmediateLocation::TupleField)) - || matches!(self.name_ctx(), Some(NameContext { kind: NameKind::RecordField, .. })) - } - /// Whether the cursor is right after a trait or impl header. /// trait Foo ident$0 // FIXME: This probably shouldn't exist @@ -1276,10 +1264,19 @@ impl<'a> CompletionContext<'a> { Some(SyntaxKind::MACRO_PAT) => Some(PathKind::Pat), Some(SyntaxKind::MACRO_TYPE) => Some(PathKind::Type), Some(SyntaxKind::ITEM_LIST) => Some(PathKind::Item { kind: ItemListKind::Module }), - Some(SyntaxKind::ASSOC_ITEM_LIST) => Some(PathKind::Item { kind: match parent.and_then(|it| it.parent()).map(|it| it.kind()) { - Some(SyntaxKind::TRAIT) => ItemListKind::Trait, - Some(SyntaxKind::IMPL) => ItemListKind::Impl, - _ => return Some(None), + Some(SyntaxKind::ASSOC_ITEM_LIST) => Some(PathKind::Item { kind: match parent.and_then(|it| it.parent()) { + Some(it) => match_ast! { + match it { + ast::Trait(_) => ItemListKind::Trait, + ast::Impl(it) => if it.trait_().is_some() { + ItemListKind::TraitImpl + } else { + ItemListKind::Impl + }, + _ => return Some(None) + } + }, + None => return Some(None), } }), Some(SyntaxKind::EXTERN_ITEM_LIST) => Some(PathKind::Item { kind: ItemListKind::ExternBlock }), Some(SyntaxKind::SOURCE_FILE) => Some(PathKind::Item { kind: ItemListKind::SourceFile }), @@ -1313,12 +1310,18 @@ impl<'a> CompletionContext<'a> { ast::UseTree(_) => Some(PathKind::Use), ast::ItemList(_) => Some(PathKind::Item { kind: ItemListKind::Module }), ast::AssocItemList(it) => Some(PathKind::Item { kind: { - match it.syntax().parent()?.kind() { - SyntaxKind::TRAIT => ItemListKind::Trait, - SyntaxKind::IMPL => ItemListKind::Impl, - _ => return None, + match_ast! { + match (it.syntax().parent()?) { + ast::Trait(_) => ItemListKind::Trait, + ast::Impl(it) => if it.trait_().is_some() { + ItemListKind::TraitImpl + } else { + ItemListKind::Impl + }, + _ => return None } - }}), + } + }}), ast::ExternItemList(_) => Some(PathKind::Item { kind: ItemListKind::ExternBlock }), ast::SourceFile(_) => Some(PathKind::Item { kind: ItemListKind::SourceFile }), _ => return None, diff --git a/crates/ide-completion/src/tests/item.rs b/crates/ide-completion/src/tests/item.rs index 537c9a7fa24..d1f5d2a33c0 100644 --- a/crates/ide-completion/src/tests/item.rs +++ b/crates/ide-completion/src/tests/item.rs @@ -88,58 +88,19 @@ fn after_target_name_in_impl() { #[test] fn after_struct_name() { - // FIXME: This should emit `kw where` only - check( - r"struct Struct $0", - expect![[r#" - kw const - kw enum - kw extern - kw fn - kw impl - kw mod - kw pub - kw pub(crate) - kw pub(super) - kw static - kw struct - kw trait - kw type - kw union - kw unsafe - kw use - "#]], - ); + // FIXME: This should emit `kw where` + check(r"struct Struct $0", expect![[r#""#]]); } #[test] fn after_fn_name() { - // FIXME: This should emit `kw where` only - check( - r"fn func() $0", - expect![[r#" - kw const - kw enum - kw extern - kw fn - kw impl - kw mod - kw pub - kw pub(crate) - kw pub(super) - kw static - kw struct - kw trait - kw type - kw union - kw unsafe - kw use - "#]], - ); + // FIXME: This should emit `kw where` + check(r"fn func() $0", expect![[r#""#]]); } #[test] fn before_record_field() { + // FIXME: This should emit visibility qualifiers check( r#" struct Foo { @@ -147,10 +108,6 @@ struct Foo { pub f: i32, } "#, - expect![[r#" - kw pub - kw pub(crate) - kw pub(super) - "#]], + expect![[r#""#]], ) } diff --git a/crates/ide-completion/src/tests/item_list.rs b/crates/ide-completion/src/tests/item_list.rs index d03a4fd5cd1..edc896636f4 100644 --- a/crates/ide-completion/src/tests/item_list.rs +++ b/crates/ide-completion/src/tests/item_list.rs @@ -137,6 +137,7 @@ fn after_visibility() { expect![[r#" kw const kw enum + kw extern kw fn kw mod kw static @@ -152,12 +153,10 @@ fn after_visibility() { #[test] fn after_visibility_unsafe() { - // FIXME this shouldn't show `impl` check( r#"pub unsafe $0"#, expect![[r#" kw fn - kw impl kw trait "#]], ); @@ -178,7 +177,6 @@ fn in_impl_assoc_item_list() { kw pub(super) kw self:: kw super:: - kw type kw unsafe "#]], ) @@ -199,7 +197,6 @@ fn in_impl_assoc_item_list_after_attr() { kw pub(super) kw self:: kw super:: - kw type kw unsafe "#]], ) @@ -249,16 +246,9 @@ impl Test for () { ma makro!(…) macro_rules! makro md module ta type Type1 = - kw const kw crate:: - kw fn - kw pub - kw pub(crate) - kw pub(super) kw self:: kw super:: - kw type - kw unsafe "#]], ); } diff --git a/crates/ide-completion/src/tests/type_pos.rs b/crates/ide-completion/src/tests/type_pos.rs index 1e5e86eef59..76942110f88 100644 --- a/crates/ide-completion/src/tests/type_pos.rs +++ b/crates/ide-completion/src/tests/type_pos.rs @@ -38,13 +38,14 @@ struct Foo<'lt, T, const C: usize> { #[test] fn tuple_struct_field() { + // FIXME: This should emit visibility qualifiers check( r#" struct Foo<'lt, T, const C: usize>(f$0); "#, expect![[r#" en Enum - ma makro!(…) macro_rules! makro + ma makro!(…) macro_rules! makro md module sp Self st Foo<…> @@ -56,9 +57,6 @@ struct Foo<'lt, T, const C: usize>(f$0); un Union bt u32 kw crate:: - kw pub - kw pub(crate) - kw pub(super) kw self:: kw super:: "#]],