//! Renderer for macro invocations. use hir::{db::HirDatabase, Documentation, HasAttrs, HasSource}; use syntax::display::macro_label; use test_utils::mark; use crate::{ item::{CompletionItem, CompletionItemKind, CompletionKind}, 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)] struct MacroRender<'a> { ctx: RenderContext<'a>, name: String, macro_: hir::MacroDef, docs: Option, bra: &'static str, ket: &'static str, } pub fn guess_macro_braces( db: &dyn HirDatabase, macro_: hir::MacroDef, ) -> (&'static str, &'static str) { let macro_name = match macro_.name(db) { Some(name) => name.to_string(), None => return ("(", ")"), }; let macro_docs = macro_.docs(db); let macro_docs = macro_docs.as_ref().map(Documentation::as_str).unwrap_or(""); let mut votes = [0, 0, 0]; for (idx, s) in macro_docs.match_indices(¯o_name) { let (before, after) = (¯o_docs[..idx], ¯o_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) } impl<'a> MacroRender<'a> { fn new(ctx: RenderContext<'a>, name: String, macro_: hir::MacroDef) -> MacroRender<'a> { let docs = ctx.docs(macro_); let (bra, ket) = guess_macro_braces(ctx.db(), macro_); MacroRender { ctx, name, macro_, docs, bra, ket } } 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 { if self.needs_bang() && self.ctx.snippet_cap().is_some() { format!("{}!{}…{}", self.name, self.bra, self.ket) } else { self.banged_name() } } fn snippet(&self) -> String { format!("{}!{}$0{}", self.name, self.bra, self.ket) } fn lookup(&self) -> String { self.banged_name() } fn banged_name(&self) -> String { format!("{}!", self.name) } fn detail(&self) -> String { let ast_node = self.macro_.source(self.ctx.db()).value; macro_label(&ast_node) } } #[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} } "#, ) } }