aboutsummaryrefslogtreecommitdiff
path: root/crates/completion/src/render.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/completion/src/render.rs')
-rw-r--r--crates/completion/src/render.rs848
1 files changed, 848 insertions, 0 deletions
diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs
new file mode 100644
index 000000000..1fa02c375
--- /dev/null
+++ b/crates/completion/src/render.rs
@@ -0,0 +1,848 @@
1//! `render` module provides utilities for rendering completion suggestions
2//! into code pieces that will be presented to user.
3
4pub(crate) mod macro_;
5pub(crate) mod function;
6pub(crate) mod enum_variant;
7pub(crate) mod const_;
8pub(crate) mod type_alias;
9
10mod builder_ext;
11
12use hir::{Documentation, HasAttrs, HirDisplay, Mutability, ScopeDef, Type};
13use ide_db::RootDatabase;
14use syntax::TextRange;
15use test_utils::mark;
16
17use crate::{
18 config::SnippetCap, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind,
19 CompletionScore,
20};
21
22use crate::render::{enum_variant::render_enum_variant, function::render_fn, macro_::render_macro};
23
24pub(crate) fn render_field<'a>(
25 ctx: RenderContext<'a>,
26 field: hir::Field,
27 ty: &Type,
28) -> CompletionItem {
29 Render::new(ctx).add_field(field, ty)
30}
31
32pub(crate) fn render_tuple_field<'a>(
33 ctx: RenderContext<'a>,
34 field: usize,
35 ty: &Type,
36) -> CompletionItem {
37 Render::new(ctx).add_tuple_field(field, ty)
38}
39
40pub(crate) fn render_resolution<'a>(
41 ctx: RenderContext<'a>,
42 local_name: String,
43 resolution: &ScopeDef,
44) -> Option<CompletionItem> {
45 Render::new(ctx).render_resolution(local_name, resolution)
46}
47
48/// Interface for data and methods required for items rendering.
49#[derive(Debug)]
50pub(crate) struct RenderContext<'a> {
51 completion: &'a CompletionContext<'a>,
52}
53
54impl<'a> RenderContext<'a> {
55 pub(crate) fn new(completion: &'a CompletionContext<'a>) -> RenderContext<'a> {
56 RenderContext { completion }
57 }
58
59 fn snippet_cap(&self) -> Option<SnippetCap> {
60 self.completion.config.snippet_cap.clone()
61 }
62
63 fn db(&self) -> &'a RootDatabase {
64 &self.completion.db
65 }
66
67 fn source_range(&self) -> TextRange {
68 self.completion.source_range()
69 }
70
71 fn is_deprecated(&self, node: impl HasAttrs) -> bool {
72 node.attrs(self.db()).by_key("deprecated").exists()
73 }
74
75 fn docs(&self, node: impl HasAttrs) -> Option<Documentation> {
76 node.docs(self.db())
77 }
78
79 fn active_name_and_type(&self) -> Option<(String, Type)> {
80 if let Some(record_field) = &self.completion.record_field_syntax {
81 mark::hit!(record_field_type_match);
82 let (struct_field, _local) = self.completion.sema.resolve_record_field(record_field)?;
83 Some((struct_field.name(self.db()).to_string(), struct_field.signature_ty(self.db())))
84 } else if let Some(active_parameter) = &self.completion.active_parameter {
85 mark::hit!(active_param_type_match);
86 Some((active_parameter.name.clone(), active_parameter.ty.clone()))
87 } else {
88 None
89 }
90 }
91}
92
93/// Generic renderer for completion items.
94#[derive(Debug)]
95struct Render<'a> {
96 ctx: RenderContext<'a>,
97}
98
99impl<'a> Render<'a> {
100 fn new(ctx: RenderContext<'a>) -> Render<'a> {
101 Render { ctx }
102 }
103
104 fn add_field(&mut self, field: hir::Field, ty: &Type) -> CompletionItem {
105 let is_deprecated = self.ctx.is_deprecated(field);
106 let name = field.name(self.ctx.db());
107 let mut item = CompletionItem::new(
108 CompletionKind::Reference,
109 self.ctx.source_range(),
110 name.to_string(),
111 )
112 .kind(CompletionItemKind::Field)
113 .detail(ty.display(self.ctx.db()).to_string())
114 .set_documentation(field.docs(self.ctx.db()))
115 .set_deprecated(is_deprecated);
116
117 if let Some(score) = compute_score(&self.ctx, &ty, &name.to_string()) {
118 item = item.set_score(score);
119 }
120
121 item.build()
122 }
123
124 fn add_tuple_field(&mut self, field: usize, ty: &Type) -> CompletionItem {
125 CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), field.to_string())
126 .kind(CompletionItemKind::Field)
127 .detail(ty.display(self.ctx.db()).to_string())
128 .build()
129 }
130
131 fn render_resolution(
132 self,
133 local_name: String,
134 resolution: &ScopeDef,
135 ) -> Option<CompletionItem> {
136 use hir::ModuleDef::*;
137
138 let completion_kind = match resolution {
139 ScopeDef::ModuleDef(BuiltinType(..)) => CompletionKind::BuiltinType,
140 _ => CompletionKind::Reference,
141 };
142
143 let kind = match resolution {
144 ScopeDef::ModuleDef(Function(func)) => {
145 let item = render_fn(self.ctx, Some(local_name), *func);
146 return Some(item);
147 }
148 ScopeDef::ModuleDef(EnumVariant(var)) => {
149 let item = render_enum_variant(self.ctx, Some(local_name), *var, None);
150 return Some(item);
151 }
152 ScopeDef::MacroDef(mac) => {
153 let item = render_macro(self.ctx, local_name, *mac);
154 return item;
155 }
156
157 ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::Module,
158 ScopeDef::ModuleDef(Adt(hir::Adt::Struct(_))) => CompletionItemKind::Struct,
159 // FIXME: add CompletionItemKind::Union
160 ScopeDef::ModuleDef(Adt(hir::Adt::Union(_))) => CompletionItemKind::Struct,
161 ScopeDef::ModuleDef(Adt(hir::Adt::Enum(_))) => CompletionItemKind::Enum,
162 ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::Const,
163 ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::Static,
164 ScopeDef::ModuleDef(Trait(..)) => CompletionItemKind::Trait,
165 ScopeDef::ModuleDef(TypeAlias(..)) => CompletionItemKind::TypeAlias,
166 ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType,
167 ScopeDef::GenericParam(..) => CompletionItemKind::TypeParam,
168 ScopeDef::Local(..) => CompletionItemKind::Binding,
169 // (does this need its own kind?)
170 ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => CompletionItemKind::TypeParam,
171 ScopeDef::Unknown => {
172 let item = CompletionItem::new(
173 CompletionKind::Reference,
174 self.ctx.source_range(),
175 local_name,
176 )
177 .kind(CompletionItemKind::UnresolvedReference)
178 .build();
179 return Some(item);
180 }
181 };
182
183 let docs = self.docs(resolution);
184
185 let mut item =
186 CompletionItem::new(completion_kind, self.ctx.source_range(), local_name.clone());
187 if let ScopeDef::Local(local) = resolution {
188 let ty = local.ty(self.ctx.db());
189 if !ty.is_unknown() {
190 item = item.detail(ty.display(self.ctx.db()).to_string());
191 }
192 };
193
194 let mut ref_match = None;
195 if let ScopeDef::Local(local) = resolution {
196 if let Some((active_name, active_type)) = self.ctx.active_name_and_type() {
197 let ty = local.ty(self.ctx.db());
198 if let Some(score) =
199 compute_score_from_active(&active_type, &active_name, &ty, &local_name)
200 {
201 item = item.set_score(score);
202 }
203 ref_match = refed_type_matches(&active_type, &active_name, &ty, &local_name);
204 }
205 }
206
207 // Add `<>` for generic types
208 if self.ctx.completion.is_path_type
209 && !self.ctx.completion.has_type_args
210 && self.ctx.completion.config.add_call_parenthesis
211 {
212 if let Some(cap) = self.ctx.snippet_cap() {
213 let has_non_default_type_params = match resolution {
214 ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(self.ctx.db()),
215 ScopeDef::ModuleDef(TypeAlias(it)) => {
216 it.has_non_default_type_params(self.ctx.db())
217 }
218 _ => false,
219 };
220 if has_non_default_type_params {
221 mark::hit!(inserts_angle_brackets_for_generics);
222 item = item
223 .lookup_by(local_name.clone())
224 .label(format!("{}<…>", local_name))
225 .insert_snippet(cap, format!("{}<$0>", local_name));
226 }
227 }
228 }
229
230 let item = item.kind(kind).set_documentation(docs).set_ref_match(ref_match).build();
231 Some(item)
232 }
233
234 fn docs(&self, resolution: &ScopeDef) -> Option<Documentation> {
235 use hir::ModuleDef::*;
236 match resolution {
237 ScopeDef::ModuleDef(Module(it)) => it.docs(self.ctx.db()),
238 ScopeDef::ModuleDef(Adt(it)) => it.docs(self.ctx.db()),
239 ScopeDef::ModuleDef(EnumVariant(it)) => it.docs(self.ctx.db()),
240 ScopeDef::ModuleDef(Const(it)) => it.docs(self.ctx.db()),
241 ScopeDef::ModuleDef(Static(it)) => it.docs(self.ctx.db()),
242 ScopeDef::ModuleDef(Trait(it)) => it.docs(self.ctx.db()),
243 ScopeDef::ModuleDef(TypeAlias(it)) => it.docs(self.ctx.db()),
244 _ => None,
245 }
246 }
247}
248
249fn compute_score_from_active(
250 active_type: &Type,
251 active_name: &str,
252 ty: &Type,
253 name: &str,
254) -> Option<CompletionScore> {
255 // Compute score
256 // For the same type
257 if active_type != ty {
258 return None;
259 }
260
261 let mut res = CompletionScore::TypeMatch;
262
263 // If same type + same name then go top position
264 if active_name == name {
265 res = CompletionScore::TypeAndNameMatch
266 }
267
268 Some(res)
269}
270fn refed_type_matches(
271 active_type: &Type,
272 active_name: &str,
273 ty: &Type,
274 name: &str,
275) -> Option<(Mutability, CompletionScore)> {
276 let derefed_active = active_type.remove_ref()?;
277 let score = compute_score_from_active(&derefed_active, &active_name, &ty, &name)?;
278 Some((
279 if active_type.is_mutable_reference() { Mutability::Mut } else { Mutability::Shared },
280 score,
281 ))
282}
283
284fn compute_score(ctx: &RenderContext, ty: &Type, name: &str) -> Option<CompletionScore> {
285 let (active_name, active_type) = ctx.active_name_and_type()?;
286 compute_score_from_active(&active_type, &active_name, ty, name)
287}
288
289#[cfg(test)]
290mod tests {
291 use std::cmp::Reverse;
292
293 use expect_test::{expect, Expect};
294 use test_utils::mark;
295
296 use crate::{
297 test_utils::{check_edit, do_completion, get_all_items},
298 CompletionConfig, CompletionKind, CompletionScore,
299 };
300
301 fn check(ra_fixture: &str, expect: Expect) {
302 let actual = do_completion(ra_fixture, CompletionKind::Reference);
303 expect.assert_debug_eq(&actual);
304 }
305
306 fn check_scores(ra_fixture: &str, expect: Expect) {
307 fn display_score(score: Option<CompletionScore>) -> &'static str {
308 match score {
309 Some(CompletionScore::TypeMatch) => "[type]",
310 Some(CompletionScore::TypeAndNameMatch) => "[type+name]",
311 None => "[]".into(),
312 }
313 }
314
315 let mut completions = get_all_items(CompletionConfig::default(), ra_fixture);
316 completions.sort_by_key(|it| (Reverse(it.score()), it.label().to_string()));
317 let actual = completions
318 .into_iter()
319 .filter(|it| it.completion_kind == CompletionKind::Reference)
320 .map(|it| {
321 let tag = it.kind().unwrap().tag();
322 let score = display_score(it.score());
323 format!("{} {} {}\n", tag, it.label(), score)
324 })
325 .collect::<String>();
326 expect.assert_eq(&actual);
327 }
328
329 #[test]
330 fn enum_detail_includes_record_fields() {
331 check(
332 r#"
333enum Foo { Foo { x: i32, y: i32 } }
334
335fn main() { Foo::Fo<|> }
336"#,
337 expect![[r#"
338 [
339 CompletionItem {
340 label: "Foo",
341 source_range: 54..56,
342 delete: 54..56,
343 insert: "Foo",
344 kind: EnumVariant,
345 detail: "{ x: i32, y: i32 }",
346 },
347 ]
348 "#]],
349 );
350 }
351
352 #[test]
353 fn enum_detail_doesnt_include_tuple_fields() {
354 check(
355 r#"
356enum Foo { Foo (i32, i32) }
357
358fn main() { Foo::Fo<|> }
359"#,
360 expect![[r#"
361 [
362 CompletionItem {
363 label: "Foo(…)",
364 source_range: 46..48,
365 delete: 46..48,
366 insert: "Foo($0)",
367 kind: EnumVariant,
368 lookup: "Foo",
369 detail: "(i32, i32)",
370 trigger_call_info: true,
371 },
372 ]
373 "#]],
374 );
375 }
376
377 #[test]
378 fn enum_detail_just_parentheses_for_unit() {
379 check(
380 r#"
381enum Foo { Foo }
382
383fn main() { Foo::Fo<|> }
384"#,
385 expect![[r#"
386 [
387 CompletionItem {
388 label: "Foo",
389 source_range: 35..37,
390 delete: 35..37,
391 insert: "Foo",
392 kind: EnumVariant,
393 detail: "()",
394 },
395 ]
396 "#]],
397 );
398 }
399
400 #[test]
401 fn lookup_enums_by_two_qualifiers() {
402 check(
403 r#"
404mod m {
405 pub enum Spam { Foo, Bar(i32) }
406}
407fn main() { let _: m::Spam = S<|> }
408"#,
409 expect![[r#"
410 [
411 CompletionItem {
412 label: "Spam::Bar(…)",
413 source_range: 75..76,
414 delete: 75..76,
415 insert: "Spam::Bar($0)",
416 kind: EnumVariant,
417 lookup: "Spam::Bar",
418 detail: "(i32)",
419 trigger_call_info: true,
420 },
421 CompletionItem {
422 label: "m",
423 source_range: 75..76,
424 delete: 75..76,
425 insert: "m",
426 kind: Module,
427 },
428 CompletionItem {
429 label: "m::Spam::Foo",
430 source_range: 75..76,
431 delete: 75..76,
432 insert: "m::Spam::Foo",
433 kind: EnumVariant,
434 lookup: "Spam::Foo",
435 detail: "()",
436 },
437 CompletionItem {
438 label: "main()",
439 source_range: 75..76,
440 delete: 75..76,
441 insert: "main()$0",
442 kind: Function,
443 lookup: "main",
444 detail: "fn main()",
445 },
446 ]
447 "#]],
448 )
449 }
450
451 #[test]
452 fn sets_deprecated_flag_in_items() {
453 check(
454 r#"
455#[deprecated]
456fn something_deprecated() {}
457#[deprecated(since = "1.0.0")]
458fn something_else_deprecated() {}
459
460fn main() { som<|> }
461"#,
462 expect![[r#"
463 [
464 CompletionItem {
465 label: "main()",
466 source_range: 121..124,
467 delete: 121..124,
468 insert: "main()$0",
469 kind: Function,
470 lookup: "main",
471 detail: "fn main()",
472 },
473 CompletionItem {
474 label: "something_deprecated()",
475 source_range: 121..124,
476 delete: 121..124,
477 insert: "something_deprecated()$0",
478 kind: Function,
479 lookup: "something_deprecated",
480 detail: "fn something_deprecated()",
481 deprecated: true,
482 },
483 CompletionItem {
484 label: "something_else_deprecated()",
485 source_range: 121..124,
486 delete: 121..124,
487 insert: "something_else_deprecated()$0",
488 kind: Function,
489 lookup: "something_else_deprecated",
490 detail: "fn something_else_deprecated()",
491 deprecated: true,
492 },
493 ]
494 "#]],
495 );
496
497 check(
498 r#"
499struct A { #[deprecated] the_field: u32 }
500fn foo() { A { the<|> } }
501"#,
502 expect![[r#"
503 [
504 CompletionItem {
505 label: "the_field",
506 source_range: 57..60,
507 delete: 57..60,
508 insert: "the_field",
509 kind: Field,
510 detail: "u32",
511 deprecated: true,
512 },
513 ]
514 "#]],
515 );
516 }
517
518 #[test]
519 fn renders_docs() {
520 check(
521 r#"
522struct S {
523 /// Field docs
524 foo:
525}
526impl S {
527 /// Method docs
528 fn bar(self) { self.<|> }
529}"#,
530 expect![[r#"
531 [
532 CompletionItem {
533 label: "bar()",
534 source_range: 94..94,
535 delete: 94..94,
536 insert: "bar()$0",
537 kind: Method,
538 lookup: "bar",
539 detail: "fn bar(self)",
540 documentation: Documentation(
541 "Method docs",
542 ),
543 },
544 CompletionItem {
545 label: "foo",
546 source_range: 94..94,
547 delete: 94..94,
548 insert: "foo",
549 kind: Field,
550 detail: "{unknown}",
551 documentation: Documentation(
552 "Field docs",
553 ),
554 },
555 ]
556 "#]],
557 );
558
559 check(
560 r#"
561use self::my<|>;
562
563/// mod docs
564mod my { }
565
566/// enum docs
567enum E {
568 /// variant docs
569 V
570}
571use self::E::*;
572"#,
573 expect![[r#"
574 [
575 CompletionItem {
576 label: "E",
577 source_range: 10..12,
578 delete: 10..12,
579 insert: "E",
580 kind: Enum,
581 documentation: Documentation(
582 "enum docs",
583 ),
584 },
585 CompletionItem {
586 label: "V",
587 source_range: 10..12,
588 delete: 10..12,
589 insert: "V",
590 kind: EnumVariant,
591 detail: "()",
592 documentation: Documentation(
593 "variant docs",
594 ),
595 },
596 CompletionItem {
597 label: "my",
598 source_range: 10..12,
599 delete: 10..12,
600 insert: "my",
601 kind: Module,
602 documentation: Documentation(
603 "mod docs",
604 ),
605 },
606 ]
607 "#]],
608 )
609 }
610
611 #[test]
612 fn dont_render_attrs() {
613 check(
614 r#"
615struct S;
616impl S {
617 #[inline]
618 fn the_method(&self) { }
619}
620fn foo(s: S) { s.<|> }
621"#,
622 expect![[r#"
623 [
624 CompletionItem {
625 label: "the_method()",
626 source_range: 81..81,
627 delete: 81..81,
628 insert: "the_method()$0",
629 kind: Method,
630 lookup: "the_method",
631 detail: "fn the_method(&self)",
632 },
633 ]
634 "#]],
635 )
636 }
637
638 #[test]
639 fn no_call_parens_if_fn_ptr_needed() {
640 mark::check!(no_call_parens_if_fn_ptr_needed);
641 check_edit(
642 "foo",
643 r#"
644fn foo(foo: u8, bar: u8) {}
645struct ManualVtable { f: fn(u8, u8) }
646
647fn main() -> ManualVtable {
648 ManualVtable { f: f<|> }
649}
650"#,
651 r#"
652fn foo(foo: u8, bar: u8) {}
653struct ManualVtable { f: fn(u8, u8) }
654
655fn main() -> ManualVtable {
656 ManualVtable { f: foo }
657}
658"#,
659 );
660 }
661
662 #[test]
663 fn no_parens_in_use_item() {
664 mark::check!(no_parens_in_use_item);
665 check_edit(
666 "foo",
667 r#"
668mod m { pub fn foo() {} }
669use crate::m::f<|>;
670"#,
671 r#"
672mod m { pub fn foo() {} }
673use crate::m::foo;
674"#,
675 );
676 }
677
678 #[test]
679 fn no_parens_in_call() {
680 check_edit(
681 "foo",
682 r#"
683fn foo(x: i32) {}
684fn main() { f<|>(); }
685"#,
686 r#"
687fn foo(x: i32) {}
688fn main() { foo(); }
689"#,
690 );
691 check_edit(
692 "foo",
693 r#"
694struct Foo;
695impl Foo { fn foo(&self){} }
696fn f(foo: &Foo) { foo.f<|>(); }
697"#,
698 r#"
699struct Foo;
700impl Foo { fn foo(&self){} }
701fn f(foo: &Foo) { foo.foo(); }
702"#,
703 );
704 }
705
706 #[test]
707 fn inserts_angle_brackets_for_generics() {
708 mark::check!(inserts_angle_brackets_for_generics);
709 check_edit(
710 "Vec",
711 r#"
712struct Vec<T> {}
713fn foo(xs: Ve<|>)
714"#,
715 r#"
716struct Vec<T> {}
717fn foo(xs: Vec<$0>)
718"#,
719 );
720 check_edit(
721 "Vec",
722 r#"
723type Vec<T> = (T,);
724fn foo(xs: Ve<|>)
725"#,
726 r#"
727type Vec<T> = (T,);
728fn foo(xs: Vec<$0>)
729"#,
730 );
731 check_edit(
732 "Vec",
733 r#"
734struct Vec<T = i128> {}
735fn foo(xs: Ve<|>)
736"#,
737 r#"
738struct Vec<T = i128> {}
739fn foo(xs: Vec)
740"#,
741 );
742 check_edit(
743 "Vec",
744 r#"
745struct Vec<T> {}
746fn foo(xs: Ve<|><i128>)
747"#,
748 r#"
749struct Vec<T> {}
750fn foo(xs: Vec<i128>)
751"#,
752 );
753 }
754
755 #[test]
756 fn active_param_score() {
757 mark::check!(active_param_type_match);
758 check_scores(
759 r#"
760struct S { foo: i64, bar: u32, baz: u32 }
761fn test(bar: u32) { }
762fn foo(s: S) { test(s.<|>) }
763"#,
764 expect![[r#"
765 fd bar [type+name]
766 fd baz [type]
767 fd foo []
768 "#]],
769 );
770 }
771
772 #[test]
773 fn record_field_scores() {
774 mark::check!(record_field_type_match);
775 check_scores(
776 r#"
777struct A { foo: i64, bar: u32, baz: u32 }
778struct B { x: (), y: f32, bar: u32 }
779fn foo(a: A) { B { bar: a.<|> }; }
780"#,
781 expect![[r#"
782 fd bar [type+name]
783 fd baz [type]
784 fd foo []
785 "#]],
786 )
787 }
788
789 #[test]
790 fn record_field_and_call_scores() {
791 check_scores(
792 r#"
793struct A { foo: i64, bar: u32, baz: u32 }
794struct B { x: (), y: f32, bar: u32 }
795fn f(foo: i64) { }
796fn foo(a: A) { B { bar: f(a.<|>) }; }
797"#,
798 expect![[r#"
799 fd foo [type+name]
800 fd bar []
801 fd baz []
802 "#]],
803 );
804 check_scores(
805 r#"
806struct A { foo: i64, bar: u32, baz: u32 }
807struct B { x: (), y: f32, bar: u32 }
808fn f(foo: i64) { }
809fn foo(a: A) { f(B { bar: a.<|> }); }
810"#,
811 expect![[r#"
812 fd bar [type+name]
813 fd baz [type]
814 fd foo []
815 "#]],
816 );
817 }
818
819 #[test]
820 fn prioritize_exact_ref_match() {
821 check_scores(
822 r#"
823struct WorldSnapshot { _f: () };
824fn go(world: &WorldSnapshot) { go(w<|>) }
825"#,
826 expect![[r#"
827 bn world [type+name]
828 st WorldSnapshot []
829 fn go(…) []
830 "#]],
831 );
832 }
833
834 #[test]
835 fn too_many_arguments() {
836 check_scores(
837 r#"
838struct Foo;
839fn f(foo: &Foo) { f(foo, w<|>) }
840"#,
841 expect![[r#"
842 st Foo []
843 fn f(…) []
844 bn foo []
845 "#]],
846 );
847 }
848}