diff options
| author | bors <bors@rust-lang.org> | 2022-06-03 15:20:30 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2022-06-03 15:20:30 +0000 |
| commit | d06d0f8774b3f07f9d269264f2f7eefaac459ca7 (patch) | |
| tree | 78d6cdc69d054ee30dcfede8ef58d14ab7d6df86 /crates | |
| parent | d0a7ad4a7f10239f5d800bc3a336a6fa24fef2ce (diff) | |
| parent | 2a60b8452e469f8002834a7ac14a29434d92f12b (diff) | |
| download | rust-d06d0f8774b3f07f9d269264f2f7eefaac459ca7.tar.gz rust-d06d0f8774b3f07f9d269264f2f7eefaac459ca7.zip | |
Auto merge of #12459 - Veykril:completions, r=Veykril
internal: Clean up keyword completion handling https://github.com/rust-lang/rust-analyzer/issues/12144
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/ide-completion/src/completions.rs | 21 | ||||
| -rw-r--r-- | crates/ide-completion/src/completions/expr.rs | 10 | ||||
| -rw-r--r-- | crates/ide-completion/src/completions/field.rs | 33 | ||||
| -rw-r--r-- | crates/ide-completion/src/completions/flyimport.rs | 13 | ||||
| -rw-r--r-- | crates/ide-completion/src/completions/item_list.rs | 86 | ||||
| -rw-r--r-- | crates/ide-completion/src/completions/keyword.rs | 115 | ||||
| -rw-r--r-- | crates/ide-completion/src/completions/type.rs | 9 | ||||
| -rw-r--r-- | crates/ide-completion/src/context.rs | 135 | ||||
| -rw-r--r-- | crates/ide-completion/src/lib.rs | 1 | ||||
| -rw-r--r-- | crates/ide-completion/src/patterns.rs | 160 | ||||
| -rw-r--r-- | crates/ide-completion/src/render.rs | 2 | ||||
| -rw-r--r-- | crates/ide-completion/src/render/function.rs | 2 | ||||
| -rw-r--r-- | crates/ide-completion/src/tests/item.rs | 86 | ||||
| -rw-r--r-- | crates/ide-completion/src/tests/item_list.rs | 13 | ||||
| -rw-r--r-- | crates/ide-completion/src/tests/record.rs | 1 |
15 files changed, 291 insertions, 396 deletions
diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs index 931b92dec3f..b6358d4f40c 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; @@ -110,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 ae7b42e3056..23f47523d66 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, }; @@ -177,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 { @@ -202,7 +202,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/completions/field.rs b/crates/ide-completion/src/completions/field.rs new file mode 100644 index 00000000000..17395279178 --- /dev/null +++ b/crates/ide-completion/src/completions/field.rs @@ -0,0 +1,33 @@ +//! Completion of field list position. + +use crate::{ + context::{IdentContext, NameContext, NameKind, NameRefContext, PathCompletionCtx, PathKind}, + CompletionContext, 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| acc.add_keyword_snippet(ctx, kw, snippet); + add_keyword("pub(crate)", "pub(crate)"); + add_keyword("pub(super)", "pub(super)"); + add_keyword("pub", "pub"); + } + } + _ => return, + } +} diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index 873db300b84..22068096ba0 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; } @@ -160,7 +158,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 +171,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/item_list.rs b/crates/ide-completion/src/completions/item_list.rs index edff146d8d7..287cf46f2e3 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}, + context::{ItemListKind, PathCompletionCtx, PathKind, PathQualifierCtx}, CompletionContext, 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| 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)); + 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 ctx.is_non_trivial_path() { + 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) { diff --git a/crates/ide-completion/src/completions/keyword.rs b/crates/ide-completion/src/completions/keyword.rs index 281e6e9783c..e870ecc2295 100644 --- a/crates/ide-completion/src/completions/keyword.rs +++ b/crates/ide-completion/src/completions/keyword.rs @@ -2,106 +2,39 @@ //! - `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 syntax::ast::Item; -use crate::{ - context::{NameRefContext, PathKind}, - CompletionContext, CompletionItem, CompletionItemKind, Completions, -}; +use crate::{context::NameRefContext, CompletionContext, 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 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; - } - if ctx.has_unfinished_impl_or_trait_prev_sibling() { - add_keyword("where", "where"); - if ctx.has_impl_prev_sibling() { - add_keyword("for", "for"); - } - 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}") + let item = match ctx.nameref_ctx() { + Some(NameRefContext { keyword: Some(item), record_expr: None, .. }) + if !ctx.is_non_trivial_path() => + { + item } + _ => return, + }; - 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) { - let mut item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw); + let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet); - 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); + 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"); } - None => { - item.insert_text(if snippet.contains('$') { kw } else { snippet }); + Item::Enum(_) + | Item::Fn(_) + | Item::Struct(_) + | Item::Trait(_) + | Item::TypeAlias(_) + | Item::Union(_) => { + add_keyword("where", "where"); } - }; - item.add_to(acc); + _ => (), + } } #[cfg(test)] diff --git a/crates/ide-completion/src/completions/type.rs b/crates/ide-completion/src/completions/type.rs index 91414c8bf6a..bc8c070c14d 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 f3e316ff3c1..6068a9eb32c 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,8 +48,11 @@ pub(super) enum PathKind { Expr { in_block_expr: bool, in_loop_body: bool, + after_if_expr: bool, + }, + Type { + in_tuple_struct: bool, }, - Type, Attr { kind: AttrKind, annotated_item_kind: Option<SyntaxKind>, @@ -71,6 +74,7 @@ pub(super) enum ItemListKind { SourceFile, Module, Impl, + TraitImpl, Trait, ExternBlock, } @@ -182,6 +186,8 @@ pub(super) struct NameRefContext { // FIXME: these fields are actually disjoint -> enum pub(super) dot_access: Option<DotAccess>, pub(super) path_ctx: Option<PathCompletionCtx>, + /// Position where we are only interested in keyword completions + pub(super) keyword: Option<ast::Item>, /// The record expression this nameref is a field of pub(super) record_expr: Option<(ast::RecordExpr, bool)>, } @@ -259,7 +265,6 @@ pub(crate) struct CompletionContext<'a> { pub(super) incomplete_let: bool, pub(super) completion_location: Option<ImmediateLocation>, - pub(super) prev_sibling: Option<ImmediatePrevSibling>, pub(super) previous_token: Option<SyntaxToken>, pub(super) ident_ctx: IdentContext, @@ -331,55 +336,15 @@ 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_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)) - } - // FIXME: This shouldn't exist pub(crate) fn expects_generic_arg(&self) -> bool { 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 - 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)) - } - // FIXME: This shouldn't exist pub(crate) fn is_path_disallowed(&self) -> bool { !self.qualifier_ctx.none() @@ -558,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 }, @@ -953,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()) @@ -1110,8 +1073,13 @@ impl<'a> CompletionContext<'a> { ) -> (NameRefContext, Option<PatternContext>) { 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 = @@ -1195,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 {} @@ -1208,7 +1183,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(), @@ -1221,24 +1196,27 @@ 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| { // using Option<Option<PathKind>> 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()) { - 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); } } @@ -1249,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; @@ -1266,7 +1246,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); } @@ -1274,12 +1255,21 @@ 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()).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 }), @@ -1287,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 } })); }, } @@ -1313,12 +1304,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/lib.rs b/crates/ide-completion/src/lib.rs index 9659efad619..c100dd63eac 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/patterns.rs b/crates/ide-completion/src/patterns.rs index cc599a02cb2..9abbfaa4072 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,14 +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, - TraitDefName, - ImplDefType, -} - #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum TypeAnnotation { Let(Option<ast::Pat>), @@ -39,13 +30,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), @@ -54,56 +39,6 @@ pub(crate) enum ImmediateLocation { GenericArgList(ast::GenericArgList), } -pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> { - 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, - } - } - }, - ast::Trait(it) => 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, - } - }; - Some(res) -} - pub(crate) fn determine_location( sema: &Semantics<RootDatabase>, original_file: &SyntaxNode, @@ -140,30 +75,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)?, @@ -351,83 +270,8 @@ mod tests { ); } - fn check_prev_sibling(code: &str, sibling: impl Into<Option<ImmediatePrevSibling>>) { - 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_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); - 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); - check_prev_sibling(r"fn foo() { if true {}; w$0", None); - } } diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index d51bc517d65..ca2b3ad3435 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 5e1fbfa4a21..0be51b0e3ff 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 537c9a7fa24..81303eb38f4 100644 --- a/crates/ide-completion/src/tests/item.rs +++ b/crates/ide-completion/src/tests/item.rs @@ -76,65 +76,65 @@ fn after_target_name_in_impl() { kw where "#]], ); - // FIXME: This should not emit `kw for` check( - r"impl Trait for Type $0", + r"impl Trait f$0", expect![[r#" kw for kw where "#]], ); -} - -#[test] -fn after_struct_name() { - // FIXME: This should emit `kw where` only check( - r"struct Struct $0", + r"impl Trait for Type $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 + kw where "#]], ); } #[test] -fn after_fn_name() { - // FIXME: This should emit `kw where` only +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 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 - "#]], + 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 + "#]], ); } diff --git a/crates/ide-completion/src/tests/item_list.rs b/crates/ide-completion/src/tests/item_list.rs index d03a4fd5cd1..09ea78a3d50 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#" @@ -137,6 +136,7 @@ fn after_visibility() { expect![[r#" kw const kw enum + kw extern kw fn kw mod kw static @@ -152,12 +152,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 +176,6 @@ fn in_impl_assoc_item_list() { kw pub(super) kw self:: kw super:: - kw type kw unsafe "#]], ) @@ -199,7 +196,6 @@ fn in_impl_assoc_item_list_after_attr() { kw pub(super) kw self:: kw super:: - kw type kw unsafe "#]], ) @@ -249,16 +245,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/record.rs b/crates/ide-completion/src/tests/record.rs index 9e442dbbc56..9369034cc62 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 } |
