diff options
Diffstat (limited to 'crates/completion/src/render/macro_.rs')
-rw-r--r-- | crates/completion/src/render/macro_.rs | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/crates/completion/src/render/macro_.rs b/crates/completion/src/render/macro_.rs new file mode 100644 index 000000000..96be59cc3 --- /dev/null +++ b/crates/completion/src/render/macro_.rs | |||
@@ -0,0 +1,216 @@ | |||
1 | //! Renderer for macro invocations. | ||
2 | |||
3 | use hir::{Documentation, HasSource}; | ||
4 | use syntax::display::macro_label; | ||
5 | use test_utils::mark; | ||
6 | |||
7 | use crate::{ | ||
8 | item::{CompletionItem, CompletionItemKind, CompletionKind}, | ||
9 | render::RenderContext, | ||
10 | }; | ||
11 | |||
12 | pub(crate) fn render_macro<'a>( | ||
13 | ctx: RenderContext<'a>, | ||
14 | name: String, | ||
15 | macro_: hir::MacroDef, | ||
16 | ) -> Option<CompletionItem> { | ||
17 | MacroRender::new(ctx, name, macro_).render() | ||
18 | } | ||
19 | |||
20 | #[derive(Debug)] | ||
21 | struct MacroRender<'a> { | ||
22 | ctx: RenderContext<'a>, | ||
23 | name: String, | ||
24 | macro_: hir::MacroDef, | ||
25 | docs: Option<Documentation>, | ||
26 | bra: &'static str, | ||
27 | ket: &'static str, | ||
28 | } | ||
29 | |||
30 | impl<'a> MacroRender<'a> { | ||
31 | fn new(ctx: RenderContext<'a>, name: String, macro_: hir::MacroDef) -> MacroRender<'a> { | ||
32 | let docs = ctx.docs(macro_); | ||
33 | let docs_str = docs.as_ref().map_or("", |s| s.as_str()); | ||
34 | let (bra, ket) = guess_macro_braces(&name, docs_str); | ||
35 | |||
36 | MacroRender { ctx, name, macro_, docs, bra, ket } | ||
37 | } | ||
38 | |||
39 | fn render(&self) -> Option<CompletionItem> { | ||
40 | // FIXME: Currently proc-macro do not have ast-node, | ||
41 | // such that it does not have source | ||
42 | if self.macro_.is_proc_macro() { | ||
43 | return None; | ||
44 | } | ||
45 | |||
46 | let mut builder = | ||
47 | CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), &self.label()) | ||
48 | .kind(CompletionItemKind::Macro) | ||
49 | .set_documentation(self.docs.clone()) | ||
50 | .set_deprecated(self.ctx.is_deprecated(self.macro_)) | ||
51 | .detail(self.detail()); | ||
52 | |||
53 | let needs_bang = self.needs_bang(); | ||
54 | builder = match self.ctx.snippet_cap() { | ||
55 | Some(cap) if needs_bang => { | ||
56 | let snippet = self.snippet(); | ||
57 | let lookup = self.lookup(); | ||
58 | builder.insert_snippet(cap, snippet).lookup_by(lookup) | ||
59 | } | ||
60 | None if needs_bang => builder.insert_text(self.banged_name()), | ||
61 | _ => { | ||
62 | mark::hit!(dont_insert_macro_call_parens_unncessary); | ||
63 | builder.insert_text(&self.name) | ||
64 | } | ||
65 | }; | ||
66 | |||
67 | Some(builder.build()) | ||
68 | } | ||
69 | |||
70 | fn needs_bang(&self) -> bool { | ||
71 | self.ctx.completion.use_item_syntax.is_none() && !self.ctx.completion.is_macro_call | ||
72 | } | ||
73 | |||
74 | fn label(&self) -> String { | ||
75 | if self.needs_bang() && self.ctx.snippet_cap().is_some() { | ||
76 | format!("{}!{}…{}", self.name, self.bra, self.ket) | ||
77 | } else { | ||
78 | self.banged_name() | ||
79 | } | ||
80 | } | ||
81 | |||
82 | fn snippet(&self) -> String { | ||
83 | format!("{}!{}$0{}", self.name, self.bra, self.ket) | ||
84 | } | ||
85 | |||
86 | fn lookup(&self) -> String { | ||
87 | self.banged_name() | ||
88 | } | ||
89 | |||
90 | fn banged_name(&self) -> String { | ||
91 | format!("{}!", self.name) | ||
92 | } | ||
93 | |||
94 | fn detail(&self) -> String { | ||
95 | let ast_node = self.macro_.source(self.ctx.db()).value; | ||
96 | macro_label(&ast_node) | ||
97 | } | ||
98 | } | ||
99 | |||
100 | fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) { | ||
101 | let mut votes = [0, 0, 0]; | ||
102 | for (idx, s) in docs.match_indices(¯o_name) { | ||
103 | let (before, after) = (&docs[..idx], &docs[idx + s.len()..]); | ||
104 | // Ensure to match the full word | ||
105 | if after.starts_with('!') | ||
106 | && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric()) | ||
107 | { | ||
108 | // It may have spaces before the braces like `foo! {}` | ||
109 | match after[1..].chars().find(|&c| !c.is_whitespace()) { | ||
110 | Some('{') => votes[0] += 1, | ||
111 | Some('[') => votes[1] += 1, | ||
112 | Some('(') => votes[2] += 1, | ||
113 | _ => {} | ||
114 | } | ||
115 | } | ||
116 | } | ||
117 | |||
118 | // Insert a space before `{}`. | ||
119 | // We prefer the last one when some votes equal. | ||
120 | let (_vote, (bra, ket)) = votes | ||
121 | .iter() | ||
122 | .zip(&[(" {", "}"), ("[", "]"), ("(", ")")]) | ||
123 | .max_by_key(|&(&vote, _)| vote) | ||
124 | .unwrap(); | ||
125 | (*bra, *ket) | ||
126 | } | ||
127 | |||
128 | #[cfg(test)] | ||
129 | mod tests { | ||
130 | use test_utils::mark; | ||
131 | |||
132 | use crate::test_utils::check_edit; | ||
133 | |||
134 | #[test] | ||
135 | fn dont_insert_macro_call_parens_unncessary() { | ||
136 | mark::check!(dont_insert_macro_call_parens_unncessary); | ||
137 | check_edit( | ||
138 | "frobnicate!", | ||
139 | r#" | ||
140 | //- /main.rs crate:main deps:foo | ||
141 | use foo::<|>; | ||
142 | //- /foo/lib.rs crate:foo | ||
143 | #[macro_export] | ||
144 | macro_rules frobnicate { () => () } | ||
145 | "#, | ||
146 | r#" | ||
147 | use foo::frobnicate; | ||
148 | "#, | ||
149 | ); | ||
150 | |||
151 | check_edit( | ||
152 | "frobnicate!", | ||
153 | r#" | ||
154 | macro_rules frobnicate { () => () } | ||
155 | fn main() { frob<|>!(); } | ||
156 | "#, | ||
157 | r#" | ||
158 | macro_rules frobnicate { () => () } | ||
159 | fn main() { frobnicate!(); } | ||
160 | "#, | ||
161 | ); | ||
162 | } | ||
163 | |||
164 | #[test] | ||
165 | fn guesses_macro_braces() { | ||
166 | check_edit( | ||
167 | "vec!", | ||
168 | r#" | ||
169 | /// Creates a [`Vec`] containing the arguments. | ||
170 | /// | ||
171 | /// ``` | ||
172 | /// let v = vec![1, 2, 3]; | ||
173 | /// assert_eq!(v[0], 1); | ||
174 | /// assert_eq!(v[1], 2); | ||
175 | /// assert_eq!(v[2], 3); | ||
176 | /// ``` | ||
177 | macro_rules! vec { () => {} } | ||
178 | |||
179 | fn fn main() { v<|> } | ||
180 | "#, | ||
181 | r#" | ||
182 | /// Creates a [`Vec`] containing the arguments. | ||
183 | /// | ||
184 | /// ``` | ||
185 | /// let v = vec![1, 2, 3]; | ||
186 | /// assert_eq!(v[0], 1); | ||
187 | /// assert_eq!(v[1], 2); | ||
188 | /// assert_eq!(v[2], 3); | ||
189 | /// ``` | ||
190 | macro_rules! vec { () => {} } | ||
191 | |||
192 | fn fn main() { vec![$0] } | ||
193 | "#, | ||
194 | ); | ||
195 | |||
196 | check_edit( | ||
197 | "foo!", | ||
198 | r#" | ||
199 | /// Foo | ||
200 | /// | ||
201 | /// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`, | ||
202 | /// call as `let _=foo! { hello world };` | ||
203 | macro_rules! foo { () => {} } | ||
204 | fn main() { <|> } | ||
205 | "#, | ||
206 | r#" | ||
207 | /// Foo | ||
208 | /// | ||
209 | /// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`, | ||
210 | /// call as `let _=foo! { hello world };` | ||
211 | macro_rules! foo { () => {} } | ||
212 | fn main() { foo! {$0} } | ||
213 | "#, | ||
214 | ) | ||
215 | } | ||
216 | } | ||