From fc8a1cd8006b021541ff673ec7f37a0f4b7bef57 Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Sun, 1 Nov 2020 12:35:04 +0300 Subject: [PATCH 1/9] Introduce render module --- crates/completion/src/completions.rs | 260 +------------------ crates/completion/src/lib.rs | 1 + crates/completion/src/render.rs | 50 ++++ crates/completion/src/render/builder_ext.rs | 94 +++++++ crates/completion/src/render/enum_variant.rs | 95 +++++++ crates/completion/src/render/function.rs | 87 +++++++ crates/completion/src/render/macro_.rs | 116 +++++++++ 7 files changed, 457 insertions(+), 246 deletions(-) create mode 100644 crates/completion/src/render.rs create mode 100644 crates/completion/src/render/builder_ext.rs create mode 100644 crates/completion/src/render/enum_variant.rs create mode 100644 crates/completion/src/render/function.rs create mode 100644 crates/completion/src/render/macro_.rs diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index d5fb85b7944..1ca5cf33dea 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs @@ -14,14 +14,15 @@ pub(crate) mod trait_impl; pub(crate) mod mod_; -use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, StructKind, Type}; -use itertools::Itertools; +use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, Type}; use syntax::{ast::NameOwner, display::*}; use test_utils::mark; use crate::{ - item::Builder, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, - CompletionScore, RootDatabase, + item::Builder, + render::{EnumVariantRender, FunctionRender, MacroRender}, + CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, CompletionScore, + RootDatabase, }; /// Represents an in-progress set of completions being built. @@ -189,50 +190,14 @@ pub(crate) fn add_macro( name: Option, macro_: hir::MacroDef, ) { - // FIXME: Currently proc-macro do not have ast-node, - // such that it does not have source - if macro_.is_proc_macro() { - return; - } - let name = match name { Some(it) => it, None => return, }; - let ast_node = macro_.source(ctx.db).value; - let detail = macro_label(&ast_node); - - let docs = macro_.docs(ctx.db); - - let mut builder = CompletionItem::new( - CompletionKind::Reference, - ctx.source_range(), - &format!("{}!", name), - ) - .kind(CompletionItemKind::Macro) - .set_documentation(docs.clone()) - .set_deprecated(is_deprecated(macro_, ctx.db)) - .detail(detail); - - let needs_bang = ctx.use_item_syntax.is_none() && !ctx.is_macro_call; - builder = match ctx.config.snippet_cap { - Some(cap) if needs_bang => { - let docs = docs.as_ref().map_or("", |s| s.as_str()); - let (bra, ket) = guess_macro_braces(&name, docs); - builder - .insert_snippet(cap, format!("{}!{}$0{}", name, bra, ket)) - .label(format!("{}!{}…{}", name, bra, ket)) - .lookup_by(format!("{}!", name)) - } - None if needs_bang => builder.insert_text(format!("{}!", name)), - _ => { - mark::hit!(dont_insert_macro_call_parens_unncessary); - builder.insert_text(name) - } - }; - - self.add(builder.build()); + if let Some(item) = MacroRender::new(ctx.into(), name, macro_).render() { + self.add(item); + } } pub(crate) fn add_function( @@ -241,50 +206,9 @@ pub(crate) fn add_function( func: hir::Function, local_name: Option, ) { - fn add_arg(arg: &str, ty: &Type, ctx: &CompletionContext) -> String { - if let Some(derefed_ty) = ty.remove_ref() { - for (name, local) in ctx.locals.iter() { - if name == arg && local.ty(ctx.db) == derefed_ty { - return (if ty.is_mutable_reference() { "&mut " } else { "&" }).to_string() - + &arg.to_string(); - } - } - } - arg.to_string() - }; - let name = local_name.unwrap_or_else(|| func.name(ctx.db).to_string()); - let ast_node = func.source(ctx.db).value; + let item = FunctionRender::new(ctx.into(), local_name, func).render(); - let mut builder = - CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.clone()) - .kind(if func.self_param(ctx.db).is_some() { - CompletionItemKind::Method - } else { - CompletionItemKind::Function - }) - .set_documentation(func.docs(ctx.db)) - .set_deprecated(is_deprecated(func, ctx.db)) - .detail(function_declaration(&ast_node)); - - let params_ty = func.params(ctx.db); - let params = ast_node - .param_list() - .into_iter() - .flat_map(|it| it.params()) - .zip(params_ty) - .flat_map(|(it, param_ty)| { - if let Some(pat) = it.pat() { - let name = pat.to_string(); - let arg = name.trim_start_matches("mut ").trim_start_matches('_'); - return Some(add_arg(arg, param_ty.ty(), ctx)); - } - None - }) - .collect(); - - builder = builder.add_call_parens(ctx, name, Params::Named(params)); - - self.add(builder.build()) + self.add(item) } pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) { @@ -325,7 +249,8 @@ pub(crate) fn add_qualified_enum_variant( variant: hir::EnumVariant, path: ModPath, ) { - self.add_enum_variant_impl(ctx, variant, None, Some(path)) + let item = EnumVariantRender::new(ctx.into(), None, variant, Some(path)).render(); + self.add(item); } pub(crate) fn add_enum_variant( @@ -334,63 +259,8 @@ pub(crate) fn add_enum_variant( variant: hir::EnumVariant, local_name: Option, ) { - self.add_enum_variant_impl(ctx, variant, local_name, None) - } - - fn add_enum_variant_impl( - &mut self, - ctx: &CompletionContext, - variant: hir::EnumVariant, - local_name: Option, - path: Option, - ) { - let is_deprecated = is_deprecated(variant, ctx.db); - let name = local_name.unwrap_or_else(|| variant.name(ctx.db).to_string()); - let (qualified_name, short_qualified_name) = match &path { - Some(path) => { - let full = path.to_string(); - let short = - path.segments[path.segments.len().saturating_sub(2)..].iter().join("::"); - (full, short) - } - None => (name.to_string(), name.to_string()), - }; - let detail_types = variant - .fields(ctx.db) - .into_iter() - .map(|field| (field.name(ctx.db), field.signature_ty(ctx.db))); - let variant_kind = variant.kind(ctx.db); - let detail = match variant_kind { - StructKind::Tuple | StructKind::Unit => format!( - "({})", - detail_types.map(|(_, t)| t.display(ctx.db).to_string()).format(", ") - ), - StructKind::Record => format!( - "{{ {} }}", - detail_types - .map(|(n, t)| format!("{}: {}", n, t.display(ctx.db).to_string())) - .format(", ") - ), - }; - let mut res = CompletionItem::new( - CompletionKind::Reference, - ctx.source_range(), - qualified_name.clone(), - ) - .kind(CompletionItemKind::EnumVariant) - .set_documentation(variant.docs(ctx.db)) - .set_deprecated(is_deprecated) - .detail(detail); - - if variant_kind == StructKind::Tuple { - mark::hit!(inserts_parens_for_tuple_enums); - let params = Params::Anonymous(variant.fields(ctx.db).len()); - res = res.add_call_parens(ctx, short_qualified_name, params) - } else if path.is_some() { - res = res.lookup_by(short_qualified_name); - } - - res.add_to(self); + let item = EnumVariantRender::new(ctx.into(), local_name, variant, None).render(); + self.add(item); } } @@ -434,112 +304,10 @@ fn compute_score(ctx: &CompletionContext, ty: &Type, name: &str) -> Option), - Anonymous(usize), -} - -impl Params { - fn len(&self) -> usize { - match self { - Params::Named(xs) => xs.len(), - Params::Anonymous(len) => *len, - } - } - - fn is_empty(&self) -> bool { - self.len() == 0 - } -} - -impl Builder { - fn add_call_parens(mut self, ctx: &CompletionContext, name: String, params: Params) -> Builder { - if !ctx.config.add_call_parenthesis { - return self; - } - if ctx.use_item_syntax.is_some() { - mark::hit!(no_parens_in_use_item); - return self; - } - if ctx.is_pattern_call { - mark::hit!(dont_duplicate_pattern_parens); - return self; - } - if ctx.is_call { - return self; - } - - // 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 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) - } -} - fn is_deprecated(node: impl HasAttrs, db: &RootDatabase) -> bool { node.attrs(db).by_key("deprecated").exists() } -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 std::cmp::Reverse; diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs index 89c0a997816..cb6e0554e9a 100644 --- a/crates/completion/src/lib.rs +++ b/crates/completion/src/lib.rs @@ -7,6 +7,7 @@ mod generated_lint_completions; #[cfg(test)] mod test_utils; +mod render; mod completions; diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs new file mode 100644 index 00000000000..66eb753b1ec --- /dev/null +++ b/crates/completion/src/render.rs @@ -0,0 +1,50 @@ +//! `render` module provides utilities for rendering completion suggestions +//! into code pieces that will be presented to user. + +mod macro_; +mod function; +mod builder_ext; +mod enum_variant; + +use hir::HasAttrs; +use ide_db::RootDatabase; +use syntax::TextRange; + +use crate::{config::SnippetCap, CompletionContext}; + +pub(crate) use crate::render::{ + enum_variant::EnumVariantRender, function::FunctionRender, macro_::MacroRender, +}; + +#[derive(Debug)] +pub(crate) struct RenderContext<'a> { + completion: &'a CompletionContext<'a>, +} + +impl<'a> RenderContext<'a> { + pub fn new(completion: &'a CompletionContext<'a>) -> RenderContext<'a> { + RenderContext { completion } + } + + pub fn snippet_cap(&self) -> Option { + self.completion.config.snippet_cap.clone() + } + + pub fn db(&self) -> &'a RootDatabase { + &self.completion.db + } + + pub fn source_range(&self) -> TextRange { + self.completion.source_range() + } + + pub fn is_deprecated(&self, node: impl HasAttrs) -> bool { + node.attrs(self.db()).by_key("deprecated").exists() + } +} + +impl<'a> From<&'a CompletionContext<'a>> for RenderContext<'a> { + fn from(ctx: &'a CompletionContext<'a>) -> RenderContext<'a> { + RenderContext::new(ctx) + } +} diff --git a/crates/completion/src/render/builder_ext.rs b/crates/completion/src/render/builder_ext.rs new file mode 100644 index 00000000000..37b0d0459a6 --- /dev/null +++ b/crates/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}; + +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 { + pub(super) fn should_add_parems(&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 { + mark::hit!(dont_duplicate_pattern_parens); + 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_parems(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/completion/src/render/enum_variant.rs b/crates/completion/src/render/enum_variant.rs new file mode 100644 index 00000000000..26cfdfeea70 --- /dev/null +++ b/crates/completion/src/render/enum_variant.rs @@ -0,0 +1,95 @@ +use hir::{HasAttrs, HirDisplay, ModPath, StructKind}; +use itertools::Itertools; +use test_utils::mark; + +use crate::{ + item::{CompletionItem, CompletionItemKind, CompletionKind}, + render::{builder_ext::Params, RenderContext}, +}; + +#[derive(Debug)] +pub(crate) struct EnumVariantRender<'a> { + ctx: RenderContext<'a>, + name: String, + variant: hir::EnumVariant, + path: Option, + qualified_name: String, + short_qualified_name: String, + variant_kind: StructKind, +} + +impl<'a> EnumVariantRender<'a> { + pub(crate) fn new( + ctx: RenderContext<'a>, + local_name: Option, + variant: hir::EnumVariant, + path: Option, + ) -> EnumVariantRender<'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 short = + path.segments[path.segments.len().saturating_sub(2)..].iter().join("::"); + (full, short) + } + None => (name.to_string(), name.to_string()), + }; + + EnumVariantRender { + ctx, + name, + variant, + path, + qualified_name, + short_qualified_name, + variant_kind, + } + } + + pub(crate) fn render(self) -> CompletionItem { + let mut builder = CompletionItem::new( + CompletionKind::Reference, + self.ctx.source_range(), + self.qualified_name.clone(), + ) + .kind(CompletionItemKind::EnumVariant) + .set_documentation(self.variant.docs(self.ctx.db())) + .set_deprecated(self.ctx.is_deprecated(self.variant)) + .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(", ") + ), + } + } +} diff --git a/crates/completion/src/render/function.rs b/crates/completion/src/render/function.rs new file mode 100644 index 00000000000..16f15e22c5c --- /dev/null +++ b/crates/completion/src/render/function.rs @@ -0,0 +1,87 @@ +use hir::{Documentation, HasAttrs, HasSource, Type}; +use syntax::{ast::Fn, display::function_declaration}; + +use crate::{ + item::{CompletionItem, CompletionItemKind, CompletionKind}, + render::{builder_ext::Params, RenderContext}, +}; + +#[derive(Debug)] +pub(crate) struct FunctionRender<'a> { + ctx: RenderContext<'a>, + name: String, + fn_: hir::Function, + ast_node: Fn, +} + +impl<'a> FunctionRender<'a> { + pub(crate) fn new( + ctx: RenderContext<'a>, + local_name: Option, + fn_: hir::Function, + ) -> FunctionRender<'a> { + let name = local_name.unwrap_or_else(|| fn_.name(ctx.db()).to_string()); + let ast_node = fn_.source(ctx.db()).value; + + FunctionRender { ctx, name, fn_, ast_node } + } + + pub(crate) fn render(self) -> CompletionItem { + let params = self.params(); + CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), self.name.clone()) + .kind(self.kind()) + .set_documentation(self.docs()) + .set_deprecated(self.ctx.is_deprecated(self.fn_)) + .detail(self.detail()) + .add_call_parens(self.ctx.completion, self.name, params) + .build() + } + + fn detail(&self) -> String { + function_declaration(&self.ast_node) + } + + 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 { + return (if ty.is_mutable_reference() { "&mut " } else { "&" }).to_string() + + &arg.to_string(); + } + } + } + arg.to_string() + } + + fn params(&self) -> Params { + let params_ty = self.fn_.params(self.ctx.db()); + let params = self + .ast_node + .param_list() + .into_iter() + .flat_map(|it| it.params()) + .zip(params_ty) + .flat_map(|(it, param_ty)| { + if let Some(pat) = it.pat() { + let name = pat.to_string(); + let arg = name.trim_start_matches("mut ").trim_start_matches('_'); + return Some(self.add_arg(arg, param_ty.ty())); + } + None + }) + .collect(); + Params::Named(params) + } + + fn kind(&self) -> CompletionItemKind { + if self.fn_.self_param(self.ctx.db()).is_some() { + CompletionItemKind::Method + } else { + CompletionItemKind::Function + } + } + + fn docs(&self) -> Option { + self.fn_.docs(self.ctx.db()) + } +} diff --git a/crates/completion/src/render/macro_.rs b/crates/completion/src/render/macro_.rs new file mode 100644 index 00000000000..bcf94f47eae --- /dev/null +++ b/crates/completion/src/render/macro_.rs @@ -0,0 +1,116 @@ +use hir::{Documentation, HasAttrs, HasSource}; +use syntax::display::macro_label; +use test_utils::mark; + +use crate::{ + item::{CompletionItem, CompletionItemKind, CompletionKind}, + render::RenderContext, +}; + +#[derive(Debug)] +pub(crate) struct MacroRender<'a> { + ctx: RenderContext<'a>, + name: String, + macro_: hir::MacroDef, + docs: Option, + bra: &'static str, + ket: &'static str, +} + +impl<'a> MacroRender<'a> { + pub(crate) fn new( + ctx: RenderContext<'a>, + name: String, + macro_: hir::MacroDef, + ) -> MacroRender<'a> { + let docs = macro_.docs(ctx.db()); + 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 } + } + + pub(crate) fn render(&self) -> Option { + // FIXME: Currently proc-macro do not have ast-node, + // such that it does not have source + if self.macro_.is_proc_macro() { + return None; + } + + let mut builder = + CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), &self.label()) + .kind(CompletionItemKind::Macro) + .set_documentation(self.docs.clone()) + .set_deprecated(self.ctx.is_deprecated(self.macro_)) + .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 { + format!("{}!{}…{}", self.name, self.bra, self.ket) + } + + 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) -> String { + let ast_node = self.macro_.source(self.ctx.db()).value; + 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) +} From 944ccf60758305a1b15224defe622cfca6939aaa Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Sun, 1 Nov 2020 12:59:43 +0300 Subject: [PATCH 2/9] Add ConstRender --- crates/completion/src/completions.rs | 20 ++-------- crates/completion/src/render.rs | 10 ++++- crates/completion/src/render/const_.rs | 47 ++++++++++++++++++++++++ crates/completion/src/render/function.rs | 12 ++---- crates/completion/src/render/macro_.rs | 4 +- 5 files changed, 65 insertions(+), 28 deletions(-) create mode 100644 crates/completion/src/render/const_.rs diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index 1ca5cf33dea..d8dc1b1c361 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs @@ -20,7 +20,7 @@ use crate::{ item::Builder, - render::{EnumVariantRender, FunctionRender, MacroRender}, + render::{ConstRender, EnumVariantRender, FunctionRender, MacroRender}, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, CompletionScore, RootDatabase, }; @@ -194,7 +194,6 @@ pub(crate) fn add_macro( Some(it) => it, None => return, }; - if let Some(item) = MacroRender::new(ctx.into(), name, macro_).render() { self.add(item); } @@ -207,24 +206,13 @@ pub(crate) fn add_function( local_name: Option, ) { let item = FunctionRender::new(ctx.into(), local_name, func).render(); - self.add(item) } pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) { - let ast_node = constant.source(ctx.db).value; - let name = match ast_node.name() { - Some(name) => name, - _ => return, - }; - let detail = const_label(&ast_node); - - CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.text().to_string()) - .kind(CompletionItemKind::Const) - .set_documentation(constant.docs(ctx.db)) - .set_deprecated(is_deprecated(constant, ctx.db)) - .detail(detail) - .add_to(self); + if let Some(item) = ConstRender::new(ctx.into(), constant).render() { + self.add(item); + } } pub(crate) fn add_type_alias(&mut self, ctx: &CompletionContext, type_alias: hir::TypeAlias) { diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs index 66eb753b1ec..c614a717261 100644 --- a/crates/completion/src/render.rs +++ b/crates/completion/src/render.rs @@ -5,15 +5,17 @@ mod function; mod builder_ext; mod enum_variant; +mod const_; -use hir::HasAttrs; +use hir::{Documentation, HasAttrs}; use ide_db::RootDatabase; use syntax::TextRange; use crate::{config::SnippetCap, CompletionContext}; pub(crate) use crate::render::{ - enum_variant::EnumVariantRender, function::FunctionRender, macro_::MacroRender, + const_::ConstRender, enum_variant::EnumVariantRender, function::FunctionRender, + macro_::MacroRender, }; #[derive(Debug)] @@ -41,6 +43,10 @@ pub fn source_range(&self) -> TextRange { pub fn is_deprecated(&self, node: impl HasAttrs) -> bool { node.attrs(self.db()).by_key("deprecated").exists() } + + pub fn docs(&self, node: impl HasAttrs) -> Option { + node.docs(self.db()) + } } impl<'a> From<&'a CompletionContext<'a>> for RenderContext<'a> { diff --git a/crates/completion/src/render/const_.rs b/crates/completion/src/render/const_.rs new file mode 100644 index 00000000000..d88bfa07f61 --- /dev/null +++ b/crates/completion/src/render/const_.rs @@ -0,0 +1,47 @@ +use hir::HasSource; +use syntax::{ + ast::{Const, NameOwner}, + display::const_label, +}; + +use crate::{ + item::{CompletionItem, CompletionItemKind, CompletionKind}, + render::RenderContext, +}; + +#[derive(Debug)] +pub(crate) struct ConstRender<'a> { + ctx: RenderContext<'a>, + const_: hir::Const, + ast_node: Const, +} + +impl<'a> ConstRender<'a> { + pub(crate) fn new(ctx: RenderContext<'a>, const_: hir::Const) -> ConstRender<'a> { + let ast_node = const_.source(ctx.db()).value; + ConstRender { ctx, const_, ast_node } + } + + pub(crate) fn render(self) -> Option { + let name = self.name()?; + let detail = self.detail(); + + let item = CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), name) + .kind(CompletionItemKind::Const) + .set_documentation(self.ctx.docs(self.const_)) + .set_deprecated(self.ctx.is_deprecated(self.const_)) + .detail(detail) + .build(); + + Some(item) + } + + fn name(&self) -> Option { + let ast_node = self.const_.source(self.ctx.db()).value; + ast_node.name().map(|name| name.text().to_string()) + } + + fn detail(&self) -> String { + const_label(&self.ast_node) + } +} diff --git a/crates/completion/src/render/function.rs b/crates/completion/src/render/function.rs index 16f15e22c5c..d220812361d 100644 --- a/crates/completion/src/render/function.rs +++ b/crates/completion/src/render/function.rs @@ -1,4 +1,4 @@ -use hir::{Documentation, HasAttrs, HasSource, Type}; +use hir::{HasSource, Type}; use syntax::{ast::Fn, display::function_declaration}; use crate::{ @@ -30,7 +30,7 @@ pub(crate) fn render(self) -> CompletionItem { let params = self.params(); CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), self.name.clone()) .kind(self.kind()) - .set_documentation(self.docs()) + .set_documentation(self.ctx.docs(self.fn_)) .set_deprecated(self.ctx.is_deprecated(self.fn_)) .detail(self.detail()) .add_call_parens(self.ctx.completion, self.name, params) @@ -45,8 +45,8 @@ 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 { - return (if ty.is_mutable_reference() { "&mut " } else { "&" }).to_string() - + &arg.to_string(); + let mutability = if ty.is_mutable_reference() { "&mut " } else { "&" }; + return format!("{}{}", mutability, arg); } } } @@ -80,8 +80,4 @@ fn kind(&self) -> CompletionItemKind { CompletionItemKind::Function } } - - fn docs(&self) -> Option { - self.fn_.docs(self.ctx.db()) - } } diff --git a/crates/completion/src/render/macro_.rs b/crates/completion/src/render/macro_.rs index bcf94f47eae..0ad8c03b342 100644 --- a/crates/completion/src/render/macro_.rs +++ b/crates/completion/src/render/macro_.rs @@ -1,4 +1,4 @@ -use hir::{Documentation, HasAttrs, HasSource}; +use hir::{Documentation, HasSource}; use syntax::display::macro_label; use test_utils::mark; @@ -23,7 +23,7 @@ pub(crate) fn new( name: String, macro_: hir::MacroDef, ) -> MacroRender<'a> { - let docs = macro_.docs(ctx.db()); + 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); From 15b16917fcd55068d9aba3d4b5d87763ec5deb69 Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Sun, 1 Nov 2020 13:10:02 +0300 Subject: [PATCH 3/9] Add TypeAliasRender --- crates/completion/src/completions.rs | 21 +++------- crates/completion/src/render.rs | 3 +- crates/completion/src/render/const_.rs | 3 +- crates/completion/src/render/type_alias.rs | 46 ++++++++++++++++++++++ 4 files changed, 54 insertions(+), 19 deletions(-) create mode 100644 crates/completion/src/render/type_alias.rs diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index d8dc1b1c361..434366b1206 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs @@ -14,13 +14,12 @@ pub(crate) mod trait_impl; pub(crate) mod mod_; -use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, Type}; -use syntax::{ast::NameOwner, display::*}; +use hir::{HasAttrs, HirDisplay, ModPath, Mutability, ScopeDef, Type}; use test_utils::mark; use crate::{ item::Builder, - render::{ConstRender, EnumVariantRender, FunctionRender, MacroRender}, + render::{ConstRender, EnumVariantRender, FunctionRender, MacroRender, TypeAliasRender}, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, CompletionScore, RootDatabase, }; @@ -216,19 +215,9 @@ pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const } pub(crate) fn add_type_alias(&mut self, ctx: &CompletionContext, type_alias: hir::TypeAlias) { - let type_def = type_alias.source(ctx.db).value; - let name = match type_def.name() { - Some(name) => name, - _ => return, - }; - let detail = type_label(&type_def); - - CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.text().to_string()) - .kind(CompletionItemKind::TypeAlias) - .set_documentation(type_alias.docs(ctx.db)) - .set_deprecated(is_deprecated(type_alias, ctx.db)) - .detail(detail) - .add_to(self); + if let Some(item) = TypeAliasRender::new(ctx.into(), type_alias).render() { + self.add(item) + } } pub(crate) fn add_qualified_enum_variant( diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs index c614a717261..3a14357f36d 100644 --- a/crates/completion/src/render.rs +++ b/crates/completion/src/render.rs @@ -6,6 +6,7 @@ mod builder_ext; mod enum_variant; mod const_; +mod type_alias; use hir::{Documentation, HasAttrs}; use ide_db::RootDatabase; @@ -15,7 +16,7 @@ pub(crate) use crate::render::{ const_::ConstRender, enum_variant::EnumVariantRender, function::FunctionRender, - macro_::MacroRender, + macro_::MacroRender, type_alias::TypeAliasRender, }; #[derive(Debug)] diff --git a/crates/completion/src/render/const_.rs b/crates/completion/src/render/const_.rs index d88bfa07f61..829eb574d0f 100644 --- a/crates/completion/src/render/const_.rs +++ b/crates/completion/src/render/const_.rs @@ -37,8 +37,7 @@ pub(crate) fn render(self) -> Option { } fn name(&self) -> Option { - let ast_node = self.const_.source(self.ctx.db()).value; - ast_node.name().map(|name| name.text().to_string()) + self.ast_node.name().map(|name| name.text().to_string()) } fn detail(&self) -> String { diff --git a/crates/completion/src/render/type_alias.rs b/crates/completion/src/render/type_alias.rs new file mode 100644 index 00000000000..378aa8c6752 --- /dev/null +++ b/crates/completion/src/render/type_alias.rs @@ -0,0 +1,46 @@ +use hir::HasSource; +use syntax::{ + ast::{NameOwner, TypeAlias}, + display::type_label, +}; + +use crate::{ + item::{CompletionItem, CompletionItemKind, CompletionKind}, + render::RenderContext, +}; + +#[derive(Debug)] +pub(crate) struct TypeAliasRender<'a> { + ctx: RenderContext<'a>, + type_alias: hir::TypeAlias, + ast_node: TypeAlias, +} + +impl<'a> TypeAliasRender<'a> { + pub(crate) fn new(ctx: RenderContext<'a>, type_alias: hir::TypeAlias) -> TypeAliasRender<'a> { + let ast_node = type_alias.source(ctx.db()).value; + TypeAliasRender { ctx, type_alias, ast_node } + } + + pub(crate) fn render(self) -> Option { + let name = self.name()?; + let detail = self.detail(); + + let item = CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), name) + .kind(CompletionItemKind::TypeAlias) + .set_documentation(self.ctx.docs(self.type_alias)) + .set_deprecated(self.ctx.is_deprecated(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) + } +} From 97a504805d4b0cf8b48bc5052453b2b2f3298449 Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Sun, 1 Nov 2020 13:36:30 +0300 Subject: [PATCH 4/9] Move rendering tests to the render module --- crates/completion/src/completions.rs | 1082 +----------------- crates/completion/src/context.rs | 13 - crates/completion/src/render.rs | 789 ++++++++++++- crates/completion/src/render/enum_variant.rs | 74 ++ crates/completion/src/render/function.rs | 210 ++++ crates/completion/src/render/macro_.rs | 90 ++ 6 files changed, 1163 insertions(+), 1095 deletions(-) diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index 434366b1206..b54771fcd1c 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs @@ -14,15 +14,9 @@ pub(crate) mod trait_impl; pub(crate) mod mod_; -use hir::{HasAttrs, HirDisplay, ModPath, Mutability, ScopeDef, Type}; -use test_utils::mark; +use hir::{ModPath, ScopeDef, Type}; -use crate::{ - item::Builder, - render::{ConstRender, EnumVariantRender, FunctionRender, MacroRender, TypeAliasRender}, - CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, CompletionScore, - RootDatabase, -}; +use crate::{item::Builder, render::*, CompletionContext, CompletionItem}; /// Represents an in-progress set of completions being built. #[derive(Debug, Default)] @@ -58,27 +52,13 @@ pub(crate) fn add_all(&mut self, items: I) } pub(crate) fn add_field(&mut self, ctx: &CompletionContext, field: hir::Field, ty: &Type) { - let is_deprecated = is_deprecated(field, ctx.db); - let name = field.name(ctx.db); - let mut item = - CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string()) - .kind(CompletionItemKind::Field) - .detail(ty.display(ctx.db).to_string()) - .set_documentation(field.docs(ctx.db)) - .set_deprecated(is_deprecated); - - if let Some(score) = compute_score(ctx, &ty, &name.to_string()) { - item = item.set_score(score); - } - - item.add_to(self); + let item = Render::new(ctx.into()).add_field(field, ty); + self.add(item); } pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) { - CompletionItem::new(CompletionKind::Reference, ctx.source_range(), field.to_string()) - .kind(CompletionItemKind::Field) - .detail(ty.display(ctx.db).to_string()) - .add_to(self); + let item = Render::new(ctx.into()).add_tuple_field(field, ty); + self.add(item); } pub(crate) fn add_resolution( @@ -87,100 +67,9 @@ pub(crate) fn add_resolution( local_name: String, resolution: &ScopeDef, ) { - use hir::ModuleDef::*; - - let completion_kind = match resolution { - ScopeDef::ModuleDef(BuiltinType(..)) => CompletionKind::BuiltinType, - _ => CompletionKind::Reference, - }; - - let kind = match resolution { - ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::Module, - ScopeDef::ModuleDef(Function(func)) => { - self.add_function(ctx, *func, Some(local_name)); - return; - } - ScopeDef::ModuleDef(Adt(hir::Adt::Struct(_))) => CompletionItemKind::Struct, - // FIXME: add CompletionItemKind::Union - ScopeDef::ModuleDef(Adt(hir::Adt::Union(_))) => CompletionItemKind::Struct, - ScopeDef::ModuleDef(Adt(hir::Adt::Enum(_))) => CompletionItemKind::Enum, - - ScopeDef::ModuleDef(EnumVariant(var)) => { - self.add_enum_variant(ctx, *var, Some(local_name)); - return; - } - ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::Const, - ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::Static, - ScopeDef::ModuleDef(Trait(..)) => CompletionItemKind::Trait, - ScopeDef::ModuleDef(TypeAlias(..)) => CompletionItemKind::TypeAlias, - ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType, - ScopeDef::GenericParam(..) => CompletionItemKind::TypeParam, - ScopeDef::Local(..) => CompletionItemKind::Binding, - // (does this need its own kind?) - ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => CompletionItemKind::TypeParam, - ScopeDef::MacroDef(mac) => { - self.add_macro(ctx, Some(local_name), *mac); - return; - } - ScopeDef::Unknown => { - CompletionItem::new(CompletionKind::Reference, ctx.source_range(), local_name) - .kind(CompletionItemKind::UnresolvedReference) - .add_to(self); - return; - } - }; - - let docs = match resolution { - ScopeDef::ModuleDef(Module(it)) => it.docs(ctx.db), - ScopeDef::ModuleDef(Adt(it)) => it.docs(ctx.db), - ScopeDef::ModuleDef(EnumVariant(it)) => it.docs(ctx.db), - ScopeDef::ModuleDef(Const(it)) => it.docs(ctx.db), - ScopeDef::ModuleDef(Static(it)) => it.docs(ctx.db), - ScopeDef::ModuleDef(Trait(it)) => it.docs(ctx.db), - ScopeDef::ModuleDef(TypeAlias(it)) => it.docs(ctx.db), - _ => None, - }; - - let mut item = CompletionItem::new(completion_kind, ctx.source_range(), local_name.clone()); - if let ScopeDef::Local(local) = resolution { - let ty = local.ty(ctx.db); - if !ty.is_unknown() { - item = item.detail(ty.display(ctx.db).to_string()); - } - }; - - let mut ref_match = None; - if let ScopeDef::Local(local) = resolution { - if let Some((active_name, active_type)) = ctx.active_name_and_type() { - let ty = local.ty(ctx.db); - if let Some(score) = - compute_score_from_active(&active_type, &active_name, &ty, &local_name) - { - item = item.set_score(score); - } - ref_match = refed_type_matches(&active_type, &active_name, &ty, &local_name); - } + if let Some(item) = Render::new(ctx.into()).render_resolution(local_name, resolution) { + self.add(item); } - - // Add `<>` for generic types - if ctx.is_path_type && !ctx.has_type_args && ctx.config.add_call_parenthesis { - if let Some(cap) = ctx.config.snippet_cap { - let has_non_default_type_params = match resolution { - ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db), - ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db), - _ => false, - }; - if has_non_default_type_params { - mark::hit!(inserts_angle_brackets_for_generics); - item = item - .lookup_by(local_name.clone()) - .label(format!("{}<…>", local_name)) - .insert_snippet(cap, format!("{}<$0>", local_name)); - } - } - } - - item.kind(kind).set_documentation(docs).set_ref_match(ref_match).add_to(self) } pub(crate) fn add_macro( @@ -240,958 +129,3 @@ pub(crate) fn add_enum_variant( self.add(item); } } - -fn compute_score_from_active( - active_type: &Type, - active_name: &str, - ty: &Type, - name: &str, -) -> Option { - // Compute score - // For the same type - if active_type != ty { - return None; - } - - let mut res = CompletionScore::TypeMatch; - - // If same type + same name then go top position - if active_name == name { - res = CompletionScore::TypeAndNameMatch - } - - Some(res) -} -fn refed_type_matches( - active_type: &Type, - active_name: &str, - ty: &Type, - name: &str, -) -> Option<(Mutability, CompletionScore)> { - let derefed_active = active_type.remove_ref()?; - let score = compute_score_from_active(&derefed_active, &active_name, &ty, &name)?; - Some(( - if active_type.is_mutable_reference() { Mutability::Mut } else { Mutability::Shared }, - score, - )) -} - -fn compute_score(ctx: &CompletionContext, ty: &Type, name: &str) -> Option { - let (active_name, active_type) = ctx.active_name_and_type()?; - compute_score_from_active(&active_type, &active_name, ty, name) -} - -fn is_deprecated(node: impl HasAttrs, db: &RootDatabase) -> bool { - node.attrs(db).by_key("deprecated").exists() -} - -#[cfg(test)] -mod tests { - use std::cmp::Reverse; - - use expect_test::{expect, Expect}; - use test_utils::mark; - - use crate::{ - test_utils::{check_edit, check_edit_with_config, do_completion, get_all_items}, - CompletionConfig, CompletionKind, CompletionScore, - }; - - fn check(ra_fixture: &str, expect: Expect) { - let actual = do_completion(ra_fixture, CompletionKind::Reference); - expect.assert_debug_eq(&actual); - } - - fn check_scores(ra_fixture: &str, expect: Expect) { - fn display_score(score: Option) -> &'static str { - match score { - Some(CompletionScore::TypeMatch) => "[type]", - Some(CompletionScore::TypeAndNameMatch) => "[type+name]", - None => "[]".into(), - } - } - - let mut completions = get_all_items(CompletionConfig::default(), ra_fixture); - completions.sort_by_key(|it| (Reverse(it.score()), it.label().to_string())); - let actual = completions - .into_iter() - .filter(|it| it.completion_kind == CompletionKind::Reference) - .map(|it| { - let tag = it.kind().unwrap().tag(); - let score = display_score(it.score()); - format!("{} {} {}\n", tag, it.label(), score) - }) - .collect::(); - expect.assert_eq(&actual); - } - - #[test] - fn enum_detail_includes_record_fields() { - check( - r#" -enum Foo { Foo { x: i32, y: i32 } } - -fn main() { Foo::Fo<|> } -"#, - expect![[r#" - [ - CompletionItem { - label: "Foo", - source_range: 54..56, - delete: 54..56, - insert: "Foo", - kind: EnumVariant, - detail: "{ x: i32, y: i32 }", - }, - ] - "#]], - ); - } - - #[test] - fn enum_detail_doesnt_include_tuple_fields() { - check( - r#" -enum Foo { Foo (i32, i32) } - -fn main() { Foo::Fo<|> } -"#, - expect![[r#" - [ - CompletionItem { - label: "Foo(…)", - source_range: 46..48, - delete: 46..48, - insert: "Foo($0)", - kind: EnumVariant, - lookup: "Foo", - detail: "(i32, i32)", - trigger_call_info: true, - }, - ] - "#]], - ); - } - - #[test] - fn enum_detail_just_parentheses_for_unit() { - check( - r#" -enum Foo { Foo } - -fn main() { Foo::Fo<|> } -"#, - expect![[r#" - [ - CompletionItem { - label: "Foo", - source_range: 35..37, - delete: 35..37, - insert: "Foo", - kind: EnumVariant, - detail: "()", - }, - ] - "#]], - ); - } - - #[test] - fn lookup_enums_by_two_qualifiers() { - check( - r#" -mod m { - pub enum Spam { Foo, Bar(i32) } -} -fn main() { let _: m::Spam = S<|> } -"#, - expect![[r#" - [ - CompletionItem { - label: "Spam::Bar(…)", - source_range: 75..76, - delete: 75..76, - insert: "Spam::Bar($0)", - kind: EnumVariant, - lookup: "Spam::Bar", - detail: "(i32)", - trigger_call_info: true, - }, - CompletionItem { - label: "m", - source_range: 75..76, - delete: 75..76, - insert: "m", - kind: Module, - }, - CompletionItem { - label: "m::Spam::Foo", - source_range: 75..76, - delete: 75..76, - insert: "m::Spam::Foo", - kind: EnumVariant, - lookup: "Spam::Foo", - detail: "()", - }, - CompletionItem { - label: "main()", - source_range: 75..76, - delete: 75..76, - insert: "main()$0", - kind: Function, - lookup: "main", - detail: "fn main()", - }, - ] - "#]], - ) - } - - #[test] - fn sets_deprecated_flag_in_items() { - check( - r#" -#[deprecated] -fn something_deprecated() {} -#[deprecated(since = "1.0.0")] -fn something_else_deprecated() {} - -fn main() { som<|> } -"#, - expect![[r#" - [ - CompletionItem { - label: "main()", - source_range: 121..124, - delete: 121..124, - insert: "main()$0", - kind: Function, - lookup: "main", - detail: "fn main()", - }, - CompletionItem { - label: "something_deprecated()", - source_range: 121..124, - delete: 121..124, - insert: "something_deprecated()$0", - kind: Function, - lookup: "something_deprecated", - detail: "fn something_deprecated()", - deprecated: true, - }, - CompletionItem { - label: "something_else_deprecated()", - source_range: 121..124, - delete: 121..124, - insert: "something_else_deprecated()$0", - kind: Function, - lookup: "something_else_deprecated", - detail: "fn something_else_deprecated()", - deprecated: true, - }, - ] - "#]], - ); - - check( - r#" -struct A { #[deprecated] the_field: u32 } -fn foo() { A { the<|> } } -"#, - expect![[r#" - [ - CompletionItem { - label: "the_field", - source_range: 57..60, - delete: 57..60, - insert: "the_field", - kind: Field, - detail: "u32", - deprecated: true, - }, - ] - "#]], - ); - } - - #[test] - fn renders_docs() { - check( - r#" -struct S { - /// Field docs - foo: -} -impl S { - /// Method docs - fn bar(self) { self.<|> } -}"#, - expect![[r#" - [ - CompletionItem { - label: "bar()", - source_range: 94..94, - delete: 94..94, - insert: "bar()$0", - kind: Method, - lookup: "bar", - detail: "fn bar(self)", - documentation: Documentation( - "Method docs", - ), - }, - CompletionItem { - label: "foo", - source_range: 94..94, - delete: 94..94, - insert: "foo", - kind: Field, - detail: "{unknown}", - documentation: Documentation( - "Field docs", - ), - }, - ] - "#]], - ); - - check( - r#" -use self::my<|>; - -/// mod docs -mod my { } - -/// enum docs -enum E { - /// variant docs - V -} -use self::E::*; -"#, - expect![[r#" - [ - CompletionItem { - label: "E", - source_range: 10..12, - delete: 10..12, - insert: "E", - kind: Enum, - documentation: Documentation( - "enum docs", - ), - }, - CompletionItem { - label: "V", - source_range: 10..12, - delete: 10..12, - insert: "V", - kind: EnumVariant, - detail: "()", - documentation: Documentation( - "variant docs", - ), - }, - CompletionItem { - label: "my", - source_range: 10..12, - delete: 10..12, - insert: "my", - kind: Module, - documentation: Documentation( - "mod docs", - ), - }, - ] - "#]], - ) - } - - #[test] - fn dont_render_attrs() { - check( - r#" -struct S; -impl S { - #[inline] - fn the_method(&self) { } -} -fn foo(s: S) { s.<|> } -"#, - expect![[r#" - [ - CompletionItem { - label: "the_method()", - source_range: 81..81, - delete: 81..81, - insert: "the_method()$0", - kind: Method, - lookup: "the_method", - detail: "fn the_method(&self)", - }, - ] - "#]], - ) - } - - #[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_<|> } -"#, - r#" -fn no_args() {} -fn main() { no_args()$0 } -"#, - ); - - check_edit( - "with_args", - r#" -fn with_args(x: i32, y: String) {} -fn main() { with_<|> } -"#, - 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<|> } -"#, - 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<|> -} -"#, - r#" -struct S {} -impl S { - fn foo(&self, x: i32) {} -} -fn bar(s: &S) { - s.foo(${1:x})$0 -} -"#, - ); - } - - #[test] - fn suppress_arg_snippets() { - mark::check!(suppress_arg_snippets); - check_edit_with_config( - CompletionConfig { add_call_argument_snippets: false, ..CompletionConfig::default() }, - "with_args", - r#" -fn with_args(x: i32, y: String) {} -fn main() { with_<|> } -"#, - 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<|> } -"#, - 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<|> -} -"#, - 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<|> -} -"#, - 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.<|> -} -"#, - 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<|> -} -"#, - r#" -fn take_mutably(mut x: &i32) {} - -fn main() { - take_mutably(${1:x})$0 -} -"#, - ); - } - - #[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<|> -} -"#, - r#" -enum Option { Some(T), None } -use Option::*; -fn main() -> Option { - Some($0) -} -"#, - ); - check_edit( - "Some", - r#" -enum Option { Some(T), None } -use Option::*; -fn main(value: Option) { - match value { - Som<|> - } -} -"#, - r#" -enum Option { Some(T), None } -use Option::*; -fn main(value: Option) { - match value { - Some($0) - } -} -"#, - ); - } - - #[test] - fn dont_duplicate_pattern_parens() { - mark::check!(dont_duplicate_pattern_parens); - check_edit( - "Var", - r#" -enum E { Var(i32) } -fn main() { - match E::Var(92) { - E::<|>(92) => (), - } -} -"#, - r#" -enum E { Var(i32) } -fn main() { - match E::Var(92) { - E::Var(92) => (), - } -} -"#, - ); - } - - #[test] - fn no_call_parens_if_fn_ptr_needed() { - mark::check!(no_call_parens_if_fn_ptr_needed); - check_edit( - "foo", - r#" -fn foo(foo: u8, bar: u8) {} -struct ManualVtable { f: fn(u8, u8) } - -fn main() -> ManualVtable { - ManualVtable { f: f<|> } -} -"#, - r#" -fn foo(foo: u8, bar: u8) {} -struct ManualVtable { f: fn(u8, u8) } - -fn main() -> ManualVtable { - ManualVtable { f: foo } -} -"#, - ); - } - - #[test] - fn no_parens_in_use_item() { - mark::check!(no_parens_in_use_item); - check_edit( - "foo", - r#" -mod m { pub fn foo() {} } -use crate::m::f<|>; -"#, - r#" -mod m { pub fn foo() {} } -use crate::m::foo; -"#, - ); - } - - #[test] - fn no_parens_in_call() { - check_edit( - "foo", - r#" -fn foo(x: i32) {} -fn main() { f<|>(); } -"#, - r#" -fn foo(x: i32) {} -fn main() { foo(); } -"#, - ); - check_edit( - "foo", - r#" -struct Foo; -impl Foo { fn foo(&self){} } -fn f(foo: &Foo) { foo.f<|>(); } -"#, - r#" -struct Foo; -impl Foo { fn foo(&self){} } -fn f(foo: &Foo) { foo.foo(); } -"#, - ); - } - - #[test] - fn inserts_angle_brackets_for_generics() { - mark::check!(inserts_angle_brackets_for_generics); - check_edit( - "Vec", - r#" -struct Vec {} -fn foo(xs: Ve<|>) -"#, - r#" -struct Vec {} -fn foo(xs: Vec<$0>) -"#, - ); - check_edit( - "Vec", - r#" -type Vec = (T,); -fn foo(xs: Ve<|>) -"#, - r#" -type Vec = (T,); -fn foo(xs: Vec<$0>) -"#, - ); - check_edit( - "Vec", - r#" -struct Vec {} -fn foo(xs: Ve<|>) -"#, - r#" -struct Vec {} -fn foo(xs: Vec) -"#, - ); - check_edit( - "Vec", - r#" -struct Vec {} -fn foo(xs: Ve<|>) -"#, - r#" -struct Vec {} -fn foo(xs: Vec) -"#, - ); - } - - #[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::<|>; -//- /foo/lib.rs crate:foo -#[macro_export] -macro_rules frobnicate { () => () } -"#, - r#" -use foo::frobnicate; -"#, - ); - - check_edit( - "frobnicate!", - r#" -macro_rules frobnicate { () => () } -fn main() { frob<|>!(); } -"#, - r#" -macro_rules frobnicate { () => () } -fn main() { frobnicate!(); } -"#, - ); - } - - #[test] - fn active_param_score() { - mark::check!(active_param_type_match); - check_scores( - r#" -struct S { foo: i64, bar: u32, baz: u32 } -fn test(bar: u32) { } -fn foo(s: S) { test(s.<|>) } -"#, - expect![[r#" - fd bar [type+name] - fd baz [type] - fd foo [] - "#]], - ); - } - - #[test] - fn record_field_scores() { - mark::check!(record_field_type_match); - check_scores( - r#" -struct A { foo: i64, bar: u32, baz: u32 } -struct B { x: (), y: f32, bar: u32 } -fn foo(a: A) { B { bar: a.<|> }; } -"#, - expect![[r#" - fd bar [type+name] - fd baz [type] - fd foo [] - "#]], - ) - } - - #[test] - fn record_field_and_call_scores() { - check_scores( - r#" -struct A { foo: i64, bar: u32, baz: u32 } -struct B { x: (), y: f32, bar: u32 } -fn f(foo: i64) { } -fn foo(a: A) { B { bar: f(a.<|>) }; } -"#, - expect![[r#" - fd foo [type+name] - fd bar [] - fd baz [] - "#]], - ); - check_scores( - r#" -struct A { foo: i64, bar: u32, baz: u32 } -struct B { x: (), y: f32, bar: u32 } -fn f(foo: i64) { } -fn foo(a: A) { f(B { bar: a.<|> }); } -"#, - expect![[r#" - fd bar [type+name] - fd baz [type] - fd foo [] - "#]], - ); - } - - #[test] - fn prioritize_exact_ref_match() { - check_scores( - r#" -struct WorldSnapshot { _f: () }; -fn go(world: &WorldSnapshot) { go(w<|>) } -"#, - expect![[r#" - bn world [type+name] - st WorldSnapshot [] - fn go(…) [] - "#]], - ); - } - - #[test] - fn too_many_arguments() { - check_scores( - r#" -struct Foo; -fn f(foo: &Foo) { f(foo, w<|>) } -"#, - expect![[r#" - st Foo [] - fn f(…) [] - bn foo [] - "#]], - ); - } - - #[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<|> } -"#, - 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() { <|> } -"#, - 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/completion/src/context.rs b/crates/completion/src/context.rs index dca304a8f50..bf70ee478e3 100644 --- a/crates/completion/src/context.rs +++ b/crates/completion/src/context.rs @@ -245,19 +245,6 @@ pub(crate) fn source_range(&self) -> TextRange { } } - pub(crate) fn active_name_and_type(&self) -> Option<(String, Type)> { - if let Some(record_field) = &self.record_field_syntax { - mark::hit!(record_field_type_match); - let (struct_field, _local) = self.sema.resolve_record_field(record_field)?; - Some((struct_field.name(self.db).to_string(), struct_field.signature_ty(self.db))) - } else if let Some(active_parameter) = &self.active_parameter { - mark::hit!(active_param_type_match); - Some((active_parameter.name.clone(), active_parameter.ty.clone())) - } else { - None - } - } - fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); let syntax_element = NodeOrToken::Token(fake_ident_token); diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs index 3a14357f36d..28731e04f4b 100644 --- a/crates/completion/src/render.rs +++ b/crates/completion/src/render.rs @@ -8,11 +8,15 @@ mod const_; mod type_alias; -use hir::{Documentation, HasAttrs}; +use hir::{Documentation, HasAttrs, HirDisplay, Mutability, ScopeDef, Type}; use ide_db::RootDatabase; use syntax::TextRange; +use test_utils::mark; -use crate::{config::SnippetCap, CompletionContext}; +use crate::{ + config::SnippetCap, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, + CompletionScore, +}; pub(crate) use crate::render::{ const_::ConstRender, enum_variant::EnumVariantRender, function::FunctionRender, @@ -25,29 +29,42 @@ pub(crate) struct RenderContext<'a> { } impl<'a> RenderContext<'a> { - pub fn new(completion: &'a CompletionContext<'a>) -> RenderContext<'a> { + fn new(completion: &'a CompletionContext<'a>) -> RenderContext<'a> { RenderContext { completion } } - pub fn snippet_cap(&self) -> Option { + fn snippet_cap(&self) -> Option { self.completion.config.snippet_cap.clone() } - pub fn db(&self) -> &'a RootDatabase { + fn db(&self) -> &'a RootDatabase { &self.completion.db } - pub fn source_range(&self) -> TextRange { + fn source_range(&self) -> TextRange { self.completion.source_range() } - pub fn is_deprecated(&self, node: impl HasAttrs) -> bool { + fn is_deprecated(&self, node: impl HasAttrs) -> bool { node.attrs(self.db()).by_key("deprecated").exists() } - pub fn docs(&self, node: impl HasAttrs) -> Option { + fn docs(&self, node: impl HasAttrs) -> Option { node.docs(self.db()) } + + fn active_name_and_type(&self) -> Option<(String, Type)> { + if let Some(record_field) = &self.completion.record_field_syntax { + mark::hit!(record_field_type_match); + let (struct_field, _local) = self.completion.sema.resolve_record_field(record_field)?; + Some((struct_field.name(self.db()).to_string(), struct_field.signature_ty(self.db()))) + } else if let Some(active_parameter) = &self.completion.active_parameter { + mark::hit!(active_param_type_match); + Some((active_parameter.name.clone(), active_parameter.ty.clone())) + } else { + None + } + } } impl<'a> From<&'a CompletionContext<'a>> for RenderContext<'a> { @@ -55,3 +72,759 @@ fn from(ctx: &'a CompletionContext<'a>) -> RenderContext<'a> { RenderContext::new(ctx) } } + +#[derive(Debug)] +pub(crate) struct Render<'a> { + ctx: RenderContext<'a>, +} + +impl<'a> Render<'a> { + pub(crate) fn new(ctx: RenderContext<'a>) -> Render<'a> { + Render { ctx } + } + + pub(crate) fn add_field(&mut self, field: hir::Field, ty: &Type) -> CompletionItem { + let is_deprecated = self.ctx.is_deprecated(field); + let name = field.name(self.ctx.db()); + let mut item = CompletionItem::new( + CompletionKind::Reference, + self.ctx.source_range(), + name.to_string(), + ) + .kind(CompletionItemKind::Field) + .detail(ty.display(self.ctx.db()).to_string()) + .set_documentation(field.docs(self.ctx.db())) + .set_deprecated(is_deprecated); + + if let Some(score) = compute_score(&self.ctx, &ty, &name.to_string()) { + item = item.set_score(score); + } + + return item.build(); + } + + pub(crate) fn add_tuple_field(&mut self, field: usize, ty: &Type) -> CompletionItem { + CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), field.to_string()) + .kind(CompletionItemKind::Field) + .detail(ty.display(self.ctx.db()).to_string()) + .build() + } + + pub(crate) fn render_resolution( + self, + local_name: String, + resolution: &ScopeDef, + ) -> Option { + use hir::ModuleDef::*; + + let completion_kind = match resolution { + ScopeDef::ModuleDef(BuiltinType(..)) => CompletionKind::BuiltinType, + _ => CompletionKind::Reference, + }; + + let kind = match resolution { + ScopeDef::ModuleDef(Function(func)) => { + let item = FunctionRender::new(self.ctx, Some(local_name), *func).render(); + return Some(item); + } + ScopeDef::ModuleDef(EnumVariant(var)) => { + let item = EnumVariantRender::new(self.ctx, Some(local_name), *var, None).render(); + return Some(item); + } + ScopeDef::MacroDef(mac) => { + let item = MacroRender::new(self.ctx, local_name, *mac).render(); + return item; + } + + ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::Module, + ScopeDef::ModuleDef(Adt(hir::Adt::Struct(_))) => CompletionItemKind::Struct, + // FIXME: add CompletionItemKind::Union + ScopeDef::ModuleDef(Adt(hir::Adt::Union(_))) => CompletionItemKind::Struct, + ScopeDef::ModuleDef(Adt(hir::Adt::Enum(_))) => CompletionItemKind::Enum, + ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::Const, + ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::Static, + ScopeDef::ModuleDef(Trait(..)) => CompletionItemKind::Trait, + ScopeDef::ModuleDef(TypeAlias(..)) => CompletionItemKind::TypeAlias, + ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType, + ScopeDef::GenericParam(..) => CompletionItemKind::TypeParam, + ScopeDef::Local(..) => CompletionItemKind::Binding, + // (does this need its own kind?) + ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => CompletionItemKind::TypeParam, + ScopeDef::Unknown => { + let item = CompletionItem::new( + CompletionKind::Reference, + self.ctx.source_range(), + local_name, + ) + .kind(CompletionItemKind::UnresolvedReference) + .build(); + return Some(item); + } + }; + + let docs = self.docs(resolution); + + let mut item = + CompletionItem::new(completion_kind, self.ctx.source_range(), local_name.clone()); + if let ScopeDef::Local(local) = resolution { + let ty = local.ty(self.ctx.db()); + if !ty.is_unknown() { + item = item.detail(ty.display(self.ctx.db()).to_string()); + } + }; + + let mut ref_match = None; + if let ScopeDef::Local(local) = resolution { + if let Some((active_name, active_type)) = self.ctx.active_name_and_type() { + let ty = local.ty(self.ctx.db()); + if let Some(score) = + compute_score_from_active(&active_type, &active_name, &ty, &local_name) + { + item = item.set_score(score); + } + ref_match = refed_type_matches(&active_type, &active_name, &ty, &local_name); + } + } + + // Add `<>` for generic types + if self.ctx.completion.is_path_type + && !self.ctx.completion.has_type_args + && self.ctx.completion.config.add_call_parenthesis + { + if let Some(cap) = self.ctx.snippet_cap() { + let has_non_default_type_params = match resolution { + ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(self.ctx.db()), + ScopeDef::ModuleDef(TypeAlias(it)) => { + it.has_non_default_type_params(self.ctx.db()) + } + _ => false, + }; + if has_non_default_type_params { + mark::hit!(inserts_angle_brackets_for_generics); + item = item + .lookup_by(local_name.clone()) + .label(format!("{}<…>", local_name)) + .insert_snippet(cap, format!("{}<$0>", local_name)); + } + } + } + + let item = item.kind(kind).set_documentation(docs).set_ref_match(ref_match).build(); + Some(item) + } + + fn docs(&self, resolution: &ScopeDef) -> Option { + use hir::ModuleDef::*; + match resolution { + ScopeDef::ModuleDef(Module(it)) => it.docs(self.ctx.db()), + ScopeDef::ModuleDef(Adt(it)) => it.docs(self.ctx.db()), + ScopeDef::ModuleDef(EnumVariant(it)) => it.docs(self.ctx.db()), + ScopeDef::ModuleDef(Const(it)) => it.docs(self.ctx.db()), + ScopeDef::ModuleDef(Static(it)) => it.docs(self.ctx.db()), + ScopeDef::ModuleDef(Trait(it)) => it.docs(self.ctx.db()), + ScopeDef::ModuleDef(TypeAlias(it)) => it.docs(self.ctx.db()), + _ => None, + } + } +} + +fn compute_score_from_active( + active_type: &Type, + active_name: &str, + ty: &Type, + name: &str, +) -> Option { + // Compute score + // For the same type + if active_type != ty { + return None; + } + + let mut res = CompletionScore::TypeMatch; + + // If same type + same name then go top position + if active_name == name { + res = CompletionScore::TypeAndNameMatch + } + + Some(res) +} +fn refed_type_matches( + active_type: &Type, + active_name: &str, + ty: &Type, + name: &str, +) -> Option<(Mutability, CompletionScore)> { + let derefed_active = active_type.remove_ref()?; + let score = compute_score_from_active(&derefed_active, &active_name, &ty, &name)?; + Some(( + if active_type.is_mutable_reference() { Mutability::Mut } else { Mutability::Shared }, + score, + )) +} + +fn compute_score(ctx: &RenderContext, ty: &Type, name: &str) -> Option { + let (active_name, active_type) = ctx.active_name_and_type()?; + compute_score_from_active(&active_type, &active_name, ty, name) +} + +#[cfg(test)] +mod tests { + use std::cmp::Reverse; + + use expect_test::{expect, Expect}; + use test_utils::mark; + + use crate::{ + test_utils::{check_edit, do_completion, get_all_items}, + CompletionConfig, CompletionKind, CompletionScore, + }; + + fn check(ra_fixture: &str, expect: Expect) { + let actual = do_completion(ra_fixture, CompletionKind::Reference); + expect.assert_debug_eq(&actual); + } + + fn check_scores(ra_fixture: &str, expect: Expect) { + fn display_score(score: Option) -> &'static str { + match score { + Some(CompletionScore::TypeMatch) => "[type]", + Some(CompletionScore::TypeAndNameMatch) => "[type+name]", + None => "[]".into(), + } + } + + let mut completions = get_all_items(CompletionConfig::default(), ra_fixture); + completions.sort_by_key(|it| (Reverse(it.score()), it.label().to_string())); + let actual = completions + .into_iter() + .filter(|it| it.completion_kind == CompletionKind::Reference) + .map(|it| { + let tag = it.kind().unwrap().tag(); + let score = display_score(it.score()); + format!("{} {} {}\n", tag, it.label(), score) + }) + .collect::(); + expect.assert_eq(&actual); + } + + #[test] + fn enum_detail_includes_record_fields() { + check( + r#" +enum Foo { Foo { x: i32, y: i32 } } + +fn main() { Foo::Fo<|> } +"#, + expect![[r#" + [ + CompletionItem { + label: "Foo", + source_range: 54..56, + delete: 54..56, + insert: "Foo", + kind: EnumVariant, + detail: "{ x: i32, y: i32 }", + }, + ] + "#]], + ); + } + + #[test] + fn enum_detail_doesnt_include_tuple_fields() { + check( + r#" +enum Foo { Foo (i32, i32) } + +fn main() { Foo::Fo<|> } +"#, + expect![[r#" + [ + CompletionItem { + label: "Foo(…)", + source_range: 46..48, + delete: 46..48, + insert: "Foo($0)", + kind: EnumVariant, + lookup: "Foo", + detail: "(i32, i32)", + trigger_call_info: true, + }, + ] + "#]], + ); + } + + #[test] + fn enum_detail_just_parentheses_for_unit() { + check( + r#" +enum Foo { Foo } + +fn main() { Foo::Fo<|> } +"#, + expect![[r#" + [ + CompletionItem { + label: "Foo", + source_range: 35..37, + delete: 35..37, + insert: "Foo", + kind: EnumVariant, + detail: "()", + }, + ] + "#]], + ); + } + + #[test] + fn lookup_enums_by_two_qualifiers() { + check( + r#" +mod m { + pub enum Spam { Foo, Bar(i32) } +} +fn main() { let _: m::Spam = S<|> } +"#, + expect![[r#" + [ + CompletionItem { + label: "Spam::Bar(…)", + source_range: 75..76, + delete: 75..76, + insert: "Spam::Bar($0)", + kind: EnumVariant, + lookup: "Spam::Bar", + detail: "(i32)", + trigger_call_info: true, + }, + CompletionItem { + label: "m", + source_range: 75..76, + delete: 75..76, + insert: "m", + kind: Module, + }, + CompletionItem { + label: "m::Spam::Foo", + source_range: 75..76, + delete: 75..76, + insert: "m::Spam::Foo", + kind: EnumVariant, + lookup: "Spam::Foo", + detail: "()", + }, + CompletionItem { + label: "main()", + source_range: 75..76, + delete: 75..76, + insert: "main()$0", + kind: Function, + lookup: "main", + detail: "fn main()", + }, + ] + "#]], + ) + } + + #[test] + fn sets_deprecated_flag_in_items() { + check( + r#" +#[deprecated] +fn something_deprecated() {} +#[deprecated(since = "1.0.0")] +fn something_else_deprecated() {} + +fn main() { som<|> } +"#, + expect![[r#" + [ + CompletionItem { + label: "main()", + source_range: 121..124, + delete: 121..124, + insert: "main()$0", + kind: Function, + lookup: "main", + detail: "fn main()", + }, + CompletionItem { + label: "something_deprecated()", + source_range: 121..124, + delete: 121..124, + insert: "something_deprecated()$0", + kind: Function, + lookup: "something_deprecated", + detail: "fn something_deprecated()", + deprecated: true, + }, + CompletionItem { + label: "something_else_deprecated()", + source_range: 121..124, + delete: 121..124, + insert: "something_else_deprecated()$0", + kind: Function, + lookup: "something_else_deprecated", + detail: "fn something_else_deprecated()", + deprecated: true, + }, + ] + "#]], + ); + + check( + r#" +struct A { #[deprecated] the_field: u32 } +fn foo() { A { the<|> } } +"#, + expect![[r#" + [ + CompletionItem { + label: "the_field", + source_range: 57..60, + delete: 57..60, + insert: "the_field", + kind: Field, + detail: "u32", + deprecated: true, + }, + ] + "#]], + ); + } + + #[test] + fn renders_docs() { + check( + r#" +struct S { + /// Field docs + foo: +} +impl S { + /// Method docs + fn bar(self) { self.<|> } +}"#, + expect![[r#" + [ + CompletionItem { + label: "bar()", + source_range: 94..94, + delete: 94..94, + insert: "bar()$0", + kind: Method, + lookup: "bar", + detail: "fn bar(self)", + documentation: Documentation( + "Method docs", + ), + }, + CompletionItem { + label: "foo", + source_range: 94..94, + delete: 94..94, + insert: "foo", + kind: Field, + detail: "{unknown}", + documentation: Documentation( + "Field docs", + ), + }, + ] + "#]], + ); + + check( + r#" +use self::my<|>; + +/// mod docs +mod my { } + +/// enum docs +enum E { + /// variant docs + V +} +use self::E::*; +"#, + expect![[r#" + [ + CompletionItem { + label: "E", + source_range: 10..12, + delete: 10..12, + insert: "E", + kind: Enum, + documentation: Documentation( + "enum docs", + ), + }, + CompletionItem { + label: "V", + source_range: 10..12, + delete: 10..12, + insert: "V", + kind: EnumVariant, + detail: "()", + documentation: Documentation( + "variant docs", + ), + }, + CompletionItem { + label: "my", + source_range: 10..12, + delete: 10..12, + insert: "my", + kind: Module, + documentation: Documentation( + "mod docs", + ), + }, + ] + "#]], + ) + } + + #[test] + fn dont_render_attrs() { + check( + r#" +struct S; +impl S { + #[inline] + fn the_method(&self) { } +} +fn foo(s: S) { s.<|> } +"#, + expect![[r#" + [ + CompletionItem { + label: "the_method()", + source_range: 81..81, + delete: 81..81, + insert: "the_method()$0", + kind: Method, + lookup: "the_method", + detail: "fn the_method(&self)", + }, + ] + "#]], + ) + } + + #[test] + fn no_call_parens_if_fn_ptr_needed() { + mark::check!(no_call_parens_if_fn_ptr_needed); + check_edit( + "foo", + r#" +fn foo(foo: u8, bar: u8) {} +struct ManualVtable { f: fn(u8, u8) } + +fn main() -> ManualVtable { + ManualVtable { f: f<|> } +} +"#, + r#" +fn foo(foo: u8, bar: u8) {} +struct ManualVtable { f: fn(u8, u8) } + +fn main() -> ManualVtable { + ManualVtable { f: foo } +} +"#, + ); + } + + #[test] + fn no_parens_in_use_item() { + mark::check!(no_parens_in_use_item); + check_edit( + "foo", + r#" +mod m { pub fn foo() {} } +use crate::m::f<|>; +"#, + r#" +mod m { pub fn foo() {} } +use crate::m::foo; +"#, + ); + } + + #[test] + fn no_parens_in_call() { + check_edit( + "foo", + r#" +fn foo(x: i32) {} +fn main() { f<|>(); } +"#, + r#" +fn foo(x: i32) {} +fn main() { foo(); } +"#, + ); + check_edit( + "foo", + r#" +struct Foo; +impl Foo { fn foo(&self){} } +fn f(foo: &Foo) { foo.f<|>(); } +"#, + r#" +struct Foo; +impl Foo { fn foo(&self){} } +fn f(foo: &Foo) { foo.foo(); } +"#, + ); + } + + #[test] + fn inserts_angle_brackets_for_generics() { + mark::check!(inserts_angle_brackets_for_generics); + check_edit( + "Vec", + r#" +struct Vec {} +fn foo(xs: Ve<|>) +"#, + r#" +struct Vec {} +fn foo(xs: Vec<$0>) +"#, + ); + check_edit( + "Vec", + r#" +type Vec = (T,); +fn foo(xs: Ve<|>) +"#, + r#" +type Vec = (T,); +fn foo(xs: Vec<$0>) +"#, + ); + check_edit( + "Vec", + r#" +struct Vec {} +fn foo(xs: Ve<|>) +"#, + r#" +struct Vec {} +fn foo(xs: Vec) +"#, + ); + check_edit( + "Vec", + r#" +struct Vec {} +fn foo(xs: Ve<|>) +"#, + r#" +struct Vec {} +fn foo(xs: Vec) +"#, + ); + } + + #[test] + fn active_param_score() { + mark::check!(active_param_type_match); + check_scores( + r#" +struct S { foo: i64, bar: u32, baz: u32 } +fn test(bar: u32) { } +fn foo(s: S) { test(s.<|>) } +"#, + expect![[r#" + fd bar [type+name] + fd baz [type] + fd foo [] + "#]], + ); + } + + #[test] + fn record_field_scores() { + mark::check!(record_field_type_match); + check_scores( + r#" +struct A { foo: i64, bar: u32, baz: u32 } +struct B { x: (), y: f32, bar: u32 } +fn foo(a: A) { B { bar: a.<|> }; } +"#, + expect![[r#" + fd bar [type+name] + fd baz [type] + fd foo [] + "#]], + ) + } + + #[test] + fn record_field_and_call_scores() { + check_scores( + r#" +struct A { foo: i64, bar: u32, baz: u32 } +struct B { x: (), y: f32, bar: u32 } +fn f(foo: i64) { } +fn foo(a: A) { B { bar: f(a.<|>) }; } +"#, + expect![[r#" + fd foo [type+name] + fd bar [] + fd baz [] + "#]], + ); + check_scores( + r#" +struct A { foo: i64, bar: u32, baz: u32 } +struct B { x: (), y: f32, bar: u32 } +fn f(foo: i64) { } +fn foo(a: A) { f(B { bar: a.<|> }); } +"#, + expect![[r#" + fd bar [type+name] + fd baz [type] + fd foo [] + "#]], + ); + } + + #[test] + fn prioritize_exact_ref_match() { + check_scores( + r#" +struct WorldSnapshot { _f: () }; +fn go(world: &WorldSnapshot) { go(w<|>) } +"#, + expect![[r#" + bn world [type+name] + st WorldSnapshot [] + fn go(…) [] + "#]], + ); + } + + #[test] + fn too_many_arguments() { + check_scores( + r#" +struct Foo; +fn f(foo: &Foo) { f(foo, w<|>) } +"#, + expect![[r#" + st Foo [] + fn f(…) [] + bn foo [] + "#]], + ); + } +} diff --git a/crates/completion/src/render/enum_variant.rs b/crates/completion/src/render/enum_variant.rs index 26cfdfeea70..bc96cc30362 100644 --- a/crates/completion/src/render/enum_variant.rs +++ b/crates/completion/src/render/enum_variant.rs @@ -93,3 +93,77 @@ fn detail(&self) -> String { } } } + +#[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<|> +} +"#, + r#" +enum Option { Some(T), None } +use Option::*; +fn main() -> Option { + Some($0) +} +"#, + ); + check_edit( + "Some", + r#" +enum Option { Some(T), None } +use Option::*; +fn main(value: Option) { + match value { + Som<|> + } +} +"#, + r#" +enum Option { Some(T), None } +use Option::*; +fn main(value: Option) { + match value { + Some($0) + } +} +"#, + ); + } + + #[test] + fn dont_duplicate_pattern_parens() { + mark::check!(dont_duplicate_pattern_parens); + check_edit( + "Var", + r#" +enum E { Var(i32) } +fn main() { + match E::Var(92) { + E::<|>(92) => (), + } +} +"#, + r#" +enum E { Var(i32) } +fn main() { + match E::Var(92) { + E::Var(92) => (), + } +} +"#, + ); + } +} diff --git a/crates/completion/src/render/function.rs b/crates/completion/src/render/function.rs index d220812361d..cf3852bf665 100644 --- a/crates/completion/src/render/function.rs +++ b/crates/completion/src/render/function.rs @@ -81,3 +81,213 @@ fn kind(&self) -> CompletionItemKind { } } } + +#[cfg(test)] +mod tests { + use test_utils::mark; + + use crate::{ + test_utils::{check_edit, check_edit_with_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_<|> } +"#, + r#" +fn no_args() {} +fn main() { no_args()$0 } +"#, + ); + + check_edit( + "with_args", + r#" +fn with_args(x: i32, y: String) {} +fn main() { with_<|> } +"#, + 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<|> } +"#, + 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<|> +} +"#, + r#" +struct S {} +impl S { + fn foo(&self, x: i32) {} +} +fn bar(s: &S) { + s.foo(${1:x})$0 +} +"#, + ); + } + + #[test] + fn suppress_arg_snippets() { + mark::check!(suppress_arg_snippets); + check_edit_with_config( + CompletionConfig { add_call_argument_snippets: false, ..CompletionConfig::default() }, + "with_args", + r#" +fn with_args(x: i32, y: String) {} +fn main() { with_<|> } +"#, + 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<|> } +"#, + 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<|> +} +"#, + 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<|> +} +"#, + 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.<|> +} +"#, + 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<|> +} +"#, + r#" +fn take_mutably(mut x: &i32) {} + +fn main() { + take_mutably(${1:x})$0 +} +"#, + ); + } +} diff --git a/crates/completion/src/render/macro_.rs b/crates/completion/src/render/macro_.rs index 0ad8c03b342..6df121c66df 100644 --- a/crates/completion/src/render/macro_.rs +++ b/crates/completion/src/render/macro_.rs @@ -114,3 +114,93 @@ fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static s .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::<|>; +//- /foo/lib.rs crate:foo +#[macro_export] +macro_rules frobnicate { () => () } +"#, + r#" +use foo::frobnicate; +"#, + ); + + check_edit( + "frobnicate!", + r#" +macro_rules frobnicate { () => () } +fn main() { frob<|>!(); } +"#, + 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<|> } +"#, + 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() { <|> } +"#, + r#" +/// Foo +/// +/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`, +/// call as `let _=foo! { hello world };` +macro_rules! foo { () => {} } +fn main() { foo! {$0} } +"#, + ) + } +} From af7175f3328c08e39d746c7e7b83c397dd2769bf Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Sun, 1 Nov 2020 13:39:05 +0300 Subject: [PATCH 5/9] Make structures order more logical in render.rs --- crates/completion/src/render.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs index 28731e04f4b..d78877421ad 100644 --- a/crates/completion/src/render.rs +++ b/crates/completion/src/render.rs @@ -23,6 +23,11 @@ macro_::MacroRender, type_alias::TypeAliasRender, }; +#[derive(Debug)] +pub(crate) struct Render<'a> { + ctx: RenderContext<'a>, +} + #[derive(Debug)] pub(crate) struct RenderContext<'a> { completion: &'a CompletionContext<'a>, @@ -73,11 +78,6 @@ fn from(ctx: &'a CompletionContext<'a>) -> RenderContext<'a> { } } -#[derive(Debug)] -pub(crate) struct Render<'a> { - ctx: RenderContext<'a>, -} - impl<'a> Render<'a> { pub(crate) fn new(ctx: RenderContext<'a>) -> Render<'a> { Render { ctx } From 2a214e15d38c7d97243e23e5e26fee5f4e26bb50 Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Sun, 1 Nov 2020 13:48:42 +0300 Subject: [PATCH 6/9] Add doc-comments to the new files --- crates/completion/src/render.rs | 2 ++ crates/completion/src/render/const_.rs | 2 ++ crates/completion/src/render/enum_variant.rs | 2 ++ crates/completion/src/render/function.rs | 2 ++ crates/completion/src/render/macro_.rs | 8 +++++++- crates/completion/src/render/type_alias.rs | 2 ++ 6 files changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs index d78877421ad..dd9edc51466 100644 --- a/crates/completion/src/render.rs +++ b/crates/completion/src/render.rs @@ -23,11 +23,13 @@ macro_::MacroRender, type_alias::TypeAliasRender, }; +/// Generic renderer for completion items. #[derive(Debug)] pub(crate) struct Render<'a> { ctx: RenderContext<'a>, } +/// Interface for data and methods required for items rendering. #[derive(Debug)] pub(crate) struct RenderContext<'a> { completion: &'a CompletionContext<'a>, diff --git a/crates/completion/src/render/const_.rs b/crates/completion/src/render/const_.rs index 829eb574d0f..3a195421109 100644 --- a/crates/completion/src/render/const_.rs +++ b/crates/completion/src/render/const_.rs @@ -1,3 +1,5 @@ +//! Renderer for `const` fields. + use hir::HasSource; use syntax::{ ast::{Const, NameOwner}, diff --git a/crates/completion/src/render/enum_variant.rs b/crates/completion/src/render/enum_variant.rs index bc96cc30362..f8b151318e0 100644 --- a/crates/completion/src/render/enum_variant.rs +++ b/crates/completion/src/render/enum_variant.rs @@ -1,3 +1,5 @@ +//! Renderer for `enum` variants. + use hir::{HasAttrs, HirDisplay, ModPath, StructKind}; use itertools::Itertools; use test_utils::mark; diff --git a/crates/completion/src/render/function.rs b/crates/completion/src/render/function.rs index cf3852bf665..9d9bbe30920 100644 --- a/crates/completion/src/render/function.rs +++ b/crates/completion/src/render/function.rs @@ -1,3 +1,5 @@ +//! Renderer for function calls. + use hir::{HasSource, Type}; use syntax::{ast::Fn, display::function_declaration}; diff --git a/crates/completion/src/render/macro_.rs b/crates/completion/src/render/macro_.rs index 6df121c66df..06618555929 100644 --- a/crates/completion/src/render/macro_.rs +++ b/crates/completion/src/render/macro_.rs @@ -1,3 +1,5 @@ +//! Renderer for macro invocations. + use hir::{Documentation, HasSource}; use syntax::display::macro_label; use test_utils::mark; @@ -66,7 +68,11 @@ fn needs_bang(&self) -> bool { } fn label(&self) -> String { - format!("{}!{}…{}", self.name, self.bra, self.ket) + 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 { diff --git a/crates/completion/src/render/type_alias.rs b/crates/completion/src/render/type_alias.rs index 378aa8c6752..50a6d06c236 100644 --- a/crates/completion/src/render/type_alias.rs +++ b/crates/completion/src/render/type_alias.rs @@ -1,3 +1,5 @@ +//! Renderer for type aliases. + use hir::HasSource; use syntax::{ ast::{NameOwner, TypeAlias}, From caf0fa20a7d895612ceee1948d6a9895e53bee4a Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Tue, 3 Nov 2020 10:17:59 +0300 Subject: [PATCH 7/9] Provide only explicit constructor for RenderContext --- crates/completion/src/completions.rs | 22 +++++++++++++--------- crates/completion/src/render.rs | 8 +------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index b54771fcd1c..d59f5ca05e5 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs @@ -52,12 +52,12 @@ pub(crate) fn add_all(&mut self, items: I) } pub(crate) fn add_field(&mut self, ctx: &CompletionContext, field: hir::Field, ty: &Type) { - let item = Render::new(ctx.into()).add_field(field, ty); + let item = Render::new(RenderContext::new(ctx)).add_field(field, ty); self.add(item); } pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) { - let item = Render::new(ctx.into()).add_tuple_field(field, ty); + let item = Render::new(RenderContext::new(ctx)).add_tuple_field(field, ty); self.add(item); } @@ -67,7 +67,9 @@ pub(crate) fn add_resolution( local_name: String, resolution: &ScopeDef, ) { - if let Some(item) = Render::new(ctx.into()).render_resolution(local_name, resolution) { + if let Some(item) = + Render::new(RenderContext::new(ctx)).render_resolution(local_name, resolution) + { self.add(item); } } @@ -82,7 +84,7 @@ pub(crate) fn add_macro( Some(it) => it, None => return, }; - if let Some(item) = MacroRender::new(ctx.into(), name, macro_).render() { + if let Some(item) = MacroRender::new(RenderContext::new(ctx), name, macro_).render() { self.add(item); } } @@ -93,18 +95,18 @@ pub(crate) fn add_function( func: hir::Function, local_name: Option, ) { - let item = FunctionRender::new(ctx.into(), local_name, func).render(); + let item = FunctionRender::new(RenderContext::new(ctx), local_name, func).render(); self.add(item) } pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) { - if let Some(item) = ConstRender::new(ctx.into(), constant).render() { + if let Some(item) = ConstRender::new(RenderContext::new(ctx), constant).render() { self.add(item); } } pub(crate) fn add_type_alias(&mut self, ctx: &CompletionContext, type_alias: hir::TypeAlias) { - if let Some(item) = TypeAliasRender::new(ctx.into(), type_alias).render() { + if let Some(item) = TypeAliasRender::new(RenderContext::new(ctx), type_alias).render() { self.add(item) } } @@ -115,7 +117,8 @@ pub(crate) fn add_qualified_enum_variant( variant: hir::EnumVariant, path: ModPath, ) { - let item = EnumVariantRender::new(ctx.into(), None, variant, Some(path)).render(); + let item = + EnumVariantRender::new(RenderContext::new(ctx), None, variant, Some(path)).render(); self.add(item); } @@ -125,7 +128,8 @@ pub(crate) fn add_enum_variant( variant: hir::EnumVariant, local_name: Option, ) { - let item = EnumVariantRender::new(ctx.into(), local_name, variant, None).render(); + let item = + EnumVariantRender::new(RenderContext::new(ctx), local_name, variant, None).render(); self.add(item); } } diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs index dd9edc51466..dfba8173434 100644 --- a/crates/completion/src/render.rs +++ b/crates/completion/src/render.rs @@ -36,7 +36,7 @@ pub(crate) struct RenderContext<'a> { } impl<'a> RenderContext<'a> { - fn new(completion: &'a CompletionContext<'a>) -> RenderContext<'a> { + pub(crate) fn new(completion: &'a CompletionContext<'a>) -> RenderContext<'a> { RenderContext { completion } } @@ -74,12 +74,6 @@ fn active_name_and_type(&self) -> Option<(String, Type)> { } } -impl<'a> From<&'a CompletionContext<'a>> for RenderContext<'a> { - fn from(ctx: &'a CompletionContext<'a>) -> RenderContext<'a> { - RenderContext::new(ctx) - } -} - impl<'a> Render<'a> { pub(crate) fn new(ctx: RenderContext<'a>) -> Render<'a> { Render { ctx } From 4d333ebb6372c135e5a723da899528cc22d07faa Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Tue, 3 Nov 2020 10:33:13 +0300 Subject: [PATCH 8/9] Get rid of do-er antipattern --- crates/completion/src/completions.rs | 22 ++++----- crates/completion/src/render.rs | 50 +++++++++++++++----- crates/completion/src/render/const_.rs | 13 +++-- crates/completion/src/render/enum_variant.rs | 15 ++++-- crates/completion/src/render/function.rs | 14 ++++-- crates/completion/src/render/macro_.rs | 18 ++++--- crates/completion/src/render/type_alias.rs | 13 +++-- 7 files changed, 100 insertions(+), 45 deletions(-) diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index d59f5ca05e5..162d567d978 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs @@ -52,12 +52,12 @@ pub(crate) fn add_all(&mut self, items: I) } pub(crate) fn add_field(&mut self, ctx: &CompletionContext, field: hir::Field, ty: &Type) { - let item = Render::new(RenderContext::new(ctx)).add_field(field, ty); + let item = render_field(RenderContext::new(ctx), field, ty); self.add(item); } pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) { - let item = Render::new(RenderContext::new(ctx)).add_tuple_field(field, ty); + let item = render_tuple_field(RenderContext::new(ctx), field, ty); self.add(item); } @@ -67,9 +67,7 @@ pub(crate) fn add_resolution( local_name: String, resolution: &ScopeDef, ) { - if let Some(item) = - Render::new(RenderContext::new(ctx)).render_resolution(local_name, resolution) - { + if let Some(item) = render_resolution(RenderContext::new(ctx), local_name, resolution) { self.add(item); } } @@ -84,7 +82,7 @@ pub(crate) fn add_macro( Some(it) => it, None => return, }; - if let Some(item) = MacroRender::new(RenderContext::new(ctx), name, macro_).render() { + if let Some(item) = render_macro(RenderContext::new(ctx), name, macro_) { self.add(item); } } @@ -95,18 +93,18 @@ pub(crate) fn add_function( func: hir::Function, local_name: Option, ) { - let item = FunctionRender::new(RenderContext::new(ctx), local_name, func).render(); + let item = render_fn(RenderContext::new(ctx), local_name, func); self.add(item) } pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) { - if let Some(item) = ConstRender::new(RenderContext::new(ctx), constant).render() { + if let Some(item) = render_const(RenderContext::new(ctx), constant) { self.add(item); } } pub(crate) fn add_type_alias(&mut self, ctx: &CompletionContext, type_alias: hir::TypeAlias) { - if let Some(item) = TypeAliasRender::new(RenderContext::new(ctx), type_alias).render() { + if let Some(item) = render_type_alias(RenderContext::new(ctx), type_alias) { self.add(item) } } @@ -117,8 +115,7 @@ pub(crate) fn add_qualified_enum_variant( variant: hir::EnumVariant, path: ModPath, ) { - let item = - EnumVariantRender::new(RenderContext::new(ctx), None, variant, Some(path)).render(); + let item = render_enum_variant(RenderContext::new(ctx), None, variant, Some(path)); self.add(item); } @@ -128,8 +125,7 @@ pub(crate) fn add_enum_variant( variant: hir::EnumVariant, local_name: Option, ) { - let item = - EnumVariantRender::new(RenderContext::new(ctx), local_name, variant, None).render(); + let item = render_enum_variant(RenderContext::new(ctx), local_name, variant, None); self.add(item); } } diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs index dfba8173434..b54241d5122 100644 --- a/crates/completion/src/render.rs +++ b/crates/completion/src/render.rs @@ -19,14 +19,32 @@ }; pub(crate) use crate::render::{ - const_::ConstRender, enum_variant::EnumVariantRender, function::FunctionRender, - macro_::MacroRender, type_alias::TypeAliasRender, + const_::render_const, enum_variant::render_enum_variant, function::render_fn, + macro_::render_macro, type_alias::render_type_alias, }; -/// Generic renderer for completion items. -#[derive(Debug)] -pub(crate) struct Render<'a> { +pub(crate) fn render_field<'a>( ctx: RenderContext<'a>, + field: hir::Field, + ty: &Type, +) -> CompletionItem { + Render::new(ctx).add_field(field, ty) +} + +pub(crate) fn render_tuple_field<'a>( + ctx: RenderContext<'a>, + field: usize, + ty: &Type, +) -> CompletionItem { + Render::new(ctx).add_tuple_field(field, ty) +} + +pub(crate) fn render_resolution<'a>( + ctx: RenderContext<'a>, + local_name: String, + resolution: &ScopeDef, +) -> Option { + Render::new(ctx).render_resolution(local_name, resolution) } /// Interface for data and methods required for items rendering. @@ -74,12 +92,18 @@ fn active_name_and_type(&self) -> Option<(String, Type)> { } } +/// Generic renderer for completion items. +#[derive(Debug)] +struct Render<'a> { + ctx: RenderContext<'a>, +} + impl<'a> Render<'a> { - pub(crate) fn new(ctx: RenderContext<'a>) -> Render<'a> { + fn new(ctx: RenderContext<'a>) -> Render<'a> { Render { ctx } } - pub(crate) fn add_field(&mut self, field: hir::Field, ty: &Type) -> CompletionItem { + fn add_field(&mut self, field: hir::Field, ty: &Type) -> CompletionItem { let is_deprecated = self.ctx.is_deprecated(field); let name = field.name(self.ctx.db()); let mut item = CompletionItem::new( @@ -96,17 +120,17 @@ pub(crate) fn add_field(&mut self, field: hir::Field, ty: &Type) -> CompletionIt item = item.set_score(score); } - return item.build(); + item.build() } - pub(crate) fn add_tuple_field(&mut self, field: usize, ty: &Type) -> CompletionItem { + fn add_tuple_field(&mut self, field: usize, ty: &Type) -> CompletionItem { CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), field.to_string()) .kind(CompletionItemKind::Field) .detail(ty.display(self.ctx.db()).to_string()) .build() } - pub(crate) fn render_resolution( + fn render_resolution( self, local_name: String, resolution: &ScopeDef, @@ -120,15 +144,15 @@ pub(crate) fn render_resolution( let kind = match resolution { ScopeDef::ModuleDef(Function(func)) => { - let item = FunctionRender::new(self.ctx, Some(local_name), *func).render(); + let item = render_fn(self.ctx, Some(local_name), *func); return Some(item); } ScopeDef::ModuleDef(EnumVariant(var)) => { - let item = EnumVariantRender::new(self.ctx, Some(local_name), *var, None).render(); + let item = render_enum_variant(self.ctx, Some(local_name), *var, None); return Some(item); } ScopeDef::MacroDef(mac) => { - let item = MacroRender::new(self.ctx, local_name, *mac).render(); + let item = render_macro(self.ctx, local_name, *mac); return item; } diff --git a/crates/completion/src/render/const_.rs b/crates/completion/src/render/const_.rs index 3a195421109..039bdabc051 100644 --- a/crates/completion/src/render/const_.rs +++ b/crates/completion/src/render/const_.rs @@ -11,20 +11,27 @@ render::RenderContext, }; +pub(crate) fn render_const<'a>( + ctx: RenderContext<'a>, + const_: hir::Const, +) -> Option { + ConstRender::new(ctx, const_).render() +} + #[derive(Debug)] -pub(crate) struct ConstRender<'a> { +struct ConstRender<'a> { ctx: RenderContext<'a>, const_: hir::Const, ast_node: Const, } impl<'a> ConstRender<'a> { - pub(crate) fn new(ctx: RenderContext<'a>, const_: hir::Const) -> ConstRender<'a> { + fn new(ctx: RenderContext<'a>, const_: hir::Const) -> ConstRender<'a> { let ast_node = const_.source(ctx.db()).value; ConstRender { ctx, const_, ast_node } } - pub(crate) fn render(self) -> Option { + fn render(self) -> Option { let name = self.name()?; let detail = self.detail(); diff --git a/crates/completion/src/render/enum_variant.rs b/crates/completion/src/render/enum_variant.rs index f8b151318e0..fd412ed0eef 100644 --- a/crates/completion/src/render/enum_variant.rs +++ b/crates/completion/src/render/enum_variant.rs @@ -9,8 +9,17 @@ render::{builder_ext::Params, RenderContext}, }; +pub(crate) fn render_enum_variant<'a>( + ctx: RenderContext<'a>, + local_name: Option, + variant: hir::EnumVariant, + path: Option, +) -> CompletionItem { + EnumVariantRender::new(ctx, local_name, variant, path).render() +} + #[derive(Debug)] -pub(crate) struct EnumVariantRender<'a> { +struct EnumVariantRender<'a> { ctx: RenderContext<'a>, name: String, variant: hir::EnumVariant, @@ -21,7 +30,7 @@ pub(crate) struct EnumVariantRender<'a> { } impl<'a> EnumVariantRender<'a> { - pub(crate) fn new( + fn new( ctx: RenderContext<'a>, local_name: Option, variant: hir::EnumVariant, @@ -51,7 +60,7 @@ pub(crate) fn new( } } - pub(crate) fn render(self) -> CompletionItem { + fn render(self) -> CompletionItem { let mut builder = CompletionItem::new( CompletionKind::Reference, self.ctx.source_range(), diff --git a/crates/completion/src/render/function.rs b/crates/completion/src/render/function.rs index 9d9bbe30920..4fa6eafd72f 100644 --- a/crates/completion/src/render/function.rs +++ b/crates/completion/src/render/function.rs @@ -8,8 +8,16 @@ render::{builder_ext::Params, RenderContext}, }; +pub(crate) fn render_fn<'a>( + ctx: RenderContext<'a>, + local_name: Option, + fn_: hir::Function, +) -> CompletionItem { + FunctionRender::new(ctx, local_name, fn_).render() +} + #[derive(Debug)] -pub(crate) struct FunctionRender<'a> { +struct FunctionRender<'a> { ctx: RenderContext<'a>, name: String, fn_: hir::Function, @@ -17,7 +25,7 @@ pub(crate) struct FunctionRender<'a> { } impl<'a> FunctionRender<'a> { - pub(crate) fn new( + fn new( ctx: RenderContext<'a>, local_name: Option, fn_: hir::Function, @@ -28,7 +36,7 @@ pub(crate) fn new( FunctionRender { ctx, name, fn_, ast_node } } - pub(crate) fn render(self) -> CompletionItem { + fn render(self) -> CompletionItem { let params = self.params(); CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), self.name.clone()) .kind(self.kind()) diff --git a/crates/completion/src/render/macro_.rs b/crates/completion/src/render/macro_.rs index 06618555929..96be59cc336 100644 --- a/crates/completion/src/render/macro_.rs +++ b/crates/completion/src/render/macro_.rs @@ -9,8 +9,16 @@ render::RenderContext, }; +pub(crate) fn render_macro<'a>( + ctx: RenderContext<'a>, + name: String, + macro_: hir::MacroDef, +) -> Option { + MacroRender::new(ctx, name, macro_).render() +} + #[derive(Debug)] -pub(crate) struct MacroRender<'a> { +struct MacroRender<'a> { ctx: RenderContext<'a>, name: String, macro_: hir::MacroDef, @@ -20,11 +28,7 @@ pub(crate) struct MacroRender<'a> { } impl<'a> MacroRender<'a> { - pub(crate) fn new( - ctx: RenderContext<'a>, - name: String, - macro_: hir::MacroDef, - ) -> 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); @@ -32,7 +36,7 @@ pub(crate) fn new( MacroRender { ctx, name, macro_, docs, bra, ket } } - pub(crate) fn render(&self) -> Option { + fn render(&self) -> Option { // FIXME: Currently proc-macro do not have ast-node, // such that it does not have source if self.macro_.is_proc_macro() { diff --git a/crates/completion/src/render/type_alias.rs b/crates/completion/src/render/type_alias.rs index 50a6d06c236..9605c7fa942 100644 --- a/crates/completion/src/render/type_alias.rs +++ b/crates/completion/src/render/type_alias.rs @@ -11,20 +11,27 @@ 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)] -pub(crate) struct TypeAliasRender<'a> { +struct TypeAliasRender<'a> { ctx: RenderContext<'a>, type_alias: hir::TypeAlias, ast_node: TypeAlias, } impl<'a> TypeAliasRender<'a> { - pub(crate) fn new(ctx: RenderContext<'a>, type_alias: hir::TypeAlias) -> TypeAliasRender<'a> { + fn new(ctx: RenderContext<'a>, type_alias: hir::TypeAlias) -> TypeAliasRender<'a> { let ast_node = type_alias.source(ctx.db()).value; TypeAliasRender { ctx, type_alias, ast_node } } - pub(crate) fn render(self) -> Option { + fn render(self) -> Option { let name = self.name()?; let detail = self.detail(); From 8efe43245bc64ed27f88c333541b2cb7af2ce44c Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Tue, 3 Nov 2020 10:36:01 +0300 Subject: [PATCH 9/9] Remove intra-crate facade from completions --- crates/completion/src/completions.rs | 10 +++++++++- crates/completion/src/render.rs | 16 +++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index 162d567d978..75dbb1a23bb 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs @@ -16,7 +16,15 @@ use hir::{ModPath, ScopeDef, Type}; -use crate::{item::Builder, render::*, CompletionContext, CompletionItem}; +use crate::{ + item::Builder, + render::{ + const_::render_const, enum_variant::render_enum_variant, function::render_fn, + macro_::render_macro, render_field, render_resolution, render_tuple_field, + type_alias::render_type_alias, RenderContext, + }, + CompletionContext, CompletionItem, +}; /// Represents an in-progress set of completions being built. #[derive(Debug, Default)] diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs index b54241d5122..1fa02c37522 100644 --- a/crates/completion/src/render.rs +++ b/crates/completion/src/render.rs @@ -1,12 +1,13 @@ //! `render` module provides utilities for rendering completion suggestions //! into code pieces that will be presented to user. -mod macro_; -mod function; +pub(crate) mod macro_; +pub(crate) mod function; +pub(crate) mod enum_variant; +pub(crate) mod const_; +pub(crate) mod type_alias; + mod builder_ext; -mod enum_variant; -mod const_; -mod type_alias; use hir::{Documentation, HasAttrs, HirDisplay, Mutability, ScopeDef, Type}; use ide_db::RootDatabase; @@ -18,10 +19,7 @@ CompletionScore, }; -pub(crate) use crate::render::{ - const_::render_const, enum_variant::render_enum_variant, function::render_fn, - macro_::render_macro, type_alias::render_type_alias, -}; +use crate::render::{enum_variant::render_enum_variant, function::render_fn, macro_::render_macro}; pub(crate) fn render_field<'a>( ctx: RenderContext<'a>,