]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/annotations.rs
Merge #7941
[rust.git] / crates / ide / src / annotations.rs
1 use hir::Semantics;
2 use ide_db::{
3     base_db::{FileId, FilePosition, FileRange, SourceDatabase},
4     RootDatabase, SymbolKind,
5 };
6 use syntax::TextRange;
7
8 use 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)]
22 pub struct Annotation {
23     pub range: TextRange,
24     pub kind: AnnotationKind,
25 }
26
27 #[derive(Debug)]
28 pub 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
34 pub 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
44 pub(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 config.run {
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: false, runnable: runnable.clone() },
66                 });
67             }
68
69             if action.debugee && config.debug {
70                 annotations.push(Annotation {
71                     range,
72                     kind: AnnotationKind::Runnable { debug: true, 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
125 pub(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
148 fn 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)]
156 mod 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#"
189 const DEMO: i32 = 123;
190
191 const UNUSED: i32 = 123;
192
193 fn main() {
194     let hello = DEMO;
195 }
196             "#,
197             expect![[r#"
198                 [
199                     Annotation {
200                         range: 50..85,
201                         kind: Runnable {
202                             debug: false,
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: true,
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#"
295 struct Test;
296
297 fn main() {
298     let test = Test;
299 }
300             "#,
301             expect![[r#"
302                 [
303                     Annotation {
304                         range: 14..48,
305                         kind: Runnable {
306                             debug: false,
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: true,
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#"
399 struct Test;
400
401 trait MyCoolTrait {}
402
403 impl MyCoolTrait for Test {}
404
405 fn main() {
406     let test = Test;
407 }
408             "#,
409             expect![[r#"
410                 [
411                     Annotation {
412                         range: 66..100,
413                         kind: Runnable {
414                             debug: false,
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: true,
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#"
568 fn main() {}
569             "#,
570             expect![[r#"
571                 [
572                     Annotation {
573                         range: 0..12,
574                         kind: Runnable {
575                             debug: false,
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: true,
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#"
633 struct Test;
634
635 impl Test {
636     fn self_by_ref(&self) {}
637 }
638
639 fn main() {
640     Test.self_by_ref();
641 }
642             "#,
643             expect![[r#"
644                 [
645                     Annotation {
646                         range: 58..95,
647                         kind: Runnable {
648                             debug: false,
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: true,
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#"
778 fn main() {}
779
780 mod 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: false,
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: true,
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: false,
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: true,
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: false,
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: true,
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 }