1use hir::HirDisplay;
4use ide_db::{SymbolKind, documentation::Documentation};
5use syntax::{SmolStr, ToSmolStr, format_smolstr};
6
7use crate::{
8 context::{PathCompletionCtx, PathKind, PatternContext},
9 item::{Builder, CompletionItem},
10 render::RenderContext,
11};
12
13pub(crate) fn render_macro(
14 ctx: RenderContext<'_>,
15 PathCompletionCtx { kind, has_macro_bang, has_call_parens, .. }: &PathCompletionCtx<'_>,
16
17 name: hir::Name,
18 macro_: hir::Macro,
19) -> Builder {
20 let _p = tracing::info_span!("render_macro").entered();
21 render(ctx, *kind == PathKind::Use, *has_macro_bang, *has_call_parens, name, macro_)
22}
23
24pub(crate) fn render_macro_pat(
25 ctx: RenderContext<'_>,
26 _pattern_ctx: &PatternContext,
27 name: hir::Name,
28 macro_: hir::Macro,
29) -> Builder {
30 let _p = tracing::info_span!("render_macro_pat").entered();
31 render(ctx, false, false, false, name, macro_)
32}
33
34fn render(
35 ctx @ RenderContext { completion, .. }: RenderContext<'_>,
36 is_use_path: bool,
37 has_macro_bang: bool,
38 has_call_parens: bool,
39 name: hir::Name,
40 macro_: hir::Macro,
41) -> Builder {
42 let source_range = if ctx.is_immediately_after_macro_bang() {
43 cov_mark::hit!(completes_macro_call_if_cursor_at_bang_token);
44 completion.token.parent().map_or_else(|| ctx.source_range(), |it| it.text_range())
45 } else {
46 ctx.source_range()
47 };
48
49 let (name, escaped_name) =
50 (name.as_str(), name.display(ctx.db(), completion.edition).to_smolstr());
51 let docs = ctx.docs(macro_);
52 let docs_str = docs.as_ref().map(Documentation::as_str).unwrap_or_default();
53 let is_fn_like = macro_.is_fn_like(completion.db);
54 let (bra, ket) = if is_fn_like { guess_macro_braces(name, docs_str) } else { ("", "") };
55
56 let needs_bang = is_fn_like && !is_use_path && !has_macro_bang;
57
58 let mut item = CompletionItem::new(
59 SymbolKind::from(macro_.kind(completion.db)),
60 source_range,
61 label(&ctx, needs_bang, bra, ket, &name.to_smolstr()),
62 completion.edition,
63 );
64 item.set_deprecated(ctx.is_deprecated(macro_))
65 .detail(macro_.display(completion.db, completion.display_target).to_string())
66 .set_documentation(docs)
67 .set_relevance(ctx.completion_relevance());
68
69 match ctx.snippet_cap() {
70 Some(cap) if needs_bang && !has_call_parens => {
71 let snippet = format!("{escaped_name}!{bra}$0{ket}");
72 let lookup = banged_name(name);
73 item.insert_snippet(cap, snippet).lookup_by(lookup);
74 }
75 _ if needs_bang => {
76 item.insert_text(banged_name(&escaped_name)).lookup_by(banged_name(name));
77 }
78 _ => {
79 cov_mark::hit!(dont_insert_macro_call_parens_unnecessary);
80 item.insert_text(escaped_name);
81 }
82 };
83 if let Some(import_to_add) = ctx.import_to_add {
84 item.add_import(import_to_add);
85 }
86
87 item
88}
89
90fn label(
91 ctx: &RenderContext<'_>,
92 needs_bang: bool,
93 bra: &str,
94 ket: &str,
95 name: &SmolStr,
96) -> SmolStr {
97 if needs_bang {
98 if ctx.snippet_cap().is_some() {
99 format_smolstr!("{name}!{bra}…{ket}",)
100 } else {
101 banged_name(name)
102 }
103 } else {
104 name.clone()
105 }
106}
107
108fn banged_name(name: &str) -> SmolStr {
109 SmolStr::from_iter([name, "!"])
110}
111
112fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) {
113 let mut votes = [0, 0, 0];
114 for (idx, s) in docs.match_indices(¯o_name) {
115 let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
116 if after.starts_with('!')
118 && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
119 {
120 match after[1..].chars().find(|&c| !c.is_whitespace()) {
122 Some('{') => votes[0] += 1,
123 Some('[') => votes[1] += 1,
124 Some('(') => votes[2] += 1,
125 _ => {}
126 }
127 }
128 }
129
130 let (_vote, (bra, ket)) = votes
133 .iter()
134 .zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
135 .max_by_key(|&(&vote, _)| vote)
136 .unwrap();
137 (*bra, *ket)
138}
139
140#[cfg(test)]
141mod tests {
142 use crate::tests::check_edit;
143
144 #[test]
145 fn dont_insert_macro_call_parens_unnecessary() {
146 cov_mark::check!(dont_insert_macro_call_parens_unnecessary);
147 check_edit(
148 "frobnicate",
149 r#"
150//- /main.rs crate:main deps:foo
151use foo::$0;
152//- /foo/lib.rs crate:foo
153#[macro_export]
154macro_rules! frobnicate { () => () }
155"#,
156 r#"
157use foo::frobnicate;
158"#,
159 );
160
161 check_edit(
162 "frobnicate",
163 r#"
164macro_rules! frobnicate { () => () }
165fn main() { frob$0!(); }
166"#,
167 r#"
168macro_rules! frobnicate { () => () }
169fn main() { frobnicate!(); }
170"#,
171 );
172 }
173
174 #[test]
175 fn add_bang_to_parens() {
176 check_edit(
177 "frobnicate!",
178 r#"
179macro_rules! frobnicate { () => () }
180fn main() {
181 frob$0()
182}
183"#,
184 r#"
185macro_rules! frobnicate { () => () }
186fn main() {
187 frobnicate!()
188}
189"#,
190 );
191 }
192
193 #[test]
194 fn guesses_macro_braces() {
195 check_edit(
196 "vec!",
197 r#"
198/// Creates a [`Vec`] containing the arguments.
199///
200/// ```
201/// let v = vec![1, 2, 3];
202/// assert_eq!(v[0], 1);
203/// assert_eq!(v[1], 2);
204/// assert_eq!(v[2], 3);
205/// ```
206macro_rules! vec { () => {} }
207
208fn main() { v$0 }
209"#,
210 r#"
211/// Creates a [`Vec`] containing the arguments.
212///
213/// ```
214/// let v = vec![1, 2, 3];
215/// assert_eq!(v[0], 1);
216/// assert_eq!(v[1], 2);
217/// assert_eq!(v[2], 3);
218/// ```
219macro_rules! vec { () => {} }
220
221fn main() { vec![$0] }
222"#,
223 );
224
225 check_edit(
226 "foo!",
227 r#"
228/// Foo
229///
230/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
231/// call as `let _=foo! { hello world };`
232macro_rules! foo { () => {} }
233fn main() { $0 }
234"#,
235 r#"
236/// Foo
237///
238/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
239/// call as `let _=foo! { hello world };`
240macro_rules! foo { () => {} }
241fn main() { foo! {$0} }
242"#,
243 )
244 }
245
246 #[test]
247 fn completes_macro_call_if_cursor_at_bang_token() {
248 cov_mark::check!(completes_macro_call_if_cursor_at_bang_token);
250 check_edit(
251 "foo!",
252 r#"
253macro_rules! foo {
254 () => {}
255}
256
257fn main() {
258 foo!$0
259}
260"#,
261 r#"
262macro_rules! foo {
263 () => {}
264}
265
266fn main() {
267 foo!($0)
268}
269"#,
270 );
271 }
272
273 #[test]
274 fn complete_missing_macro_arg() {
275 check_edit(
277 "BAR",
278 r#"
279macro_rules! foo {
280 ($val:ident, $val2: ident) => {
281 $val $val2
282 };
283}
284
285const BAR: u32 = 9;
286fn main() {
287 foo!(BAR, $0)
288}
289"#,
290 r#"
291macro_rules! foo {
292 ($val:ident, $val2: ident) => {
293 $val $val2
294 };
295}
296
297const BAR: u32 = 9;
298fn main() {
299 foo!(BAR, BAR)
300}
301"#,
302 );
303 check_edit(
304 "BAR",
305 r#"
306macro_rules! foo {
307 ($val:ident, $val2: ident) => {
308 $val $val2
309 };
310}
311
312const BAR: u32 = 9;
313fn main() {
314 foo!($0)
315}
316"#,
317 r#"
318macro_rules! foo {
319 ($val:ident, $val2: ident) => {
320 $val $val2
321 };
322}
323
324const BAR: u32 = 9;
325fn main() {
326 foo!(BAR)
327}
328"#,
329 );
330 }
331}