aboutsummaryrefslogtreecommitdiff
path: root/crates/completion
diff options
context:
space:
mode:
Diffstat (limited to 'crates/completion')
-rw-r--r--crates/completion/src/completions.rs260
-rw-r--r--crates/completion/src/lib.rs1
-rw-r--r--crates/completion/src/render.rs50
-rw-r--r--crates/completion/src/render/builder_ext.rs94
-rw-r--r--crates/completion/src/render/enum_variant.rs95
-rw-r--r--crates/completion/src/render/function.rs87
-rw-r--r--crates/completion/src/render/macro_.rs116
7 files changed, 457 insertions, 246 deletions
diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs
index d5fb85b79..1ca5cf33d 100644
--- a/crates/completion/src/completions.rs
+++ b/crates/completion/src/completions.rs
@@ -14,14 +14,15 @@ pub(crate) mod macro_in_item_position;
14pub(crate) mod trait_impl; 14pub(crate) mod trait_impl;
15pub(crate) mod mod_; 15pub(crate) mod mod_;
16 16
17use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, StructKind, Type}; 17use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, Type};
18use itertools::Itertools;
19use syntax::{ast::NameOwner, display::*}; 18use syntax::{ast::NameOwner, display::*};
20use test_utils::mark; 19use test_utils::mark;
21 20
22use crate::{ 21use crate::{
23 item::Builder, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, 22 item::Builder,
24 CompletionScore, RootDatabase, 23 render::{EnumVariantRender, FunctionRender, MacroRender},
24 CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, CompletionScore,
25 RootDatabase,
25}; 26};
26 27
27/// Represents an in-progress set of completions being built. 28/// Represents an in-progress set of completions being built.
@@ -189,50 +190,14 @@ impl Completions {
189 name: Option<String>, 190 name: Option<String>,
190 macro_: hir::MacroDef, 191 macro_: hir::MacroDef,
191 ) { 192 ) {
192 // FIXME: Currently proc-macro do not have ast-node,
193 // such that it does not have source
194 if macro_.is_proc_macro() {
195 return;
196 }
197
198 let name = match name { 193 let name = match name {
199 Some(it) => it, 194 Some(it) => it,
200 None => return, 195 None => return,
201 }; 196 };
202 197
203 let ast_node = macro_.source(ctx.db).value; 198 if let Some(item) = MacroRender::new(ctx.into(), name, macro_).render() {
204 let detail = macro_label(&ast_node); 199 self.add(item);
205 200 }
206 let docs = macro_.docs(ctx.db);
207
208 let mut builder = CompletionItem::new(
209 CompletionKind::Reference,
210 ctx.source_range(),
211 &format!("{}!", name),
212 )
213 .kind(CompletionItemKind::Macro)
214 .set_documentation(docs.clone())
215 .set_deprecated(is_deprecated(macro_, ctx.db))
216 .detail(detail);
217
218 let needs_bang = ctx.use_item_syntax.is_none() && !ctx.is_macro_call;
219 builder = match ctx.config.snippet_cap {
220 Some(cap) if needs_bang => {
221 let docs = docs.as_ref().map_or("", |s| s.as_str());
222 let (bra, ket) = guess_macro_braces(&name, docs);
223 builder
224 .insert_snippet(cap, format!("{}!{}$0{}", name, bra, ket))
225 .label(format!("{}!{}…{}", name, bra, ket))
226 .lookup_by(format!("{}!", name))
227 }
228 None if needs_bang => builder.insert_text(format!("{}!", name)),
229 _ => {
230 mark::hit!(dont_insert_macro_call_parens_unncessary);
231 builder.insert_text(name)
232 }
233 };
234
235 self.add(builder.build());
236 } 201 }
237 202
238 pub(crate) fn add_function( 203 pub(crate) fn add_function(
@@ -241,50 +206,9 @@ impl Completions {
241 func: hir::Function, 206 func: hir::Function,
242 local_name: Option<String>, 207 local_name: Option<String>,
243 ) { 208 ) {
244 fn add_arg(arg: &str, ty: &Type, ctx: &CompletionContext) -> String { 209 let item = FunctionRender::new(ctx.into(), local_name, func).render();
245 if let Some(derefed_ty) = ty.remove_ref() {
246 for (name, local) in ctx.locals.iter() {
247 if name == arg && local.ty(ctx.db) == derefed_ty {
248 return (if ty.is_mutable_reference() { "&mut " } else { "&" }).to_string()
249 + &arg.to_string();
250 }
251 }
252 }
253 arg.to_string()
254 };
255 let name = local_name.unwrap_or_else(|| func.name(ctx.db).to_string());
256 let ast_node = func.source(ctx.db).value;
257
258 let mut builder =
259 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.clone())
260 .kind(if func.self_param(ctx.db).is_some() {
261 CompletionItemKind::Method
262 } else {
263 CompletionItemKind::Function
264 })
265 .set_documentation(func.docs(ctx.db))
266 .set_deprecated(is_deprecated(func, ctx.db))
267 .detail(function_declaration(&ast_node));
268
269 let params_ty = func.params(ctx.db);
270 let params = ast_node
271 .param_list()
272 .into_iter()
273 .flat_map(|it| it.params())
274 .zip(params_ty)
275 .flat_map(|(it, param_ty)| {
276 if let Some(pat) = it.pat() {
277 let name = pat.to_string();
278 let arg = name.trim_start_matches("mut ").trim_start_matches('_');
279 return Some(add_arg(arg, param_ty.ty(), ctx));
280 }
281 None
282 })
283 .collect();
284 210
285 builder = builder.add_call_parens(ctx, name, Params::Named(params)); 211 self.add(item)
286
287 self.add(builder.build())
288 } 212 }
289 213
290 pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) { 214 pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) {
@@ -325,7 +249,8 @@ impl Completions {
325 variant: hir::EnumVariant, 249 variant: hir::EnumVariant,
326 path: ModPath, 250 path: ModPath,
327 ) { 251 ) {
328 self.add_enum_variant_impl(ctx, variant, None, Some(path)) 252 let item = EnumVariantRender::new(ctx.into(), None, variant, Some(path)).render();
253 self.add(item);
329 } 254 }
330 255
331 pub(crate) fn add_enum_variant( 256 pub(crate) fn add_enum_variant(
@@ -334,63 +259,8 @@ impl Completions {
334 variant: hir::EnumVariant, 259 variant: hir::EnumVariant,
335 local_name: Option<String>, 260 local_name: Option<String>,
336 ) { 261 ) {
337 self.add_enum_variant_impl(ctx, variant, local_name, None) 262 let item = EnumVariantRender::new(ctx.into(), local_name, variant, None).render();
338 } 263 self.add(item);
339
340 fn add_enum_variant_impl(
341 &mut self,
342 ctx: &CompletionContext,
343 variant: hir::EnumVariant,
344 local_name: Option<String>,
345 path: Option<ModPath>,
346 ) {
347 let is_deprecated = is_deprecated(variant, ctx.db);
348 let name = local_name.unwrap_or_else(|| variant.name(ctx.db).to_string());
349 let (qualified_name, short_qualified_name) = match &path {
350 Some(path) => {
351 let full = path.to_string();
352 let short =
353 path.segments[path.segments.len().saturating_sub(2)..].iter().join("::");
354 (full, short)
355 }
356 None => (name.to_string(), name.to_string()),
357 };
358 let detail_types = variant
359 .fields(ctx.db)
360 .into_iter()
361 .map(|field| (field.name(ctx.db), field.signature_ty(ctx.db)));
362 let variant_kind = variant.kind(ctx.db);
363 let detail = match variant_kind {
364 StructKind::Tuple | StructKind::Unit => format!(
365 "({})",
366 detail_types.map(|(_, t)| t.display(ctx.db).to_string()).format(", ")
367 ),
368 StructKind::Record => format!(
369 "{{ {} }}",
370 detail_types
371 .map(|(n, t)| format!("{}: {}", n, t.display(ctx.db).to_string()))
372 .format(", ")
373 ),
374 };
375 let mut res = CompletionItem::new(
376 CompletionKind::Reference,
377 ctx.source_range(),
378 qualified_name.clone(),
379 )
380 .kind(CompletionItemKind::EnumVariant)
381 .set_documentation(variant.docs(ctx.db))
382 .set_deprecated(is_deprecated)
383 .detail(detail);
384
385 if variant_kind == StructKind::Tuple {
386 mark::hit!(inserts_parens_for_tuple_enums);
387 let params = Params::Anonymous(variant.fields(ctx.db).len());
388 res = res.add_call_parens(ctx, short_qualified_name, params)
389 } else if path.is_some() {
390 res = res.lookup_by(short_qualified_name);
391 }
392
393 res.add_to(self);
394 } 264 }
395} 265}
396 266
@@ -434,112 +304,10 @@ fn compute_score(ctx: &CompletionContext, ty: &Type, name: &str) -> Option<Compl
434 compute_score_from_active(&active_type, &active_name, ty, name) 304 compute_score_from_active(&active_type, &active_name, ty, name)
435} 305}
436 306
437enum Params {
438 Named(Vec<String>),
439 Anonymous(usize),
440}
441
442impl Params {
443 fn len(&self) -> usize {
444 match self {
445 Params::Named(xs) => xs.len(),
446 Params::Anonymous(len) => *len,
447 }
448 }
449
450 fn is_empty(&self) -> bool {
451 self.len() == 0
452 }
453}
454
455impl Builder {
456 fn add_call_parens(mut self, ctx: &CompletionContext, name: String, params: Params) -> Builder {
457 if !ctx.config.add_call_parenthesis {
458 return self;
459 }
460 if ctx.use_item_syntax.is_some() {
461 mark::hit!(no_parens_in_use_item);
462 return self;
463 }
464 if ctx.is_pattern_call {
465 mark::hit!(dont_duplicate_pattern_parens);
466 return self;
467 }
468 if ctx.is_call {
469 return self;
470 }
471
472 // Don't add parentheses if the expected type is some function reference.
473 if let Some(ty) = &ctx.expected_type {
474 if ty.is_fn() {
475 mark::hit!(no_call_parens_if_fn_ptr_needed);
476 return self;
477 }
478 }
479
480 let cap = match ctx.config.snippet_cap {
481 Some(it) => it,
482 None => return self,
483 };
484 // If not an import, add parenthesis automatically.
485 mark::hit!(inserts_parens_for_function_calls);
486
487 let (snippet, label) = if params.is_empty() {
488 (format!("{}()$0", name), format!("{}()", name))
489 } else {
490 self = self.trigger_call_info();
491 let snippet = match (ctx.config.add_call_argument_snippets, params) {
492 (true, Params::Named(params)) => {
493 let function_params_snippet =
494 params.iter().enumerate().format_with(", ", |(index, param_name), f| {
495 f(&format_args!("${{{}:{}}}", index + 1, param_name))
496 });
497 format!("{}({})$0", name, function_params_snippet)
498 }
499 _ => {
500 mark::hit!(suppress_arg_snippets);
501 format!("{}($0)", name)
502 }
503 };
504
505 (snippet, format!("{}(…)", name))
506 };
507 self.lookup_by(name).label(label).insert_snippet(cap, snippet)
508 }
509}
510
511fn is_deprecated(node: impl HasAttrs, db: &RootDatabase) -> bool { 307fn is_deprecated(node: impl HasAttrs, db: &RootDatabase) -> bool {
512 node.attrs(db).by_key("deprecated").exists() 308 node.attrs(db).by_key("deprecated").exists()
513} 309}
514 310
515fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) {
516 let mut votes = [0, 0, 0];
517 for (idx, s) in docs.match_indices(&macro_name) {
518 let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
519 // Ensure to match the full word
520 if after.starts_with('!')
521 && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
522 {
523 // It may have spaces before the braces like `foo! {}`
524 match after[1..].chars().find(|&c| !c.is_whitespace()) {
525 Some('{') => votes[0] += 1,
526 Some('[') => votes[1] += 1,
527 Some('(') => votes[2] += 1,
528 _ => {}
529 }
530 }
531 }
532
533 // Insert a space before `{}`.
534 // We prefer the last one when some votes equal.
535 let (_vote, (bra, ket)) = votes
536 .iter()
537 .zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
538 .max_by_key(|&(&vote, _)| vote)
539 .unwrap();
540 (*bra, *ket)
541}
542
543#[cfg(test)] 311#[cfg(test)]
544mod tests { 312mod tests {
545 use std::cmp::Reverse; 313 use std::cmp::Reverse;
diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs
index 89c0a9978..cb6e0554e 100644
--- a/crates/completion/src/lib.rs
+++ b/crates/completion/src/lib.rs
@@ -7,6 +7,7 @@ mod patterns;
7mod generated_lint_completions; 7mod generated_lint_completions;
8#[cfg(test)] 8#[cfg(test)]
9mod test_utils; 9mod test_utils;
10mod render;
10 11
11mod completions; 12mod completions;
12 13
diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs
new file mode 100644
index 000000000..66eb753b1
--- /dev/null
+++ b/crates/completion/src/render.rs
@@ -0,0 +1,50 @@
1//! `render` module provides utilities for rendering completion suggestions
2//! into code pieces that will be presented to user.
3
4mod macro_;
5mod function;
6mod builder_ext;
7mod enum_variant;
8
9use hir::HasAttrs;
10use ide_db::RootDatabase;
11use syntax::TextRange;
12
13use crate::{config::SnippetCap, CompletionContext};
14
15pub(crate) use crate::render::{
16 enum_variant::EnumVariantRender, function::FunctionRender, macro_::MacroRender,
17};
18
19#[derive(Debug)]
20pub(crate) struct RenderContext<'a> {
21 completion: &'a CompletionContext<'a>,
22}
23
24impl<'a> RenderContext<'a> {
25 pub fn new(completion: &'a CompletionContext<'a>) -> RenderContext<'a> {
26 RenderContext { completion }
27 }
28
29 pub fn snippet_cap(&self) -> Option<SnippetCap> {
30 self.completion.config.snippet_cap.clone()
31 }
32
33 pub fn db(&self) -> &'a RootDatabase {
34 &self.completion.db
35 }
36
37 pub fn source_range(&self) -> TextRange {
38 self.completion.source_range()
39 }
40
41 pub fn is_deprecated(&self, node: impl HasAttrs) -> bool {
42 node.attrs(self.db()).by_key("deprecated").exists()
43 }
44}
45
46impl<'a> From<&'a CompletionContext<'a>> for RenderContext<'a> {
47 fn from(ctx: &'a CompletionContext<'a>) -> RenderContext<'a> {
48 RenderContext::new(ctx)
49 }
50}
diff --git a/crates/completion/src/render/builder_ext.rs b/crates/completion/src/render/builder_ext.rs
new file mode 100644
index 000000000..37b0d0459
--- /dev/null
+++ b/crates/completion/src/render/builder_ext.rs
@@ -0,0 +1,94 @@
1//! Extensions for `Builder` structure required for item rendering.
2
3use itertools::Itertools;
4use test_utils::mark;
5
6use crate::{item::Builder, CompletionContext};
7
8pub(super) enum Params {
9 Named(Vec<String>),
10 Anonymous(usize),
11}
12
13impl Params {
14 pub(super) fn len(&self) -> usize {
15 match self {
16 Params::Named(xs) => xs.len(),
17 Params::Anonymous(len) => *len,
18 }
19 }
20
21 pub(super) fn is_empty(&self) -> bool {
22 self.len() == 0
23 }
24}
25
26impl Builder {
27 pub(super) fn should_add_parems(&self, ctx: &CompletionContext) -> bool {
28 if !ctx.config.add_call_parenthesis {
29 return false;
30 }
31 if ctx.use_item_syntax.is_some() {
32 mark::hit!(no_parens_in_use_item);
33 return false;
34 }
35 if ctx.is_pattern_call {
36 mark::hit!(dont_duplicate_pattern_parens);
37 return false;
38 }
39 if ctx.is_call {
40 return false;
41 }
42
43 // Don't add parentheses if the expected type is some function reference.
44 if let Some(ty) = &ctx.expected_type {
45 if ty.is_fn() {
46 mark::hit!(no_call_parens_if_fn_ptr_needed);
47 return false;
48 }
49 }
50
51 // Nothing prevents us from adding parentheses
52 true
53 }
54
55 pub(super) fn add_call_parens(
56 mut self,
57 ctx: &CompletionContext,
58 name: String,
59 params: Params,
60 ) -> Builder {
61 if !self.should_add_parems(ctx) {
62 return self;
63 }
64
65 let cap = match ctx.config.snippet_cap {
66 Some(it) => it,
67 None => return self,
68 };
69 // If not an import, add parenthesis automatically.
70 mark::hit!(inserts_parens_for_function_calls);
71
72 let (snippet, label) = if params.is_empty() {
73 (format!("{}()$0", name), format!("{}()", name))
74 } else {
75 self = self.trigger_call_info();
76 let snippet = match (ctx.config.add_call_argument_snippets, params) {
77 (true, Params::Named(params)) => {
78 let function_params_snippet =
79 params.iter().enumerate().format_with(", ", |(index, param_name), f| {
80 f(&format_args!("${{{}:{}}}", index + 1, param_name))
81 });
82 format!("{}({})$0", name, function_params_snippet)
83 }
84 _ => {
85 mark::hit!(suppress_arg_snippets);
86 format!("{}($0)", name)
87 }
88 };
89
90 (snippet, format!("{}(…)", name))
91 };
92 self.lookup_by(name).label(label).insert_snippet(cap, snippet)
93 }
94}
diff --git a/crates/completion/src/render/enum_variant.rs b/crates/completion/src/render/enum_variant.rs
new file mode 100644
index 000000000..26cfdfeea
--- /dev/null
+++ b/crates/completion/src/render/enum_variant.rs
@@ -0,0 +1,95 @@
1use hir::{HasAttrs, HirDisplay, ModPath, StructKind};
2use itertools::Itertools;
3use test_utils::mark;
4
5use crate::{
6 item::{CompletionItem, CompletionItemKind, CompletionKind},
7 render::{builder_ext::Params, RenderContext},
8};
9
10#[derive(Debug)]
11pub(crate) struct EnumVariantRender<'a> {
12 ctx: RenderContext<'a>,
13 name: String,
14 variant: hir::EnumVariant,
15 path: Option<ModPath>,
16 qualified_name: String,
17 short_qualified_name: String,
18 variant_kind: StructKind,
19}
20
21impl<'a> EnumVariantRender<'a> {
22 pub(crate) fn new(
23 ctx: RenderContext<'a>,
24 local_name: Option<String>,
25 variant: hir::EnumVariant,
26 path: Option<ModPath>,
27 ) -> EnumVariantRender<'a> {
28 let name = local_name.unwrap_or_else(|| variant.name(ctx.db()).to_string());
29 let variant_kind = variant.kind(ctx.db());
30
31 let (qualified_name, short_qualified_name) = match &path {
32 Some(path) => {
33 let full = path.to_string();
34 let short =
35 path.segments[path.segments.len().saturating_sub(2)..].iter().join("::");
36 (full, short)
37 }
38 None => (name.to_string(), name.to_string()),
39 };
40
41 EnumVariantRender {
42 ctx,
43 name,
44 variant,
45 path,
46 qualified_name,
47 short_qualified_name,
48 variant_kind,
49 }
50 }
51
52 pub(crate) fn render(self) -> CompletionItem {
53 let mut builder = CompletionItem::new(
54 CompletionKind::Reference,
55 self.ctx.source_range(),
56 self.qualified_name.clone(),
57 )
58 .kind(CompletionItemKind::EnumVariant)
59 .set_documentation(self.variant.docs(self.ctx.db()))
60 .set_deprecated(self.ctx.is_deprecated(self.variant))
61 .detail(self.detail());
62
63 if self.variant_kind == StructKind::Tuple {
64 mark::hit!(inserts_parens_for_tuple_enums);
65 let params = Params::Anonymous(self.variant.fields(self.ctx.db()).len());
66 builder =
67 builder.add_call_parens(self.ctx.completion, self.short_qualified_name, params);
68 } else if self.path.is_some() {
69 builder = builder.lookup_by(self.short_qualified_name);
70 }
71
72 builder.build()
73 }
74
75 fn detail(&self) -> String {
76 let detail_types = self
77 .variant
78 .fields(self.ctx.db())
79 .into_iter()
80 .map(|field| (field.name(self.ctx.db()), field.signature_ty(self.ctx.db())));
81
82 match self.variant_kind {
83 StructKind::Tuple | StructKind::Unit => format!(
84 "({})",
85 detail_types.map(|(_, t)| t.display(self.ctx.db()).to_string()).format(", ")
86 ),
87 StructKind::Record => format!(
88 "{{ {} }}",
89 detail_types
90 .map(|(n, t)| format!("{}: {}", n, t.display(self.ctx.db()).to_string()))
91 .format(", ")
92 ),
93 }
94 }
95}
diff --git a/crates/completion/src/render/function.rs b/crates/completion/src/render/function.rs
new file mode 100644
index 000000000..16f15e22c
--- /dev/null
+++ b/crates/completion/src/render/function.rs
@@ -0,0 +1,87 @@
1use hir::{Documentation, HasAttrs, HasSource, Type};
2use syntax::{ast::Fn, display::function_declaration};
3
4use crate::{
5 item::{CompletionItem, CompletionItemKind, CompletionKind},
6 render::{builder_ext::Params, RenderContext},
7};
8
9#[derive(Debug)]
10pub(crate) struct FunctionRender<'a> {
11 ctx: RenderContext<'a>,
12 name: String,
13 fn_: hir::Function,
14 ast_node: Fn,
15}
16
17impl<'a> FunctionRender<'a> {
18 pub(crate) fn new(
19 ctx: RenderContext<'a>,
20 local_name: Option<String>,
21 fn_: hir::Function,
22 ) -> FunctionRender<'a> {
23 let name = local_name.unwrap_or_else(|| fn_.name(ctx.db()).to_string());
24 let ast_node = fn_.source(ctx.db()).value;
25
26 FunctionRender { ctx, name, fn_, ast_node }
27 }
28
29 pub(crate) fn render(self) -> CompletionItem {
30 let params = self.params();
31 CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), self.name.clone())
32 .kind(self.kind())
33 .set_documentation(self.docs())
34 .set_deprecated(self.ctx.is_deprecated(self.fn_))
35 .detail(self.detail())
36 .add_call_parens(self.ctx.completion, self.name, params)
37 .build()
38 }
39
40 fn detail(&self) -> String {
41 function_declaration(&self.ast_node)
42 }
43
44 fn add_arg(&self, arg: &str, ty: &Type) -> String {
45 if let Some(derefed_ty) = ty.remove_ref() {
46 for (name, local) in self.ctx.completion.locals.iter() {
47 if name == arg && local.ty(self.ctx.db()) == derefed_ty {
48 return (if ty.is_mutable_reference() { "&mut " } else { "&" }).to_string()
49 + &arg.to_string();
50 }
51 }
52 }
53 arg.to_string()
54 }
55
56 fn params(&self) -> Params {
57 let params_ty = self.fn_.params(self.ctx.db());
58 let params = self
59 .ast_node
60 .param_list()
61 .into_iter()
62 .flat_map(|it| it.params())
63 .zip(params_ty)
64 .flat_map(|(it, param_ty)| {
65 if let Some(pat) = it.pat() {
66 let name = pat.to_string();
67 let arg = name.trim_start_matches("mut ").trim_start_matches('_');
68 return Some(self.add_arg(arg, param_ty.ty()));
69 }
70 None
71 })
72 .collect();
73 Params::Named(params)
74 }
75
76 fn kind(&self) -> CompletionItemKind {
77 if self.fn_.self_param(self.ctx.db()).is_some() {
78 CompletionItemKind::Method
79 } else {
80 CompletionItemKind::Function
81 }
82 }
83
84 fn docs(&self) -> Option<Documentation> {
85 self.fn_.docs(self.ctx.db())
86 }
87}
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}