1 //! Renderer for macro invocations.
5 use ide_db::SymbolKind;
7 display::{fn_as_proc_macro_label, macro_label},
13 item::{CompletionItem, ImportEdit},
14 render::RenderContext,
17 pub(crate) fn render_macro(
18 ctx: RenderContext<'_>,
19 import_to_add: Option<ImportEdit>,
21 macro_: hir::MacroDef,
22 ) -> Option<CompletionItem> {
23 let _p = profile::span("render_macro");
24 MacroRender::new(ctx, name, macro_).render(import_to_add)
28 struct MacroRender<'a> {
29 ctx: RenderContext<'a>,
31 macro_: hir::MacroDef,
32 docs: Option<hir::Documentation>,
37 impl<'a> MacroRender<'a> {
38 fn new(ctx: RenderContext<'a>, name: hir::Name, macro_: hir::MacroDef) -> MacroRender<'a> {
39 let name = name.to_smol_str();
40 let docs = ctx.docs(macro_);
41 let docs_str = docs.as_ref().map_or("", |s| s.as_str());
43 if macro_.is_fn_like() { guess_macro_braces(&name, docs_str) } else { ("", "") };
45 MacroRender { ctx, name, macro_, docs, bra, ket }
48 fn render(self, import_to_add: Option<ImportEdit>) -> Option<CompletionItem> {
49 let source_range = if self.ctx.completion.is_immediately_after_macro_bang() {
50 cov_mark::hit!(completes_macro_call_if_cursor_at_bang_token);
51 self.ctx.completion.token.parent().map(|it| it.text_range())
53 Some(self.ctx.source_range())
55 let kind = match self.macro_.kind() {
56 hir::MacroKind::Derive => SymbolKind::Derive,
57 hir::MacroKind::Attr => SymbolKind::Attribute,
58 hir::MacroKind::BuiltIn | hir::MacroKind::Declarative | hir::MacroKind::ProcMacro => {
62 let mut item = CompletionItem::new(kind, source_range, self.label());
63 item.set_deprecated(self.ctx.is_deprecated(self.macro_)).set_detail(self.detail());
65 if let Some(import_to_add) = import_to_add {
66 item.add_import(import_to_add);
69 let needs_bang = self.macro_.is_fn_like()
70 && !matches!(self.ctx.completion.path_kind(), Some(PathKind::Mac | PathKind::Use));
71 let has_parens = self.ctx.completion.path_is_call();
73 match self.ctx.snippet_cap() {
74 Some(cap) if needs_bang && !has_parens => {
75 let snippet = format!("{}!{}$0{}", self.name, self.bra, self.ket);
76 let lookup = self.banged_name();
77 item.insert_snippet(cap, snippet).lookup_by(lookup);
80 let lookup = self.banged_name();
81 item.insert_text(self.banged_name()).lookup_by(lookup);
84 cov_mark::hit!(dont_insert_macro_call_parens_unncessary);
85 item.insert_text(&*self.name);
89 item.set_documentation(self.docs);
93 fn needs_bang(&self) -> bool {
94 !matches!(self.ctx.completion.path_kind(), Some(PathKind::Mac | PathKind::Use))
97 fn label(&self) -> SmolStr {
98 if !self.macro_.is_fn_like() {
100 } else if self.needs_bang() && self.ctx.snippet_cap().is_some() {
101 SmolStr::from_iter([&*self.name, "!", self.bra, "…", self.ket])
107 fn banged_name(&self) -> SmolStr {
108 SmolStr::from_iter([&*self.name, "!"])
111 fn detail(&self) -> Option<String> {
112 let detail = match self.macro_.source(self.ctx.db())?.value {
113 Either::Left(node) => macro_label(&node),
114 Either::Right(node) => fn_as_proc_macro_label(&node),
120 fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) {
121 let mut votes = [0, 0, 0];
122 for (idx, s) in docs.match_indices(¯o_name) {
123 let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
124 // Ensure to match the full word
125 if after.starts_with('!')
126 && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
128 // It may have spaces before the braces like `foo! {}`
129 match after[1..].chars().find(|&c| !c.is_whitespace()) {
130 Some('{') => votes[0] += 1,
131 Some('[') => votes[1] += 1,
132 Some('(') => votes[2] += 1,
138 // Insert a space before `{}`.
139 // We prefer the last one when some votes equal.
140 let (_vote, (bra, ket)) = votes
142 .zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
143 .max_by_key(|&(&vote, _)| vote)
150 use crate::tests::check_edit;
153 fn dont_insert_macro_call_parens_unncessary() {
154 cov_mark::check!(dont_insert_macro_call_parens_unncessary);
158 //- /main.rs crate:main deps:foo
160 //- /foo/lib.rs crate:foo
162 macro_rules! frobnicate { () => () }
172 macro_rules! frobnicate { () => () }
173 fn main() { frob$0!(); }
176 macro_rules! frobnicate { () => () }
177 fn main() { frobnicate!(); }
183 fn add_bang_to_parens() {
187 macro_rules! frobnicate { () => () }
193 macro_rules! frobnicate { () => () }
202 fn guesses_macro_braces() {
206 /// Creates a [`Vec`] containing the arguments.
209 /// let v = vec![1, 2, 3];
210 /// assert_eq!(v[0], 1);
211 /// assert_eq!(v[1], 2);
212 /// assert_eq!(v[2], 3);
214 macro_rules! vec { () => {} }
219 /// Creates a [`Vec`] containing the arguments.
222 /// let v = vec![1, 2, 3];
223 /// assert_eq!(v[0], 1);
224 /// assert_eq!(v[1], 2);
225 /// assert_eq!(v[2], 3);
227 macro_rules! vec { () => {} }
229 fn main() { vec![$0] }
238 /// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
239 /// call as `let _=foo! { hello world };`
240 macro_rules! foo { () => {} }
246 /// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
247 /// call as `let _=foo! { hello world };`
248 macro_rules! foo { () => {} }
249 fn main() { foo! {$0} }
255 fn completes_macro_call_if_cursor_at_bang_token() {
256 // Regression test for https://github.com/rust-analyzer/rust-analyzer/issues/9904
257 cov_mark::check!(completes_macro_call_if_cursor_at_bang_token);