1 use hir::{HasSource, InFile, Semantics};
3 base_db::{FileId, FilePosition, FileRange},
5 helpers::visit_file_defs,
8 use syntax::{ast::HasName, 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,
43 pub annotate_enum_variant_references: bool,
46 pub(crate) fn annotations(
48 config: &AnnotationConfig,
50 ) -> Vec<Annotation> {
51 let mut annotations = Vec::default();
53 if config.annotate_runnables {
54 for runnable in runnables(db, file_id) {
55 if should_skip_runnable(&runnable.kind, config.binary_target) {
59 let range = runnable.nav.focus_or_full_range();
61 annotations.push(Annotation { range, kind: AnnotationKind::Runnable(runnable) });
65 visit_file_defs(&Semantics::new(db), file_id, &mut |def| {
66 let range = match def {
67 Definition::Const(konst) if config.annotate_references => {
68 konst.source(db).and_then(|node| name_range(&node, file_id))
70 Definition::Trait(trait_) if config.annotate_references || config.annotate_impls => {
71 trait_.source(db).and_then(|node| name_range(&node, file_id))
73 Definition::Adt(adt) => match adt {
74 hir::Adt::Enum(enum_) => {
75 if config.annotate_enum_variant_references {
80 variant.source(db).and_then(|node| name_range(&node, file_id))
82 .filter_map(std::convert::identity)
84 annotations.push(Annotation {
86 kind: AnnotationKind::HasReferences {
87 position: FilePosition { file_id, offset: range.start() },
93 if config.annotate_references || config.annotate_impls {
94 enum_.source(db).and_then(|node| name_range(&node, file_id))
100 if config.annotate_references || config.annotate_impls {
101 adt.source(db).and_then(|node| name_range(&node, file_id))
110 let (range, offset) = match range {
111 Some(range) => (range, range.start()),
115 if config.annotate_impls && !matches!(def, Definition::Const(_)) {
116 annotations.push(Annotation {
118 kind: AnnotationKind::HasImpls {
119 position: FilePosition { file_id, offset },
124 if config.annotate_references {
125 annotations.push(Annotation {
127 kind: AnnotationKind::HasReferences {
128 position: FilePosition { file_id, offset },
134 fn name_range<T: HasName>(node: &InFile<T>, file_id: FileId) -> Option<TextRange> {
135 if node.file_id == file_id.into() {
136 node.value.name().map(|it| it.syntax().text_range())
138 // Node is outside the file we are adding annotations to (e.g. macros).
144 if config.annotate_method_references {
145 annotations.extend(find_all_methods(db, file_id).into_iter().map(
146 |FileRange { file_id, range }| Annotation {
148 kind: AnnotationKind::HasReferences {
149 position: FilePosition { file_id, offset: range.start() },
159 pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation) -> Annotation {
160 match &mut annotation.kind {
161 AnnotationKind::HasImpls { position, data } => {
162 *data = goto_implementation(db, *position).map(|range| range.info);
164 AnnotationKind::HasReferences { position, data } => {
165 *data = find_all_refs(&Semantics::new(db), *position, None).map(|result| {
168 .flat_map(|res| res.references)
169 .map(|(file_id, access)| {
170 access.into_iter().map(move |(range, _)| FileRange { file_id, range })
182 fn should_skip_runnable(kind: &RunnableKind, binary_target: bool) -> bool {
184 RunnableKind::Bin => !binary_target,
191 use expect_test::{expect, Expect};
193 use crate::{fixture, Annotation, AnnotationConfig};
195 fn check(ra_fixture: &str, expect: Expect) {
196 let (analysis, file_id) = fixture::file(ra_fixture);
198 let annotations: Vec<Annotation> = analysis
202 annotate_runnables: true,
203 annotate_impls: true,
204 annotate_references: true,
205 annotate_method_references: true,
206 annotate_enum_variant_references: true,
212 .map(|annotation| analysis.resolve_annotation(annotation).unwrap())
215 expect.assert_debug_eq(&annotations);
219 fn const_annotations() {
222 const DEMO: i32 = 123;
224 const UNUSED: i32 = 123;
236 use_name_in_title: false,
237 nav: NavigationTarget {
253 kind: HasReferences {
254 position: FilePosition {
274 kind: HasReferences {
275 position: FilePosition {
288 kind: HasReferences {
289 position: FilePosition {
306 fn struct_references_annotations() {
321 use_name_in_title: false,
322 nav: NavigationTarget {
339 position: FilePosition {
352 kind: HasReferences {
353 position: FilePosition {
373 kind: HasReferences {
374 position: FilePosition {
391 fn struct_and_trait_impls_annotations() {
398 impl MyCoolTrait for Test {}
410 use_name_in_title: false,
411 nav: NavigationTarget {
428 position: FilePosition {
451 kind: HasReferences {
452 position: FilePosition {
479 position: FilePosition {
502 kind: HasReferences {
503 position: FilePosition {
523 kind: HasReferences {
524 position: FilePosition {
541 fn runnable_annotation() {
552 use_name_in_title: false,
553 nav: NavigationTarget {
569 kind: HasReferences {
570 position: FilePosition {
587 fn method_annotations() {
593 fn self_by_ref(&self) {}
606 use_name_in_title: false,
607 nav: NavigationTarget {
624 position: FilePosition {
647 kind: HasReferences {
648 position: FilePosition {
674 kind: HasReferences {
675 position: FilePosition {
695 kind: HasReferences {
696 position: FilePosition {
713 fn test_annotations() {
729 use_name_in_title: false,
730 nav: NavigationTarget {
748 use_name_in_title: false,
749 nav: NavigationTarget {
757 description: "mod tests",
770 use_name_in_title: false,
771 nav: NavigationTarget {
777 name: "my_cool_test",
782 "tests::my_cool_test",
794 kind: HasReferences {
795 position: FilePosition {
812 fn test_no_annotations_outside_module_tree() {
818 // this file comes last since `check` checks the first file only
827 fn test_no_annotations_macro_struct_def() {