From 519ac81b578fcda24f8f71eb99f287bce829bb71 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Jun 2022 15:15:21 +0200 Subject: [PATCH 1/6] internal: Move most remaining keyword completions to item list completions --- .../src/completions/item_list.rs | 108 ++++++++++++++++-- .../ide-completion/src/completions/keyword.rs | 50 -------- crates/ide-completion/src/context.rs | 47 ++++---- crates/ide-completion/src/tests/item.rs | 55 +-------- crates/ide-completion/src/tests/item_list.rs | 12 +- crates/ide-completion/src/tests/type_pos.rs | 6 +- 6 files changed, 135 insertions(+), 143 deletions(-) diff --git a/crates/ide-completion/src/completions/item_list.rs b/crates/ide-completion/src/completions/item_list.rs index edff146d8d..b78ed26ec3 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 281e6e9783..d55046e710 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 f3e316ff3c..4eac86162a 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 537c9a7fa2..d1f5d2a33c 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 d03a4fd5cd..edc896636f 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 1e5e86eef5..76942110f8 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:: "#]], From c5dcc77b40a19dada837e50374d1851754f5eb2a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Jun 2022 15:41:51 +0200 Subject: [PATCH 2/6] Fix visibility mods not being completed for field defs --- crates/ide-completion/src/completions.rs | 1 + .../ide-completion/src/completions/field.rs | 53 +++++++++++++++++++ .../src/completions/flyimport.rs | 9 ++-- crates/ide-completion/src/completions/type.rs | 9 ++-- crates/ide-completion/src/context.rs | 10 ++-- crates/ide-completion/src/lib.rs | 1 + crates/ide-completion/src/render.rs | 2 +- crates/ide-completion/src/render/function.rs | 2 +- crates/ide-completion/src/tests/item.rs | 7 ++- crates/ide-completion/src/tests/type_pos.rs | 6 ++- 10 files changed, 85 insertions(+), 15 deletions(-) create mode 100644 crates/ide-completion/src/completions/field.rs diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs index 931b92dec3..d020b49cde 100644 --- a/crates/ide-completion/src/completions.rs +++ b/crates/ide-completion/src/completions.rs @@ -4,6 +4,7 @@ pub(crate) mod attribute; pub(crate) mod dot; pub(crate) mod expr; pub(crate) mod extern_abi; +pub(crate) mod field; pub(crate) mod flyimport; pub(crate) mod fn_param; pub(crate) mod format_string; diff --git a/crates/ide-completion/src/completions/field.rs b/crates/ide-completion/src/completions/field.rs new file mode 100644 index 0000000000..d81e48cbab --- /dev/null +++ b/crates/ide-completion/src/completions/field.rs @@ -0,0 +1,53 @@ +//! Completion of field list position. + +use crate::{ + context::{IdentContext, NameContext, NameKind, NameRefContext, PathCompletionCtx, PathKind}, + CompletionContext, CompletionItem, CompletionItemKind, Completions, +}; + +pub(crate) fn complete_field_list(acc: &mut Completions, ctx: &CompletionContext) { + match &ctx.ident_ctx { + IdentContext::Name(NameContext { kind: NameKind::RecordField, .. }) + | IdentContext::NameRef(NameRefContext { + path_ctx: + Some(PathCompletionCtx { + has_macro_bang: false, + is_absolute_path: false, + qualifier: None, + parent: None, + kind: PathKind::Type { in_tuple_struct: true }, + has_type_args: false, + .. + }), + .. + }) => { + if ctx.qualifier_ctx.vis_node.is_none() { + let mut add_keyword = |kw, snippet| add_keyword(acc, ctx, kw, snippet); + add_keyword("pub(crate)", "pub(crate)"); + add_keyword("pub(super)", "pub(super)"); + add_keyword("pub", "pub"); + } + } + _ => return, + } +} + +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/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index 873db300b8..c3bf298bc6 100644 --- a/crates/ide-completion/src/completions/flyimport.rs +++ b/crates/ide-completion/src/completions/flyimport.rs @@ -160,7 +160,10 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) (_, ItemInNs::Types(hir::ModuleDef::Module(_))) => true, // and so are macros(except for attributes) ( - PathKind::Expr { .. } | PathKind::Type | PathKind::Item { .. } | PathKind::Pat, + PathKind::Expr { .. } + | PathKind::Type { .. } + | PathKind::Item { .. } + | PathKind::Pat, ItemInNs::Macros(mac), ) => mac.is_fn_like(ctx.db), (PathKind::Item { .. }, _) => true, @@ -170,14 +173,14 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) (PathKind::Pat, ItemInNs::Types(_)) => true, (PathKind::Pat, ItemInNs::Values(def)) => matches!(def, hir::ModuleDef::Const(_)), - (PathKind::Type, ItemInNs::Types(ty)) => { + (PathKind::Type { .. }, ItemInNs::Types(ty)) => { if matches!(ctx.completion_location, Some(ImmediateLocation::TypeBound)) { matches!(ty, ModuleDef::Trait(_)) } else { true } } - (PathKind::Type, ItemInNs::Values(_)) => false, + (PathKind::Type { .. }, ItemInNs::Values(_)) => false, (PathKind::Attr { .. }, ItemInNs::Macros(mac)) => mac.is_attr(ctx.db), (PathKind::Attr { .. }, _) => false, diff --git a/crates/ide-completion/src/completions/type.rs b/crates/ide-completion/src/completions/type.rs index 91414c8bf6..bc8c070c14 100644 --- a/crates/ide-completion/src/completions/type.rs +++ b/crates/ide-completion/src/completions/type.rs @@ -18,9 +18,12 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext) } let (&is_absolute_path, qualifier) = match ctx.path_context() { - Some(PathCompletionCtx { kind: PathKind::Type, is_absolute_path, qualifier, .. }) => { - (is_absolute_path, qualifier) - } + Some(PathCompletionCtx { + kind: PathKind::Type { .. }, + is_absolute_path, + qualifier, + .. + }) => (is_absolute_path, qualifier), _ => return, }; diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 4eac86162a..a4b38d3f24 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -49,7 +49,9 @@ pub(super) enum PathKind { in_block_expr: bool, in_loop_body: bool, }, - Type, + Type { + in_tuple_struct: bool, + }, Attr { kind: AttrKind, annotated_item_kind: Option, @@ -1222,7 +1224,9 @@ impl<'a> CompletionContext<'a> { // using Option> as extra controlflow let kind = match_ast! { match it { - ast::PathType(_) => Some(PathKind::Type), + ast::PathType(it) => Some(PathKind::Type { + in_tuple_struct: it.syntax().parent().map_or(false, |it| ast::TupleField::can_cast(it.kind())) + }), ast::PathExpr(it) => { if let Some(p) = it.syntax().parent() { if ast::ExprStmt::can_cast(p.kind()) { @@ -1262,7 +1266,7 @@ impl<'a> CompletionContext<'a> { let parent = it.syntax().parent(); match parent.as_ref().map(|it| it.kind()) { Some(SyntaxKind::MACRO_PAT) => Some(PathKind::Pat), - Some(SyntaxKind::MACRO_TYPE) => Some(PathKind::Type), + Some(SyntaxKind::MACRO_TYPE) => Some(PathKind::Type { in_tuple_struct: false }), 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()) { Some(it) => match_ast! { diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs index 9659efad61..c100dd63ea 100644 --- a/crates/ide-completion/src/lib.rs +++ b/crates/ide-completion/src/lib.rs @@ -158,6 +158,7 @@ pub fn completions( completions::dot::complete_dot(acc, ctx); completions::expr::complete_expr_path(acc, ctx); completions::extern_abi::complete_extern_abi(acc, ctx); + completions::field::complete_field_list(acc, ctx); completions::flyimport::import_on_the_fly(acc, ctx); completions::fn_param::complete_fn_param(acc, ctx); completions::format_string::format_string(acc, ctx); diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index d51bc517d6..ca2b3ad343 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -286,7 +286,7 @@ fn render_resolution_simple_( // Add `<>` for generic types let type_path_no_ty_args = matches!( ctx.completion.path_context(), - Some(PathCompletionCtx { kind: PathKind::Type, has_type_args: false, .. }) + Some(PathCompletionCtx { kind: PathKind::Type { .. }, has_type_args: false, .. }) ) && ctx.completion.config.callable.is_some(); if type_path_no_ty_args { if let Some(cap) = ctx.snippet_cap() { diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs index 5e1fbfa4a2..0be51b0e3f 100644 --- a/crates/ide-completion/src/render/function.rs +++ b/crates/ide-completion/src/render/function.rs @@ -202,7 +202,7 @@ fn should_add_parens(ctx: &CompletionContext) -> bool { Some(PathCompletionCtx { kind: PathKind::Expr { .. }, has_call_parens: true, .. }) => { return false } - Some(PathCompletionCtx { kind: PathKind::Use | PathKind::Type, .. }) => { + Some(PathCompletionCtx { kind: PathKind::Use | PathKind::Type { .. }, .. }) => { cov_mark::hit!(no_parens_in_use_item); return false; } diff --git a/crates/ide-completion/src/tests/item.rs b/crates/ide-completion/src/tests/item.rs index d1f5d2a33c..9e50e00ab7 100644 --- a/crates/ide-completion/src/tests/item.rs +++ b/crates/ide-completion/src/tests/item.rs @@ -100,7 +100,6 @@ fn after_fn_name() { #[test] fn before_record_field() { - // FIXME: This should emit visibility qualifiers check( r#" struct Foo { @@ -108,6 +107,10 @@ struct Foo { pub f: i32, } "#, - expect![[r#""#]], + expect![[r#" + kw pub + kw pub(crate) + kw pub(super) + "#]], ) } diff --git a/crates/ide-completion/src/tests/type_pos.rs b/crates/ide-completion/src/tests/type_pos.rs index 76942110f8..1e5e86eef5 100644 --- a/crates/ide-completion/src/tests/type_pos.rs +++ b/crates/ide-completion/src/tests/type_pos.rs @@ -38,14 +38,13 @@ 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<…> @@ -57,6 +56,9 @@ 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:: "#]], From c522669f00865c6bfc9160b2c810499ef8fa5552 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Jun 2022 15:46:17 +0200 Subject: [PATCH 3/6] Remove dead code --- .../src/completions/flyimport.rs | 4 +- crates/ide-completion/src/context.rs | 8 --- crates/ide-completion/src/patterns.rs | 69 +------------------ 3 files changed, 2 insertions(+), 79 deletions(-) diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index c3bf298bc6..22068096ba 100644 --- a/crates/ide-completion/src/completions/flyimport.rs +++ b/crates/ide-completion/src/completions/flyimport.rs @@ -110,10 +110,8 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) if !ctx.config.enable_imports_on_the_fly { return None; } - if matches!(ctx.path_kind(), Some(PathKind::Vis { .. } | PathKind::Use)) + if matches!(ctx.path_kind(), Some(PathKind::Vis { .. } | PathKind::Use | PathKind::Item { .. })) || ctx.is_path_disallowed() - || ctx.expects_item() - || ctx.expects_assoc_item() { return None; } diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index a4b38d3f24..f8073f5431 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -334,14 +334,6 @@ impl<'a> CompletionContext<'a> { self.dot_receiver().is_some() } - pub(crate) fn expects_assoc_item(&self) -> bool { - matches!(self.completion_location, Some(ImmediateLocation::Trait | ImmediateLocation::Impl)) - } - - pub(crate) fn expects_item(&self) -> bool { - matches!(self.completion_location, Some(ImmediateLocation::ItemList)) - } - // FIXME: This shouldn't exist pub(crate) fn expects_generic_arg(&self) -> bool { matches!(self.completion_location, Some(ImmediateLocation::GenericArgList(_))) diff --git a/crates/ide-completion/src/patterns.rs b/crates/ide-completion/src/patterns.rs index cc599a02cb..27b271dde4 100644 --- a/crates/ide-completion/src/patterns.rs +++ b/crates/ide-completion/src/patterns.rs @@ -39,13 +39,7 @@ pub(crate) enum TypeAnnotation { /// from which file the nodes are. #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum ImmediateLocation { - Impl, - Trait, - TupleField, RefExpr, - IdentPat, - StmtList, - ItemList, TypeBound, /// Original file ast node TypeAnnotation(TypeAnnotation), @@ -140,30 +134,14 @@ pub(crate) fn determine_location( _ => parent, }, // SourceFile - None => { - return match node.kind() { - MACRO_ITEMS | SOURCE_FILE => Some(ImmediateLocation::ItemList), - _ => None, - } - } + None => return None, }; let res = match_ast! { match parent { - ast::IdentPat(_) => ImmediateLocation::IdentPat, - ast::StmtList(_) => ImmediateLocation::StmtList, - ast::SourceFile(_) => ImmediateLocation::ItemList, - ast::ItemList(_) => ImmediateLocation::ItemList, ast::RefExpr(_) => ImmediateLocation::RefExpr, - ast::TupleField(_) => ImmediateLocation::TupleField, - ast::TupleFieldList(_) => ImmediateLocation::TupleField, ast::TypeBound(_) => ImmediateLocation::TypeBound, ast::TypeBoundList(_) => ImmediateLocation::TypeBound, - ast::AssocItemList(it) => match it.syntax().parent().map(|it| it.kind()) { - Some(IMPL) => ImmediateLocation::Impl, - Some(TRAIT) => ImmediateLocation::Trait, - _ => return None, - }, ast::GenericArgList(_) => sema .find_node_at_offset_with_macros(original_file, offset) .map(ImmediateLocation::GenericArgList)?, @@ -359,56 +337,11 @@ mod tests { }); } - #[test] - fn test_trait_loc() { - check_location(r"trait A { f$0 }", ImmediateLocation::Trait); - check_location(r"trait A { #[attr] f$0 }", ImmediateLocation::Trait); - check_location(r"trait A { f$0 fn f() {} }", ImmediateLocation::Trait); - check_location(r"trait A { fn f() {} f$0 }", ImmediateLocation::Trait); - check_location(r"trait A$0 {}", None); - check_location(r"trait A { fn f$0 }", None); - } - - #[test] - fn test_impl_loc() { - check_location(r"impl A { f$0 }", ImmediateLocation::Impl); - check_location(r"impl A { #[attr] f$0 }", ImmediateLocation::Impl); - check_location(r"impl A { f$0 fn f() {} }", ImmediateLocation::Impl); - check_location(r"impl A { fn f() {} f$0 }", ImmediateLocation::Impl); - check_location(r"impl A$0 {}", None); - check_location(r"impl A { fn f$0 }", None); - } - - #[test] - fn test_block_expr_loc() { - check_location(r"fn my_fn() { let a = 2; f$0 }", ImmediateLocation::StmtList); - check_location(r"fn my_fn() { f$0 f }", ImmediateLocation::StmtList); - } - - #[test] - fn test_ident_pat_loc() { - check_location(r"fn my_fn(m$0) {}", ImmediateLocation::IdentPat); - check_location(r"fn my_fn() { let m$0 }", ImmediateLocation::IdentPat); - check_location(r"fn my_fn(&m$0) {}", ImmediateLocation::IdentPat); - check_location(r"fn my_fn() { let &m$0 }", ImmediateLocation::IdentPat); - } - #[test] fn test_ref_expr_loc() { check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr); } - #[test] - fn test_item_list_loc() { - check_location(r"i$0", ImmediateLocation::ItemList); - check_location(r"#[attr] i$0", ImmediateLocation::ItemList); - check_location(r"fn f() {} i$0", ImmediateLocation::ItemList); - check_location(r"mod foo { f$0 }", ImmediateLocation::ItemList); - check_location(r"mod foo { #[attr] f$0 }", ImmediateLocation::ItemList); - check_location(r"mod foo { fn f() {} f$0 }", ImmediateLocation::ItemList); - check_location(r"mod foo$0 {}", None); - } - #[test] fn test_impl_prev_sibling() { check_prev_sibling(r"impl A w$0 ", ImmediatePrevSibling::ImplDefType); From 6550a241fb370d21c8a3fc31053ceb823628d42e Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Jun 2022 16:11:26 +0200 Subject: [PATCH 4/6] More precise where keyword completions --- .../src/completions/item_list.rs | 2 +- .../ide-completion/src/completions/keyword.rs | 47 +++++++------- crates/ide-completion/src/context.rs | 42 ++++++------- crates/ide-completion/src/patterns.rs | 29 --------- crates/ide-completion/src/tests/item.rs | 62 +++++++++++++++---- crates/ide-completion/src/tests/item_list.rs | 1 - crates/ide-completion/src/tests/record.rs | 1 - 7 files changed, 96 insertions(+), 88 deletions(-) diff --git a/crates/ide-completion/src/completions/item_list.rs b/crates/ide-completion/src/completions/item_list.rs index b78ed26ec3..aa0d04cf6c 100644 --- a/crates/ide-completion/src/completions/item_list.rs +++ b/crates/ide-completion/src/completions/item_list.rs @@ -36,7 +36,7 @@ pub(crate) fn complete_item_list(acc: &mut Completions, ctx: &CompletionContext) let in_block = matches!(kind, None); 'block: loop { - if path_qualifier.is_some() { + if ctx.is_non_trivial_path() { break 'block; } if !in_trait_impl { diff --git a/crates/ide-completion/src/completions/keyword.rs b/crates/ide-completion/src/completions/keyword.rs index d55046e710..d6df5002f5 100644 --- a/crates/ide-completion/src/completions/keyword.rs +++ b/crates/ide-completion/src/completions/keyword.rs @@ -2,35 +2,40 @@ //! - `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::ast::Item; + use crate::{ - context::{NameRefContext, PathKind}, - CompletionContext, CompletionItem, CompletionItemKind, Completions, + context::NameRefContext, CompletionContext, CompletionItem, CompletionItemKind, Completions, }; pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { - if matches!(ctx.nameref_ctx(), Some(NameRefContext { record_expr: Some(_), .. })) { - cov_mark::hit!(no_keyword_completion_in_record_lit); - return; - } - if ctx.is_non_trivial_path() { - cov_mark::hit!(no_keyword_completion_in_non_trivial_path); - return; - } - if ctx.pattern_ctx.is_some() { - return; - } + let item = match ctx.nameref_ctx() { + Some(NameRefContext { keyword: Some(item), record_expr: None, .. }) + if !ctx.is_non_trivial_path() => + { + item + } + _ => return, + }; let mut add_keyword = |kw, snippet| add_keyword(acc, ctx, kw, snippet); - if let Some(PathKind::Vis { .. }) = ctx.path_kind() { - return; - } - if ctx.has_unfinished_impl_or_trait_prev_sibling() { - add_keyword("where", "where"); - if ctx.has_impl_prev_sibling() { - add_keyword("for", "for"); + match item { + Item::Impl(it) => { + if it.for_token().is_none() && it.trait_().is_none() && it.self_ty().is_some() { + add_keyword("for", "for"); + } + add_keyword("where", "where"); } - return; + Item::Enum(_) + | Item::Fn(_) + | Item::Struct(_) + | Item::Trait(_) + | Item::TypeAlias(_) + | Item::Union(_) => { + add_keyword("where", "where"); + } + _ => (), } } diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index f8073f5431..ccc7c10746 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -185,6 +185,8 @@ pub(super) struct NameRefContext { // FIXME: these fields are actually disjoint -> enum pub(super) dot_access: Option, pub(super) path_ctx: Option, + /// Position where we are only interested in keyword completions + pub(super) keyword: Option, /// The record expression this nameref is a field of pub(super) record_expr: Option<(ast::RecordExpr, bool)>, } @@ -343,21 +345,6 @@ impl<'a> CompletionContext<'a> { matches!(self.completion_location, Some(ImmediateLocation::RefExpr)) } - /// Whether the cursor is right after a trait or impl header. - /// trait Foo ident$0 - // FIXME: This probably shouldn't exist - pub(crate) fn has_unfinished_impl_or_trait_prev_sibling(&self) -> bool { - matches!( - self.prev_sibling, - Some(ImmediatePrevSibling::ImplDefType | ImmediatePrevSibling::TraitDefName) - ) - } - - // FIXME: This probably shouldn't exist - pub(crate) fn has_impl_prev_sibling(&self) -> bool { - matches!(self.prev_sibling, Some(ImmediatePrevSibling::ImplDefType)) - } - pub(crate) fn after_if(&self) -> bool { matches!(self.prev_sibling, Some(ImmediatePrevSibling::IfExpr)) } @@ -1092,8 +1079,13 @@ impl<'a> CompletionContext<'a> { ) -> (NameRefContext, Option) { let nameref = find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); - let mut nameref_ctx = - NameRefContext { dot_access: None, path_ctx: None, nameref, record_expr: None }; + let mut nameref_ctx = NameRefContext { + dot_access: None, + path_ctx: None, + nameref, + record_expr: None, + keyword: None, + }; if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) { nameref_ctx.record_expr = @@ -1190,7 +1182,7 @@ impl<'a> CompletionContext<'a> { syntax::algo::non_trivia_sibling(node.into(), syntax::Direction::Prev) { if let Some(item) = ast::Item::cast(n) { - match item { + let is_inbetween = match &item { ast::Item::Const(it) => it.body().is_none(), ast::Item::Enum(it) => it.variant_list().is_none(), ast::Item::ExternBlock(it) => it.extern_item_list().is_none(), @@ -1203,13 +1195,13 @@ impl<'a> CompletionContext<'a> { ast::Item::TypeAlias(it) => it.ty().is_none(), ast::Item::Union(it) => it.record_field_list().is_none(), _ => false, + }; + if is_inbetween { + return Some(item); } - } else { - false } - } else { - false } + None }; let kind = path.syntax().ancestors().find_map(|it| { @@ -1222,7 +1214,8 @@ impl<'a> CompletionContext<'a> { ast::PathExpr(it) => { if let Some(p) = it.syntax().parent() { if ast::ExprStmt::can_cast(p.kind()) { - if inbetween_body_and_decl_check(p) { + if let Some(kind) = inbetween_body_and_decl_check(p) { + nameref_ctx.keyword = Some(kind); return Some(None); } } @@ -1250,7 +1243,8 @@ impl<'a> CompletionContext<'a> { Some(PathKind::Pat) }, ast::MacroCall(it) => { - if inbetween_body_and_decl_check(it.syntax().clone()) { + if let Some(kind) = inbetween_body_and_decl_check(it.syntax().clone()) { + nameref_ctx.keyword = Some(kind); return Some(None); } diff --git a/crates/ide-completion/src/patterns.rs b/crates/ide-completion/src/patterns.rs index 27b271dde4..34bfa4517c 100644 --- a/crates/ide-completion/src/patterns.rs +++ b/crates/ide-completion/src/patterns.rs @@ -21,8 +21,6 @@ use crate::tests::check_pattern_is_applicable; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum ImmediatePrevSibling { IfExpr, - TraitDefName, - ImplDefType, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -81,17 +79,6 @@ pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option if it.assoc_item_list().is_none() { - ImmediatePrevSibling::TraitDefName - } else { - return None - }, - ast::Impl(it) => if it.assoc_item_list().is_none() - && (it.for_token().is_none() || it.self_ty().is_some()) { - ImmediatePrevSibling::ImplDefType - } else { - return None - }, _ => return None, } }; @@ -342,22 +329,6 @@ mod tests { check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr); } - #[test] - fn test_impl_prev_sibling() { - check_prev_sibling(r"impl A w$0 ", ImmediatePrevSibling::ImplDefType); - check_prev_sibling(r"impl A w$0 {}", ImmediatePrevSibling::ImplDefType); - check_prev_sibling(r"impl A for A w$0 ", ImmediatePrevSibling::ImplDefType); - check_prev_sibling(r"impl A for A w$0 {}", ImmediatePrevSibling::ImplDefType); - check_prev_sibling(r"impl A for w$0 {}", None); - check_prev_sibling(r"impl A for w$0", None); - } - - #[test] - fn test_trait_prev_sibling() { - check_prev_sibling(r"trait A w$0 ", ImmediatePrevSibling::TraitDefName); - check_prev_sibling(r"trait A w$0 {}", ImmediatePrevSibling::TraitDefName); - } - #[test] fn test_if_expr_prev_sibling() { check_prev_sibling(r"fn foo() { if true {} w$0", ImmediatePrevSibling::IfExpr); diff --git a/crates/ide-completion/src/tests/item.rs b/crates/ide-completion/src/tests/item.rs index 9e50e00ab7..81303eb38f 100644 --- a/crates/ide-completion/src/tests/item.rs +++ b/crates/ide-completion/src/tests/item.rs @@ -76,26 +76,66 @@ fn after_target_name_in_impl() { kw where "#]], ); - // FIXME: This should not emit `kw for` + check( + r"impl Trait f$0", + expect![[r#" + kw for + kw where + "#]], + ); check( r"impl Trait for Type $0", expect![[r#" - kw for kw where "#]], ); } #[test] -fn after_struct_name() { - // FIXME: This should emit `kw where` - check(r"struct Struct $0", expect![[r#""#]]); -} - -#[test] -fn after_fn_name() { - // FIXME: This should emit `kw where` - check(r"fn func() $0", expect![[r#""#]]); +fn completes_where() { + check( + r"struct Struct $0", + expect![[r#" + kw where + "#]], + ); + check( + r"struct Struct $0 {}", + expect![[r#" + kw where + "#]], + ); + // FIXME: This shouldn't be completed here + check( + r"struct Struct $0 ()", + expect![[r#" + kw where + "#]], + ); + check( + r"fn func() $0", + expect![[r#" + kw where + "#]], + ); + check( + r"enum Enum $0", + expect![[r#" + kw where + "#]], + ); + check( + r"enum Enum $0 {}", + expect![[r#" + kw where + "#]], + ); + check( + r"trait Trait $0 {}", + expect![[r#" + kw where + "#]], + ); } #[test] diff --git a/crates/ide-completion/src/tests/item_list.rs b/crates/ide-completion/src/tests/item_list.rs index edc896636f..09ea78a3d5 100644 --- a/crates/ide-completion/src/tests/item_list.rs +++ b/crates/ide-completion/src/tests/item_list.rs @@ -108,7 +108,6 @@ fn in_item_list_after_attr() { #[test] fn in_qualified_path() { - cov_mark::check!(no_keyword_completion_in_non_trivial_path); check( r#"crate::$0"#, expect![[r#" diff --git a/crates/ide-completion/src/tests/record.rs b/crates/ide-completion/src/tests/record.rs index 9e442dbbc5..9369034cc6 100644 --- a/crates/ide-completion/src/tests/record.rs +++ b/crates/ide-completion/src/tests/record.rs @@ -9,7 +9,6 @@ fn check(ra_fixture: &str, expect: Expect) { #[test] fn without_default_impl() { - cov_mark::check!(no_keyword_completion_in_record_lit); check( r#" struct Struct { foo: u32, bar: usize } From 522f66545ffe709527dc2102aefe738ffe9ecc17 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Jun 2022 16:25:37 +0200 Subject: [PATCH 5/6] Remove prev-sibling completion machinery --- crates/ide-completion/src/completions/expr.rs | 7 ++- crates/ide-completion/src/context.rs | 28 +++++---- crates/ide-completion/src/patterns.rs | 62 +------------------ 3 files changed, 21 insertions(+), 76 deletions(-) diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index ae7b42e305..780869bb8c 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -15,12 +15,12 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext) return; } - let (is_absolute_path, qualifier, in_block_expr, in_loop_body, is_func_update) = + let (is_absolute_path, qualifier, in_block_expr, in_loop_body, is_func_update, after_if_expr) = match ctx.nameref_ctx() { Some(NameRefContext { path_ctx: Some(PathCompletionCtx { - kind: PathKind::Expr { in_block_expr, in_loop_body }, + kind: PathKind::Expr { in_block_expr, in_loop_body, after_if_expr }, is_absolute_path, qualifier, .. @@ -33,6 +33,7 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext) *in_block_expr, *in_loop_body, record_expr.as_ref().map_or(false, |&(_, it)| it), + *after_if_expr, ), _ => return, }; @@ -202,7 +203,7 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext) add_keyword("let", "let"); } - if ctx.after_if() { + if after_if_expr { add_keyword("else", "else {\n $0\n}"); add_keyword("else if", "else if $1 {\n $0\n}"); } diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index ccc7c10746..6068a9eb32 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -15,7 +15,7 @@ use ide_db::{ use syntax::{ algo::{find_node_at_offset, non_trivia_sibling}, ast::{self, AttrKind, HasArgList, HasName, NameOrNameRef}, - match_ast, AstNode, AstToken, NodeOrToken, + match_ast, AstNode, AstToken, Direction, NodeOrToken, SyntaxKind::{self, *}, SyntaxNode, SyntaxToken, TextRange, TextSize, T, }; @@ -23,8 +23,8 @@ use text_edit::Indel; use crate::{ patterns::{ - determine_location, determine_prev_sibling, is_in_loop_body, is_in_token_of_for_loop, - previous_token, ImmediateLocation, ImmediatePrevSibling, + determine_location, is_in_loop_body, is_in_token_of_for_loop, previous_token, + ImmediateLocation, }, CompletionConfig, }; @@ -48,6 +48,7 @@ pub(super) enum PathKind { Expr { in_block_expr: bool, in_loop_body: bool, + after_if_expr: bool, }, Type { in_tuple_struct: bool, @@ -264,7 +265,6 @@ pub(crate) struct CompletionContext<'a> { pub(super) incomplete_let: bool, pub(super) completion_location: Option, - pub(super) prev_sibling: Option, pub(super) previous_token: Option, pub(super) ident_ctx: IdentContext, @@ -345,10 +345,6 @@ impl<'a> CompletionContext<'a> { matches!(self.completion_location, Some(ImmediateLocation::RefExpr)) } - pub(crate) fn after_if(&self) -> bool { - matches!(self.prev_sibling, Some(ImmediatePrevSibling::IfExpr)) - } - // FIXME: This shouldn't exist pub(crate) fn is_path_disallowed(&self) -> bool { !self.qualifier_ctx.none() @@ -527,7 +523,6 @@ impl<'a> CompletionContext<'a> { impl_def: None, incomplete_let: false, completion_location: None, - prev_sibling: None, previous_token: None, // dummy value, will be overwritten ident_ctx: IdentContext::UnexpandedAttrTT { fake_attribute_under_caret: None }, @@ -922,7 +917,6 @@ impl<'a> CompletionContext<'a> { }; self.completion_location = determine_location(&self.sema, original_file, offset, &name_like); - self.prev_sibling = determine_prev_sibling(&name_like); self.impl_def = self .sema .token_ancestors_with_macros(self.token.clone()) @@ -1169,6 +1163,13 @@ impl<'a> CompletionContext<'a> { find_node_in_file_compensated(original_file, &record_expr).zip(Some(true)); } }; + let after_if_expr = |node: SyntaxNode| { + let prev_expr = (|| { + let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?; + ast::ExprStmt::cast(prev_sibling)?.expr() + })(); + matches!(prev_expr, Some(ast::Expr::IfExpr(_))) + }; // We do not want to generate path completions when we are sandwiched between an item decl signature and its body. // ex. trait Foo $0 {} @@ -1226,7 +1227,9 @@ impl<'a> CompletionContext<'a> { path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind())); let in_block_expr = is_in_block(it.syntax()); let in_loop_body = is_in_loop_body(it.syntax()); - Some(PathKind::Expr { in_block_expr, in_loop_body }) + let after_if_expr = after_if_expr(it.syntax().clone()); + + Some(PathKind::Expr { in_block_expr, in_loop_body, after_if_expr }) }, ast::TupleStructPat(it) => { path_ctx.has_call_parens = true; @@ -1274,8 +1277,9 @@ impl<'a> CompletionContext<'a> { return Some(parent.and_then(ast::MacroExpr::cast).map(|it| { let in_loop_body = is_in_loop_body(it.syntax()); let in_block_expr = is_in_block(it.syntax()); + let after_if_expr = after_if_expr(it.syntax().clone()); fill_record_expr(it.syntax()); - PathKind::Expr { in_block_expr, in_loop_body } + PathKind::Expr { in_block_expr, in_loop_body, after_if_expr } })); }, } diff --git a/crates/ide-completion/src/patterns.rs b/crates/ide-completion/src/patterns.rs index 34bfa4517c..9abbfaa407 100644 --- a/crates/ide-completion/src/patterns.rs +++ b/crates/ide-completion/src/patterns.rs @@ -7,9 +7,8 @@ use hir::Semantics; use ide_db::RootDatabase; use syntax::{ - algo::non_trivia_sibling, ast::{self, HasLoopBody, HasName}, - match_ast, AstNode, Direction, SyntaxElement, + match_ast, AstNode, SyntaxElement, SyntaxKind::*, SyntaxNode, SyntaxToken, TextRange, TextSize, }; @@ -17,12 +16,6 @@ use syntax::{ #[cfg(test)] use crate::tests::check_pattern_is_applicable; -/// Immediate previous node to what we are completing. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub(crate) enum ImmediatePrevSibling { - IfExpr, -} - #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum TypeAnnotation { Let(Option), @@ -46,45 +39,6 @@ pub(crate) enum ImmediateLocation { GenericArgList(ast::GenericArgList), } -pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option { - let node = match name_like { - ast::NameLike::NameRef(name_ref) => maximize_name_ref(name_ref), - ast::NameLike::Name(n) => n.syntax().clone(), - ast::NameLike::Lifetime(lt) => lt.syntax().clone(), - }; - let node = match node.parent().and_then(ast::MacroCall::cast) { - // When a path is being typed after the name of a trait/type of an impl it is being - // parsed as a macro, so when the trait/impl has a block following it an we are between the - // name and block the macro will attach the block to itself so maximizing fails to take - // that into account - // FIXME path expr and statement have a similar problem with attrs - Some(call) - if call.excl_token().is_none() - && call.token_tree().map_or(false, |t| t.l_curly_token().is_some()) - && call.semicolon_token().is_none() => - { - call.syntax().clone() - } - _ => node, - }; - let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?; - let res = match_ast! { - match prev_sibling { - ast::ExprStmt(it) => { - let node = it.expr().filter(|_| it.semicolon_token().is_none())?.syntax().clone(); - match_ast! { - match node { - ast::IfExpr(_) => ImmediatePrevSibling::IfExpr, - _ => return None, - } - } - }, - _ => return None, - } - }; - Some(res) -} - pub(crate) fn determine_location( sema: &Semantics, original_file: &SyntaxNode, @@ -316,22 +270,8 @@ mod tests { ); } - fn check_prev_sibling(code: &str, sibling: impl Into>) { - check_pattern_is_applicable(code, |e| { - let name = &e.parent().and_then(ast::NameLike::cast).expect("Expected a namelike"); - assert_eq!(determine_prev_sibling(name), sibling.into()); - true - }); - } - #[test] fn test_ref_expr_loc() { check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr); } - - #[test] - fn test_if_expr_prev_sibling() { - check_prev_sibling(r"fn foo() { if true {} w$0", ImmediatePrevSibling::IfExpr); - check_prev_sibling(r"fn foo() { if true {}; w$0", None); - } } From 2a60b8452e469f8002834a7ac14a29434d92f12b Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Jun 2022 16:33:37 +0200 Subject: [PATCH 6/6] Deduplicate --- crates/ide-completion/src/completions.rs | 20 ++++++++++++++ crates/ide-completion/src/completions/expr.rs | 3 +-- .../ide-completion/src/completions/field.rs | 24 ++--------------- .../src/completions/item_list.rs | 24 ++--------------- .../ide-completion/src/completions/keyword.rs | 26 ++----------------- 5 files changed, 27 insertions(+), 70 deletions(-) diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs index d020b49cde..b6358d4f40 100644 --- a/crates/ide-completion/src/completions.rs +++ b/crates/ide-completion/src/completions.rs @@ -111,6 +111,26 @@ impl Completions { ["self", "super", "crate"].into_iter().for_each(|kw| self.add_keyword(ctx, kw)); } + pub(crate) fn add_keyword_snippet(&mut self, 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(self); + } + pub(crate) fn add_crate_roots(&mut self, ctx: &CompletionContext) { ctx.process_all_names(&mut |name, res| match res { ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) if m.is_crate_root(ctx.db) => { diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index 780869bb8c..23f47523d6 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -178,8 +178,7 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext) }); if !is_func_update { - let mut add_keyword = - |kw, snippet| super::keyword::add_keyword(acc, ctx, kw, snippet); + let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet); if ctx.expects_expression() { if !in_block_expr { diff --git a/crates/ide-completion/src/completions/field.rs b/crates/ide-completion/src/completions/field.rs index d81e48cbab..1739527917 100644 --- a/crates/ide-completion/src/completions/field.rs +++ b/crates/ide-completion/src/completions/field.rs @@ -2,7 +2,7 @@ use crate::{ context::{IdentContext, NameContext, NameKind, NameRefContext, PathCompletionCtx, PathKind}, - CompletionContext, CompletionItem, CompletionItemKind, Completions, + CompletionContext, Completions, }; pub(crate) fn complete_field_list(acc: &mut Completions, ctx: &CompletionContext) { @@ -22,7 +22,7 @@ pub(crate) fn complete_field_list(acc: &mut Completions, ctx: &CompletionContext .. }) => { if ctx.qualifier_ctx.vis_node.is_none() { - let mut add_keyword = |kw, snippet| add_keyword(acc, ctx, kw, snippet); + let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet); add_keyword("pub(crate)", "pub(crate)"); add_keyword("pub(super)", "pub(super)"); add_keyword("pub", "pub"); @@ -31,23 +31,3 @@ pub(crate) fn complete_field_list(acc: &mut Completions, ctx: &CompletionContext _ => return, } } - -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/item_list.rs b/crates/ide-completion/src/completions/item_list.rs index aa0d04cf6c..287cf46f2e 100644 --- a/crates/ide-completion/src/completions/item_list.rs +++ b/crates/ide-completion/src/completions/item_list.rs @@ -3,7 +3,7 @@ use crate::{ completions::module_or_fn_macro, context::{ItemListKind, PathCompletionCtx, PathKind, PathQualifierCtx}, - CompletionContext, CompletionItem, CompletionItemKind, Completions, + CompletionContext, Completions, }; pub(crate) fn complete_item_list(acc: &mut Completions, ctx: &CompletionContext) { @@ -24,7 +24,7 @@ pub(crate) fn complete_item_list(acc: &mut Completions, ctx: &CompletionContext) }) => (is_absolute_path, qualifier, None), _ => return, }; - let mut add_keyword = |kw, snippet| add_keyword(acc, ctx, kw, snippet); + let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(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)); @@ -121,23 +121,3 @@ 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 d6df5002f5..e870ecc229 100644 --- a/crates/ide-completion/src/completions/keyword.rs +++ b/crates/ide-completion/src/completions/keyword.rs @@ -4,9 +4,7 @@ use syntax::ast::Item; -use crate::{ - context::NameRefContext, CompletionContext, CompletionItem, CompletionItemKind, Completions, -}; +use crate::{context::NameRefContext, CompletionContext, Completions}; pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { let item = match ctx.nameref_ctx() { @@ -18,7 +16,7 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte _ => return, }; - let mut add_keyword = |kw, snippet| add_keyword(acc, ctx, kw, snippet); + let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet); match item { Item::Impl(it) => { @@ -39,26 +37,6 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte } } -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); -} - #[cfg(test)] mod tests { use expect_test::{expect, Expect};