aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api/src/runnables.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_api/src/runnables.rs')
-rw-r--r--crates/ra_ide_api/src/runnables.rs89
1 files changed, 89 insertions, 0 deletions
diff --git a/crates/ra_ide_api/src/runnables.rs b/crates/ra_ide_api/src/runnables.rs
new file mode 100644
index 000000000..98b1d2d55
--- /dev/null
+++ b/crates/ra_ide_api/src/runnables.rs
@@ -0,0 +1,89 @@
1use itertools::Itertools;
2use ra_syntax::{
3 TextRange, SyntaxNode,
4 ast::{self, AstNode, NameOwner, ModuleItemOwner},
5};
6use ra_db::{Cancelable, SyntaxDatabase};
7
8use crate::{db::RootDatabase, FileId};
9
10#[derive(Debug)]
11pub struct Runnable {
12 pub range: TextRange,
13 pub kind: RunnableKind,
14}
15
16#[derive(Debug)]
17pub enum RunnableKind {
18 Test { name: String },
19 TestMod { path: String },
20 Bin,
21}
22
23pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Cancelable<Vec<Runnable>> {
24 let source_file = db.source_file(file_id);
25 let res = source_file
26 .syntax()
27 .descendants()
28 .filter_map(|i| runnable(db, file_id, i))
29 .collect();
30 Ok(res)
31}
32
33fn runnable(db: &RootDatabase, file_id: FileId, item: &SyntaxNode) -> Option<Runnable> {
34 if let Some(fn_def) = ast::FnDef::cast(item) {
35 runnable_fn(fn_def)
36 } else if let Some(m) = ast::Module::cast(item) {
37 runnable_mod(db, file_id, m)
38 } else {
39 None
40 }
41}
42
43fn runnable_fn(fn_def: &ast::FnDef) -> Option<Runnable> {
44 let name = fn_def.name()?.text();
45 let kind = if name == "main" {
46 RunnableKind::Bin
47 } else if fn_def.has_atom_attr("test") {
48 RunnableKind::Test {
49 name: name.to_string(),
50 }
51 } else {
52 return None;
53 };
54 Some(Runnable {
55 range: fn_def.syntax().range(),
56 kind,
57 })
58}
59
60fn runnable_mod(db: &RootDatabase, file_id: FileId, module: &ast::Module) -> Option<Runnable> {
61 let has_test_function = module
62 .item_list()?
63 .items()
64 .filter_map(|it| match it.kind() {
65 ast::ModuleItemKind::FnDef(it) => Some(it),
66 _ => None,
67 })
68 .any(|f| f.has_atom_attr("test"));
69 if !has_test_function {
70 return None;
71 }
72 let range = module.syntax().range();
73 let module =
74 hir::source_binder::module_from_child_node(db, file_id, module.syntax()).ok()??;
75
76 // FIXME: thread cancellation instead of `.ok`ing
77 let path = module
78 .path_to_root(db)
79 .ok()?
80 .into_iter()
81 .rev()
82 .filter_map(|it| it.name(db).ok())
83 .filter_map(|it| it)
84 .join("::");
85 Some(Runnable {
86 range,
87 kind: RunnableKind::TestMod { path },
88 })
89}