1 //! Renderer for macro invocations.
3 use hir::{Documentation, HirDisplay};
4 use ide_db::SymbolKind;
8 context::{PathCompletionCtx, PathKind},
9 item::{Builder, CompletionItem},
10 render::RenderContext,
13 pub(crate) fn render_macro(ctx: RenderContext<'_>, name: hir::Name, macro_: hir::Macro) -> Builder {
14 let _p = profile::span("render_macro");
15 render(ctx, name, macro_)
19 ctx @ RenderContext { completion, .. }: RenderContext<'_>,
23 let source_range = if completion.is_immediately_after_macro_bang() {
24 cov_mark::hit!(completes_macro_call_if_cursor_at_bang_token);
25 completion.token.parent().map_or_else(|| ctx.source_range(), |it| it.text_range())
30 let name = name.to_smol_str();
31 let docs = ctx.docs(macro_);
32 let docs_str = docs.as_ref().map(Documentation::as_str).unwrap_or_default();
33 let is_fn_like = macro_.is_fn_like(completion.db);
34 let (bra, ket) = if is_fn_like { guess_macro_braces(&name, docs_str) } else { ("", "") };
36 let needs_bang = match completion.path_context() {
37 Some(&PathCompletionCtx { kind, has_macro_bang, .. }) => {
38 is_fn_like && kind != PathKind::Use && !has_macro_bang
43 let mut item = CompletionItem::new(
44 SymbolKind::from(macro_.kind(completion.db)),
46 label(&ctx, needs_bang, bra, ket, &name),
48 item.set_deprecated(ctx.is_deprecated(macro_))
49 .detail(macro_.display(completion.db).to_string())
50 .set_documentation(docs)
51 .set_relevance(ctx.completion_relevance());
54 match ctx.snippet_cap() {
55 Some(cap) if needs_bang && !completion.path_is_call() => {
56 let snippet = format!("{}!{}$0{}", name, bra, ket);
57 let lookup = banged_name(name);
58 item.insert_snippet(cap, snippet).lookup_by(lookup);
61 let banged_name = banged_name(name);
62 item.insert_text(banged_name.clone()).lookup_by(banged_name);
65 cov_mark::hit!(dont_insert_macro_call_parens_unncessary);
66 item.insert_text(name);
69 if let Some(import_to_add) = ctx.import_to_add {
70 item.add_import(import_to_add);
77 ctx: &RenderContext<'_>,
84 if ctx.snippet_cap().is_some() {
85 SmolStr::from_iter([&*name, "!", bra, "…", ket])
94 fn banged_name(name: &str) -> SmolStr {
95 SmolStr::from_iter([name, "!"])
98 fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) {
99 let mut votes = [0, 0, 0];
100 for (idx, s) in docs.match_indices(¯o_name) {
101 let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
102 // Ensure to match the full word
103 if after.starts_with('!')
104 && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
106 // It may have spaces before the braces like `foo! {}`
107 match after[1..].chars().find(|&c| !c.is_whitespace()) {
108 Some('{') => votes[0] += 1,
109 Some('[') => votes[1] += 1,
110 Some('(') => votes[2] += 1,
116 // Insert a space before `{}`.
117 // We prefer the last one when some votes equal.
118 let (_vote, (bra, ket)) = votes
120 .zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
121 .max_by_key(|&(&vote, _)| vote)
128 use crate::tests::check_edit;
131 fn dont_insert_macro_call_parens_unncessary() {
132 cov_mark::check!(dont_insert_macro_call_parens_unncessary);
136 //- /main.rs crate:main deps:foo
138 //- /foo/lib.rs crate:foo
140 macro_rules! frobnicate { () => () }
150 macro_rules! frobnicate { () => () }
151 fn main() { frob$0!(); }
154 macro_rules! frobnicate { () => () }
155 fn main() { frobnicate!(); }
161 fn add_bang_to_parens() {
165 macro_rules! frobnicate { () => () }
171 macro_rules! frobnicate { () => () }
180 fn guesses_macro_braces() {
184 /// Creates a [`Vec`] containing the arguments.
187 /// let v = vec![1, 2, 3];
188 /// assert_eq!(v[0], 1);
189 /// assert_eq!(v[1], 2);
190 /// assert_eq!(v[2], 3);
192 macro_rules! vec { () => {} }
197 /// Creates a [`Vec`] containing the arguments.
200 /// let v = vec![1, 2, 3];
201 /// assert_eq!(v[0], 1);
202 /// assert_eq!(v[1], 2);
203 /// assert_eq!(v[2], 3);
205 macro_rules! vec { () => {} }
207 fn main() { vec![$0] }
216 /// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
217 /// call as `let _=foo! { hello world };`
218 macro_rules! foo { () => {} }
224 /// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
225 /// call as `let _=foo! { hello world };`
226 macro_rules! foo { () => {} }
227 fn main() { foo! {$0} }
233 fn completes_macro_call_if_cursor_at_bang_token() {
234 // Regression test for https://github.com/rust-analyzer/rust-analyzer/issues/9904
235 cov_mark::check!(completes_macro_call_if_cursor_at_bang_token);