]> git.lizzy.rs Git - rust.git/blob - crates/ide-completion/src/render/macro_.rs
9c51a6311a4dbc5a736fc59727be5acc14e6a688
[rust.git] / crates / ide-completion / src / render / macro_.rs
1 //! Renderer for macro invocations.
2
3 use hir::{Documentation, HirDisplay};
4 use ide_db::SymbolKind;
5 use syntax::SmolStr;
6
7 use crate::{
8     context::{PathCompletionCtx, PathKind},
9     item::{Builder, CompletionItem},
10     render::RenderContext,
11 };
12
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_)
16 }
17
18 fn render(
19     ctx @ RenderContext { completion, .. }: RenderContext<'_>,
20     name: hir::Name,
21     macro_: hir::Macro,
22 ) -> Builder {
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())
26     } else {
27         ctx.source_range()
28     };
29
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 { ("", "") };
35
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
39         }
40         _ => is_fn_like,
41     };
42
43     let mut item = CompletionItem::new(
44         SymbolKind::from(macro_.kind(completion.db)),
45         source_range,
46         label(&ctx, needs_bang, bra, ket, &name),
47     );
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());
52
53     let name = &*name;
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);
59         }
60         _ if needs_bang => {
61             let banged_name = banged_name(name);
62             item.insert_text(banged_name.clone()).lookup_by(banged_name);
63         }
64         _ => {
65             cov_mark::hit!(dont_insert_macro_call_parens_unncessary);
66             item.insert_text(name);
67         }
68     };
69     if let Some(import_to_add) = ctx.import_to_add {
70         item.add_import(import_to_add);
71     }
72
73     item
74 }
75
76 fn label(
77     ctx: &RenderContext<'_>,
78     needs_bang: bool,
79     bra: &str,
80     ket: &str,
81     name: &SmolStr,
82 ) -> SmolStr {
83     if needs_bang {
84         if ctx.snippet_cap().is_some() {
85             SmolStr::from_iter([&*name, "!", bra, "…", ket])
86         } else {
87             banged_name(name)
88         }
89     } else {
90         name.clone()
91     }
92 }
93
94 fn banged_name(name: &str) -> SmolStr {
95     SmolStr::from_iter([name, "!"])
96 }
97
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(&macro_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())
105         {
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,
111                 _ => {}
112             }
113         }
114     }
115
116     // Insert a space before `{}`.
117     // We prefer the last one when some votes equal.
118     let (_vote, (bra, ket)) = votes
119         .iter()
120         .zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
121         .max_by_key(|&(&vote, _)| vote)
122         .unwrap();
123     (*bra, *ket)
124 }
125
126 #[cfg(test)]
127 mod tests {
128     use crate::tests::check_edit;
129
130     #[test]
131     fn dont_insert_macro_call_parens_unncessary() {
132         cov_mark::check!(dont_insert_macro_call_parens_unncessary);
133         check_edit(
134             "frobnicate",
135             r#"
136 //- /main.rs crate:main deps:foo
137 use foo::$0;
138 //- /foo/lib.rs crate:foo
139 #[macro_export]
140 macro_rules! frobnicate { () => () }
141 "#,
142             r#"
143 use foo::frobnicate;
144 "#,
145         );
146
147         check_edit(
148             "frobnicate",
149             r#"
150 macro_rules! frobnicate { () => () }
151 fn main() { frob$0!(); }
152 "#,
153             r#"
154 macro_rules! frobnicate { () => () }
155 fn main() { frobnicate!(); }
156 "#,
157         );
158     }
159
160     #[test]
161     fn add_bang_to_parens() {
162         check_edit(
163             "frobnicate!",
164             r#"
165 macro_rules! frobnicate { () => () }
166 fn main() {
167     frob$0()
168 }
169 "#,
170             r#"
171 macro_rules! frobnicate { () => () }
172 fn main() {
173     frobnicate!()
174 }
175 "#,
176         );
177     }
178
179     #[test]
180     fn guesses_macro_braces() {
181         check_edit(
182             "vec!",
183             r#"
184 /// Creates a [`Vec`] containing the arguments.
185 ///
186 /// ```
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);
191 /// ```
192 macro_rules! vec { () => {} }
193
194 fn main() { v$0 }
195 "#,
196             r#"
197 /// Creates a [`Vec`] containing the arguments.
198 ///
199 /// ```
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);
204 /// ```
205 macro_rules! vec { () => {} }
206
207 fn main() { vec![$0] }
208 "#,
209         );
210
211         check_edit(
212             "foo!",
213             r#"
214 /// Foo
215 ///
216 /// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
217 /// call as `let _=foo!  { hello world };`
218 macro_rules! foo { () => {} }
219 fn main() { $0 }
220 "#,
221             r#"
222 /// Foo
223 ///
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} }
228 "#,
229         )
230     }
231
232     #[test]
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);
236         check_edit(
237             "foo!",
238             r#"
239 macro_rules! foo {
240     () => {}
241 }
242
243 fn main() {
244     foo!$0
245 }
246 "#,
247             r#"
248 macro_rules! foo {
249     () => {}
250 }
251
252 fn main() {
253     foo!($0)
254 }
255 "#,
256         );
257     }
258 }