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(adt) => {
74 adt.source(db).and_then(|node| name_range(&node, file_id))
78 let (range, offset) = match range {
79 Some(range) => (range, range.start()),
83 if config.annotate_impls && !matches!(def, hir::ModuleDef::Const(_)) {
84 annotations.push(Annotation {
86 kind: AnnotationKind::HasImpls {
87 position: FilePosition { file_id, offset },
92 if config.annotate_references {
93 annotations.push(Annotation {
95 kind: AnnotationKind::HasReferences {
96 position: FilePosition { file_id, offset },
102 fn name_range<T: NameOwner>(node: &InFile<T>, file_id: FileId) -> Option<TextRange> {
103 if node.file_id == file_id.into() {
104 node.value.name().map(|it| it.syntax().text_range())
106 // Node is outside the file we are adding annotations to (e.g. macros).
111 Either::Right(_) => (),
114 if config.annotate_method_references {
115 annotations.extend(find_all_methods(db, file_id).into_iter().map(
116 |FileRange { file_id, range }| Annotation {
118 kind: AnnotationKind::HasReferences {
119 position: FilePosition { file_id, offset: range.start() },
129 pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation) -> Annotation {
130 match &mut annotation.kind {
131 AnnotationKind::HasImpls { position, data } => {
132 *data = goto_implementation(db, *position).map(|range| range.info);
134 AnnotationKind::HasReferences { position, data } => {
135 *data = find_all_refs(&Semantics::new(db), *position, None).map(|result| {
139 .map(|(file_id, access)| {
140 access.into_iter().map(move |(range, _)| FileRange { file_id, range })
152 fn should_skip_runnable(kind: &RunnableKind, binary_target: bool) -> bool {
154 RunnableKind::Bin => !binary_target,
161 use expect_test::{expect, Expect};
163 use crate::{fixture, Annotation, AnnotationConfig};
165 fn check(ra_fixture: &str, expect: Expect) {
166 let (analysis, file_id) = fixture::file(ra_fixture);
168 let annotations: Vec<Annotation> = analysis
172 annotate_runnables: true,
173 annotate_impls: true,
174 annotate_references: true,
175 annotate_method_references: true,
181 .map(|annotation| analysis.resolve_annotation(annotation).unwrap())
184 expect.assert_debug_eq(&annotations);
188 fn const_annotations() {
191 const DEMO: i32 = 123;
193 const UNUSED: i32 = 123;
205 use_name_in_title: false,
206 nav: NavigationTarget {
222 kind: HasReferences {
223 position: FilePosition {
243 kind: HasReferences {
244 position: FilePosition {
257 kind: HasReferences {
258 position: FilePosition {
275 fn struct_references_annotations() {
290 use_name_in_title: false,
291 nav: NavigationTarget {
308 position: FilePosition {
321 kind: HasReferences {
322 position: FilePosition {
342 kind: HasReferences {
343 position: FilePosition {
360 fn struct_and_trait_impls_annotations() {
367 impl MyCoolTrait for Test {}
379 use_name_in_title: false,
380 nav: NavigationTarget {
397 position: FilePosition {
420 kind: HasReferences {
421 position: FilePosition {
448 position: FilePosition {
471 kind: HasReferences {
472 position: FilePosition {
492 kind: HasReferences {
493 position: FilePosition {
510 fn runnable_annotation() {
521 use_name_in_title: false,
522 nav: NavigationTarget {
538 kind: HasReferences {
539 position: FilePosition {
556 fn method_annotations() {
562 fn self_by_ref(&self) {}
575 use_name_in_title: false,
576 nav: NavigationTarget {
593 position: FilePosition {
616 kind: HasReferences {
617 position: FilePosition {
643 kind: HasReferences {
644 position: FilePosition {
664 kind: HasReferences {
665 position: FilePosition {
682 fn test_annotations() {
698 use_name_in_title: false,
699 nav: NavigationTarget {
717 use_name_in_title: false,
718 nav: NavigationTarget {
726 description: "mod tests",
739 use_name_in_title: false,
740 nav: NavigationTarget {
746 name: "my_cool_test",
751 "tests::my_cool_test",
763 kind: HasReferences {
764 position: FilePosition {
781 fn test_no_annotations_outside_module_tree() {
787 // this file comes last since `check` checks the first file only
796 fn test_no_annotations_macro_struct_def() {