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 | |
parent | 245e1b533b5be5ea4a917957fb02d7f57e6b4661 (diff) |
Introduce render module
-rw-r--r-- | crates/completion/src/completions.rs | 260 | ||||
-rw-r--r-- | crates/completion/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/completion/src/render.rs | 50 | ||||
-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 |
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; | |||
14 | pub(crate) mod trait_impl; | 14 | pub(crate) mod trait_impl; |
15 | pub(crate) mod mod_; | 15 | pub(crate) mod mod_; |
16 | 16 | ||
17 | use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, StructKind, Type}; | 17 | use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, Type}; |
18 | use itertools::Itertools; | ||
19 | use syntax::{ast::NameOwner, display::*}; | 18 | use syntax::{ast::NameOwner, display::*}; |
20 | use test_utils::mark; | 19 | use test_utils::mark; |
21 | 20 | ||
22 | use crate::{ | 21 | use 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 | ||
437 | enum Params { | ||
438 | Named(Vec<String>), | ||
439 | Anonymous(usize), | ||
440 | } | ||
441 | |||
442 | impl 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 | |||
455 | impl 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 | |||
511 | fn is_deprecated(node: impl HasAttrs, db: &RootDatabase) -> bool { | 307 | fn 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 | ||
515 | fn 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(¯o_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)] |
544 | mod tests { | 312 | mod 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; | |||
7 | mod generated_lint_completions; | 7 | mod generated_lint_completions; |
8 | #[cfg(test)] | 8 | #[cfg(test)] |
9 | mod test_utils; | 9 | mod test_utils; |
10 | mod render; | ||
10 | 11 | ||
11 | mod completions; | 12 | mod 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 | |||
4 | mod macro_; | ||
5 | mod function; | ||
6 | mod builder_ext; | ||
7 | mod enum_variant; | ||
8 | |||
9 | use hir::HasAttrs; | ||
10 | use ide_db::RootDatabase; | ||
11 | use syntax::TextRange; | ||
12 | |||
13 | use crate::{config::SnippetCap, CompletionContext}; | ||
14 | |||
15 | pub(crate) use crate::render::{ | ||
16 | enum_variant::EnumVariantRender, function::FunctionRender, macro_::MacroRender, | ||
17 | }; | ||
18 | |||
19 | #[derive(Debug)] | ||
20 | pub(crate) struct RenderContext<'a> { | ||
21 | completion: &'a CompletionContext<'a>, | ||
22 | } | ||
23 | |||
24 | impl<'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 | |||
46 | impl<'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 | |||
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 | } | ||