mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-27 20:42:04 +00:00
parent
6908268712
commit
05daa86634
8 changed files with 288 additions and 63 deletions
|
@ -181,6 +181,28 @@ impl AnalysisImpl {
|
||||||
};
|
};
|
||||||
Ok(query.search(&buf))
|
Ok(query.search(&buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn module_path(&self, position: FilePosition) -> Cancelable<Option<String>> {
|
||||||
|
let descr = match source_binder::module_from_position(&*self.db, position)? {
|
||||||
|
None => return Ok(None),
|
||||||
|
Some(it) => it,
|
||||||
|
};
|
||||||
|
let name = match descr.name() {
|
||||||
|
None => return Ok(None),
|
||||||
|
Some(it) => it.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let modules = descr.path_to_root();
|
||||||
|
|
||||||
|
let path = modules
|
||||||
|
.iter()
|
||||||
|
.filter_map(|s| s.name())
|
||||||
|
.skip(1) // name is already part of the string.
|
||||||
|
.fold(name, |path, it| format!("{}::{}", it, path));
|
||||||
|
|
||||||
|
Ok(Some(path.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
/// This returns `Vec` because a module may be included from several places. We
|
/// This returns `Vec` because a module may be included from several places. We
|
||||||
/// don't handle this case yet though, so the Vec has length at most one.
|
/// don't handle this case yet though, so the Vec has length at most one.
|
||||||
pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<(FileId, FileSymbol)>> {
|
pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<(FileId, FileSymbol)>> {
|
||||||
|
|
|
@ -15,6 +15,7 @@ mod imp;
|
||||||
mod completion;
|
mod completion;
|
||||||
mod symbol_index;
|
mod symbol_index;
|
||||||
pub mod mock_analysis;
|
pub mod mock_analysis;
|
||||||
|
mod runnables;
|
||||||
|
|
||||||
mod extend_selection;
|
mod extend_selection;
|
||||||
mod syntax_highlighting;
|
mod syntax_highlighting;
|
||||||
|
@ -33,10 +34,12 @@ use crate::{
|
||||||
symbol_index::SymbolIndex,
|
symbol_index::SymbolIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::completion::{CompletionItem, CompletionItemKind, InsertText};
|
pub use crate::{
|
||||||
|
completion::{CompletionItem, CompletionItemKind, InsertText},
|
||||||
|
runnables::{Runnable, RunnableKind}
|
||||||
|
};
|
||||||
pub use ra_editor::{
|
pub use ra_editor::{
|
||||||
FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, Runnable, RunnableKind, StructureNode,
|
FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, StructureNode, Severity
|
||||||
Severity
|
|
||||||
};
|
};
|
||||||
pub use hir::FnSignatureInfo;
|
pub use hir::FnSignatureInfo;
|
||||||
|
|
||||||
|
@ -336,6 +339,9 @@ impl Analysis {
|
||||||
pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<(FileId, FileSymbol)>> {
|
pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<(FileId, FileSymbol)>> {
|
||||||
self.imp.parent_module(position)
|
self.imp.parent_module(position)
|
||||||
}
|
}
|
||||||
|
pub fn module_path(&self, position: FilePosition) -> Cancelable<Option<String>> {
|
||||||
|
self.imp.module_path(position)
|
||||||
|
}
|
||||||
pub fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> {
|
pub fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> {
|
||||||
self.imp.crate_for(file_id)
|
self.imp.crate_for(file_id)
|
||||||
}
|
}
|
||||||
|
@ -344,7 +350,7 @@ impl Analysis {
|
||||||
}
|
}
|
||||||
pub fn runnables(&self, file_id: FileId) -> Cancelable<Vec<Runnable>> {
|
pub fn runnables(&self, file_id: FileId) -> Cancelable<Vec<Runnable>> {
|
||||||
let file = self.imp.file_syntax(file_id);
|
let file = self.imp.file_syntax(file_id);
|
||||||
Ok(ra_editor::runnables(&file))
|
Ok(runnables::runnables(self, &file, file_id))
|
||||||
}
|
}
|
||||||
pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> {
|
pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> {
|
||||||
syntax_highlighting::highlight(&*self.imp.db, file_id)
|
syntax_highlighting::highlight(&*self.imp.db, file_id)
|
||||||
|
|
72
crates/ra_analysis/src/runnables.rs
Normal file
72
crates/ra_analysis/src/runnables.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
use ra_syntax::{
|
||||||
|
ast::{self, AstNode, NameOwner, ModuleItemOwner},
|
||||||
|
SourceFileNode, TextRange, SyntaxNodeRef,
|
||||||
|
TextUnit,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
Analysis, FileId, FilePosition
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Runnable {
|
||||||
|
pub range: TextRange,
|
||||||
|
pub kind: RunnableKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum RunnableKind {
|
||||||
|
Test { name: String },
|
||||||
|
TestMod { path: String },
|
||||||
|
Bin,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runnables(
|
||||||
|
analysis: &Analysis,
|
||||||
|
file_node: &SourceFileNode,
|
||||||
|
file_id: FileId,
|
||||||
|
) -> Vec<Runnable> {
|
||||||
|
file_node
|
||||||
|
.syntax()
|
||||||
|
.descendants()
|
||||||
|
.filter_map(|i| runnable(analysis, i, file_id))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runnable<'a>(analysis: &Analysis, item: SyntaxNodeRef<'a>, file_id: FileId) -> Option<Runnable> {
|
||||||
|
if let Some(f) = ast::FnDef::cast(item) {
|
||||||
|
let name = f.name()?.text();
|
||||||
|
let kind = if name == "main" {
|
||||||
|
RunnableKind::Bin
|
||||||
|
} else if f.has_atom_attr("test") {
|
||||||
|
RunnableKind::Test {
|
||||||
|
name: name.to_string(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(Runnable {
|
||||||
|
range: f.syntax().range(),
|
||||||
|
kind,
|
||||||
|
})
|
||||||
|
} else if let Some(m) = ast::Module::cast(item) {
|
||||||
|
if m.item_list()?
|
||||||
|
.items()
|
||||||
|
.map(ast::ModuleItem::syntax)
|
||||||
|
.filter_map(ast::FnDef::cast)
|
||||||
|
.any(|f| f.has_atom_attr("test"))
|
||||||
|
{
|
||||||
|
let postition = FilePosition {
|
||||||
|
file_id,
|
||||||
|
offset: m.syntax().range().start() + TextUnit::from_usize(1),
|
||||||
|
};
|
||||||
|
analysis.module_path(postition).ok()?.map(|path| Runnable {
|
||||||
|
range: m.syntax().range(),
|
||||||
|
kind: RunnableKind::TestMod { path },
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
118
crates/ra_analysis/tests/runnables.rs
Normal file
118
crates/ra_analysis/tests/runnables.rs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
extern crate ra_analysis;
|
||||||
|
extern crate ra_editor;
|
||||||
|
extern crate ra_syntax;
|
||||||
|
extern crate relative_path;
|
||||||
|
extern crate rustc_hash;
|
||||||
|
extern crate test_utils;
|
||||||
|
|
||||||
|
use test_utils::assert_eq_dbg;
|
||||||
|
|
||||||
|
use ra_analysis::{
|
||||||
|
mock_analysis::{analysis_and_position},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_runnables() {
|
||||||
|
let (analysis, pos) = analysis_and_position(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs
|
||||||
|
<|> //empty
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_foo() {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn test_foo() {}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let runnables = analysis.runnables(pos.file_id).unwrap();
|
||||||
|
assert_eq_dbg(
|
||||||
|
r#"[Runnable { range: [1; 21), kind: Bin },
|
||||||
|
Runnable { range: [22; 46), kind: Test { name: "test_foo" } },
|
||||||
|
Runnable { range: [47; 81), kind: Test { name: "test_foo" } }]"#,
|
||||||
|
&runnables,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_runnables_module() {
|
||||||
|
let (analysis, pos) = analysis_and_position(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs
|
||||||
|
<|> //empty
|
||||||
|
mod test_mod {
|
||||||
|
#[test]
|
||||||
|
fn test_foo1() {}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let runnables = analysis.runnables(pos.file_id).unwrap();
|
||||||
|
assert_eq_dbg(
|
||||||
|
r#"[Runnable { range: [1; 59), kind: TestMod { path: "test_mod" } },
|
||||||
|
Runnable { range: [28; 57), kind: Test { name: "test_foo1" } }]"#,
|
||||||
|
&runnables,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_runnables_one_depth_layer_module() {
|
||||||
|
let (analysis, pos) = analysis_and_position(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs
|
||||||
|
<|> //empty
|
||||||
|
mod foo {
|
||||||
|
mod test_mod {
|
||||||
|
#[test]
|
||||||
|
fn test_foo1() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let runnables = analysis.runnables(pos.file_id).unwrap();
|
||||||
|
assert_eq_dbg(
|
||||||
|
r#"[Runnable { range: [23; 85), kind: TestMod { path: "foo::test_mod" } },
|
||||||
|
Runnable { range: [46; 79), kind: Test { name: "test_foo1" } }]"#,
|
||||||
|
&runnables,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_runnables_multiple_depth_module() {
|
||||||
|
let (analysis, pos) = analysis_and_position(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs
|
||||||
|
<|> //empty
|
||||||
|
mod foo {
|
||||||
|
mod bar {
|
||||||
|
mod test_mod {
|
||||||
|
#[test]
|
||||||
|
fn test_foo1() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let runnables = analysis.runnables(pos.file_id).unwrap();
|
||||||
|
assert_eq_dbg(
|
||||||
|
r#"[Runnable { range: [41; 115), kind: TestMod { path: "foo::bar::test_mod" } },
|
||||||
|
Runnable { range: [68; 105), kind: Test { name: "test_foo1" } }]"#,
|
||||||
|
&runnables,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_runnables_no_test_function_in_module() {
|
||||||
|
let (analysis, pos) = analysis_and_position(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs
|
||||||
|
<|> //empty
|
||||||
|
mod test_mod {
|
||||||
|
fn foo1() {}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let runnables = analysis.runnables(pos.file_id).unwrap();
|
||||||
|
assert_eq_dbg(r#"[]"#, &runnables)
|
||||||
|
}
|
|
@ -131,6 +131,56 @@ fn test_resolve_parent_module_for_inline() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_path_one_layer() {
|
||||||
|
let (analysis, pos) = analysis_and_position(
|
||||||
|
"
|
||||||
|
//- /lib.rs
|
||||||
|
mod foo;
|
||||||
|
//- /foo/mod.rs
|
||||||
|
mod bla;
|
||||||
|
//- /foo/bla.rs
|
||||||
|
<|> //empty
|
||||||
|
",
|
||||||
|
);
|
||||||
|
let symbols = analysis.module_path(pos).unwrap().unwrap();
|
||||||
|
assert_eq!("foo::bla", &symbols);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_path_two_layer() {
|
||||||
|
let (analysis, pos) = analysis_and_position(
|
||||||
|
"
|
||||||
|
//- /lib.rs
|
||||||
|
mod foo;
|
||||||
|
//- /foo/mod.rs
|
||||||
|
mod bla;
|
||||||
|
//- /foo/bla/mod.rs
|
||||||
|
mod more;
|
||||||
|
//- /foo/bla/more.rs
|
||||||
|
<|> //empty
|
||||||
|
",
|
||||||
|
);
|
||||||
|
let symbols = analysis.module_path(pos).unwrap().unwrap();
|
||||||
|
assert_eq!("foo::bla::more", &symbols);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_path_in_file_mod() {
|
||||||
|
let (analysis, pos) = analysis_and_position(
|
||||||
|
"
|
||||||
|
//- /lib.rs
|
||||||
|
mod foo;
|
||||||
|
//- /foo.rs
|
||||||
|
mod bar {
|
||||||
|
<|> //empty
|
||||||
|
}
|
||||||
|
",
|
||||||
|
);
|
||||||
|
let symbols = analysis.module_path(pos).unwrap().unwrap();
|
||||||
|
assert_eq!("foo::bar", &symbols);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_resolve_crate_root() {
|
fn test_resolve_crate_root() {
|
||||||
let mock = MockAnalysis::with_files(
|
let mock = MockAnalysis::with_files(
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub use self::{
|
||||||
use ra_text_edit::{TextEdit, TextEditBuilder};
|
use ra_text_edit::{TextEdit, TextEditBuilder};
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
algo::find_leaf_at_offset,
|
algo::find_leaf_at_offset,
|
||||||
ast::{self, AstNode, NameOwner},
|
ast::{self, AstNode},
|
||||||
SourceFileNode,
|
SourceFileNode,
|
||||||
SyntaxKind::{self, *},
|
SyntaxKind::{self, *},
|
||||||
SyntaxNodeRef, TextRange, TextUnit, Direction,
|
SyntaxNodeRef, TextRange, TextUnit, Direction,
|
||||||
|
@ -49,18 +49,6 @@ pub struct Diagnostic {
|
||||||
pub fix: Option<LocalEdit>,
|
pub fix: Option<LocalEdit>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Runnable {
|
|
||||||
pub range: TextRange,
|
|
||||||
pub kind: RunnableKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum RunnableKind {
|
|
||||||
Test { name: String },
|
|
||||||
Bin,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn matching_brace(file: &SourceFileNode, offset: TextUnit) -> Option<TextUnit> {
|
pub fn matching_brace(file: &SourceFileNode, offset: TextUnit) -> Option<TextUnit> {
|
||||||
const BRACES: &[SyntaxKind] = &[
|
const BRACES: &[SyntaxKind] = &[
|
||||||
L_CURLY, R_CURLY, L_BRACK, R_BRACK, L_PAREN, R_PAREN, L_ANGLE, R_ANGLE,
|
L_CURLY, R_CURLY, L_BRACK, R_BRACK, L_PAREN, R_PAREN, L_ANGLE, R_ANGLE,
|
||||||
|
@ -133,29 +121,6 @@ pub fn syntax_tree(file: &SourceFileNode) -> String {
|
||||||
::ra_syntax::utils::dump_tree(file.syntax())
|
::ra_syntax::utils::dump_tree(file.syntax())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn runnables(file: &SourceFileNode) -> Vec<Runnable> {
|
|
||||||
file.syntax()
|
|
||||||
.descendants()
|
|
||||||
.filter_map(ast::FnDef::cast)
|
|
||||||
.filter_map(|f| {
|
|
||||||
let name = f.name()?.text();
|
|
||||||
let kind = if name == "main" {
|
|
||||||
RunnableKind::Bin
|
|
||||||
} else if f.has_atom_attr("test") {
|
|
||||||
RunnableKind::Test {
|
|
||||||
name: name.to_string(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
Some(Runnable {
|
|
||||||
range: f.syntax().range(),
|
|
||||||
kind,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_node_at_offset<'a, N: AstNode<'a>>(
|
pub fn find_node_at_offset<'a, N: AstNode<'a>>(
|
||||||
syntax: SyntaxNodeRef<'a>,
|
syntax: SyntaxNodeRef<'a>,
|
||||||
offset: TextUnit,
|
offset: TextUnit,
|
||||||
|
@ -190,29 +155,6 @@ fn main() {}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_runnables() {
|
|
||||||
let file = SourceFileNode::parse(
|
|
||||||
r#"
|
|
||||||
fn main() {}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_foo() {}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[ignore]
|
|
||||||
fn test_foo() {}
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
let runnables = runnables(&file);
|
|
||||||
assert_eq_dbg(
|
|
||||||
r#"[Runnable { range: [1; 13), kind: Bin },
|
|
||||||
Runnable { range: [15; 39), kind: Test { name: "test_foo" } },
|
|
||||||
Runnable { range: [41; 75), kind: Test { name: "test_foo" } }]"#,
|
|
||||||
&runnables,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_matching_brace() {
|
fn test_matching_brace() {
|
||||||
fn do_check(before: &str, after: &str) {
|
fn do_check(before: &str, after: &str) {
|
||||||
|
|
|
@ -75,6 +75,11 @@ impl Module {
|
||||||
Some(Crate::new(crate_id))
|
Some(Crate::new(crate_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the all modulkes on the way to the root.
|
||||||
|
pub fn path_to_root(&self) -> Vec<Module> {
|
||||||
|
generate(Some(self.clone()), move |it| it.parent()).collect::<Vec<Module>>()
|
||||||
|
}
|
||||||
|
|
||||||
/// The root of the tree this module is part of
|
/// The root of the tree this module is part of
|
||||||
pub fn crate_root(&self) -> Module {
|
pub fn crate_root(&self) -> Module {
|
||||||
let root_id = self.module_id.crate_root(&self.tree);
|
let root_id = self.module_id.crate_root(&self.tree);
|
||||||
|
|
|
@ -257,6 +257,7 @@ pub fn handle_runnables(
|
||||||
range: runnable.range.conv_with(&line_index),
|
range: runnable.range.conv_with(&line_index),
|
||||||
label: match &runnable.kind {
|
label: match &runnable.kind {
|
||||||
RunnableKind::Test { name } => format!("test {}", name),
|
RunnableKind::Test { name } => format!("test {}", name),
|
||||||
|
RunnableKind::TestMod { path } => format!("test-mod {}", path),
|
||||||
RunnableKind::Bin => "run binary".to_string(),
|
RunnableKind::Bin => "run binary".to_string(),
|
||||||
},
|
},
|
||||||
bin: "cargo".to_string(),
|
bin: "cargo".to_string(),
|
||||||
|
@ -308,6 +309,15 @@ pub fn handle_runnables(
|
||||||
res.push(name.to_string());
|
res.push(name.to_string());
|
||||||
res.push("--nocapture".to_string());
|
res.push("--nocapture".to_string());
|
||||||
}
|
}
|
||||||
|
RunnableKind::TestMod { path } => {
|
||||||
|
res.push("test".to_string());
|
||||||
|
if let Some(spec) = spec {
|
||||||
|
spec.push_to(&mut res);
|
||||||
|
}
|
||||||
|
res.push("--".to_string());
|
||||||
|
res.push(path.to_string());
|
||||||
|
res.push("--nocapture".to_string());
|
||||||
|
}
|
||||||
RunnableKind::Bin => {
|
RunnableKind::Bin => {
|
||||||
res.push("run".to_string());
|
res.push("run".to_string());
|
||||||
if let Some(spec) = spec {
|
if let Some(spec) = spec {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue