diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index 322a9b820a6..428b8d1109f 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -69,24 +69,17 @@ pub(crate) fn inlay_hints( let mut hints = Vec::new(); - if let Some(range_limit) = range_limit { - let range_limit = range_limit.range; - match file.covering_element(range_limit) { + let get_hints = |node| get_hints(&mut hints, &sema, config, node); + match range_limit { + Some(FileRange { range, .. }) => match file.covering_element(range) { NodeOrToken::Token(_) => return hints, - NodeOrToken::Node(n) => { - for node in n - .descendants() - .filter(|descendant| range_limit.contains_range(descendant.text_range())) - { - get_hints(&mut hints, &sema, config, node); - } - } - } - } else { - for node in file.descendants() { - get_hints(&mut hints, &sema, config, node); - } - } + NodeOrToken::Node(n) => n + .descendants() + .filter(|descendant| range.contains_range(descendant.text_range())) + .for_each(get_hints), + }, + None => file.descendants().for_each(get_hints), + }; hints } diff --git a/crates/ide_completion/src/completions.rs b/crates/ide_completion/src/completions.rs index 91e6b84294e..867e6bcf48d 100644 --- a/crates/ide_completion/src/completions.rs +++ b/crates/ide_completion/src/completions.rs @@ -29,11 +29,11 @@ use crate::{ item::Builder, render::{ const_::render_const, - enum_variant::render_variant, function::{render_fn, render_method}, + literal::{render_struct_literal, render_variant_lit}, + macro_::render_macro, pattern::{render_struct_pat, render_variant_pat}, - render_field, render_resolution, render_tuple_field, - struct_literal::render_struct_literal, + render_field, render_resolution, render_resolution_simple, render_tuple_field, type_alias::{render_type_alias, render_type_alias_with_eq}, union_literal::render_union_literal, RenderContext, @@ -124,7 +124,37 @@ impl Completions { cov_mark::hit!(qualified_path_doc_hidden); return; } - self.add(render_resolution(RenderContext::new(ctx, false), local_name, resolution)); + self.add(render_resolution(RenderContext::new(ctx), local_name, resolution)); + } + + pub(crate) fn add_resolution_simple( + &mut self, + ctx: &CompletionContext, + local_name: hir::Name, + resolution: hir::ScopeDef, + ) { + if ctx.is_scope_def_hidden(resolution) { + return; + } + self.add(render_resolution_simple(RenderContext::new(ctx), local_name, resolution)); + } + + pub(crate) fn add_macro( + &mut self, + ctx: &CompletionContext, + mac: hir::Macro, + local_name: hir::Name, + ) { + let is_private_editable = match ctx.is_visible(&mac) { + Visible::Yes => false, + Visible::Editable => true, + Visible::No => return, + }; + self.add(render_macro( + RenderContext::new(ctx).private_editable(is_private_editable), + local_name, + mac, + )); } pub(crate) fn add_function( @@ -138,7 +168,11 @@ impl Completions { Visible::Editable => true, Visible::No => return, }; - self.add(render_fn(RenderContext::new(ctx, is_private_editable), None, local_name, func)); + self.add(render_fn( + RenderContext::new(ctx).private_editable(is_private_editable), + local_name, + func, + )); } pub(crate) fn add_method( @@ -154,8 +188,7 @@ impl Completions { Visible::No => return, }; self.add(render_method( - RenderContext::new(ctx, is_private_editable), - None, + RenderContext::new(ctx).private_editable(is_private_editable), receiver, local_name, func, @@ -168,7 +201,10 @@ impl Completions { Visible::Editable => true, Visible::No => return, }; - self.add_opt(render_const(RenderContext::new(ctx, is_private_editable), konst)); + self.add_opt(render_const( + RenderContext::new(ctx).private_editable(is_private_editable), + konst, + )); } pub(crate) fn add_type_alias(&mut self, ctx: &CompletionContext, type_alias: hir::TypeAlias) { @@ -177,7 +213,10 @@ impl Completions { Visible::Editable => true, Visible::No => return, }; - self.add_opt(render_type_alias(RenderContext::new(ctx, is_private_editable), type_alias)); + self.add_opt(render_type_alias( + RenderContext::new(ctx).private_editable(is_private_editable), + type_alias, + )); } pub(crate) fn add_type_alias_with_eq( @@ -185,7 +224,7 @@ impl Completions { ctx: &CompletionContext, type_alias: hir::TypeAlias, ) { - self.add_opt(render_type_alias_with_eq(RenderContext::new(ctx, false), type_alias)); + self.add_opt(render_type_alias_with_eq(RenderContext::new(ctx), type_alias)); } pub(crate) fn add_qualified_enum_variant( @@ -194,8 +233,7 @@ impl Completions { variant: hir::Variant, path: hir::ModPath, ) { - let item = render_variant(RenderContext::new(ctx, false), None, None, variant, Some(path)); - self.add(item); + self.add_opt(render_variant_lit(RenderContext::new(ctx), None, variant, Some(path))); } pub(crate) fn add_enum_variant( @@ -204,8 +242,7 @@ impl Completions { variant: hir::Variant, local_name: Option, ) { - let item = render_variant(RenderContext::new(ctx, false), None, local_name, variant, None); - self.add(item); + self.add_opt(render_variant_lit(RenderContext::new(ctx), local_name, variant, None)); } pub(crate) fn add_field( @@ -220,7 +257,12 @@ impl Completions { Visible::Editable => true, Visible::No => return, }; - let item = render_field(RenderContext::new(ctx, is_private_editable), receiver, field, ty); + let item = render_field( + RenderContext::new(ctx).private_editable(is_private_editable), + receiver, + field, + ty, + ); self.add(item); } @@ -231,7 +273,7 @@ impl Completions { path: Option, local_name: Option, ) { - let item = render_struct_literal(RenderContext::new(ctx, false), strukt, path, local_name); + let item = render_struct_literal(RenderContext::new(ctx), strukt, path, local_name); self.add_opt(item); } @@ -242,7 +284,7 @@ impl Completions { path: Option, local_name: Option, ) { - let item = render_union_literal(RenderContext::new(ctx, false), un, path, local_name); + let item = render_union_literal(RenderContext::new(ctx), un, path, local_name); self.add_opt(item); } @@ -253,7 +295,7 @@ impl Completions { field: usize, ty: &hir::Type, ) { - let item = render_tuple_field(RenderContext::new(ctx, false), receiver, field, ty); + let item = render_tuple_field(RenderContext::new(ctx), receiver, field, ty); self.add(item); } @@ -272,7 +314,12 @@ impl Completions { variant: hir::Variant, local_name: Option, ) { - self.add_opt(render_variant_pat(RenderContext::new(ctx, false), variant, local_name, None)); + self.add_opt(render_variant_pat( + RenderContext::new(ctx), + variant, + local_name.clone(), + None, + )); } pub(crate) fn add_qualified_variant_pat( @@ -281,7 +328,8 @@ impl Completions { variant: hir::Variant, path: hir::ModPath, ) { - self.add_opt(render_variant_pat(RenderContext::new(ctx, false), variant, None, Some(path))); + let path = Some(&path); + self.add_opt(render_variant_pat(RenderContext::new(ctx), variant, None, path)); } pub(crate) fn add_struct_pat( @@ -290,7 +338,7 @@ impl Completions { strukt: hir::Struct, local_name: Option, ) { - self.add_opt(render_struct_pat(RenderContext::new(ctx, false), strukt, local_name)); + self.add_opt(render_struct_pat(RenderContext::new(ctx), strukt, local_name)); } } diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs index aee2bbb53c3..6c8878a7bb0 100644 --- a/crates/ide_completion/src/completions/flyimport.rs +++ b/crates/ide_completion/src/completions/flyimport.rs @@ -198,7 +198,7 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) }) .filter_map(|import| { render_resolution_with_import( - RenderContext::new(ctx, false), + RenderContext::new(ctx), ImportEdit { import, scope: import_scope.clone() }, ) }), diff --git a/crates/ide_completion/src/completions/pattern.rs b/crates/ide_completion/src/completions/pattern.rs index 958c892b8d4..6c17da07d6f 100644 --- a/crates/ide_completion/src/completions/pattern.rs +++ b/crates/ide_completion/src/completions/pattern.rs @@ -54,8 +54,7 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { { if refutable || single_variant_enum(e) { super::enum_variants_with_paths(acc, ctx, e, |acc, ctx, variant, path| { - acc.add_qualified_variant_pat(ctx, variant, path.clone()); - acc.add_qualified_enum_variant(ctx, variant, path); + acc.add_qualified_variant_pat(ctx, variant, path); }); } } @@ -63,7 +62,7 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { // FIXME: ideally, we should look at the type we are matching against and // suggest variants + auto-imports ctx.process_all_names(&mut |name, res| { - let add_resolution = match res { + let add_simple_path = match res { hir::ScopeDef::ModuleDef(def) => match def { hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => { acc.add_struct_pat(ctx, strukt, Some(name.clone())); @@ -76,8 +75,11 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { true } hir::ModuleDef::Adt(hir::Adt::Enum(e)) => refutable || single_variant_enum(e), - hir::ModuleDef::Const(..) | hir::ModuleDef::Module(..) => refutable, - hir::ModuleDef::Macro(mac) => mac.is_fn_like(ctx.db), + hir::ModuleDef::Const(..) => refutable, + hir::ModuleDef::Module(..) => true, + hir::ModuleDef::Macro(mac) if mac.is_fn_like(ctx.db) => { + return acc.add_macro(ctx, mac, name) + } _ => false, }, hir::ScopeDef::ImplSelfType(impl_) => match impl_.self_ty(ctx.db).as_adt() { @@ -85,13 +87,19 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { acc.add_struct_pat(ctx, strukt, Some(name.clone())); true } - Some(hir::Adt::Enum(_)) => refutable, - _ => true, + Some(hir::Adt::Enum(e)) => refutable || single_variant_enum(e), + Some(hir::Adt::Union(_)) => true, + _ => false, }, - _ => false, + ScopeDef::GenericParam(hir::GenericParam::ConstParam(_)) => true, + ScopeDef::GenericParam(_) + | ScopeDef::AdtSelfType(_) + | ScopeDef::Local(_) + | ScopeDef::Label(_) + | ScopeDef::Unknown => false, }; - if add_resolution { - acc.add_resolution(ctx, name, res); + if add_simple_path { + acc.add_resolution_simple(ctx, name, res); } }); } diff --git a/crates/ide_completion/src/completions/record.rs b/crates/ide_completion/src/completions/record.rs index 264b3784bf1..5509ec922f3 100644 --- a/crates/ide_completion/src/completions/record.rs +++ b/crates/ide_completion/src/completions/record.rs @@ -84,13 +84,16 @@ pub(crate) fn complete_record_literal( match ctx.expected_type.as_ref()?.as_adt()? { hir::Adt::Struct(strukt) if ctx.path_qual().is_none() => { let module = if let Some(module) = ctx.module { module } else { strukt.module(ctx.db) }; - let path = module.find_use_path(ctx.db, hir::ModuleDef::from(strukt)); + let path = module + .find_use_path(ctx.db, hir::ModuleDef::from(strukt)) + .filter(|it| it.len() > 1); acc.add_struct_literal(ctx, strukt, path, None); } hir::Adt::Union(un) if ctx.path_qual().is_none() => { let module = if let Some(module) = ctx.module { module } else { un.module(ctx.db) }; - let path = module.find_use_path(ctx.db, hir::ModuleDef::from(un)); + let path = + module.find_use_path(ctx.db, hir::ModuleDef::from(un)).filter(|it| it.len() > 1); acc.add_union_literal(ctx, un, path, None); } @@ -132,7 +135,7 @@ fn baz() { #[test] fn literal_struct_completion_from_sub_modules() { check_edit( - "Struct {…}", + "submod::Struct {…}", r#" mod submod { pub struct Struct { diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index 1ad233494a9..ed59eb6bd3e 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs @@ -58,7 +58,7 @@ pub(super) enum PathKind { #[derive(Debug)] pub(crate) struct PathCompletionCtx { - /// If this is a call with () already there + /// If this is a call with () already there (or {} in case of record patterns) pub(super) has_call_parens: bool, /// Whether this path stars with a `::`. pub(super) is_absolute_path: bool, @@ -890,6 +890,7 @@ impl<'a> CompletionContext<'a> { Some(PathKind::Pat) }, ast::RecordPat(it) => { + path_ctx.has_call_parens = true; pat_ctx = Some(pattern_context_for(original_file, it.into())); Some(PathKind::Pat) }, diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index 0ed346c55e2..10211cd9710 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs @@ -3,13 +3,12 @@ pub(crate) mod macro_; pub(crate) mod function; -pub(crate) mod enum_variant; pub(crate) mod const_; pub(crate) mod pattern; pub(crate) mod type_alias; -pub(crate) mod struct_literal; -pub(crate) mod compound; +pub(crate) mod variant; pub(crate) mod union_literal; +pub(crate) mod literal; use hir::{AsAssocItem, HasAttrs, HirDisplay, ScopeDef}; use ide_db::{helpers::item_name, RootDatabase, SnippetCap, SymbolKind}; @@ -18,22 +17,30 @@ use syntax::{SmolStr, SyntaxKind, TextRange}; use crate::{ context::{PathCompletionCtx, PathKind}, item::{CompletionRelevanceTypeMatch, ImportEdit}, - render::{enum_variant::render_variant, function::render_fn, macro_::render_macro}, + render::{function::render_fn, literal::render_variant_lit, macro_::render_macro}, CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance, }; /// Interface for data and methods required for items rendering. -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) struct RenderContext<'a> { completion: &'a CompletionContext<'a>, is_private_editable: bool, + import_to_add: Option, } impl<'a> RenderContext<'a> { - pub(crate) fn new( - completion: &'a CompletionContext<'a>, - is_private_editable: bool, - ) -> RenderContext<'a> { - RenderContext { completion, is_private_editable } + pub(crate) fn new(completion: &'a CompletionContext<'a>) -> RenderContext<'a> { + RenderContext { completion, is_private_editable: false, import_to_add: None } + } + + pub(crate) fn private_editable(mut self, private_editable: bool) -> Self { + self.is_private_editable = private_editable; + self + } + + pub(crate) fn import_to_add(mut self, import_to_add: Option) -> Self { + self.import_to_add = import_to_add; + self } fn snippet_cap(&self) -> Option { @@ -139,6 +146,14 @@ pub(crate) fn render_resolution( render_resolution_(ctx, local_name, None, resolution) } +pub(crate) fn render_resolution_simple( + ctx: RenderContext<'_>, + local_name: hir::Name, + resolution: ScopeDef, +) -> CompletionItem { + render_resolution_simple_(ctx, local_name, None, resolution) +} + pub(crate) fn render_resolution_with_import( ctx: RenderContext<'_>, import_edit: ImportEdit, @@ -162,31 +177,42 @@ fn render_resolution_( let _p = profile::span("render_resolution"); use hir::ModuleDef::*; - let db = ctx.db(); - - let kind = match resolution { + match resolution { + ScopeDef::ModuleDef(Macro(mac)) => { + let ctx = ctx.import_to_add(import_to_add); + return render_macro(ctx, local_name, mac); + } ScopeDef::ModuleDef(Function(func)) => { - return render_fn(ctx, import_to_add, Some(local_name), func); + let ctx = ctx.import_to_add(import_to_add); + return render_fn(ctx, Some(local_name), func); } ScopeDef::ModuleDef(Variant(var)) if ctx.completion.pattern_ctx.is_none() => { - return render_variant(ctx, import_to_add, Some(local_name), var, None); - } - ScopeDef::ModuleDef(Macro(mac)) => { - return render_macro(ctx, import_to_add, local_name, mac) - } - ScopeDef::Unknown => { - let mut item = CompletionItem::new( - CompletionItemKind::UnresolvedReference, - ctx.source_range(), - local_name.to_smol_str(), - ); - if let Some(import_to_add) = import_to_add { - item.add_import(import_to_add); + let ctx = ctx.clone().import_to_add(import_to_add.clone()); + if let Some(item) = render_variant_lit(ctx, Some(local_name.clone()), var, None) { + return item; } - return item.build(); } + _ => (), + } + render_resolution_simple_(ctx, local_name, import_to_add, resolution) +} +fn render_resolution_simple_( + ctx: RenderContext<'_>, + local_name: hir::Name, + import_to_add: Option, + resolution: ScopeDef, +) -> CompletionItem { + let _p = profile::span("render_resolution"); + use hir::ModuleDef::*; + + let db = ctx.db(); + let ctx = ctx.import_to_add(import_to_add); + let kind = match resolution { + ScopeDef::Unknown => CompletionItemKind::UnresolvedReference, + ScopeDef::ModuleDef(Function(_)) => CompletionItemKind::SymbolKind(SymbolKind::Function), ScopeDef::ModuleDef(Variant(_)) => CompletionItemKind::SymbolKind(SymbolKind::Variant), + ScopeDef::ModuleDef(Macro(_)) => CompletionItemKind::SymbolKind(SymbolKind::Macro), ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::SymbolKind(SymbolKind::Module), ScopeDef::ModuleDef(Adt(adt)) => CompletionItemKind::SymbolKind(match adt { hir::Adt::Struct(_) => SymbolKind::Struct, @@ -253,7 +279,7 @@ fn render_resolution_( item.set_documentation(scope_def_docs(db, resolution)) .set_deprecated(scope_def_is_deprecated(&ctx, resolution)); - if let Some(import_to_add) = import_to_add { + if let Some(import_to_add) = ctx.import_to_add { item.add_import(import_to_add); } item.build() @@ -577,7 +603,7 @@ fn main() { let _: m::Spam = S$0 } kind: SymbolKind( Variant, ), - lookup: "Spam::Bar", + lookup: "Spam::Bar(…)", detail: "m::Spam::Bar(i32)", relevance: CompletionRelevance { exact_name_match: false, @@ -1156,6 +1182,7 @@ fn main() { "#, expect![[r#" lc s [type+name+local] + st S [type] st S [] fn main() [] fn foo(…) [] @@ -1172,6 +1199,7 @@ fn main() { "#, expect![[r#" lc ssss [type+local] + st S [type] st S [] fn main() [] fn foo(…) [] diff --git a/crates/ide_completion/src/render/enum_variant.rs b/crates/ide_completion/src/render/enum_variant.rs deleted file mode 100644 index 5b485005d3f..00000000000 --- a/crates/ide_completion/src/render/enum_variant.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! Renderer for `enum` variants. - -use hir::{HasAttrs, StructKind}; -use ide_db::SymbolKind; -use syntax::SmolStr; - -use crate::{ - item::{CompletionItem, ImportEdit}, - render::{ - compound::{format_literal_label, render_record, render_tuple, RenderedCompound}, - compute_ref_match, compute_type_match, RenderContext, - }, - CompletionRelevance, -}; - -pub(crate) fn render_variant( - ctx: RenderContext<'_>, - import_to_add: Option, - local_name: Option, - variant: hir::Variant, - path: Option, -) -> CompletionItem { - let _p = profile::span("render_enum_variant"); - render(ctx, local_name, variant, path, import_to_add) -} - -fn render( - ctx @ RenderContext { completion, .. }: RenderContext<'_>, - local_name: Option, - variant: hir::Variant, - path: Option, - import_to_add: Option, -) -> CompletionItem { - let db = completion.db; - let name = local_name.unwrap_or_else(|| variant.name(db)); - let variant_kind = variant.kind(db); - - let (qualified_name, short_qualified_name, qualified) = match path { - Some(path) => { - let short = hir::ModPath::from_segments( - hir::PathKind::Plain, - path.segments().iter().skip(path.segments().len().saturating_sub(2)).cloned(), - ); - (path, short, true) - } - None => (name.clone().into(), name.into(), false), - }; - let qualified_name = qualified_name.to_string(); - let short_qualified_name: SmolStr = short_qualified_name.to_string().into(); - - let mut rendered = match variant_kind { - StructKind::Tuple => { - render_tuple(db, ctx.snippet_cap(), &variant.fields(db), Some(&qualified_name)) - } - StructKind::Record => { - render_record(db, ctx.snippet_cap(), &variant.fields(db), Some(&qualified_name)) - } - StructKind::Unit => { - RenderedCompound { literal: qualified_name.clone(), detail: qualified_name.clone() } - } - }; - - if ctx.snippet_cap().is_some() { - rendered.literal.push_str("$0"); - } - - let mut item = CompletionItem::new( - SymbolKind::Variant, - ctx.source_range(), - format_literal_label(&qualified_name, variant_kind), - ); - - item.set_documentation(variant.docs(db)) - .set_deprecated(ctx.is_deprecated(variant)) - .detail(rendered.detail); - - match ctx.snippet_cap() { - Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal), - None => item.insert_text(rendered.literal), - }; - - if let Some(import_to_add) = import_to_add { - item.add_import(import_to_add); - } - - if qualified { - item.lookup_by(short_qualified_name); - } - - let ty = variant.parent_enum(completion.db).ty(completion.db); - item.set_relevance(CompletionRelevance { - type_match: compute_type_match(completion, &ty), - ..ctx.completion_relevance() - }); - - if let Some(ref_match) = compute_ref_match(completion, &ty) { - item.ref_match(ref_match); - } - - item.build() -} diff --git a/crates/ide_completion/src/render/function.rs b/crates/ide_completion/src/render/function.rs index 2b9f82fc54e..7df13988ad6 100644 --- a/crates/ide_completion/src/render/function.rs +++ b/crates/ide_completion/src/render/function.rs @@ -8,7 +8,7 @@ use syntax::SmolStr; use crate::{ context::{CompletionContext, PathCompletionCtx, PathKind}, - item::{Builder, CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit}, + item::{Builder, CompletionItem, CompletionItemKind, CompletionRelevance}, patterns::ImmediateLocation, render::{compute_exact_name_match, compute_ref_match, compute_type_match, RenderContext}, }; @@ -20,23 +20,21 @@ enum FuncKind { pub(crate) fn render_fn( ctx: RenderContext<'_>, - import_to_add: Option, local_name: Option, func: hir::Function, ) -> CompletionItem { let _p = profile::span("render_fn"); - render(ctx, local_name, func, FuncKind::Function, import_to_add) + render(ctx, local_name, func, FuncKind::Function) } pub(crate) fn render_method( ctx: RenderContext<'_>, - import_to_add: Option, receiver: Option, local_name: Option, func: hir::Function, ) -> CompletionItem { let _p = profile::span("render_method"); - render(ctx, local_name, func, FuncKind::Method(receiver), import_to_add) + render(ctx, local_name, func, FuncKind::Method(receiver)) } fn render( @@ -44,7 +42,6 @@ fn render( local_name: Option, func: hir::Function, func_kind: FuncKind, - import_to_add: Option, ) -> CompletionItem { let db = completion.db; @@ -98,17 +95,18 @@ fn render( _ => (), } - if import_to_add.is_none() { - if let Some(actm) = func.as_assoc_item(db) { - if let Some(trt) = actm.containing_trait_or_trait_impl(db) { - item.trait_name(trt.name(db).to_smol_str()); + match ctx.import_to_add { + Some(import_to_add) => { + item.add_import(import_to_add); + } + None => { + if let Some(actm) = func.as_assoc_item(db) { + if let Some(trt) = actm.containing_trait_or_trait_impl(db) { + item.trait_name(trt.name(db).to_smol_str()); + } } } } - - if let Some(import_to_add) = import_to_add { - item.add_import(import_to_add); - } item.build() } @@ -192,7 +190,7 @@ fn should_add_parens(ctx: &CompletionContext) -> bool { Some(PathCompletionCtx { kind: Some(PathKind::Expr), has_call_parens: true, .. }) => { return false } - Some(PathCompletionCtx { kind: Some(PathKind::Use), .. }) => { + Some(PathCompletionCtx { kind: Some(PathKind::Use | PathKind::Type), .. }) => { cov_mark::hit!(no_parens_in_use_item); return false; } diff --git a/crates/ide_completion/src/render/literal.rs b/crates/ide_completion/src/render/literal.rs new file mode 100644 index 00000000000..d91e80f90c0 --- /dev/null +++ b/crates/ide_completion/src/render/literal.rs @@ -0,0 +1,174 @@ +//! Renderer for `enum` variants. + +use hir::{db::HirDatabase, Documentation, HasAttrs, StructKind}; +use ide_db::SymbolKind; + +use crate::{ + context::{CompletionContext, PathCompletionCtx}, + item::CompletionItem, + render::{ + compute_ref_match, compute_type_match, + variant::{ + format_literal_label, render_record_lit, render_tuple_lit, visible_fields, + RenderedLiteral, + }, + RenderContext, + }, + CompletionItemKind, CompletionRelevance, +}; + +pub(crate) fn render_variant_lit( + ctx: RenderContext<'_>, + local_name: Option, + variant: hir::Variant, + path: Option, +) -> Option { + let _p = profile::span("render_enum_variant"); + let db = ctx.db(); + + let name = local_name.unwrap_or_else(|| variant.name(db)); + render(ctx, Variant::EnumVariant(variant), name, path) +} + +pub(crate) fn render_struct_literal( + ctx: RenderContext<'_>, + strukt: hir::Struct, + path: Option, + local_name: Option, +) -> Option { + let _p = profile::span("render_struct_literal"); + let db = ctx.db(); + + let name = local_name.unwrap_or_else(|| strukt.name(db)); + render(ctx, Variant::Struct(strukt), name, path) +} + +fn render( + ctx @ RenderContext { completion, .. }: RenderContext<'_>, + thing: Variant, + name: hir::Name, + path: Option, +) -> Option { + if let Some(PathCompletionCtx { has_call_parens: true, .. }) = completion.path_context { + return None; + } + let db = completion.db; + let fields = thing.fields(completion)?; + + let (qualified_name, short_qualified_name, qualified) = match path { + Some(path) => { + let short = hir::ModPath::from_segments( + hir::PathKind::Plain, + path.segments().iter().skip(path.segments().len().saturating_sub(2)).cloned(), + ); + (path, short, true) + } + None => (name.clone().into(), name.into(), false), + }; + let qualified_name = qualified_name.to_string(); + let snippet_cap = ctx.snippet_cap(); + + let kind = thing.kind(db); + let mut rendered = match kind { + StructKind::Tuple => render_tuple_lit(db, snippet_cap, &fields, &qualified_name), + StructKind::Record => render_record_lit(db, snippet_cap, &fields, &qualified_name), + StructKind::Unit => { + RenderedLiteral { literal: qualified_name.clone(), detail: qualified_name.clone() } + } + }; + + if snippet_cap.is_some() { + rendered.literal.push_str("$0"); + } + + let mut item = CompletionItem::new( + CompletionItemKind::SymbolKind(thing.symbol_kind()), + ctx.source_range(), + format_literal_label(&qualified_name, kind), + ); + + item.detail(rendered.detail); + + match snippet_cap { + Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal), + None => item.insert_text(rendered.literal), + }; + + if qualified { + item.lookup_by(format_literal_label(&short_qualified_name.to_string(), kind)); + } + item.set_documentation(thing.docs(db)).set_deprecated(thing.is_deprecated(&ctx)); + + let ty = thing.ty(db); + item.set_relevance(CompletionRelevance { + type_match: compute_type_match(ctx.completion, &ty), + ..ctx.completion_relevance() + }); + if let Some(ref_match) = compute_ref_match(completion, &ty) { + item.ref_match(ref_match); + } + + if let Some(import_to_add) = ctx.import_to_add { + item.add_import(import_to_add); + } + Some(item.build()) +} + +#[derive(Clone, Copy)] +enum Variant { + Struct(hir::Struct), + EnumVariant(hir::Variant), +} + +impl Variant { + fn fields(self, ctx: &CompletionContext) -> Option> { + let fields = match self { + Variant::Struct(it) => it.fields(ctx.db), + Variant::EnumVariant(it) => it.fields(ctx.db), + }; + let (visible_fields, fields_omitted) = match self { + Variant::Struct(it) => visible_fields(ctx, &fields, it)?, + Variant::EnumVariant(it) => visible_fields(ctx, &fields, it)?, + }; + if !fields_omitted { + Some(visible_fields) + } else { + None + } + } + + fn kind(self, db: &dyn HirDatabase) -> StructKind { + match self { + Variant::Struct(it) => it.kind(db), + Variant::EnumVariant(it) => it.kind(db), + } + } + + fn symbol_kind(self) -> SymbolKind { + match self { + Variant::Struct(_) => SymbolKind::Struct, + Variant::EnumVariant(_) => SymbolKind::Variant, + } + } + + fn docs(self, db: &dyn HirDatabase) -> Option { + match self { + Variant::Struct(it) => it.docs(db), + Variant::EnumVariant(it) => it.docs(db), + } + } + + fn is_deprecated(self, ctx: &RenderContext<'_>) -> bool { + match self { + Variant::Struct(it) => ctx.is_deprecated(it), + Variant::EnumVariant(it) => ctx.is_deprecated(it), + } + } + + fn ty(self, db: &dyn HirDatabase) -> hir::Type { + match self { + Variant::Struct(it) => it.ty(db), + Variant::EnumVariant(it) => it.parent_enum(db).ty(db), + } + } +} diff --git a/crates/ide_completion/src/render/macro_.rs b/crates/ide_completion/src/render/macro_.rs index d3b0de429ca..9f848febeb3 100644 --- a/crates/ide_completion/src/render/macro_.rs +++ b/crates/ide_completion/src/render/macro_.rs @@ -4,27 +4,21 @@ use hir::{Documentation, HirDisplay}; use ide_db::SymbolKind; use syntax::SmolStr; -use crate::{ - context::PathKind, - item::{CompletionItem, ImportEdit}, - render::RenderContext, -}; +use crate::{context::PathKind, item::CompletionItem, render::RenderContext}; pub(crate) fn render_macro( ctx: RenderContext<'_>, - import_to_add: Option, name: hir::Name, macro_: hir::Macro, ) -> CompletionItem { let _p = profile::span("render_macro"); - render(ctx, name, macro_, import_to_add) + render(ctx, name, macro_) } fn render( ctx @ RenderContext { completion, .. }: RenderContext<'_>, name: hir::Name, macro_: hir::Macro, - import_to_add: Option, ) -> CompletionItem { let source_range = if completion.is_immediately_after_macro_bang() { cov_mark::hit!(completes_macro_call_if_cursor_at_bang_token); @@ -52,12 +46,7 @@ fn render( .set_documentation(docs) .set_relevance(ctx.completion_relevance()); - if let Some(import_to_add) = import_to_add { - item.add_import(import_to_add); - } - let name = &*name; - match ctx.snippet_cap() { Some(cap) if needs_bang && !completion.path_is_call() => { let snippet = format!("{}!{}$0{}", name, bra, ket); @@ -73,6 +62,9 @@ fn render( item.insert_text(name); } }; + if let Some(import_to_add) = ctx.import_to_add { + item.add_import(import_to_add); + } item.build() } diff --git a/crates/ide_completion/src/render/pattern.rs b/crates/ide_completion/src/render/pattern.rs index c2d51b1252c..efceb85718d 100644 --- a/crates/ide_completion/src/render/pattern.rs +++ b/crates/ide_completion/src/render/pattern.rs @@ -1,13 +1,13 @@ //! Renderer for patterns. -use hir::{db::HirDatabase, HasAttrs, HasVisibility, Name, StructKind}; +use hir::{db::HirDatabase, HasAttrs, Name, StructKind}; use ide_db::SnippetCap; use itertools::Itertools; use syntax::SmolStr; use crate::{ context::{ParamKind, PatternContext}, - render::RenderContext, + render::{variant::visible_fields, RenderContext}, CompletionItem, CompletionItemKind, }; @@ -19,7 +19,7 @@ pub(crate) fn render_struct_pat( let _p = profile::span("render_struct_pat"); let fields = strukt.fields(ctx.db()); - let (visible_fields, fields_omitted) = visible_fields(&ctx, &fields, strukt)?; + let (visible_fields, fields_omitted) = visible_fields(ctx.completion, &fields, strukt)?; if visible_fields.is_empty() { // Matching a struct without matching its fields is pointless, unlike matching a Variant without its fields @@ -36,14 +36,14 @@ pub(crate) fn render_variant_pat( ctx: RenderContext<'_>, variant: hir::Variant, local_name: Option, - path: Option, + path: Option<&hir::ModPath>, ) -> Option { let _p = profile::span("render_variant_pat"); let fields = variant.fields(ctx.db()); - let (visible_fields, fields_omitted) = visible_fields(&ctx, &fields, variant)?; + let (visible_fields, fields_omitted) = visible_fields(ctx.completion, &fields, variant)?; - let name = match &path { + let name = match path { Some(path) => path.to_string().into(), None => local_name.unwrap_or_else(|| variant.name(ctx.db())).to_smol_str(), }; @@ -78,9 +78,7 @@ fn render_pat( fields_omitted: bool, ) -> Option { let mut pat = match kind { - StructKind::Tuple if ctx.snippet_cap().is_some() => { - render_tuple_as_pat(fields, name, fields_omitted) - } + StructKind::Tuple => render_tuple_as_pat(ctx.snippet_cap(), fields, name, fields_omitted), StructKind::Record => { render_record_as_pat(ctx.db(), ctx.snippet_cap(), fields, name, fields_omitted) } @@ -113,49 +111,53 @@ fn render_record_as_pat( fields_omitted: bool, ) -> String { let fields = fields.iter(); - if snippet_cap.is_some() { - format!( - "{name} {{ {}{} }}", - fields - .enumerate() - .map(|(idx, field)| format!("{}${}", field.name(db), idx + 1)) - .format(", "), - if fields_omitted { ", .." } else { "" }, - name = name - ) - } else { - format!( - "{name} {{ {}{} }}", - fields.map(|field| field.name(db)).format(", "), - if fields_omitted { ", .." } else { "" }, - name = name - ) + match snippet_cap { + Some(_) => { + format!( + "{name} {{ {}{} }}", + fields.enumerate().format_with(", ", |(idx, field), f| { + f(&format_args!("{}${}", field.name(db), idx + 1)) + }), + if fields_omitted { ", .." } else { "" }, + name = name + ) + } + None => { + format!( + "{name} {{ {}{} }}", + fields.map(|field| field.name(db)).format(", "), + if fields_omitted { ", .." } else { "" }, + name = name + ) + } } } -fn render_tuple_as_pat(fields: &[hir::Field], name: &str, fields_omitted: bool) -> String { - format!( - "{name}({}{})", - fields.iter().enumerate().map(|(idx, _)| format!("${}", idx + 1)).format(", "), - if fields_omitted { ", .." } else { "" }, - name = name - ) -} - -fn visible_fields( - ctx: &RenderContext<'_>, +fn render_tuple_as_pat( + snippet_cap: Option, fields: &[hir::Field], - item: impl HasAttrs, -) -> Option<(Vec, bool)> { - let module = ctx.completion.module?; - let n_fields = fields.len(); - let fields = fields - .iter() - .filter(|field| field.is_visible_from(ctx.db(), module)) - .copied() - .collect::>(); - - let fields_omitted = - n_fields - fields.len() > 0 || item.attrs(ctx.db()).by_key("non_exhaustive").exists(); - Some((fields, fields_omitted)) + name: &str, + fields_omitted: bool, +) -> String { + let fields = fields.iter(); + match snippet_cap { + Some(_) => { + format!( + "{name}({}{})", + fields + .enumerate() + .format_with(", ", |(idx, _), f| { f(&format_args!("${}", idx + 1)) }), + if fields_omitted { ", .." } else { "" }, + name = name + ) + } + None => { + format!( + "{name}({}{})", + fields.enumerate().map(|(idx, _)| idx).format(", "), + if fields_omitted { ", .." } else { "" }, + name = name + ) + } + } } diff --git a/crates/ide_completion/src/render/struct_literal.rs b/crates/ide_completion/src/render/struct_literal.rs deleted file mode 100644 index a686be66913..00000000000 --- a/crates/ide_completion/src/render/struct_literal.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! Renderer for `struct` literal. - -use hir::{HasAttrs, Name, StructKind}; -use syntax::SmolStr; - -use crate::{ - render::compound::{ - format_literal_label, render_record, render_tuple, visible_fields, RenderedCompound, - }, - render::RenderContext, - CompletionItem, CompletionItemKind, -}; - -pub(crate) fn render_struct_literal( - ctx: RenderContext<'_>, - strukt: hir::Struct, - path: Option, - local_name: Option, -) -> Option { - let _p = profile::span("render_struct_literal"); - - let fields = strukt.fields(ctx.db()); - let (visible_fields, fields_omitted) = visible_fields(&ctx, &fields, strukt)?; - - if fields_omitted { - // If some fields are private you can't make `struct` literal. - return None; - } - - let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())).to_smol_str(); - - let rendered = render_literal(&ctx, path, &name, strukt.kind(ctx.db()), &visible_fields)?; - - Some(build_completion(&ctx, name, rendered, strukt.kind(ctx.db()), strukt)) -} - -fn build_completion( - ctx: &RenderContext<'_>, - name: SmolStr, - rendered: RenderedCompound, - kind: StructKind, - def: impl HasAttrs + Copy, -) -> CompletionItem { - let mut item = CompletionItem::new( - CompletionItemKind::Snippet, - ctx.source_range(), - format_literal_label(&name, kind), - ); - - item.set_documentation(ctx.docs(def)) - .set_deprecated(ctx.is_deprecated(def)) - .detail(&rendered.detail) - .set_relevance(ctx.completion_relevance()); - match ctx.snippet_cap() { - Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal), - None => item.insert_text(rendered.literal), - }; - item.build() -} - -fn render_literal( - ctx: &RenderContext<'_>, - path: Option, - name: &str, - kind: StructKind, - fields: &[hir::Field], -) -> Option { - let path_string; - - let qualified_name = if let Some(path) = path { - path_string = path.to_string(); - &path_string - } else { - name - }; - - let mut rendered = match kind { - StructKind::Tuple if ctx.snippet_cap().is_some() => { - render_tuple(ctx.db(), ctx.snippet_cap(), fields, Some(qualified_name)) - } - StructKind::Record => { - render_record(ctx.db(), ctx.snippet_cap(), fields, Some(qualified_name)) - } - _ => return None, - }; - - if ctx.snippet_cap().is_some() { - rendered.literal.push_str("$0"); - } - Some(rendered) -} diff --git a/crates/ide_completion/src/render/union_literal.rs b/crates/ide_completion/src/render/union_literal.rs index 80499e102b4..aafedaf5aa7 100644 --- a/crates/ide_completion/src/render/union_literal.rs +++ b/crates/ide_completion/src/render/union_literal.rs @@ -1,11 +1,12 @@ //! Renderer for `union` literals. use hir::{HirDisplay, Name, StructKind}; +use ide_db::SymbolKind; use itertools::Itertools; use crate::{ render::{ - compound::{format_literal_label, visible_fields}, + variant::{format_literal_label, visible_fields}, RenderContext, }, CompletionItem, CompletionItemKind, @@ -25,13 +26,13 @@ pub(crate) fn render_union_literal( }; let mut item = CompletionItem::new( - CompletionItemKind::Snippet, + CompletionItemKind::SymbolKind(SymbolKind::Union), ctx.source_range(), format_literal_label(&name, StructKind::Record), ); let fields = un.fields(ctx.db()); - let (fields, fields_omitted) = visible_fields(&ctx, &fields, un)?; + let (fields, fields_omitted) = visible_fields(ctx.completion, &fields, un)?; if fields.is_empty() { return None; diff --git a/crates/ide_completion/src/render/compound.rs b/crates/ide_completion/src/render/variant.rs similarity index 76% rename from crates/ide_completion/src/render/compound.rs rename to crates/ide_completion/src/render/variant.rs index c7f3bd1f79a..a37b4237c45 100644 --- a/crates/ide_completion/src/render/compound.rs +++ b/crates/ide_completion/src/render/variant.rs @@ -1,6 +1,6 @@ //! Code common to structs, unions, and enum variants. -use crate::render::RenderContext; +use crate::context::CompletionContext; use hir::{db::HirDatabase, HasAttrs, HasVisibility, HirDisplay, StructKind}; use ide_db::SnippetCap; use itertools::Itertools; @@ -9,19 +9,19 @@ use syntax::SmolStr; /// A rendered struct, union, or enum variant, split into fields for actual /// auto-completion (`literal`, using `field: ()`) and display in the /// completions menu (`detail`, using `field: type`). -pub(crate) struct RenderedCompound { +pub(crate) struct RenderedLiteral { pub(crate) literal: String, pub(crate) detail: String, } /// Render a record type (or sub-type) to a `RenderedCompound`. Use `None` for /// the `name` argument for an anonymous type. -pub(crate) fn render_record( +pub(crate) fn render_record_lit( db: &dyn HirDatabase, snippet_cap: Option, fields: &[hir::Field], - name: Option<&str>, -) -> RenderedCompound { + path: &str, +) -> RenderedLiteral { let completions = fields.iter().enumerate().format_with(", ", |(idx, field), f| { if snippet_cap.is_some() { f(&format_args!("{}: ${{{}:()}}", field.name(db), idx + 1)) @@ -34,20 +34,20 @@ pub(crate) fn render_record( f(&format_args!("{}: {}", field.name(db), field.ty(db).display(db))) }); - RenderedCompound { - literal: format!("{} {{ {} }}", name.unwrap_or(""), completions), - detail: format!("{} {{ {} }}", name.unwrap_or(""), types), + RenderedLiteral { + literal: format!("{} {{ {} }}", path, completions), + detail: format!("{} {{ {} }}", path, types), } } /// Render a tuple type (or sub-type) to a `RenderedCompound`. Use `None` for /// the `name` argument for an anonymous type. -pub(crate) fn render_tuple( +pub(crate) fn render_tuple_lit( db: &dyn HirDatabase, snippet_cap: Option, fields: &[hir::Field], - name: Option<&str>, -) -> RenderedCompound { + path: &str, +) -> RenderedLiteral { let completions = fields.iter().enumerate().format_with(", ", |(idx, _), f| { if snippet_cap.is_some() { f(&format_args!("${{{}:()}}", idx + 1)) @@ -58,9 +58,9 @@ pub(crate) fn render_tuple( let types = fields.iter().format_with(", ", |field, f| f(&field.ty(db).display(db))); - RenderedCompound { - literal: format!("{}({})", name.unwrap_or(""), completions), - detail: format!("{}({})", name.unwrap_or(""), types), + RenderedLiteral { + literal: format!("{}({})", path, completions), + detail: format!("{}({})", path, types), } } @@ -68,20 +68,20 @@ pub(crate) fn render_tuple( /// fields, plus a boolean for whether the list is comprehensive (contains no /// private fields and its item is not marked `#[non_exhaustive]`). pub(crate) fn visible_fields( - ctx: &RenderContext<'_>, + ctx: &CompletionContext, fields: &[hir::Field], item: impl HasAttrs, ) -> Option<(Vec, bool)> { - let module = ctx.completion.module?; + let module = ctx.module?; let n_fields = fields.len(); let fields = fields .iter() - .filter(|field| field.is_visible_from(ctx.db(), module)) + .filter(|field| field.is_visible_from(ctx.db, module)) .copied() .collect::>(); let fields_omitted = - n_fields - fields.len() > 0 || item.attrs(ctx.db()).by_key("non_exhaustive").exists(); + n_fields - fields.len() > 0 || item.attrs(ctx.db).by_key("non_exhaustive").exists(); Some((fields, fields_omitted)) } diff --git a/crates/ide_completion/src/tests/pattern.rs b/crates/ide_completion/src/tests/pattern.rs index 891c1346dfc..50d5e01979b 100644 --- a/crates/ide_completion/src/tests/pattern.rs +++ b/crates/ide_completion/src/tests/pattern.rs @@ -150,6 +150,7 @@ fn foo() { bn Tuple Tuple($1)$0 st Tuple ev Variant + md module en SingleVariantEnum st Unit ma makro!(…) macro_rules! makro @@ -171,6 +172,7 @@ fn foo(a$0) { st Record bn Tuple Tuple($1): Tuple$0 st Tuple + md module st Unit ma makro!(…) macro_rules! makro "#]], @@ -187,6 +189,7 @@ fn foo(a$0: Tuple) { st Record bn Tuple Tuple($1)$0 st Tuple + md module st Unit ma makro!(…) macro_rules! makro "#]], @@ -228,7 +231,6 @@ fn foo() { expect![[r#" kw ref kw mut - ev E::X E::X en E ma m!(…) macro_rules! m "#]], @@ -378,3 +380,67 @@ fn foo() { "#]], ) } + +#[test] +fn completes_no_delims_if_existing() { + check_empty( + r#" +struct Bar(u32); +fn foo() { + match Bar(0) { + B$0(b) => {} + } +} +"#, + expect![[r#" + kw self:: + kw super:: + kw crate:: + "#]], + ); + check_empty( + r#" +struct Foo { bar: u32 } +fn foo() { + match Foo { bar: 0 } { + F$0 { bar } => {} + } +} +"#, + expect![[r#" + kw return + kw self + kw super + kw crate + st Foo + fn foo() fn() + bt u32 + "#]], + ); + check_empty( + r#" +enum Enum { + TupleVariant(u32) +} +fn foo() { + match Enum::TupleVariant(0) { + Enum::T$0(b) => {} + } +} +"#, + expect![[r#""#]], + ); + check_empty( + r#" +enum Enum { + RecordVariant { field: u32 } +} +fn foo() { + match (Enum::RecordVariant { field: 0 }) { + Enum::RecordV$0 { field } => {} + } +} +"#, + expect![[r#""#]], + ); +} diff --git a/crates/ide_completion/src/tests/record.rs b/crates/ide_completion/src/tests/record.rs index 5e9367960f7..0322ecbe39f 100644 --- a/crates/ide_completion/src/tests/record.rs +++ b/crates/ide_completion/src/tests/record.rs @@ -166,7 +166,7 @@ fn main() { kw true kw false kw return - sn Foo {…} Foo { foo1: u32, foo2: u32 } + st Foo {…} Foo { foo1: u32, foo2: u32 } fd ..Default::default() fd foo1 u32 fd foo2 u32