rust-analyzer/crates/ide/src/annotations.rs

1015 lines
36 KiB
Rust
Raw Normal View History

2021-03-14 15:12:38 +00:00
use either::Either;
2021-03-24 07:00:38 +00:00
use hir::{HasSource, InFile, Semantics};
2021-02-13 11:07:47 +00:00
use ide_db::{
2021-03-14 15:12:38 +00:00
base_db::{FileId, FilePosition, FileRange},
helpers::visit_file_defs,
2021-03-14 15:12:38 +00:00
RootDatabase,
2021-02-13 11:07:47 +00:00
};
2021-05-26 10:23:51 +00:00
use syntax::{ast::NameOwner, AstNode, TextRange};
2021-02-13 11:07:47 +00:00
use crate::{
fn_references::find_all_methods,
goto_implementation::goto_implementation,
references::find_all_refs,
runnables::{runnables, Runnable},
2021-03-14 15:12:38 +00:00
NavigationTarget, RunnableKind,
2021-02-13 11:07:47 +00:00
};
// Feature: Annotations
//
// Provides user with annotations above items for looking up references or impl blocks
// and running/debugging binaries.
//
// image::https://user-images.githubusercontent.com/48062697/113020672-b7c34f00-917a-11eb-8f6e-858735660a0e.png[]
2021-02-13 13:27:04 +00:00
#[derive(Debug)]
2021-02-13 11:07:47 +00:00
pub struct Annotation {
pub range: TextRange,
pub kind: AnnotationKind,
}
2021-02-13 13:27:04 +00:00
#[derive(Debug)]
2021-02-13 11:07:47 +00:00
pub enum AnnotationKind {
Runnable { debug: bool, runnable: Runnable },
HasImpls { position: FilePosition, data: Option<Vec<NavigationTarget>> },
HasReferences { position: FilePosition, data: Option<Vec<FileRange>> },
}
pub struct AnnotationConfig {
pub binary_target: bool,
pub annotate_runnables: bool,
pub annotate_impls: bool,
pub annotate_references: bool,
pub annotate_method_references: bool,
pub run: bool,
pub debug: bool,
}
pub(crate) fn annotations(
db: &RootDatabase,
config: &AnnotationConfig,
2021-02-13 11:07:47 +00:00
file_id: FileId,
) -> Vec<Annotation> {
let mut annotations = Vec::default();
if config.annotate_runnables {
for runnable in runnables(db, file_id) {
if should_skip_runnable(&runnable.kind, config.binary_target) {
2021-02-13 11:07:47 +00:00
continue;
}
2021-05-28 10:55:07 +00:00
let range = runnable.nav.focus_or_full_range();
2021-02-13 11:07:47 +00:00
// dbg_runnable should go after the run annotation, to prevent a clone we do it this way
let dbg_runnable = (runnable.debugee() && config.debug).then(|| Annotation {
range,
kind: AnnotationKind::Runnable { debug: true, runnable: runnable.clone() },
});
2021-02-15 14:09:50 +00:00
if config.run {
2021-02-13 11:07:47 +00:00
annotations.push(Annotation {
range,
kind: AnnotationKind::Runnable { debug: false, runnable },
2021-02-13 11:07:47 +00:00
});
}
annotations.extend(dbg_runnable);
2021-02-13 11:07:47 +00:00
}
}
visit_file_defs(&Semantics::new(db), file_id, &mut |def| match def {
2021-03-14 15:12:38 +00:00
Either::Left(def) => {
2021-05-26 10:23:51 +00:00
let range = match def {
2021-03-14 15:12:38 +00:00
hir::ModuleDef::Const(konst) => {
2021-05-26 10:23:51 +00:00
konst.source(db).and_then(|node| name_range(&node, file_id))
2021-03-14 15:12:38 +00:00
}
hir::ModuleDef::Trait(trait_) => {
2021-05-26 10:23:51 +00:00
trait_.source(db).and_then(|node| name_range(&node, file_id))
2021-03-14 15:12:38 +00:00
}
hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => {
2021-05-26 10:23:51 +00:00
strukt.source(db).and_then(|node| name_range(&node, file_id))
2021-03-14 15:12:38 +00:00
}
hir::ModuleDef::Adt(hir::Adt::Enum(enum_)) => {
2021-05-26 10:23:51 +00:00
enum_.source(db).and_then(|node| name_range(&node, file_id))
2021-03-14 15:12:38 +00:00
}
hir::ModuleDef::Adt(hir::Adt::Union(union)) => {
2021-05-26 10:23:51 +00:00
union.source(db).and_then(|node| name_range(&node, file_id))
2021-03-14 15:12:38 +00:00
}
_ => None,
};
2021-05-26 10:23:51 +00:00
let (range, offset) = match range {
Some(range) => (range, range.start()),
2021-03-14 15:12:38 +00:00
None => return,
};
if config.annotate_impls && !matches!(def, hir::ModuleDef::Const(_)) {
2021-02-13 11:07:47 +00:00
annotations.push(Annotation {
2021-03-14 15:12:38 +00:00
range,
2021-02-13 11:07:47 +00:00
kind: AnnotationKind::HasImpls {
2021-03-14 15:12:38 +00:00
position: FilePosition { file_id, offset },
2021-02-13 11:07:47 +00:00
data: None,
},
});
}
if config.annotate_references {
annotations.push(Annotation {
2021-03-14 15:12:38 +00:00
range,
2021-02-13 11:07:47 +00:00
kind: AnnotationKind::HasReferences {
2021-03-14 15:12:38 +00:00
position: FilePosition { file_id, offset },
2021-02-13 11:07:47 +00:00
data: None,
},
});
}
2021-03-14 15:12:38 +00:00
2021-05-26 10:23:51 +00:00
fn name_range<T: NameOwner>(node: &InFile<T>, file_id: FileId) -> Option<TextRange> {
if node.file_id == file_id.into() {
node.value.name().map(|it| it.syntax().text_range())
} else {
2021-03-24 07:47:55 +00:00
// Node is outside the file we are adding annotations to (e.g. macros).
2021-03-24 07:00:38 +00:00
None
}
2021-03-14 15:12:38 +00:00
}
}
Either::Right(_) => (),
});
2021-02-13 11:07:47 +00:00
if config.annotate_method_references {
2021-05-26 10:23:51 +00:00
annotations.extend(find_all_methods(db, file_id).into_iter().map(
|FileRange { file_id, range }| Annotation {
range,
kind: AnnotationKind::HasReferences {
position: FilePosition { file_id, offset: range.start() },
data: None,
},
2021-02-13 11:07:47 +00:00
},
2021-05-26 10:23:51 +00:00
));
2021-02-13 11:07:47 +00:00
}
annotations
}
pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation) -> Annotation {
match annotation.kind {
AnnotationKind::HasImpls { position, ref mut data } => {
*data = goto_implementation(db, position).map(|range| range.info);
}
AnnotationKind::HasReferences { position, ref mut data } => {
*data = find_all_refs(&Semantics::new(db), position, None).map(|result| {
result
.references
.into_iter()
2021-02-13 11:22:12 +00:00
.map(|(file_id, access)| {
access.into_iter().map(move |(range, _)| FileRange { file_id, range })
})
2021-02-13 11:07:47 +00:00
.flatten()
.collect()
});
}
_ => {}
};
annotation
}
2021-02-13 13:27:04 +00:00
fn should_skip_runnable(kind: &RunnableKind, binary_target: bool) -> bool {
match kind {
RunnableKind::Bin => !binary_target,
_ => false,
}
}
2021-02-13 13:27:04 +00:00
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};
2021-02-13 13:27:04 +00:00
use crate::{fixture, Annotation, AnnotationConfig};
2021-02-13 13:27:04 +00:00
fn check(ra_fixture: &str, expect: Expect) {
2021-02-13 13:27:04 +00:00
let (analysis, file_id) = fixture::file(ra_fixture);
let annotations: Vec<Annotation> = analysis
.annotations(
&AnnotationConfig {
binary_target: true,
annotate_runnables: true,
annotate_impls: true,
annotate_references: true,
annotate_method_references: true,
run: true,
debug: true,
},
file_id,
)
2021-02-13 13:27:04 +00:00
.unwrap()
.into_iter()
.map(|annotation| analysis.resolve_annotation(annotation).unwrap())
2021-02-13 13:27:04 +00:00
.collect();
expect.assert_debug_eq(&annotations);
2021-02-13 13:27:04 +00:00
}
#[test]
fn const_annotations() {
check(
2021-02-13 13:27:04 +00:00
r#"
const DEMO: i32 = 123;
const UNUSED: i32 = 123;
2021-02-13 13:27:04 +00:00
fn main() {
let hello = DEMO;
}
"#,
expect![[r#"
[
Annotation {
2021-05-28 10:55:07 +00:00
range: 53..57,
kind: Runnable {
2021-02-15 14:09:50 +00:00
debug: false,
runnable: Runnable {
use_name_in_title: false,
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 50..85,
focus_range: 53..57,
name: "main",
kind: Function,
},
kind: Bin,
cfg: None,
},
},
},
Annotation {
2021-05-28 10:55:07 +00:00
range: 53..57,
kind: Runnable {
2021-02-15 14:09:50 +00:00
debug: true,
runnable: Runnable {
use_name_in_title: false,
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 50..85,
focus_range: 53..57,
name: "main",
kind: Function,
},
kind: Bin,
cfg: None,
},
},
},
Annotation {
2021-05-26 10:23:51 +00:00
range: 6..10,
kind: HasReferences {
position: FilePosition {
file_id: FileId(
0,
),
offset: 6,
},
data: Some(
[
FileRange {
file_id: FileId(
0,
),
range: 78..82,
},
],
),
},
},
Annotation {
2021-05-26 10:23:51 +00:00
range: 30..36,
kind: HasReferences {
position: FilePosition {
file_id: FileId(
0,
),
offset: 30,
},
data: Some(
[],
),
},
},
Annotation {
range: 53..57,
kind: HasReferences {
position: FilePosition {
file_id: FileId(
0,
),
offset: 53,
},
data: Some(
[],
),
},
},
]
"#]],
2021-02-13 13:27:04 +00:00
);
}
#[test]
fn struct_references_annotations() {
check(
2021-02-13 13:27:04 +00:00
r#"
struct Test;
fn main() {
let test = Test;
}
"#,
expect![[r#"
[
Annotation {
2021-05-28 10:55:07 +00:00
range: 17..21,
kind: Runnable {
2021-02-15 14:09:50 +00:00
debug: false,
runnable: Runnable {
use_name_in_title: false,
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 14..48,
focus_range: 17..21,
name: "main",
kind: Function,
},
kind: Bin,
cfg: None,
},
},
},
Annotation {
2021-05-28 10:55:07 +00:00
range: 17..21,
kind: Runnable {
2021-02-15 14:09:50 +00:00
debug: true,
runnable: Runnable {
use_name_in_title: false,
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 14..48,
focus_range: 17..21,
name: "main",
kind: Function,
},
kind: Bin,
cfg: None,
},
},
},
Annotation {
2021-05-26 10:23:51 +00:00
range: 7..11,
kind: HasImpls {
position: FilePosition {
file_id: FileId(
0,
),
offset: 7,
},
data: Some(
[],
),
},
},
Annotation {
2021-05-26 10:23:51 +00:00
range: 7..11,
kind: HasReferences {
position: FilePosition {
file_id: FileId(
0,
),
offset: 7,
},
data: Some(
[
FileRange {
file_id: FileId(
0,
),
range: 41..45,
},
],
),
},
},
Annotation {
range: 17..21,
kind: HasReferences {
position: FilePosition {
file_id: FileId(
0,
),
offset: 17,
},
data: Some(
[],
),
},
},
]
"#]],
2021-02-13 13:27:04 +00:00
);
}
#[test]
fn struct_and_trait_impls_annotations() {
check(
2021-02-13 13:27:04 +00:00
r#"
struct Test;
trait MyCoolTrait {}
impl MyCoolTrait for Test {}
fn main() {
let test = Test;
}
"#,
expect![[r#"
[
Annotation {
2021-05-28 10:55:07 +00:00
range: 69..73,
kind: Runnable {
2021-02-15 14:09:50 +00:00
debug: false,
runnable: Runnable {
use_name_in_title: false,
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 66..100,
focus_range: 69..73,
name: "main",
kind: Function,
},
kind: Bin,
cfg: None,
},
},
},
Annotation {
2021-05-28 10:55:07 +00:00
range: 69..73,
kind: Runnable {
2021-02-15 14:09:50 +00:00
debug: true,
runnable: Runnable {
use_name_in_title: false,
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 66..100,
focus_range: 69..73,
name: "main",
kind: Function,
},
kind: Bin,
cfg: None,
},
},
},
Annotation {
2021-05-26 10:23:51 +00:00
range: 7..11,
kind: HasImpls {
position: FilePosition {
file_id: FileId(
0,
),
offset: 7,
},
data: Some(
[
NavigationTarget {
file_id: FileId(
0,
),
full_range: 36..64,
focus_range: 57..61,
name: "impl",
kind: Impl,
},
],
),
},
},
Annotation {
2021-05-26 10:23:51 +00:00
range: 7..11,
kind: HasReferences {
position: FilePosition {
file_id: FileId(
0,
),
offset: 7,
},
data: Some(
[
FileRange {
file_id: FileId(
0,
),
range: 57..61,
},
FileRange {
file_id: FileId(
0,
),
range: 93..97,
},
],
),
},
},
Annotation {
2021-05-26 10:23:51 +00:00
range: 20..31,
kind: HasImpls {
position: FilePosition {
file_id: FileId(
0,
),
offset: 20,
},
data: Some(
[
NavigationTarget {
file_id: FileId(
0,
),
full_range: 36..64,
focus_range: 57..61,
name: "impl",
kind: Impl,
},
],
),
},
},
Annotation {
2021-05-26 10:23:51 +00:00
range: 20..31,
kind: HasReferences {
position: FilePosition {
file_id: FileId(
0,
),
offset: 20,
},
data: Some(
[
FileRange {
file_id: FileId(
0,
),
range: 41..52,
},
],
),
},
},
Annotation {
range: 69..73,
kind: HasReferences {
position: FilePosition {
file_id: FileId(
0,
),
offset: 69,
},
data: Some(
[],
),
},
},
]
"#]],
2021-02-13 13:27:04 +00:00
);
}
#[test]
fn runnable_annotation() {
check(
2021-02-13 13:27:04 +00:00
r#"
fn main() {}
"#,
expect![[r#"
[
Annotation {
2021-05-28 10:55:07 +00:00
range: 3..7,
kind: Runnable {
2021-02-15 14:09:50 +00:00
debug: false,
runnable: Runnable {
use_name_in_title: false,
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..12,
focus_range: 3..7,
name: "main",
kind: Function,
},
kind: Bin,
cfg: None,
},
},
},
Annotation {
2021-05-28 10:55:07 +00:00
range: 3..7,
kind: Runnable {
2021-02-15 14:09:50 +00:00
debug: true,
runnable: Runnable {
use_name_in_title: false,
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..12,
focus_range: 3..7,
name: "main",
kind: Function,
},
kind: Bin,
cfg: None,
},
},
},
Annotation {
range: 3..7,
kind: HasReferences {
position: FilePosition {
file_id: FileId(
0,
),
offset: 3,
},
data: Some(
[],
),
},
},
]
"#]],
2021-02-13 13:27:04 +00:00
);
}
#[test]
fn method_annotations() {
check(
2021-02-13 13:27:04 +00:00
r#"
struct Test;
impl Test {
fn self_by_ref(&self) {}
}
fn main() {
Test.self_by_ref();
}
"#,
expect![[r#"
[
Annotation {
2021-05-28 10:55:07 +00:00
range: 61..65,
kind: Runnable {
2021-02-15 14:09:50 +00:00
debug: false,
runnable: Runnable {
use_name_in_title: false,
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 58..95,
focus_range: 61..65,
name: "main",
kind: Function,
},
kind: Bin,
cfg: None,
},
},
},
Annotation {
2021-05-28 10:55:07 +00:00
range: 61..65,
kind: Runnable {
2021-02-15 14:09:50 +00:00
debug: true,
runnable: Runnable {
use_name_in_title: false,
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 58..95,
focus_range: 61..65,
name: "main",
kind: Function,
},
kind: Bin,
cfg: None,
},
},
},
Annotation {
2021-05-26 10:23:51 +00:00
range: 7..11,
kind: HasImpls {
position: FilePosition {
file_id: FileId(
0,
),
offset: 7,
},
data: Some(
[
NavigationTarget {
file_id: FileId(
0,
),
full_range: 14..56,
focus_range: 19..23,
name: "impl",
kind: Impl,
},
],
),
},
},
Annotation {
2021-05-26 10:23:51 +00:00
range: 7..11,
kind: HasReferences {
position: FilePosition {
file_id: FileId(
0,
),
offset: 7,
},
data: Some(
[
FileRange {
file_id: FileId(
0,
),
range: 19..23,
},
FileRange {
file_id: FileId(
0,
),
range: 74..78,
},
],
),
},
},
Annotation {
range: 33..44,
kind: HasReferences {
position: FilePosition {
file_id: FileId(
0,
),
offset: 33,
},
data: Some(
[
FileRange {
file_id: FileId(
0,
),
range: 79..90,
},
],
),
},
},
Annotation {
range: 61..65,
kind: HasReferences {
position: FilePosition {
file_id: FileId(
0,
),
offset: 61,
},
data: Some(
[],
),
},
},
]
"#]],
);
}
#[test]
fn test_annotations() {
check(
r#"
fn main() {}
mod tests {
#[test]
fn my_cool_test() {}
}
"#,
expect![[r#"
[
Annotation {
2021-05-28 10:55:07 +00:00
range: 3..7,
kind: Runnable {
2021-02-15 14:09:50 +00:00
debug: false,
runnable: Runnable {
use_name_in_title: false,
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..12,
focus_range: 3..7,
name: "main",
kind: Function,
},
kind: Bin,
cfg: None,
},
},
},
Annotation {
2021-05-28 10:55:07 +00:00
range: 3..7,
kind: Runnable {
2021-02-15 14:09:50 +00:00
debug: true,
runnable: Runnable {
use_name_in_title: false,
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..12,
focus_range: 3..7,
name: "main",
kind: Function,
},
kind: Bin,
cfg: None,
},
},
},
Annotation {
2021-05-28 10:55:07 +00:00
range: 18..23,
kind: Runnable {
2021-02-15 14:09:50 +00:00
debug: false,
runnable: Runnable {
use_name_in_title: false,
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 14..64,
focus_range: 18..23,
name: "tests",
kind: Module,
description: "mod tests",
},
kind: TestMod {
path: "tests",
},
cfg: None,
},
},
},
Annotation {
2021-05-28 10:55:07 +00:00
range: 18..23,
kind: Runnable {
2021-02-15 14:09:50 +00:00
debug: true,
runnable: Runnable {
use_name_in_title: false,
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 14..64,
focus_range: 18..23,
name: "tests",
kind: Module,
description: "mod tests",
},
kind: TestMod {
path: "tests",
},
cfg: None,
},
},
},
Annotation {
2021-05-28 10:55:07 +00:00
range: 45..57,
kind: Runnable {
2021-02-15 14:09:50 +00:00
debug: false,
runnable: Runnable {
use_name_in_title: false,
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 30..62,
focus_range: 45..57,
name: "my_cool_test",
kind: Function,
},
kind: Test {
test_id: Path(
"tests::my_cool_test",
),
attr: TestAttr {
ignore: false,
},
},
cfg: None,
},
},
},
Annotation {
2021-05-28 10:55:07 +00:00
range: 45..57,
kind: Runnable {
2021-02-15 14:09:50 +00:00
debug: true,
runnable: Runnable {
use_name_in_title: false,
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 30..62,
focus_range: 45..57,
name: "my_cool_test",
kind: Function,
},
kind: Test {
test_id: Path(
"tests::my_cool_test",
),
attr: TestAttr {
ignore: false,
},
},
cfg: None,
},
},
},
Annotation {
range: 3..7,
kind: HasReferences {
position: FilePosition {
file_id: FileId(
0,
),
offset: 3,
},
data: Some(
[],
),
},
},
]
"#]],
2021-02-13 13:27:04 +00:00
);
}
2021-03-14 15:12:38 +00:00
#[test]
fn test_no_annotations_outside_module_tree() {
check(
r#"
//- /foo.rs
struct Foo;
//- /lib.rs
// this file comes last since `check` checks the first file only
2021-03-24 07:00:38 +00:00
"#,
expect![[r#"
[]
"#]],
);
}
#[test]
fn test_no_annotations_macro_struct_def() {
check(
r#"
//- /lib.rs
macro_rules! m {
() => {
struct A {}
};
}
m!();
2021-03-14 15:12:38 +00:00
"#,
expect![[r#"
[]
"#]],
);
}
2021-02-13 13:27:04 +00:00
}