From fc8a1cd8006b021541ff673ec7f37a0f4b7bef57 Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Sun, 1 Nov 2020 12:35:04 +0300 Subject: 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 (limited to 'crates/completion/src') diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index d5fb85b79..1ca5cf33d 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs @@ -14,14 +14,15 @@ pub(crate) mod macro_in_item_position; 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 @@ impl Completions { 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 @@ impl Completions { 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 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(); + let item = FunctionRender::new(ctx.into(), local_name, func).render(); - 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 @@ impl Completions { 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 @@ impl Completions { 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 89c0a9978..cb6e0554e 100644 --- a/crates/completion/src/lib.rs +++ b/crates/completion/src/lib.rs @@ -7,6 +7,7 @@ mod patterns; 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 000000000..66eb753b1 --- /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 000000000..37b0d0459 --- /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 000000000..26cfdfeea --- /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 000000000..16f15e22c --- /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 000000000..bcf94f47e --- /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) +} -- cgit v1.2.3