1use hir::{HirDisplay, db::HirDatabase};
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 is_fn_like = macro_.is_fn_like(completion.db);
53 let (bra, ket) = if is_fn_like {
54 guess_macro_braces(ctx.db(), macro_, name, docs.as_ref())
55 } else {
56 ("", "")
57 };
58
59 let needs_bang = is_fn_like && !is_use_path && !has_macro_bang;
60
61 let mut item = CompletionItem::new(
62 SymbolKind::from(macro_.kind(completion.db)),
63 source_range,
64 label(&ctx, needs_bang, bra, ket, &name.to_smolstr()),
65 completion.edition,
66 );
67 item.set_deprecated(ctx.is_deprecated(macro_))
68 .detail(macro_.display(completion.db, completion.display_target).to_string())
69 .set_documentation(docs)
70 .set_relevance(ctx.completion_relevance());
71
72 match ctx.snippet_cap() {
73 Some(cap) if needs_bang && !has_call_parens => {
74 let snippet = format!("{escaped_name}!{bra}$0{ket}");
75 let lookup = banged_name(name);
76 item.insert_snippet(cap, snippet).lookup_by(lookup);
77 }
78 _ if needs_bang => {
79 item.insert_text(banged_name(&escaped_name)).lookup_by(banged_name(name));
80 }
81 _ => {
82 cov_mark::hit!(dont_insert_macro_call_parens_unnecessary);
83 item.insert_text(escaped_name);
84 }
85 };
86 if let Some(import_to_add) = ctx.import_to_add {
87 item.add_import(import_to_add);
88 }
89
90 item
91}
92
93fn label(
94 ctx: &RenderContext<'_>,
95 needs_bang: bool,
96 bra: &str,
97 ket: &str,
98 name: &SmolStr,
99) -> SmolStr {
100 if needs_bang {
101 if ctx.snippet_cap().is_some() {
102 format_smolstr!("{name}!{bra}…{ket}",)
103 } else {
104 banged_name(name)
105 }
106 } else {
107 name.clone()
108 }
109}
110
111fn banged_name(name: &str) -> SmolStr {
112 SmolStr::from_iter([name, "!"])
113}
114
115fn guess_macro_braces(
116 db: &dyn HirDatabase,
117 macro_: hir::Macro,
118 macro_name: &str,
119 docs: Option<&Documentation<'_>>,
120) -> (&'static str, &'static str) {
121 if let Some(style) = macro_.preferred_brace_style(db) {
122 return match style {
123 hir::MacroBraces::Braces => (" {", "}"),
124 hir::MacroBraces::Brackets => ("[", "]"),
125 hir::MacroBraces::Parentheses => ("(", ")"),
126 };
127 }
128
129 let orig_name = macro_.name(db);
130 let docs = docs.map(Documentation::as_str).unwrap_or_default();
131
132 let mut votes = [0, 0, 0];
133 for (idx, s) in docs.match_indices(macro_name).chain(docs.match_indices(orig_name.as_str())) {
134 let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
135 if after.starts_with('!')
137 && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
138 {
139 match after[1..].chars().find(|&c| !c.is_whitespace()) {
141 Some('{') => votes[0] += 1,
142 Some('[') => votes[1] += 1,
143 Some('(') => votes[2] += 1,
144 _ => {}
145 }
146 }
147 }
148
149 let (_vote, (bra, ket)) = votes
152 .iter()
153 .zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
154 .max_by_key(|&(&vote, _)| vote)
155 .unwrap();
156 (*bra, *ket)
157}
158
159#[cfg(test)]
160mod tests {
161 use crate::tests::check_edit;
162
163 #[test]
164 fn dont_insert_macro_call_parens_unnecessary() {
165 cov_mark::check!(dont_insert_macro_call_parens_unnecessary);
166 check_edit(
167 "frobnicate",
168 r#"
169//- /main.rs crate:main deps:foo
170use foo::$0;
171//- /foo/lib.rs crate:foo
172#[macro_export]
173macro_rules! frobnicate { () => () }
174"#,
175 r#"
176use foo::frobnicate;
177"#,
178 );
179
180 check_edit(
181 "frobnicate",
182 r#"
183macro_rules! frobnicate { () => () }
184fn main() { frob$0!(); }
185"#,
186 r#"
187macro_rules! frobnicate { () => () }
188fn main() { frobnicate!(); }
189"#,
190 );
191 }
192
193 #[test]
194 fn add_bang_to_parens() {
195 check_edit(
196 "frobnicate!",
197 r#"
198macro_rules! frobnicate { () => () }
199fn main() {
200 frob$0()
201}
202"#,
203 r#"
204macro_rules! frobnicate { () => () }
205fn main() {
206 frobnicate!()
207}
208"#,
209 );
210 }
211
212 #[test]
213 fn preferred_macro_braces() {
214 check_edit(
215 "vec!",
216 r#"
217#[rust_analyzer::macro_style(brackets)]
218macro_rules! vec { () => {} }
219
220fn main() { v$0 }
221"#,
222 r#"
223#[rust_analyzer::macro_style(brackets)]
224macro_rules! vec { () => {} }
225
226fn main() { vec![$0] }
227"#,
228 );
229
230 check_edit(
231 "foo!",
232 r#"
233#[rust_analyzer::macro_style(braces)]
234macro_rules! foo { () => {} }
235fn main() { $0 }
236"#,
237 r#"
238#[rust_analyzer::macro_style(braces)]
239macro_rules! foo { () => {} }
240fn main() { foo! {$0} }
241"#,
242 );
243
244 check_edit(
245 "bar!",
246 r#"
247#[macro_export]
248#[rust_analyzer::macro_style(brackets)]
249macro_rules! foo { () => {} }
250pub use crate::foo as bar;
251fn main() { $0 }
252"#,
253 r#"
254#[macro_export]
255#[rust_analyzer::macro_style(brackets)]
256macro_rules! foo { () => {} }
257pub use crate::foo as bar;
258fn main() { bar![$0] }
259"#,
260 );
261 }
262
263 #[test]
264 fn guesses_macro_braces() {
265 check_edit(
266 "vec!",
267 r#"
268/// Creates a [`Vec`] containing the arguments.
269///
270/// ```
271/// let v = vec![1, 2, 3];
272/// assert_eq!(v[0], 1);
273/// assert_eq!(v[1], 2);
274/// assert_eq!(v[2], 3);
275/// ```
276macro_rules! vec { () => {} }
277
278fn main() { v$0 }
279"#,
280 r#"
281/// Creates a [`Vec`] containing the arguments.
282///
283/// ```
284/// let v = vec![1, 2, 3];
285/// assert_eq!(v[0], 1);
286/// assert_eq!(v[1], 2);
287/// assert_eq!(v[2], 3);
288/// ```
289macro_rules! vec { () => {} }
290
291fn main() { vec![$0] }
292"#,
293 );
294
295 check_edit(
296 "foo!",
297 r#"
298/// Foo
299///
300/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
301/// call as `let _=foo! { hello world };`
302macro_rules! foo { () => {} }
303fn main() { $0 }
304"#,
305 r#"
306/// Foo
307///
308/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
309/// call as `let _=foo! { hello world };`
310macro_rules! foo { () => {} }
311fn main() { foo! {$0} }
312"#,
313 );
314
315 check_edit(
316 "bar!",
317 r#"
318/// `foo![]`
319#[macro_export]
320macro_rules! foo { () => {} }
321pub use crate::foo as bar;
322fn main() { $0 }
323"#,
324 r#"
325/// `foo![]`
326#[macro_export]
327macro_rules! foo { () => {} }
328pub use crate::foo as bar;
329fn main() { bar![$0] }
330"#,
331 );
332 }
333
334 #[test]
335 fn completes_macro_call_if_cursor_at_bang_token() {
336 cov_mark::check!(completes_macro_call_if_cursor_at_bang_token);
338 check_edit(
339 "foo!",
340 r#"
341macro_rules! foo {
342 () => {}
343}
344
345fn main() {
346 foo!$0
347}
348"#,
349 r#"
350macro_rules! foo {
351 () => {}
352}
353
354fn main() {
355 foo!($0)
356}
357"#,
358 );
359 }
360
361 #[test]
362 fn complete_missing_macro_arg() {
363 check_edit(
365 "BAR",
366 r#"
367macro_rules! foo {
368 ($val:ident, $val2: ident) => {
369 $val $val2
370 };
371}
372
373const BAR: u32 = 9;
374fn main() {
375 foo!(BAR, $0)
376}
377"#,
378 r#"
379macro_rules! foo {
380 ($val:ident, $val2: ident) => {
381 $val $val2
382 };
383}
384
385const BAR: u32 = 9;
386fn main() {
387 foo!(BAR, BAR)
388}
389"#,
390 );
391 check_edit(
392 "BAR",
393 r#"
394macro_rules! foo {
395 ($val:ident, $val2: ident) => {
396 $val $val2
397 };
398}
399
400const BAR: u32 = 9;
401fn main() {
402 foo!($0)
403}
404"#,
405 r#"
406macro_rules! foo {
407 ($val:ident, $val2: ident) => {
408 $val $val2
409 };
410}
411
412const BAR: u32 = 9;
413fn main() {
414 foo!(BAR)
415}
416"#,
417 );
418 }
419}