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(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))
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(db, node, file_id))
100 if config.annotate_references || config.annotate_impls {
101 adt.source(db).and_then(|node| name_range(db, 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>(
137 source_file_id: FileId,
138 ) -> Option<TextRange> {
139 if let Some(InFile { file_id, value }) = node.original_ast_node(db) {
140 if file_id == source_file_id.into() {
141 return value.name().map(|it| it.syntax().text_range());
148 if config.annotate_method_references {
149 annotations.extend(find_all_methods(db, file_id).into_iter().map(
150 |FileRange { file_id, range }| Annotation {
152 kind: AnnotationKind::HasReferences {
153 position: FilePosition { file_id, offset: range.start() },
163 pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation) -> Annotation {
164 match &mut annotation.kind {
165 AnnotationKind::HasImpls { position, data } => {
166 *data = goto_implementation(db, *position).map(|range| range.info);
168 AnnotationKind::HasReferences { position, data } => {
169 *data = find_all_refs(&Semantics::new(db), *position, None).map(|result| {
172 .flat_map(|res| res.references)
173 .map(|(file_id, access)| {
174 access.into_iter().map(move |(range, _)| FileRange { file_id, range })
186 fn should_skip_runnable(kind: &RunnableKind, binary_target: bool) -> bool {
188 RunnableKind::Bin => !binary_target,
195 use expect_test::{expect, Expect};
197 use crate::{fixture, Annotation, AnnotationConfig};
199 fn check(ra_fixture: &str, expect: Expect) {
200 let (analysis, file_id) = fixture::file(ra_fixture);
202 let annotations: Vec<Annotation> = analysis
206 annotate_runnables: true,
207 annotate_impls: true,
208 annotate_references: true,
209 annotate_method_references: true,
210 annotate_enum_variant_references: true,
216 .map(|annotation| analysis.resolve_annotation(annotation).unwrap())
219 expect.assert_debug_eq(&annotations);
223 fn const_annotations() {
226 const DEMO: i32 = 123;
228 const UNUSED: i32 = 123;
240 use_name_in_title: false,
241 nav: NavigationTarget {
257 kind: HasReferences {
258 position: FilePosition {
278 kind: HasReferences {
279 position: FilePosition {
292 kind: HasReferences {
293 position: FilePosition {
310 fn struct_references_annotations() {
325 use_name_in_title: false,
326 nav: NavigationTarget {
343 position: FilePosition {
356 kind: HasReferences {
357 position: FilePosition {
377 kind: HasReferences {
378 position: FilePosition {
395 fn struct_and_trait_impls_annotations() {
402 impl MyCoolTrait for Test {}
414 use_name_in_title: false,
415 nav: NavigationTarget {
432 position: FilePosition {
455 kind: HasReferences {
456 position: FilePosition {
483 position: FilePosition {
506 kind: HasReferences {
507 position: FilePosition {
527 kind: HasReferences {
528 position: FilePosition {
545 fn runnable_annotation() {
556 use_name_in_title: false,
557 nav: NavigationTarget {
573 kind: HasReferences {
574 position: FilePosition {
591 fn method_annotations() {
597 fn self_by_ref(&self) {}
610 use_name_in_title: false,
611 nav: NavigationTarget {
628 position: FilePosition {
651 kind: HasReferences {
652 position: FilePosition {
678 kind: HasReferences {
679 position: FilePosition {
699 kind: HasReferences {
700 position: FilePosition {
717 fn test_annotations() {
733 use_name_in_title: false,
734 nav: NavigationTarget {
752 use_name_in_title: false,
753 nav: NavigationTarget {
761 description: "mod tests",
774 use_name_in_title: false,
775 nav: NavigationTarget {
781 name: "my_cool_test",
786 "tests::my_cool_test",
798 kind: HasReferences {
799 position: FilePosition {
816 fn test_no_annotations_outside_module_tree() {
822 // this file comes last since `check` checks the first file only
831 fn test_no_annotations_macro_struct_def() {