diff options
Diffstat (limited to 'crates/completion/src/render/macro_.rs')
-rw-r--r-- | crates/completion/src/render/macro_.rs | 116 |
1 files changed, 116 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..bcf94f47e --- /dev/null +++ b/crates/completion/src/render/macro_.rs | |||
@@ -0,0 +1,116 @@ | |||
1 | use hir::{Documentation, HasAttrs, HasSource}; | ||
2 | use syntax::display::macro_label; | ||
3 | use test_utils::mark; | ||
4 | |||
5 | use crate::{ | ||
6 | item::{CompletionItem, CompletionItemKind, CompletionKind}, | ||
7 | render::RenderContext, | ||
8 | }; | ||
9 | |||
10 | #[derive(Debug)] | ||
11 | pub(crate) struct MacroRender<'a> { | ||
12 | ctx: RenderContext<'a>, | ||
13 | name: String, | ||
14 | macro_: hir::MacroDef, | ||
15 | docs: Option<Documentation>, | ||
16 | bra: &'static str, | ||
17 | ket: &'static str, | ||
18 | } | ||
19 | |||
20 | impl<'a> MacroRender<'a> { | ||
21 | pub(crate) fn new( | ||
22 | ctx: RenderContext<'a>, | ||
23 | name: String, | ||
24 | macro_: hir::MacroDef, | ||
25 | ) -> MacroRender<'a> { | ||
26 | let docs = macro_.docs(ctx.db()); | ||
27 | let docs_str = docs.as_ref().map_or("", |s| s.as_str()); | ||
28 | let (bra, ket) = guess_macro_braces(&name, docs_str); | ||
29 | |||
30 | MacroRender { ctx, name, macro_, docs, bra, ket } | ||
31 | } | ||
32 | |||
33 | pub(crate) fn render(&self) -> Option<CompletionItem> { | ||
34 | // FIXME: Currently proc-macro do not have ast-node, | ||
35 | // such that it does not have source | ||
36 | if self.macro_.is_proc_macro() { | ||
37 | return None; | ||
38 | } | ||
39 | |||
40 | let mut builder = | ||
41 | CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), &self.label()) | ||
42 | .kind(CompletionItemKind::Macro) | ||
43 | .set_documentation(self.docs.clone()) | ||
44 | .set_deprecated(self.ctx.is_deprecated(self.macro_)) | ||
45 | .detail(self.detail()); | ||
46 | |||
47 | let needs_bang = self.needs_bang(); | ||
48 | builder = match self.ctx.snippet_cap() { | ||
49 | Some(cap) if needs_bang => { | ||
50 | let snippet = self.snippet(); | ||
51 | let lookup = self.lookup(); | ||
52 | builder.insert_snippet(cap, snippet).lookup_by(lookup) | ||
53 | } | ||
54 | None if needs_bang => builder.insert_text(self.banged_name()), | ||
55 | _ => { | ||
56 | mark::hit!(dont_insert_macro_call_parens_unncessary); | ||
57 | builder.insert_text(&self.name) | ||
58 | } | ||
59 | }; | ||
60 | |||
61 | Some(builder.build()) | ||
62 | } | ||
63 | |||
64 | fn needs_bang(&self) -> bool { | ||
65 | self.ctx.completion.use_item_syntax.is_none() && !self.ctx.completion.is_macro_call | ||
66 | } | ||
67 | |||
68 | fn label(&self) -> String { | ||
69 | format!("{}!{}…{}", self.name, self.bra, self.ket) | ||
70 | } | ||
71 | |||
72 | fn snippet(&self) -> String { | ||
73 | format!("{}!{}$0{}", self.name, self.bra, self.ket) | ||
74 | } | ||
75 | |||
76 | fn lookup(&self) -> String { | ||
77 | self.banged_name() | ||
78 | } | ||
79 | |||
80 | fn banged_name(&self) -> String { | ||
81 | format!("{}!", self.name) | ||
82 | } | ||
83 | |||
84 | fn detail(&self) -> String { | ||
85 | let ast_node = self.macro_.source(self.ctx.db()).value; | ||
86 | macro_label(&ast_node) | ||
87 | } | ||
88 | } | ||
89 | |||
90 | fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) { | ||
91 | let mut votes = [0, 0, 0]; | ||
92 | for (idx, s) in docs.match_indices(¯o_name) { | ||
93 | let (before, after) = (&docs[..idx], &docs[idx + s.len()..]); | ||
94 | // Ensure to match the full word | ||
95 | if after.starts_with('!') | ||
96 | && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric()) | ||
97 | { | ||
98 | // It may have spaces before the braces like `foo! {}` | ||
99 | match after[1..].chars().find(|&c| !c.is_whitespace()) { | ||
100 | Some('{') => votes[0] += 1, | ||
101 | Some('[') => votes[1] += 1, | ||
102 | Some('(') => votes[2] += 1, | ||
103 | _ => {} | ||
104 | } | ||
105 | } | ||
106 | } | ||
107 | |||
108 | // Insert a space before `{}`. | ||
109 | // We prefer the last one when some votes equal. | ||
110 | let (_vote, (bra, ket)) = votes | ||
111 | .iter() | ||
112 | .zip(&[(" {", "}"), ("[", "]"), ("(", ")")]) | ||
113 | .max_by_key(|&(&vote, _)| vote) | ||
114 | .unwrap(); | ||
115 | (*bra, *ket) | ||
116 | } | ||