aboutsummaryrefslogtreecommitdiff
path: root/crates/completion/src/render
diff options
context:
space:
mode:
Diffstat (limited to 'crates/completion/src/render')
-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
4 files changed, 392 insertions, 0 deletions
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}