aboutsummaryrefslogtreecommitdiff
path: root/crates/completion/src/render/macro_.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/completion/src/render/macro_.rs')
-rw-r--r--crates/completion/src/render/macro_.rs116
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 @@
1use hir::{Documentation, HasAttrs, HasSource};
2use syntax::display::macro_label;
3use test_utils::mark;
4
5use crate::{
6 item::{CompletionItem, CompletionItemKind, CompletionKind},
7 render::RenderContext,
8};
9
10#[derive(Debug)]
11pub(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
20impl<'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
90fn 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(&macro_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}