diff options
Diffstat (limited to 'crates/ide_completion/src/render/macro_.rs')
-rw-r--r-- | crates/ide_completion/src/render/macro_.rs | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/crates/ide_completion/src/render/macro_.rs b/crates/ide_completion/src/render/macro_.rs new file mode 100644 index 000000000..a4535786f --- /dev/null +++ b/crates/ide_completion/src/render/macro_.rs | |||
@@ -0,0 +1,214 @@ | |||
1 | //! Renderer for macro invocations. | ||
2 | |||
3 | use hir::{Documentation, HasSource}; | ||
4 | use ide_db::SymbolKind; | ||
5 | use syntax::display::macro_label; | ||
6 | use test_utils::mark; | ||
7 | |||
8 | use crate::{ | ||
9 | item::{CompletionItem, CompletionKind, ImportEdit}, | ||
10 | render::RenderContext, | ||
11 | }; | ||
12 | |||
13 | pub(crate) fn render_macro<'a>( | ||
14 | ctx: RenderContext<'a>, | ||
15 | import_to_add: Option<ImportEdit>, | ||
16 | name: String, | ||
17 | macro_: hir::MacroDef, | ||
18 | ) -> Option<CompletionItem> { | ||
19 | let _p = profile::span("render_macro"); | ||
20 | MacroRender::new(ctx, name, macro_).render(import_to_add) | ||
21 | } | ||
22 | |||
23 | #[derive(Debug)] | ||
24 | struct MacroRender<'a> { | ||
25 | ctx: RenderContext<'a>, | ||
26 | name: String, | ||
27 | macro_: hir::MacroDef, | ||
28 | docs: Option<Documentation>, | ||
29 | bra: &'static str, | ||
30 | ket: &'static str, | ||
31 | } | ||
32 | |||
33 | impl<'a> MacroRender<'a> { | ||
34 | fn new(ctx: RenderContext<'a>, name: String, macro_: hir::MacroDef) -> MacroRender<'a> { | ||
35 | let docs = ctx.docs(macro_); | ||
36 | let docs_str = docs.as_ref().map_or("", |s| s.as_str()); | ||
37 | let (bra, ket) = guess_macro_braces(&name, docs_str); | ||
38 | |||
39 | MacroRender { ctx, name, macro_, docs, bra, ket } | ||
40 | } | ||
41 | |||
42 | fn render(&self, import_to_add: Option<ImportEdit>) -> Option<CompletionItem> { | ||
43 | let mut builder = | ||
44 | CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), &self.label()) | ||
45 | .kind(SymbolKind::Macro) | ||
46 | .set_documentation(self.docs.clone()) | ||
47 | .set_deprecated(self.ctx.is_deprecated(self.macro_)) | ||
48 | .add_import(import_to_add) | ||
49 | .set_detail(self.detail()); | ||
50 | |||
51 | let needs_bang = self.needs_bang(); | ||
52 | builder = match self.ctx.snippet_cap() { | ||
53 | Some(cap) if needs_bang => { | ||
54 | let snippet = self.snippet(); | ||
55 | let lookup = self.lookup(); | ||
56 | builder.insert_snippet(cap, snippet).lookup_by(lookup) | ||
57 | } | ||
58 | None if needs_bang => builder.insert_text(self.banged_name()), | ||
59 | _ => { | ||
60 | mark::hit!(dont_insert_macro_call_parens_unncessary); | ||
61 | builder.insert_text(&self.name) | ||
62 | } | ||
63 | }; | ||
64 | |||
65 | Some(builder.build()) | ||
66 | } | ||
67 | |||
68 | fn needs_bang(&self) -> bool { | ||
69 | self.ctx.completion.use_item_syntax.is_none() && !self.ctx.completion.is_macro_call | ||
70 | } | ||
71 | |||
72 | fn label(&self) -> String { | ||
73 | if self.needs_bang() && self.ctx.snippet_cap().is_some() { | ||
74 | format!("{}!{}…{}", self.name, self.bra, self.ket) | ||
75 | } else { | ||
76 | self.banged_name() | ||
77 | } | ||
78 | } | ||
79 | |||
80 | fn snippet(&self) -> String { | ||
81 | format!("{}!{}$0{}", self.name, self.bra, self.ket) | ||
82 | } | ||
83 | |||
84 | fn lookup(&self) -> String { | ||
85 | self.banged_name() | ||
86 | } | ||
87 | |||
88 | fn banged_name(&self) -> String { | ||
89 | format!("{}!", self.name) | ||
90 | } | ||
91 | |||
92 | fn detail(&self) -> Option<String> { | ||
93 | let ast_node = self.macro_.source(self.ctx.db())?.value; | ||
94 | Some(macro_label(&ast_node)) | ||
95 | } | ||
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(¯o_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 test_utils::mark; | ||
129 | |||
130 | use crate::test_utils::check_edit; | ||
131 | |||
132 | #[test] | ||
133 | fn dont_insert_macro_call_parens_unncessary() { | ||
134 | mark::check!(dont_insert_macro_call_parens_unncessary); | ||
135 | check_edit( | ||
136 | "frobnicate!", | ||
137 | r#" | ||
138 | //- /main.rs crate:main deps:foo | ||
139 | use foo::$0; | ||
140 | //- /foo/lib.rs crate:foo | ||
141 | #[macro_export] | ||
142 | macro_rules! frobnicate { () => () } | ||
143 | "#, | ||
144 | r#" | ||
145 | use foo::frobnicate; | ||
146 | "#, | ||
147 | ); | ||
148 | |||
149 | check_edit( | ||
150 | "frobnicate!", | ||
151 | r#" | ||
152 | macro_rules! frobnicate { () => () } | ||
153 | fn main() { frob$0!(); } | ||
154 | "#, | ||
155 | r#" | ||
156 | macro_rules! frobnicate { () => () } | ||
157 | fn main() { frobnicate!(); } | ||
158 | "#, | ||
159 | ); | ||
160 | } | ||
161 | |||
162 | #[test] | ||
163 | fn guesses_macro_braces() { | ||
164 | check_edit( | ||
165 | "vec!", | ||
166 | r#" | ||
167 | /// Creates a [`Vec`] containing the arguments. | ||
168 | /// | ||
169 | /// ``` | ||
170 | /// let v = vec![1, 2, 3]; | ||
171 | /// assert_eq!(v[0], 1); | ||
172 | /// assert_eq!(v[1], 2); | ||
173 | /// assert_eq!(v[2], 3); | ||
174 | /// ``` | ||
175 | macro_rules! vec { () => {} } | ||
176 | |||
177 | fn fn main() { v$0 } | ||
178 | "#, | ||
179 | r#" | ||
180 | /// Creates a [`Vec`] containing the arguments. | ||
181 | /// | ||
182 | /// ``` | ||
183 | /// let v = vec![1, 2, 3]; | ||
184 | /// assert_eq!(v[0], 1); | ||
185 | /// assert_eq!(v[1], 2); | ||
186 | /// assert_eq!(v[2], 3); | ||
187 | /// ``` | ||
188 | macro_rules! vec { () => {} } | ||
189 | |||
190 | fn fn main() { vec![$0] } | ||
191 | "#, | ||
192 | ); | ||
193 | |||
194 | check_edit( | ||
195 | "foo!", | ||
196 | r#" | ||
197 | /// Foo | ||
198 | /// | ||
199 | /// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`, | ||
200 | /// call as `let _=foo! { hello world };` | ||
201 | macro_rules! foo { () => {} } | ||
202 | fn main() { $0 } | ||
203 | "#, | ||
204 | r#" | ||
205 | /// Foo | ||
206 | /// | ||
207 | /// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`, | ||
208 | /// call as `let _=foo! { hello world };` | ||
209 | macro_rules! foo { () => {} } | ||
210 | fn main() { foo! {$0} } | ||
211 | "#, | ||
212 | ) | ||
213 | } | ||
214 | } | ||