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 { file_id: FileId, data: Option<Vec<NavigationTarget>> },
34 HasReferences { file_id: FileId, 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(db, node, file_id))
70 Definition::Trait(trait_) if config.annotate_references || config.annotate_impls => {
71 trait_.source(db).and_then(|node| name_range(db, 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(db, node, file_id))
84 annotations.push(Annotation {
86 kind: AnnotationKind::HasReferences { file_id, data: None },
90 if config.annotate_references || config.annotate_impls {
91 enum_.source(db).and_then(|node| name_range(db, node, file_id))
97 if config.annotate_references || config.annotate_impls {
98 adt.source(db).and_then(|node| name_range(db, node, file_id))
107 let range = match range {
108 Some(range) => range,
112 if config.annotate_impls && !matches!(def, Definition::Const(_)) {
114 .push(Annotation { range, kind: AnnotationKind::HasImpls { file_id, data: None } });
116 if config.annotate_references {
117 annotations.push(Annotation {
119 kind: AnnotationKind::HasReferences { file_id, data: None },
123 fn name_range<T: HasName>(
126 source_file_id: FileId,
127 ) -> Option<TextRange> {
128 if let Some(InFile { file_id, value }) = node.original_ast_node(db) {
129 if file_id == source_file_id.into() {
130 return value.name().map(|it| it.syntax().text_range());
137 if config.annotate_method_references {
138 annotations.extend(find_all_methods(db, file_id).into_iter().map(
139 |FileRange { file_id, range }| Annotation {
141 kind: AnnotationKind::HasReferences { file_id, data: None },
149 pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation) -> Annotation {
150 match annotation.kind {
151 AnnotationKind::HasImpls { file_id, ref mut data } => {
153 goto_implementation(db, FilePosition { file_id, offset: annotation.range.start() })
154 .map(|range| range.info);
156 AnnotationKind::HasReferences { file_id, ref mut data } => {
157 *data = find_all_refs(
159 FilePosition { file_id, offset: annotation.range.start() },
165 .flat_map(|res| res.references)
166 .flat_map(|(file_id, access)| {
167 access.into_iter().map(move |(range, _)| FileRange { file_id, range })
178 fn should_skip_runnable(kind: &RunnableKind, binary_target: bool) -> bool {
180 RunnableKind::Bin => !binary_target,
187 use expect_test::{expect, Expect};
189 use crate::{fixture, Annotation, AnnotationConfig};
191 fn check(ra_fixture: &str, expect: Expect) {
192 let (analysis, file_id) = fixture::file(ra_fixture);
194 let annotations: Vec<Annotation> = analysis
198 annotate_runnables: true,
199 annotate_impls: true,
200 annotate_references: true,
201 annotate_method_references: true,
202 annotate_enum_variant_references: true,
208 .map(|annotation| analysis.resolve_annotation(annotation).unwrap())
211 expect.assert_debug_eq(&annotations);
215 fn const_annotations() {
218 const DEMO: i32 = 123;
220 const UNUSED: i32 = 123;
232 use_name_in_title: false,
233 nav: NavigationTarget {
249 kind: HasReferences {
267 kind: HasReferences {
278 kind: HasReferences {
293 fn struct_references_annotations() {
308 use_name_in_title: false,
309 nav: NavigationTarget {
336 kind: HasReferences {
354 kind: HasReferences {
369 fn struct_and_trait_impls_annotations() {
376 impl MyCoolTrait for Test {}
388 use_name_in_title: false,
389 nav: NavigationTarget {
426 kind: HasReferences {
471 kind: HasReferences {
489 kind: HasReferences {
504 fn runnable_annotation() {
515 use_name_in_title: false,
516 nav: NavigationTarget {
532 kind: HasReferences {
547 fn method_annotations() {
553 fn self_by_ref(&self) {}
566 use_name_in_title: false,
567 nav: NavigationTarget {
604 kind: HasReferences {
628 kind: HasReferences {
646 kind: HasReferences {
661 fn test_annotations() {
677 use_name_in_title: false,
678 nav: NavigationTarget {
696 use_name_in_title: false,
697 nav: NavigationTarget {
705 description: "mod tests",
718 use_name_in_title: false,
719 nav: NavigationTarget {
725 name: "my_cool_test",
730 "tests::my_cool_test",
742 kind: HasReferences {
757 fn test_no_annotations_outside_module_tree() {
763 // this file comes last since `check` checks the first file only
772 fn test_no_annotations_macro_struct_def() {