diff options
author | Igor Aleksanov <[email protected]> | 2020-11-01 09:35:04 +0000 |
---|---|---|
committer | Igor Aleksanov <[email protected]> | 2020-11-03 07:16:35 +0000 |
commit | fc8a1cd8006b021541ff673ec7f37a0f4b7bef57 (patch) | |
tree | b5153e990c97a86608ad69b4aacc2b2dd88f1614 /crates/completion/src/render | |
parent | 245e1b533b5be5ea4a917957fb02d7f57e6b4661 (diff) |
Introduce render module
Diffstat (limited to 'crates/completion/src/render')
-rw-r--r-- | crates/completion/src/render/builder_ext.rs | 94 | ||||
-rw-r--r-- | crates/completion/src/render/enum_variant.rs | 95 | ||||
-rw-r--r-- | crates/completion/src/render/function.rs | 87 | ||||
-rw-r--r-- | crates/completion/src/render/macro_.rs | 116 |
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 | |||
3 | use itertools::Itertools; | ||
4 | use test_utils::mark; | ||
5 | |||
6 | use crate::{item::Builder, CompletionContext}; | ||
7 | |||
8 | pub(super) enum Params { | ||
9 | Named(Vec<String>), | ||
10 | Anonymous(usize), | ||
11 | } | ||
12 | |||
13 | impl 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 | |||
26 | impl 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 @@ | |||
1 | use hir::{HasAttrs, HirDisplay, ModPath, StructKind}; | ||
2 | use itertools::Itertools; | ||
3 | use test_utils::mark; | ||
4 | |||
5 | use crate::{ | ||
6 | item::{CompletionItem, CompletionItemKind, CompletionKind}, | ||
7 | render::{builder_ext::Params, RenderContext}, | ||
8 | }; | ||
9 | |||
10 | #[derive(Debug)] | ||
11 | pub(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 | |||
21 | impl<'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 @@ | |||
1 | use hir::{Documentation, HasAttrs, HasSource, Type}; | ||
2 | use syntax::{ast::Fn, display::function_declaration}; | ||
3 | |||
4 | use crate::{ | ||
5 | item::{CompletionItem, CompletionItemKind, CompletionKind}, | ||
6 | render::{builder_ext::Params, RenderContext}, | ||
7 | }; | ||
8 | |||
9 | #[derive(Debug)] | ||
10 | pub(crate) struct FunctionRender<'a> { | ||
11 | ctx: RenderContext<'a>, | ||
12 | name: String, | ||
13 | fn_: hir::Function, | ||
14 | ast_node: Fn, | ||
15 | } | ||
16 | |||
17 | impl<'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 @@ | |||
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 | } | ||