aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/annotations.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/annotations.rs')
-rw-r--r--crates/ide/src/annotations.rs937
1 files changed, 937 insertions, 0 deletions
diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs
new file mode 100644
index 000000000..414a60bed
--- /dev/null
+++ b/crates/ide/src/annotations.rs
@@ -0,0 +1,937 @@
1use hir::Semantics;
2use ide_db::{
3 base_db::{FileId, FilePosition, FileRange, SourceDatabase},
4 RootDatabase, SymbolKind,
5};
6use syntax::TextRange;
7
8use crate::{
9 file_structure::file_structure,
10 fn_references::find_all_methods,
11 goto_implementation::goto_implementation,
12 references::find_all_refs,
13 runnables::{runnables, Runnable},
14 NavigationTarget, RunnableKind,
15};
16
17// Feature: Annotations
18//
19// Provides user with annotations above items for looking up references or impl blocks
20// and running/debugging binaries.
21#[derive(Debug)]
22pub struct Annotation {
23 pub range: TextRange,
24 pub kind: AnnotationKind,
25}
26
27#[derive(Debug)]
28pub enum AnnotationKind {
29 Runnable { debug: bool, runnable: Runnable },
30 HasImpls { position: FilePosition, data: Option<Vec<NavigationTarget>> },
31 HasReferences { position: FilePosition, data: Option<Vec<FileRange>> },
32}
33
34pub struct AnnotationConfig {
35 pub binary_target: bool,
36 pub annotate_runnables: bool,
37 pub annotate_impls: bool,
38 pub annotate_references: bool,
39 pub annotate_method_references: bool,
40 pub run: bool,
41 pub debug: bool,
42}
43
44pub(crate) fn annotations(
45 db: &RootDatabase,
46 file_id: FileId,
47 config: AnnotationConfig,
48) -> Vec<Annotation> {
49 let mut annotations = Vec::default();
50
51 if config.annotate_runnables {
52 for runnable in runnables(db, file_id) {
53 if should_skip_runnable(&runnable.kind, config.binary_target) {
54 continue;
55 }
56
57 let action = runnable.action();
58 let range = runnable.nav.full_range;
59
60 if action.debugee && config.debug {
61 annotations.push(Annotation {
62 range,
63
64 // FIXME: This one allocates without reason if run is enabled, but debug is disabled
65 kind: AnnotationKind::Runnable { debug: true, runnable: runnable.clone() },
66 });
67 }
68
69 if config.run {
70 annotations.push(Annotation {
71 range,
72 kind: AnnotationKind::Runnable { debug: false, runnable },
73 });
74 }
75 }
76 }
77
78 file_structure(&db.parse(file_id).tree())
79 .into_iter()
80 .filter(|node| {
81 matches!(
82 node.kind,
83 SymbolKind::Trait
84 | SymbolKind::Struct
85 | SymbolKind::Enum
86 | SymbolKind::Union
87 | SymbolKind::Const
88 )
89 })
90 .for_each(|node| {
91 if config.annotate_impls && node.kind != SymbolKind::Const {
92 annotations.push(Annotation {
93 range: node.node_range,
94 kind: AnnotationKind::HasImpls {
95 position: FilePosition { file_id, offset: node.navigation_range.start() },
96 data: None,
97 },
98 });
99 }
100
101 if config.annotate_references {
102 annotations.push(Annotation {
103 range: node.node_range,
104 kind: AnnotationKind::HasReferences {
105 position: FilePosition { file_id, offset: node.navigation_range.start() },
106 data: None,
107 },
108 });
109 }
110 });
111
112 if config.annotate_method_references {
113 annotations.extend(find_all_methods(db, file_id).into_iter().map(|method| Annotation {
114 range: method.range,
115 kind: AnnotationKind::HasReferences {
116 position: FilePosition { file_id, offset: method.range.start() },
117 data: None,
118 },
119 }));
120 }
121
122 annotations
123}
124
125pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation) -> Annotation {
126 match annotation.kind {
127 AnnotationKind::HasImpls { position, ref mut data } => {
128 *data = goto_implementation(db, position).map(|range| range.info);
129 }
130 AnnotationKind::HasReferences { position, ref mut data } => {
131 *data = find_all_refs(&Semantics::new(db), position, None).map(|result| {
132 result
133 .references
134 .into_iter()
135 .map(|(file_id, access)| {
136 access.into_iter().map(move |(range, _)| FileRange { file_id, range })
137 })
138 .flatten()
139 .collect()
140 });
141 }
142 _ => {}
143 };
144
145 annotation
146}
147
148fn should_skip_runnable(kind: &RunnableKind, binary_target: bool) -> bool {
149 match kind {
150 RunnableKind::Bin => !binary_target,
151 _ => false,
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use expect_test::{expect, Expect};
158
159 use crate::{fixture, Annotation, AnnotationConfig};
160
161 fn check(ra_fixture: &str, expect: Expect) {
162 let (analysis, file_id) = fixture::file(ra_fixture);
163
164 let annotations: Vec<Annotation> = analysis
165 .annotations(
166 file_id,
167 AnnotationConfig {
168 binary_target: true,
169 annotate_runnables: true,
170 annotate_impls: true,
171 annotate_references: true,
172 annotate_method_references: true,
173 run: true,
174 debug: true,
175 },
176 )
177 .unwrap()
178 .into_iter()
179 .map(|annotation| analysis.resolve_annotation(annotation).unwrap())
180 .collect();
181
182 expect.assert_debug_eq(&annotations);
183 }
184
185 #[test]
186 fn const_annotations() {
187 check(
188 r#"
189const DEMO: i32 = 123;
190
191const UNUSED: i32 = 123;
192
193fn main() {
194 let hello = DEMO;
195}
196 "#,
197 expect![[r#"
198 [
199 Annotation {
200 range: 50..85,
201 kind: Runnable {
202 debug: true,
203 runnable: Runnable {
204 nav: NavigationTarget {
205 file_id: FileId(
206 0,
207 ),
208 full_range: 50..85,
209 focus_range: 53..57,
210 name: "main",
211 kind: Function,
212 },
213 kind: Bin,
214 cfg: None,
215 },
216 },
217 },
218 Annotation {
219 range: 50..85,
220 kind: Runnable {
221 debug: false,
222 runnable: Runnable {
223 nav: NavigationTarget {
224 file_id: FileId(
225 0,
226 ),
227 full_range: 50..85,
228 focus_range: 53..57,
229 name: "main",
230 kind: Function,
231 },
232 kind: Bin,
233 cfg: None,
234 },
235 },
236 },
237 Annotation {
238 range: 0..22,
239 kind: HasReferences {
240 position: FilePosition {
241 file_id: FileId(
242 0,
243 ),
244 offset: 6,
245 },
246 data: Some(
247 [
248 FileRange {
249 file_id: FileId(
250 0,
251 ),
252 range: 78..82,
253 },
254 ],
255 ),
256 },
257 },
258 Annotation {
259 range: 24..48,
260 kind: HasReferences {
261 position: FilePosition {
262 file_id: FileId(
263 0,
264 ),
265 offset: 30,
266 },
267 data: Some(
268 [],
269 ),
270 },
271 },
272 Annotation {
273 range: 53..57,
274 kind: HasReferences {
275 position: FilePosition {
276 file_id: FileId(
277 0,
278 ),
279 offset: 53,
280 },
281 data: Some(
282 [],
283 ),
284 },
285 },
286 ]
287 "#]],
288 );
289 }
290
291 #[test]
292 fn struct_references_annotations() {
293 check(
294 r#"
295struct Test;
296
297fn main() {
298 let test = Test;
299}
300 "#,
301 expect![[r#"
302 [
303 Annotation {
304 range: 14..48,
305 kind: Runnable {
306 debug: true,
307 runnable: Runnable {
308 nav: NavigationTarget {
309 file_id: FileId(
310 0,
311 ),
312 full_range: 14..48,
313 focus_range: 17..21,
314 name: "main",
315 kind: Function,
316 },
317 kind: Bin,
318 cfg: None,
319 },
320 },
321 },
322 Annotation {
323 range: 14..48,
324 kind: Runnable {
325 debug: false,
326 runnable: Runnable {
327 nav: NavigationTarget {
328 file_id: FileId(
329 0,
330 ),
331 full_range: 14..48,
332 focus_range: 17..21,
333 name: "main",
334 kind: Function,
335 },
336 kind: Bin,
337 cfg: None,
338 },
339 },
340 },
341 Annotation {
342 range: 0..12,
343 kind: HasImpls {
344 position: FilePosition {
345 file_id: FileId(
346 0,
347 ),
348 offset: 7,
349 },
350 data: Some(
351 [],
352 ),
353 },
354 },
355 Annotation {
356 range: 0..12,
357 kind: HasReferences {
358 position: FilePosition {
359 file_id: FileId(
360 0,
361 ),
362 offset: 7,
363 },
364 data: Some(
365 [
366 FileRange {
367 file_id: FileId(
368 0,
369 ),
370 range: 41..45,
371 },
372 ],
373 ),
374 },
375 },
376 Annotation {
377 range: 17..21,
378 kind: HasReferences {
379 position: FilePosition {
380 file_id: FileId(
381 0,
382 ),
383 offset: 17,
384 },
385 data: Some(
386 [],
387 ),
388 },
389 },
390 ]
391 "#]],
392 );
393 }
394
395 #[test]
396 fn struct_and_trait_impls_annotations() {
397 check(
398 r#"
399struct Test;
400
401trait MyCoolTrait {}
402
403impl MyCoolTrait for Test {}
404
405fn main() {
406 let test = Test;
407}
408 "#,
409 expect![[r#"
410 [
411 Annotation {
412 range: 66..100,
413 kind: Runnable {
414 debug: true,
415 runnable: Runnable {
416 nav: NavigationTarget {
417 file_id: FileId(
418 0,
419 ),
420 full_range: 66..100,
421 focus_range: 69..73,
422 name: "main",
423 kind: Function,
424 },
425 kind: Bin,
426 cfg: None,
427 },
428 },
429 },
430 Annotation {
431 range: 66..100,
432 kind: Runnable {
433 debug: false,
434 runnable: Runnable {
435 nav: NavigationTarget {
436 file_id: FileId(
437 0,
438 ),
439 full_range: 66..100,
440 focus_range: 69..73,
441 name: "main",
442 kind: Function,
443 },
444 kind: Bin,
445 cfg: None,
446 },
447 },
448 },
449 Annotation {
450 range: 0..12,
451 kind: HasImpls {
452 position: FilePosition {
453 file_id: FileId(
454 0,
455 ),
456 offset: 7,
457 },
458 data: Some(
459 [
460 NavigationTarget {
461 file_id: FileId(
462 0,
463 ),
464 full_range: 36..64,
465 focus_range: 57..61,
466 name: "impl",
467 kind: Impl,
468 },
469 ],
470 ),
471 },
472 },
473 Annotation {
474 range: 0..12,
475 kind: HasReferences {
476 position: FilePosition {
477 file_id: FileId(
478 0,
479 ),
480 offset: 7,
481 },
482 data: Some(
483 [
484 FileRange {
485 file_id: FileId(
486 0,
487 ),
488 range: 57..61,
489 },
490 FileRange {
491 file_id: FileId(
492 0,
493 ),
494 range: 93..97,
495 },
496 ],
497 ),
498 },
499 },
500 Annotation {
501 range: 14..34,
502 kind: HasImpls {
503 position: FilePosition {
504 file_id: FileId(
505 0,
506 ),
507 offset: 20,
508 },
509 data: Some(
510 [
511 NavigationTarget {
512 file_id: FileId(
513 0,
514 ),
515 full_range: 36..64,
516 focus_range: 57..61,
517 name: "impl",
518 kind: Impl,
519 },
520 ],
521 ),
522 },
523 },
524 Annotation {
525 range: 14..34,
526 kind: HasReferences {
527 position: FilePosition {
528 file_id: FileId(
529 0,
530 ),
531 offset: 20,
532 },
533 data: Some(
534 [
535 FileRange {
536 file_id: FileId(
537 0,
538 ),
539 range: 41..52,
540 },
541 ],
542 ),
543 },
544 },
545 Annotation {
546 range: 69..73,
547 kind: HasReferences {
548 position: FilePosition {
549 file_id: FileId(
550 0,
551 ),
552 offset: 69,
553 },
554 data: Some(
555 [],
556 ),
557 },
558 },
559 ]
560 "#]],
561 );
562 }
563
564 #[test]
565 fn runnable_annotation() {
566 check(
567 r#"
568fn main() {}
569 "#,
570 expect![[r#"
571 [
572 Annotation {
573 range: 0..12,
574 kind: Runnable {
575 debug: true,
576 runnable: Runnable {
577 nav: NavigationTarget {
578 file_id: FileId(
579 0,
580 ),
581 full_range: 0..12,
582 focus_range: 3..7,
583 name: "main",
584 kind: Function,
585 },
586 kind: Bin,
587 cfg: None,
588 },
589 },
590 },
591 Annotation {
592 range: 0..12,
593 kind: Runnable {
594 debug: false,
595 runnable: Runnable {
596 nav: NavigationTarget {
597 file_id: FileId(
598 0,
599 ),
600 full_range: 0..12,
601 focus_range: 3..7,
602 name: "main",
603 kind: Function,
604 },
605 kind: Bin,
606 cfg: None,
607 },
608 },
609 },
610 Annotation {
611 range: 3..7,
612 kind: HasReferences {
613 position: FilePosition {
614 file_id: FileId(
615 0,
616 ),
617 offset: 3,
618 },
619 data: Some(
620 [],
621 ),
622 },
623 },
624 ]
625 "#]],
626 );
627 }
628
629 #[test]
630 fn method_annotations() {
631 check(
632 r#"
633struct Test;
634
635impl Test {
636 fn self_by_ref(&self) {}
637}
638
639fn main() {
640 Test.self_by_ref();
641}
642 "#,
643 expect![[r#"
644 [
645 Annotation {
646 range: 58..95,
647 kind: Runnable {
648 debug: true,
649 runnable: Runnable {
650 nav: NavigationTarget {
651 file_id: FileId(
652 0,
653 ),
654 full_range: 58..95,
655 focus_range: 61..65,
656 name: "main",
657 kind: Function,
658 },
659 kind: Bin,
660 cfg: None,
661 },
662 },
663 },
664 Annotation {
665 range: 58..95,
666 kind: Runnable {
667 debug: false,
668 runnable: Runnable {
669 nav: NavigationTarget {
670 file_id: FileId(
671 0,
672 ),
673 full_range: 58..95,
674 focus_range: 61..65,
675 name: "main",
676 kind: Function,
677 },
678 kind: Bin,
679 cfg: None,
680 },
681 },
682 },
683 Annotation {
684 range: 0..12,
685 kind: HasImpls {
686 position: FilePosition {
687 file_id: FileId(
688 0,
689 ),
690 offset: 7,
691 },
692 data: Some(
693 [
694 NavigationTarget {
695 file_id: FileId(
696 0,
697 ),
698 full_range: 14..56,
699 focus_range: 19..23,
700 name: "impl",
701 kind: Impl,
702 },
703 ],
704 ),
705 },
706 },
707 Annotation {
708 range: 0..12,
709 kind: HasReferences {
710 position: FilePosition {
711 file_id: FileId(
712 0,
713 ),
714 offset: 7,
715 },
716 data: Some(
717 [
718 FileRange {
719 file_id: FileId(
720 0,
721 ),
722 range: 19..23,
723 },
724 FileRange {
725 file_id: FileId(
726 0,
727 ),
728 range: 74..78,
729 },
730 ],
731 ),
732 },
733 },
734 Annotation {
735 range: 33..44,
736 kind: HasReferences {
737 position: FilePosition {
738 file_id: FileId(
739 0,
740 ),
741 offset: 33,
742 },
743 data: Some(
744 [
745 FileRange {
746 file_id: FileId(
747 0,
748 ),
749 range: 79..90,
750 },
751 ],
752 ),
753 },
754 },
755 Annotation {
756 range: 61..65,
757 kind: HasReferences {
758 position: FilePosition {
759 file_id: FileId(
760 0,
761 ),
762 offset: 61,
763 },
764 data: Some(
765 [],
766 ),
767 },
768 },
769 ]
770 "#]],
771 );
772 }
773
774 #[test]
775 fn test_annotations() {
776 check(
777 r#"
778fn main() {}
779
780mod tests {
781 #[test]
782 fn my_cool_test() {}
783}
784 "#,
785 expect![[r#"
786 [
787 Annotation {
788 range: 0..12,
789 kind: Runnable {
790 debug: true,
791 runnable: Runnable {
792 nav: NavigationTarget {
793 file_id: FileId(
794 0,
795 ),
796 full_range: 0..12,
797 focus_range: 3..7,
798 name: "main",
799 kind: Function,
800 },
801 kind: Bin,
802 cfg: None,
803 },
804 },
805 },
806 Annotation {
807 range: 0..12,
808 kind: Runnable {
809 debug: false,
810 runnable: Runnable {
811 nav: NavigationTarget {
812 file_id: FileId(
813 0,
814 ),
815 full_range: 0..12,
816 focus_range: 3..7,
817 name: "main",
818 kind: Function,
819 },
820 kind: Bin,
821 cfg: None,
822 },
823 },
824 },
825 Annotation {
826 range: 14..64,
827 kind: Runnable {
828 debug: true,
829 runnable: Runnable {
830 nav: NavigationTarget {
831 file_id: FileId(
832 0,
833 ),
834 full_range: 14..64,
835 focus_range: 18..23,
836 name: "tests",
837 kind: Module,
838 },
839 kind: TestMod {
840 path: "tests",
841 },
842 cfg: None,
843 },
844 },
845 },
846 Annotation {
847 range: 14..64,
848 kind: Runnable {
849 debug: false,
850 runnable: Runnable {
851 nav: NavigationTarget {
852 file_id: FileId(
853 0,
854 ),
855 full_range: 14..64,
856 focus_range: 18..23,
857 name: "tests",
858 kind: Module,
859 },
860 kind: TestMod {
861 path: "tests",
862 },
863 cfg: None,
864 },
865 },
866 },
867 Annotation {
868 range: 30..62,
869 kind: Runnable {
870 debug: true,
871 runnable: Runnable {
872 nav: NavigationTarget {
873 file_id: FileId(
874 0,
875 ),
876 full_range: 30..62,
877 focus_range: 45..57,
878 name: "my_cool_test",
879 kind: Function,
880 },
881 kind: Test {
882 test_id: Path(
883 "tests::my_cool_test",
884 ),
885 attr: TestAttr {
886 ignore: false,
887 },
888 },
889 cfg: None,
890 },
891 },
892 },
893 Annotation {
894 range: 30..62,
895 kind: Runnable {
896 debug: false,
897 runnable: Runnable {
898 nav: NavigationTarget {
899 file_id: FileId(
900 0,
901 ),
902 full_range: 30..62,
903 focus_range: 45..57,
904 name: "my_cool_test",
905 kind: Function,
906 },
907 kind: Test {
908 test_id: Path(
909 "tests::my_cool_test",
910 ),
911 attr: TestAttr {
912 ignore: false,
913 },
914 },
915 cfg: None,
916 },
917 },
918 },
919 Annotation {
920 range: 3..7,
921 kind: HasReferences {
922 position: FilePosition {
923 file_id: FileId(
924 0,
925 ),
926 offset: 3,
927 },
928 data: Some(
929 [],
930 ),
931 },
932 },
933 ]
934 "#]],
935 );
936 }
937}