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