]> git.lizzy.rs Git - rust.git/blob - crates/ide_completion/src/render/macro_.rs
internal: completion: split out more PathKinds from ImmediateLocation
[rust.git] / crates / ide_completion / src / render / macro_.rs
1 //! Renderer for macro invocations.
2
3 use either::Either;
4 use hir::HasSource;
5 use ide_db::SymbolKind;
6 use syntax::{
7     display::{fn_as_proc_macro_label, macro_label},
8     SmolStr,
9 };
10
11 use crate::{
12     context::PathKind,
13     item::{CompletionItem, ImportEdit},
14     render::RenderContext,
15 };
16
17 pub(crate) fn render_macro(
18     ctx: RenderContext<'_>,
19     import_to_add: Option<ImportEdit>,
20     name: hir::Name,
21     macro_: hir::MacroDef,
22 ) -> Option<CompletionItem> {
23     let _p = profile::span("render_macro");
24     MacroRender::new(ctx, name, macro_).render(import_to_add)
25 }
26
27 #[derive(Debug)]
28 struct MacroRender<'a> {
29     ctx: RenderContext<'a>,
30     name: SmolStr,
31     macro_: hir::MacroDef,
32     docs: Option<hir::Documentation>,
33     bra: &'static str,
34     ket: &'static str,
35 }
36
37 impl<'a> MacroRender<'a> {
38     fn new(ctx: RenderContext<'a>, name: hir::Name, macro_: hir::MacroDef) -> MacroRender<'a> {
39         let name = name.to_smol_str();
40         let docs = ctx.docs(macro_);
41         let docs_str = docs.as_ref().map_or("", |s| s.as_str());
42         let (bra, ket) =
43             if macro_.is_fn_like() { guess_macro_braces(&name, docs_str) } else { ("", "") };
44
45         MacroRender { ctx, name, macro_, docs, bra, ket }
46     }
47
48     fn render(self, import_to_add: Option<ImportEdit>) -> Option<CompletionItem> {
49         let source_range = if self.ctx.completion.is_immediately_after_macro_bang() {
50             cov_mark::hit!(completes_macro_call_if_cursor_at_bang_token);
51             self.ctx.completion.token.parent().map(|it| it.text_range())
52         } else {
53             Some(self.ctx.source_range())
54         }?;
55         let kind = match self.macro_.kind() {
56             hir::MacroKind::Derive => SymbolKind::Derive,
57             hir::MacroKind::Attr => SymbolKind::Attribute,
58             hir::MacroKind::BuiltIn | hir::MacroKind::Declarative | hir::MacroKind::ProcMacro => {
59                 SymbolKind::Macro
60             }
61         };
62         let mut item = CompletionItem::new(kind, source_range, self.label());
63         item.set_deprecated(self.ctx.is_deprecated(self.macro_)).set_detail(self.detail());
64
65         if let Some(import_to_add) = import_to_add {
66             item.add_import(import_to_add);
67         }
68
69         let needs_bang = self.macro_.is_fn_like()
70             && !matches!(self.ctx.completion.path_kind(), Some(PathKind::Mac | PathKind::Use));
71         let has_parens = self.ctx.completion.path_is_call();
72
73         match self.ctx.snippet_cap() {
74             Some(cap) if needs_bang && !has_parens => {
75                 let snippet = format!("{}!{}$0{}", self.name, self.bra, self.ket);
76                 let lookup = self.banged_name();
77                 item.insert_snippet(cap, snippet).lookup_by(lookup);
78             }
79             _ if needs_bang => {
80                 let lookup = self.banged_name();
81                 item.insert_text(self.banged_name()).lookup_by(lookup);
82             }
83             _ => {
84                 cov_mark::hit!(dont_insert_macro_call_parens_unncessary);
85                 item.insert_text(&*self.name);
86             }
87         };
88
89         item.set_documentation(self.docs);
90         Some(item.build())
91     }
92
93     fn needs_bang(&self) -> bool {
94         !matches!(self.ctx.completion.path_kind(), Some(PathKind::Mac | PathKind::Use))
95     }
96
97     fn label(&self) -> SmolStr {
98         if !self.macro_.is_fn_like() {
99             self.name.clone()
100         } else if self.needs_bang() && self.ctx.snippet_cap().is_some() {
101             SmolStr::from_iter([&*self.name, "!", self.bra, "…", self.ket])
102         } else {
103             self.banged_name()
104         }
105     }
106
107     fn banged_name(&self) -> SmolStr {
108         SmolStr::from_iter([&*self.name, "!"])
109     }
110
111     fn detail(&self) -> Option<String> {
112         let detail = match self.macro_.source(self.ctx.db())?.value {
113             Either::Left(node) => macro_label(&node),
114             Either::Right(node) => fn_as_proc_macro_label(&node),
115         };
116         Some(detail)
117     }
118 }
119
120 fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) {
121     let mut votes = [0, 0, 0];
122     for (idx, s) in docs.match_indices(&macro_name) {
123         let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
124         // Ensure to match the full word
125         if after.starts_with('!')
126             && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
127         {
128             // It may have spaces before the braces like `foo! {}`
129             match after[1..].chars().find(|&c| !c.is_whitespace()) {
130                 Some('{') => votes[0] += 1,
131                 Some('[') => votes[1] += 1,
132                 Some('(') => votes[2] += 1,
133                 _ => {}
134             }
135         }
136     }
137
138     // Insert a space before `{}`.
139     // We prefer the last one when some votes equal.
140     let (_vote, (bra, ket)) = votes
141         .iter()
142         .zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
143         .max_by_key(|&(&vote, _)| vote)
144         .unwrap();
145     (*bra, *ket)
146 }
147
148 #[cfg(test)]
149 mod tests {
150     use crate::tests::check_edit;
151
152     #[test]
153     fn dont_insert_macro_call_parens_unncessary() {
154         cov_mark::check!(dont_insert_macro_call_parens_unncessary);
155         check_edit(
156             "frobnicate!",
157             r#"
158 //- /main.rs crate:main deps:foo
159 use foo::$0;
160 //- /foo/lib.rs crate:foo
161 #[macro_export]
162 macro_rules! frobnicate { () => () }
163 "#,
164             r#"
165 use foo::frobnicate;
166 "#,
167         );
168
169         check_edit(
170             "frobnicate!",
171             r#"
172 macro_rules! frobnicate { () => () }
173 fn main() { frob$0!(); }
174 "#,
175             r#"
176 macro_rules! frobnicate { () => () }
177 fn main() { frobnicate!(); }
178 "#,
179         );
180     }
181
182     #[test]
183     fn add_bang_to_parens() {
184         check_edit(
185             "frobnicate!",
186             r#"
187 macro_rules! frobnicate { () => () }
188 fn main() {
189     frob$0()
190 }
191 "#,
192             r#"
193 macro_rules! frobnicate { () => () }
194 fn main() {
195     frobnicate!()
196 }
197 "#,
198         );
199     }
200
201     #[test]
202     fn guesses_macro_braces() {
203         check_edit(
204             "vec!",
205             r#"
206 /// Creates a [`Vec`] containing the arguments.
207 ///
208 /// ```
209 /// let v = vec![1, 2, 3];
210 /// assert_eq!(v[0], 1);
211 /// assert_eq!(v[1], 2);
212 /// assert_eq!(v[2], 3);
213 /// ```
214 macro_rules! vec { () => {} }
215
216 fn main() { v$0 }
217 "#,
218             r#"
219 /// Creates a [`Vec`] containing the arguments.
220 ///
221 /// ```
222 /// let v = vec![1, 2, 3];
223 /// assert_eq!(v[0], 1);
224 /// assert_eq!(v[1], 2);
225 /// assert_eq!(v[2], 3);
226 /// ```
227 macro_rules! vec { () => {} }
228
229 fn main() { vec![$0] }
230 "#,
231         );
232
233         check_edit(
234             "foo!",
235             r#"
236 /// Foo
237 ///
238 /// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
239 /// call as `let _=foo!  { hello world };`
240 macro_rules! foo { () => {} }
241 fn main() { $0 }
242 "#,
243             r#"
244 /// Foo
245 ///
246 /// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
247 /// call as `let _=foo!  { hello world };`
248 macro_rules! foo { () => {} }
249 fn main() { foo! {$0} }
250 "#,
251         )
252     }
253
254     #[test]
255     fn completes_macro_call_if_cursor_at_bang_token() {
256         // Regression test for https://github.com/rust-analyzer/rust-analyzer/issues/9904
257         cov_mark::check!(completes_macro_call_if_cursor_at_bang_token);
258         check_edit(
259             "foo!",
260             r#"
261 macro_rules! foo {
262     () => {}
263 }
264
265 fn main() {
266     foo!$0
267 }
268 "#,
269             r#"
270 macro_rules! foo {
271     () => {}
272 }
273
274 fn main() {
275     foo!($0)
276 }
277 "#,
278         );
279     }
280 }