From 3db64a400c78bbd2708e67ddc07df1001fff3f29 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 17 Feb 2021 17:53:31 +0300 Subject: rename completion -> ide_completion We don't have completion-related PRs in flight, so lets do it --- crates/ide_completion/src/render/builder_ext.rs | 94 ++++++ crates/ide_completion/src/render/const_.rs | 59 ++++ crates/ide_completion/src/render/enum_variant.rs | 131 +++++++++ crates/ide_completion/src/render/function.rs | 345 +++++++++++++++++++++++ crates/ide_completion/src/render/macro_.rs | 214 ++++++++++++++ crates/ide_completion/src/render/pattern.rs | 150 ++++++++++ crates/ide_completion/src/render/type_alias.rs | 59 ++++ 7 files changed, 1052 insertions(+) create mode 100644 crates/ide_completion/src/render/builder_ext.rs create mode 100644 crates/ide_completion/src/render/const_.rs create mode 100644 crates/ide_completion/src/render/enum_variant.rs create mode 100644 crates/ide_completion/src/render/function.rs create mode 100644 crates/ide_completion/src/render/macro_.rs create mode 100644 crates/ide_completion/src/render/pattern.rs create mode 100644 crates/ide_completion/src/render/type_alias.rs (limited to 'crates/ide_completion/src/render') diff --git a/crates/ide_completion/src/render/builder_ext.rs b/crates/ide_completion/src/render/builder_ext.rs new file mode 100644 index 000000000..d053a988b --- /dev/null +++ b/crates/ide_completion/src/render/builder_ext.rs @@ -0,0 +1,94 @@ +//! Extensions for `Builder` structure required for item rendering. + +use itertools::Itertools; +use test_utils::mark; + +use crate::{item::Builder, CompletionContext}; + +#[derive(Debug)] +pub(super) enum Params { + Named(Vec), + Anonymous(usize), +} + +impl Params { + pub(super) fn len(&self) -> usize { + match self { + Params::Named(xs) => xs.len(), + Params::Anonymous(len) => *len, + } + } + + pub(super) fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl Builder { + fn should_add_parens(&self, ctx: &CompletionContext) -> bool { + if !ctx.config.add_call_parenthesis { + return false; + } + if ctx.use_item_syntax.is_some() { + mark::hit!(no_parens_in_use_item); + return false; + } + if ctx.is_pattern_call { + return false; + } + if ctx.is_call { + return false; + } + + // Don't add parentheses if the expected type is some function reference. + if let Some(ty) = &ctx.expected_type { + if ty.is_fn() { + mark::hit!(no_call_parens_if_fn_ptr_needed); + return false; + } + } + + // Nothing prevents us from adding parentheses + true + } + + pub(super) fn add_call_parens( + mut self, + ctx: &CompletionContext, + name: String, + params: Params, + ) -> Builder { + if !self.should_add_parens(ctx) { + return self; + } + + let cap = match ctx.config.snippet_cap { + Some(it) => it, + None => return self, + }; + // If not an import, add parenthesis automatically. + mark::hit!(inserts_parens_for_function_calls); + + let (snippet, label) = if params.is_empty() { + (format!("{}()$0", name), format!("{}()", name)) + } else { + self = self.trigger_call_info(); + let snippet = match (ctx.config.add_call_argument_snippets, params) { + (true, Params::Named(params)) => { + let function_params_snippet = + params.iter().enumerate().format_with(", ", |(index, param_name), f| { + f(&format_args!("${{{}:{}}}", index + 1, param_name)) + }); + format!("{}({})$0", name, function_params_snippet) + } + _ => { + mark::hit!(suppress_arg_snippets); + format!("{}($0)", name) + } + }; + + (snippet, format!("{}(…)", name)) + }; + self.lookup_by(name).label(label).insert_snippet(cap, snippet) + } +} diff --git a/crates/ide_completion/src/render/const_.rs b/crates/ide_completion/src/render/const_.rs new file mode 100644 index 000000000..5010b642a --- /dev/null +++ b/crates/ide_completion/src/render/const_.rs @@ -0,0 +1,59 @@ +//! Renderer for `const` fields. + +use hir::HasSource; +use ide_db::SymbolKind; +use syntax::{ + ast::{Const, NameOwner}, + display::const_label, +}; + +use crate::{ + item::{CompletionItem, CompletionKind}, + render::RenderContext, +}; + +pub(crate) fn render_const<'a>( + ctx: RenderContext<'a>, + const_: hir::Const, +) -> Option { + ConstRender::new(ctx, const_)?.render() +} + +#[derive(Debug)] +struct ConstRender<'a> { + ctx: RenderContext<'a>, + const_: hir::Const, + ast_node: Const, +} + +impl<'a> ConstRender<'a> { + fn new(ctx: RenderContext<'a>, const_: hir::Const) -> Option> { + let ast_node = const_.source(ctx.db())?.value; + Some(ConstRender { ctx, const_, ast_node }) + } + + fn render(self) -> Option { + let name = self.name()?; + let detail = self.detail(); + + let item = CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), name) + .kind(SymbolKind::Const) + .set_documentation(self.ctx.docs(self.const_)) + .set_deprecated( + self.ctx.is_deprecated(self.const_) + || self.ctx.is_deprecated_assoc_item(self.const_), + ) + .detail(detail) + .build(); + + Some(item) + } + + fn name(&self) -> Option { + self.ast_node.name().map(|name| name.text().to_string()) + } + + fn detail(&self) -> String { + const_label(&self.ast_node) + } +} diff --git a/crates/ide_completion/src/render/enum_variant.rs b/crates/ide_completion/src/render/enum_variant.rs new file mode 100644 index 000000000..9214193b4 --- /dev/null +++ b/crates/ide_completion/src/render/enum_variant.rs @@ -0,0 +1,131 @@ +//! Renderer for `enum` variants. + +use hir::{HasAttrs, HirDisplay, ModPath, StructKind}; +use ide_db::SymbolKind; +use itertools::Itertools; +use test_utils::mark; + +use crate::{ + item::{CompletionItem, CompletionKind, ImportEdit}, + render::{builder_ext::Params, RenderContext}, +}; + +pub(crate) fn render_variant<'a>( + ctx: RenderContext<'a>, + import_to_add: Option, + local_name: Option, + variant: hir::Variant, + path: Option, +) -> CompletionItem { + let _p = profile::span("render_enum_variant"); + EnumRender::new(ctx, local_name, variant, path).render(import_to_add) +} + +#[derive(Debug)] +struct EnumRender<'a> { + ctx: RenderContext<'a>, + name: String, + variant: hir::Variant, + path: Option, + qualified_name: String, + short_qualified_name: String, + variant_kind: StructKind, +} + +impl<'a> EnumRender<'a> { + fn new( + ctx: RenderContext<'a>, + local_name: Option, + variant: hir::Variant, + path: Option, + ) -> EnumRender<'a> { + let name = local_name.unwrap_or_else(|| variant.name(ctx.db()).to_string()); + let variant_kind = variant.kind(ctx.db()); + + let (qualified_name, short_qualified_name) = match &path { + Some(path) => { + let full = path.to_string(); + let segments = path.segments(); + let short = segments[segments.len().saturating_sub(2)..].iter().join("::"); + (full, short) + } + None => (name.to_string(), name.to_string()), + }; + + EnumRender { ctx, name, variant, path, qualified_name, short_qualified_name, variant_kind } + } + + fn render(self, import_to_add: Option) -> CompletionItem { + let mut builder = CompletionItem::new( + CompletionKind::Reference, + self.ctx.source_range(), + self.qualified_name.clone(), + ) + .kind(SymbolKind::Variant) + .set_documentation(self.variant.docs(self.ctx.db())) + .set_deprecated(self.ctx.is_deprecated(self.variant)) + .add_import(import_to_add) + .detail(self.detail()); + + if self.variant_kind == StructKind::Tuple { + mark::hit!(inserts_parens_for_tuple_enums); + let params = Params::Anonymous(self.variant.fields(self.ctx.db()).len()); + builder = + builder.add_call_parens(self.ctx.completion, self.short_qualified_name, params); + } else if self.path.is_some() { + builder = builder.lookup_by(self.short_qualified_name); + } + + builder.build() + } + + fn detail(&self) -> String { + let detail_types = self + .variant + .fields(self.ctx.db()) + .into_iter() + .map(|field| (field.name(self.ctx.db()), field.signature_ty(self.ctx.db()))); + + match self.variant_kind { + StructKind::Tuple | StructKind::Unit => format!( + "({})", + detail_types.map(|(_, t)| t.display(self.ctx.db()).to_string()).format(", ") + ), + StructKind::Record => format!( + "{{ {} }}", + detail_types + .map(|(n, t)| format!("{}: {}", n, t.display(self.ctx.db()).to_string())) + .format(", ") + ), + } + } +} + +#[cfg(test)] +mod tests { + use test_utils::mark; + + use crate::test_utils::check_edit; + + #[test] + fn inserts_parens_for_tuple_enums() { + mark::check!(inserts_parens_for_tuple_enums); + check_edit( + "Some", + r#" +enum Option { Some(T), None } +use Option::*; +fn main() -> Option { + Som$0 +} +"#, + r#" +enum Option { Some(T), None } +use Option::*; +fn main() -> Option { + Some($0) +} +"#, + ); + } +} diff --git a/crates/ide_completion/src/render/function.rs b/crates/ide_completion/src/render/function.rs new file mode 100644 index 000000000..e46e21d24 --- /dev/null +++ b/crates/ide_completion/src/render/function.rs @@ -0,0 +1,345 @@ +//! Renderer for function calls. + +use hir::{HasSource, HirDisplay, Type}; +use ide_db::SymbolKind; +use syntax::ast::Fn; +use test_utils::mark; + +use crate::{ + item::{CompletionItem, CompletionItemKind, CompletionKind, ImportEdit}, + render::{builder_ext::Params, RenderContext}, +}; + +pub(crate) fn render_fn<'a>( + ctx: RenderContext<'a>, + import_to_add: Option, + local_name: Option, + fn_: hir::Function, +) -> Option { + let _p = profile::span("render_fn"); + Some(FunctionRender::new(ctx, local_name, fn_)?.render(import_to_add)) +} + +#[derive(Debug)] +struct FunctionRender<'a> { + ctx: RenderContext<'a>, + name: String, + func: hir::Function, + ast_node: Fn, +} + +impl<'a> FunctionRender<'a> { + fn new( + ctx: RenderContext<'a>, + local_name: Option, + fn_: hir::Function, + ) -> Option> { + let name = local_name.unwrap_or_else(|| fn_.name(ctx.db()).to_string()); + let ast_node = fn_.source(ctx.db())?.value; + + Some(FunctionRender { ctx, name, func: fn_, ast_node }) + } + + fn render(self, import_to_add: Option) -> CompletionItem { + let params = self.params(); + CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), self.name.clone()) + .kind(self.kind()) + .set_documentation(self.ctx.docs(self.func)) + .set_deprecated( + self.ctx.is_deprecated(self.func) || self.ctx.is_deprecated_assoc_item(self.func), + ) + .detail(self.detail()) + .add_call_parens(self.ctx.completion, self.name, params) + .add_import(import_to_add) + .build() + } + + fn detail(&self) -> String { + let ty = self.func.ret_type(self.ctx.db()); + format!("-> {}", ty.display(self.ctx.db())) + } + + fn add_arg(&self, arg: &str, ty: &Type) -> String { + if let Some(derefed_ty) = ty.remove_ref() { + for (name, local) in self.ctx.completion.locals.iter() { + if name == arg && local.ty(self.ctx.db()) == derefed_ty { + let mutability = if ty.is_mutable_reference() { "&mut " } else { "&" }; + return format!("{}{}", mutability, arg); + } + } + } + arg.to_string() + } + + fn params(&self) -> Params { + let ast_params = match self.ast_node.param_list() { + Some(it) => it, + None => return Params::Named(Vec::new()), + }; + + let mut params_pats = Vec::new(); + let params_ty = if self.ctx.completion.dot_receiver.is_some() { + self.func.method_params(self.ctx.db()).unwrap_or_default() + } else { + if let Some(s) = ast_params.self_param() { + mark::hit!(parens_for_method_call_as_assoc_fn); + params_pats.push(Some(s.to_string())); + } + self.func.assoc_fn_params(self.ctx.db()) + }; + params_pats + .extend(ast_params.params().into_iter().map(|it| it.pat().map(|it| it.to_string()))); + + let params = params_pats + .into_iter() + .zip(params_ty) + .flat_map(|(pat, param_ty)| { + let pat = pat?; + let name = pat; + let arg = name.trim_start_matches("mut ").trim_start_matches('_'); + Some(self.add_arg(arg, param_ty.ty())) + }) + .collect(); + Params::Named(params) + } + + fn kind(&self) -> CompletionItemKind { + if self.func.self_param(self.ctx.db()).is_some() { + CompletionItemKind::Method + } else { + SymbolKind::Function.into() + } + } +} + +#[cfg(test)] +mod tests { + use test_utils::mark; + + use crate::{ + test_utils::{check_edit, check_edit_with_config, TEST_CONFIG}, + CompletionConfig, + }; + + #[test] + fn inserts_parens_for_function_calls() { + mark::check!(inserts_parens_for_function_calls); + check_edit( + "no_args", + r#" +fn no_args() {} +fn main() { no_$0 } +"#, + r#" +fn no_args() {} +fn main() { no_args()$0 } +"#, + ); + + check_edit( + "with_args", + r#" +fn with_args(x: i32, y: String) {} +fn main() { with_$0 } +"#, + r#" +fn with_args(x: i32, y: String) {} +fn main() { with_args(${1:x}, ${2:y})$0 } +"#, + ); + + check_edit( + "foo", + r#" +struct S; +impl S { + fn foo(&self) {} +} +fn bar(s: &S) { s.f$0 } +"#, + r#" +struct S; +impl S { + fn foo(&self) {} +} +fn bar(s: &S) { s.foo()$0 } +"#, + ); + + check_edit( + "foo", + r#" +struct S {} +impl S { + fn foo(&self, x: i32) {} +} +fn bar(s: &S) { + s.f$0 +} +"#, + r#" +struct S {} +impl S { + fn foo(&self, x: i32) {} +} +fn bar(s: &S) { + s.foo(${1:x})$0 +} +"#, + ); + } + + #[test] + fn parens_for_method_call_as_assoc_fn() { + mark::check!(parens_for_method_call_as_assoc_fn); + check_edit( + "foo", + r#" +struct S; +impl S { + fn foo(&self) {} +} +fn main() { S::f$0 } +"#, + r#" +struct S; +impl S { + fn foo(&self) {} +} +fn main() { S::foo(${1:&self})$0 } +"#, + ); + } + + #[test] + fn suppress_arg_snippets() { + mark::check!(suppress_arg_snippets); + check_edit_with_config( + CompletionConfig { add_call_argument_snippets: false, ..TEST_CONFIG }, + "with_args", + r#" +fn with_args(x: i32, y: String) {} +fn main() { with_$0 } +"#, + r#" +fn with_args(x: i32, y: String) {} +fn main() { with_args($0) } +"#, + ); + } + + #[test] + fn strips_underscores_from_args() { + check_edit( + "foo", + r#" +fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {} +fn main() { f$0 } +"#, + r#" +fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {} +fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_})$0 } +"#, + ); + } + + #[test] + fn insert_ref_when_matching_local_in_scope() { + check_edit( + "ref_arg", + r#" +struct Foo {} +fn ref_arg(x: &Foo) {} +fn main() { + let x = Foo {}; + ref_ar$0 +} +"#, + r#" +struct Foo {} +fn ref_arg(x: &Foo) {} +fn main() { + let x = Foo {}; + ref_arg(${1:&x})$0 +} +"#, + ); + } + + #[test] + fn insert_mut_ref_when_matching_local_in_scope() { + check_edit( + "ref_arg", + r#" +struct Foo {} +fn ref_arg(x: &mut Foo) {} +fn main() { + let x = Foo {}; + ref_ar$0 +} +"#, + r#" +struct Foo {} +fn ref_arg(x: &mut Foo) {} +fn main() { + let x = Foo {}; + ref_arg(${1:&mut x})$0 +} +"#, + ); + } + + #[test] + fn insert_ref_when_matching_local_in_scope_for_method() { + check_edit( + "apply_foo", + r#" +struct Foo {} +struct Bar {} +impl Bar { + fn apply_foo(&self, x: &Foo) {} +} + +fn main() { + let x = Foo {}; + let y = Bar {}; + y.$0 +} +"#, + r#" +struct Foo {} +struct Bar {} +impl Bar { + fn apply_foo(&self, x: &Foo) {} +} + +fn main() { + let x = Foo {}; + let y = Bar {}; + y.apply_foo(${1:&x})$0 +} +"#, + ); + } + + #[test] + fn trim_mut_keyword_in_func_completion() { + check_edit( + "take_mutably", + r#" +fn take_mutably(mut x: &i32) {} + +fn main() { + take_m$0 +} +"#, + r#" +fn take_mutably(mut x: &i32) {} + +fn main() { + take_mutably(${1:x})$0 +} +"#, + ); + } +} diff --git a/crates/ide_completion/src/render/macro_.rs b/crates/ide_completion/src/render/macro_.rs new file mode 100644 index 000000000..a4535786f --- /dev/null +++ b/crates/ide_completion/src/render/macro_.rs @@ -0,0 +1,214 @@ +//! Renderer for macro invocations. + +use hir::{Documentation, HasSource}; +use ide_db::SymbolKind; +use syntax::display::macro_label; +use test_utils::mark; + +use crate::{ + item::{CompletionItem, CompletionKind, ImportEdit}, + render::RenderContext, +}; + +pub(crate) fn render_macro<'a>( + ctx: RenderContext<'a>, + import_to_add: Option, + name: String, + macro_: hir::MacroDef, +) -> Option { + let _p = profile::span("render_macro"); + MacroRender::new(ctx, name, macro_).render(import_to_add) +} + +#[derive(Debug)] +struct MacroRender<'a> { + ctx: RenderContext<'a>, + name: String, + macro_: hir::MacroDef, + docs: Option, + bra: &'static str, + ket: &'static str, +} + +impl<'a> MacroRender<'a> { + fn new(ctx: RenderContext<'a>, name: String, macro_: hir::MacroDef) -> MacroRender<'a> { + let docs = ctx.docs(macro_); + let docs_str = docs.as_ref().map_or("", |s| s.as_str()); + let (bra, ket) = guess_macro_braces(&name, docs_str); + + MacroRender { ctx, name, macro_, docs, bra, ket } + } + + fn render(&self, import_to_add: Option) -> Option { + let mut builder = + CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), &self.label()) + .kind(SymbolKind::Macro) + .set_documentation(self.docs.clone()) + .set_deprecated(self.ctx.is_deprecated(self.macro_)) + .add_import(import_to_add) + .set_detail(self.detail()); + + let needs_bang = self.needs_bang(); + builder = match self.ctx.snippet_cap() { + Some(cap) if needs_bang => { + let snippet = self.snippet(); + let lookup = self.lookup(); + builder.insert_snippet(cap, snippet).lookup_by(lookup) + } + None if needs_bang => builder.insert_text(self.banged_name()), + _ => { + mark::hit!(dont_insert_macro_call_parens_unncessary); + builder.insert_text(&self.name) + } + }; + + Some(builder.build()) + } + + fn needs_bang(&self) -> bool { + self.ctx.completion.use_item_syntax.is_none() && !self.ctx.completion.is_macro_call + } + + fn label(&self) -> String { + if self.needs_bang() && self.ctx.snippet_cap().is_some() { + format!("{}!{}…{}", self.name, self.bra, self.ket) + } else { + self.banged_name() + } + } + + fn snippet(&self) -> String { + format!("{}!{}$0{}", self.name, self.bra, self.ket) + } + + fn lookup(&self) -> String { + self.banged_name() + } + + fn banged_name(&self) -> String { + format!("{}!", self.name) + } + + fn detail(&self) -> Option { + let ast_node = self.macro_.source(self.ctx.db())?.value; + Some(macro_label(&ast_node)) + } +} + +fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) { + let mut votes = [0, 0, 0]; + for (idx, s) in docs.match_indices(¯o_name) { + let (before, after) = (&docs[..idx], &docs[idx + s.len()..]); + // Ensure to match the full word + if after.starts_with('!') + && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric()) + { + // It may have spaces before the braces like `foo! {}` + match after[1..].chars().find(|&c| !c.is_whitespace()) { + Some('{') => votes[0] += 1, + Some('[') => votes[1] += 1, + Some('(') => votes[2] += 1, + _ => {} + } + } + } + + // Insert a space before `{}`. + // We prefer the last one when some votes equal. + let (_vote, (bra, ket)) = votes + .iter() + .zip(&[(" {", "}"), ("[", "]"), ("(", ")")]) + .max_by_key(|&(&vote, _)| vote) + .unwrap(); + (*bra, *ket) +} + +#[cfg(test)] +mod tests { + use test_utils::mark; + + use crate::test_utils::check_edit; + + #[test] + fn dont_insert_macro_call_parens_unncessary() { + mark::check!(dont_insert_macro_call_parens_unncessary); + check_edit( + "frobnicate!", + r#" +//- /main.rs crate:main deps:foo +use foo::$0; +//- /foo/lib.rs crate:foo +#[macro_export] +macro_rules! frobnicate { () => () } +"#, + r#" +use foo::frobnicate; +"#, + ); + + check_edit( + "frobnicate!", + r#" +macro_rules! frobnicate { () => () } +fn main() { frob$0!(); } +"#, + r#" +macro_rules! frobnicate { () => () } +fn main() { frobnicate!(); } +"#, + ); + } + + #[test] + fn guesses_macro_braces() { + check_edit( + "vec!", + r#" +/// Creates a [`Vec`] containing the arguments. +/// +/// ``` +/// let v = vec![1, 2, 3]; +/// assert_eq!(v[0], 1); +/// assert_eq!(v[1], 2); +/// assert_eq!(v[2], 3); +/// ``` +macro_rules! vec { () => {} } + +fn fn main() { v$0 } +"#, + r#" +/// Creates a [`Vec`] containing the arguments. +/// +/// ``` +/// let v = vec![1, 2, 3]; +/// assert_eq!(v[0], 1); +/// assert_eq!(v[1], 2); +/// assert_eq!(v[2], 3); +/// ``` +macro_rules! vec { () => {} } + +fn fn main() { vec![$0] } +"#, + ); + + check_edit( + "foo!", + r#" +/// Foo +/// +/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`, +/// call as `let _=foo! { hello world };` +macro_rules! foo { () => {} } +fn main() { $0 } +"#, + r#" +/// Foo +/// +/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`, +/// call as `let _=foo! { hello world };` +macro_rules! foo { () => {} } +fn main() { foo! {$0} } +"#, + ) + } +} diff --git a/crates/ide_completion/src/render/pattern.rs b/crates/ide_completion/src/render/pattern.rs new file mode 100644 index 000000000..465dfe00c --- /dev/null +++ b/crates/ide_completion/src/render/pattern.rs @@ -0,0 +1,150 @@ +//! Renderer for patterns. + +use hir::{db::HirDatabase, HasAttrs, HasVisibility, Name, StructKind}; +use ide_db::helpers::SnippetCap; +use itertools::Itertools; + +use crate::{item::CompletionKind, render::RenderContext, CompletionItem, CompletionItemKind}; + +fn visible_fields( + ctx: &RenderContext<'_>, + fields: &[hir::Field], + item: impl HasAttrs, +) -> Option<(Vec, bool)> { + let module = ctx.completion.scope.module()?; + let n_fields = fields.len(); + let fields = fields + .into_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)) +} + +pub(crate) fn render_struct_pat( + ctx: RenderContext<'_>, + strukt: hir::Struct, + local_name: Option, +) -> Option { + let _p = profile::span("render_struct_pat"); + + let fields = strukt.fields(ctx.db()); + let (visible_fields, fields_omitted) = visible_fields(&ctx, &fields, strukt)?; + + if visible_fields.is_empty() { + // Matching a struct without matching its fields is pointless, unlike matching a Variant without its fields + return None; + } + + let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())).to_string(); + let pat = render_pat(&ctx, &name, strukt.kind(ctx.db()), &visible_fields, fields_omitted)?; + + Some(build_completion(ctx, name, pat, strukt)) +} + +pub(crate) fn render_variant_pat( + ctx: RenderContext<'_>, + variant: hir::Variant, + local_name: Option, + path: Option, +) -> 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 name = match &path { + Some(path) => path.to_string(), + None => local_name.unwrap_or_else(|| variant.name(ctx.db())).to_string(), + }; + let pat = render_pat(&ctx, &name, variant.kind(ctx.db()), &visible_fields, fields_omitted)?; + + Some(build_completion(ctx, name, pat, variant)) +} + +fn build_completion( + ctx: RenderContext<'_>, + name: String, + pat: String, + item: impl HasAttrs + Copy, +) -> CompletionItem { + let completion = CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), name) + .kind(CompletionItemKind::Binding) + .set_documentation(ctx.docs(item)) + .set_deprecated(ctx.is_deprecated(item)) + .detail(&pat); + let completion = if let Some(snippet_cap) = ctx.snippet_cap() { + completion.insert_snippet(snippet_cap, pat) + } else { + completion.insert_text(pat) + }; + completion.build() +} + +fn render_pat( + ctx: &RenderContext<'_>, + name: &str, + kind: StructKind, + fields: &[hir::Field], + 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::Record => { + render_record_as_pat(ctx.db(), ctx.snippet_cap(), &fields, &name, fields_omitted) + } + _ => return None, + }; + + if ctx.completion.is_param { + pat.push(':'); + pat.push(' '); + pat.push_str(&name); + } + if ctx.snippet_cap().is_some() { + pat.push_str("$0"); + } + Some(pat) +} + +fn render_record_as_pat( + db: &dyn HirDatabase, + snippet_cap: Option, + fields: &[hir::Field], + name: &str, + 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 + ) + } +} + +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 + ) +} diff --git a/crates/ide_completion/src/render/type_alias.rs b/crates/ide_completion/src/render/type_alias.rs new file mode 100644 index 000000000..bd97c3692 --- /dev/null +++ b/crates/ide_completion/src/render/type_alias.rs @@ -0,0 +1,59 @@ +//! Renderer for type aliases. + +use hir::HasSource; +use ide_db::SymbolKind; +use syntax::{ + ast::{NameOwner, TypeAlias}, + display::type_label, +}; + +use crate::{ + item::{CompletionItem, CompletionKind}, + render::RenderContext, +}; + +pub(crate) fn render_type_alias<'a>( + ctx: RenderContext<'a>, + type_alias: hir::TypeAlias, +) -> Option { + TypeAliasRender::new(ctx, type_alias)?.render() +} + +#[derive(Debug)] +struct TypeAliasRender<'a> { + ctx: RenderContext<'a>, + type_alias: hir::TypeAlias, + ast_node: TypeAlias, +} + +impl<'a> TypeAliasRender<'a> { + fn new(ctx: RenderContext<'a>, type_alias: hir::TypeAlias) -> Option> { + let ast_node = type_alias.source(ctx.db())?.value; + Some(TypeAliasRender { ctx, type_alias, ast_node }) + } + + fn render(self) -> Option { + let name = self.name()?; + let detail = self.detail(); + + let item = CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), name) + .kind(SymbolKind::TypeAlias) + .set_documentation(self.ctx.docs(self.type_alias)) + .set_deprecated( + self.ctx.is_deprecated(self.type_alias) + || self.ctx.is_deprecated_assoc_item(self.type_alias), + ) + .detail(detail) + .build(); + + Some(item) + } + + fn name(&self) -> Option { + self.ast_node.name().map(|name| name.text().to_string()) + } + + fn detail(&self) -> String { + type_label(&self.ast_node) + } +} -- cgit v1.2.3