Make modules with tests runnable

Fixes #154
This commit is contained in:
Jan Jansen
2018-12-27 21:45:16 +01:00
parent 6908268712
commit 05daa86634
8 changed files with 288 additions and 63 deletions

View File

@@ -181,6 +181,28 @@ impl AnalysisImpl {
};
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
/// 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)>> {

View File

@@ -15,6 +15,7 @@ mod imp;
mod completion;
mod symbol_index;
pub mod mock_analysis;
mod runnables;
mod extend_selection;
mod syntax_highlighting;
@@ -33,10 +34,12 @@ use crate::{
symbol_index::SymbolIndex,
};
pub use crate::completion::{CompletionItem, CompletionItemKind, InsertText};
pub use crate::{
completion::{CompletionItem, CompletionItemKind, InsertText},
runnables::{Runnable, RunnableKind}
};
pub use ra_editor::{
FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, Runnable, RunnableKind, StructureNode,
Severity
FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, StructureNode, Severity
};
pub use hir::FnSignatureInfo;
@@ -336,6 +339,9 @@ impl Analysis {
pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<(FileId, FileSymbol)>> {
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>> {
self.imp.crate_for(file_id)
}
@@ -344,7 +350,7 @@ impl Analysis {
}
pub fn runnables(&self, file_id: FileId) -> Cancelable<Vec<Runnable>> {
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>> {
syntax_highlighting::highlight(&*self.imp.db, file_id)

View 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
}
}