2 use hir::{HasSource, InFile, Semantics};
4 base_db::{FileId, FilePosition, FileRange},
5 helpers::visit_file_defs,
8 use syntax::{ast::NameOwner, AstNode, TextRange};
11 fn_references::find_all_methods,
12 goto_implementation::goto_implementation,
13 references::find_all_refs,
14 runnables::{runnables, Runnable},
15 NavigationTarget, RunnableKind,
18 // Feature: Annotations
20 // Provides user with annotations above items for looking up references or impl blocks
21 // and running/debugging binaries.
23 // image::https://user-images.githubusercontent.com/48062697/113020672-b7c34f00-917a-11eb-8f6e-858735660a0e.png[]
25 pub struct Annotation {
27 pub kind: AnnotationKind,
31 pub enum AnnotationKind {
33 HasImpls { position: FilePosition, data: Option<Vec<NavigationTarget>> },
34 HasReferences { position: FilePosition, data: Option<Vec<FileRange>> },
37 pub struct AnnotationConfig {
38 pub binary_target: bool,
39 pub annotate_runnables: bool,
40 pub annotate_impls: bool,
41 pub annotate_references: bool,
42 pub annotate_method_references: bool,
45 pub(crate) fn annotations(
47 config: &AnnotationConfig,
49 ) -> Vec<Annotation> {
50 let mut annotations = Vec::default();
52 if config.annotate_runnables {
53 for runnable in runnables(db, file_id) {
54 if should_skip_runnable(&runnable.kind, config.binary_target) {
58 let range = runnable.nav.focus_or_full_range();
60 annotations.push(Annotation { range, kind: AnnotationKind::Runnable(runnable) });
64 visit_file_defs(&Semantics::new(db), file_id, &mut |def| match def {
65 Either::Left(def) => {
66 let range = match def {
67 hir::ModuleDef::Const(konst) => {
68 konst.source(db).and_then(|node| name_range(&node, file_id))
70 hir::ModuleDef::Trait(trait_) => {
71 trait_.source(db).and_then(|node| name_range(&node, file_id))
73 hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => {
74 strukt.source(db).and_then(|node| name_range(&node, file_id))
76 hir::ModuleDef::Adt(hir::Adt::Enum(enum_)) => {
77 enum_.source(db).and_then(|node| name_range(&node, file_id))
79 hir::ModuleDef::Adt(hir::Adt::Union(union)) => {
80 union.source(db).and_then(|node| name_range(&node, file_id))
84 let (range, offset) = match range {
85 Some(range) => (range, range.start()),
89 if config.annotate_impls && !matches!(def, hir::ModuleDef::Const(_)) {
90 annotations.push(Annotation {
92 kind: AnnotationKind::HasImpls {
93 position: FilePosition { file_id, offset },
98 if config.annotate_references {
99 annotations.push(Annotation {
101 kind: AnnotationKind::HasReferences {
102 position: FilePosition { file_id, offset },
108 fn name_range<T: NameOwner>(node: &InFile<T>, file_id: FileId) -> Option<TextRange> {
109 if node.file_id == file_id.into() {
110 node.value.name().map(|it| it.syntax().text_range())
112 // Node is outside the file we are adding annotations to (e.g. macros).
117 Either::Right(_) => (),
120 if config.annotate_method_references {
121 annotations.extend(find_all_methods(db, file_id).into_iter().map(
122 |FileRange { file_id, range }| Annotation {
124 kind: AnnotationKind::HasReferences {
125 position: FilePosition { file_id, offset: range.start() },
135 pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation) -> Annotation {
136 match annotation.kind {
137 AnnotationKind::HasImpls { position, ref mut data } => {
138 *data = goto_implementation(db, position).map(|range| range.info);
140 AnnotationKind::HasReferences { position, ref mut data } => {
141 *data = find_all_refs(&Semantics::new(db), position, None).map(|result| {
145 .map(|(file_id, access)| {
146 access.into_iter().map(move |(range, _)| FileRange { file_id, range })
158 fn should_skip_runnable(kind: &RunnableKind, binary_target: bool) -> bool {
160 RunnableKind::Bin => !binary_target,
167 use expect_test::{expect, Expect};
169 use crate::{fixture, Annotation, AnnotationConfig};
171 fn check(ra_fixture: &str, expect: Expect) {
172 let (analysis, file_id) = fixture::file(ra_fixture);
174 let annotations: Vec<Annotation> = analysis
178 annotate_runnables: true,
179 annotate_impls: true,
180 annotate_references: true,
181 annotate_method_references: true,
187 .map(|annotation| analysis.resolve_annotation(annotation).unwrap())
190 expect.assert_debug_eq(&annotations);
194 fn const_annotations() {
197 const DEMO: i32 = 123;
199 const UNUSED: i32 = 123;
211 use_name_in_title: false,
212 nav: NavigationTarget {
228 kind: HasReferences {
229 position: FilePosition {
249 kind: HasReferences {
250 position: FilePosition {
263 kind: HasReferences {
264 position: FilePosition {
281 fn struct_references_annotations() {
296 use_name_in_title: false,
297 nav: NavigationTarget {
314 position: FilePosition {
327 kind: HasReferences {
328 position: FilePosition {
348 kind: HasReferences {
349 position: FilePosition {
366 fn struct_and_trait_impls_annotations() {
373 impl MyCoolTrait for Test {}
385 use_name_in_title: false,
386 nav: NavigationTarget {
403 position: FilePosition {
426 kind: HasReferences {
427 position: FilePosition {
454 position: FilePosition {
477 kind: HasReferences {
478 position: FilePosition {
498 kind: HasReferences {
499 position: FilePosition {
516 fn runnable_annotation() {
527 use_name_in_title: false,
528 nav: NavigationTarget {
544 kind: HasReferences {
545 position: FilePosition {
562 fn method_annotations() {
568 fn self_by_ref(&self) {}
581 use_name_in_title: false,
582 nav: NavigationTarget {
599 position: FilePosition {
622 kind: HasReferences {
623 position: FilePosition {
649 kind: HasReferences {
650 position: FilePosition {
670 kind: HasReferences {
671 position: FilePosition {
688 fn test_annotations() {
704 use_name_in_title: false,
705 nav: NavigationTarget {
723 use_name_in_title: false,
724 nav: NavigationTarget {
732 description: "mod tests",
745 use_name_in_title: false,
746 nav: NavigationTarget {
752 name: "my_cool_test",
757 "tests::my_cool_test",
769 kind: HasReferences {
770 position: FilePosition {
787 fn test_no_annotations_outside_module_tree() {
793 // this file comes last since `check` checks the first file only
802 fn test_no_annotations_macro_struct_def() {