Skip to content

Commit 3434f1d

Browse files
committed
Add Run|Debug hover actions
1 parent de74c0d commit 3434f1d

File tree

6 files changed

+184
-24
lines changed

6 files changed

+184
-24
lines changed

crates/ra_ide/src/hover.rs

Lines changed: 125 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,34 +14,42 @@ use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffs
1414

1515
use crate::{
1616
display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav},
17-
FilePosition, NavigationTarget, RangeInfo,
17+
runnables::runnable,
18+
FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
1819
};
1920

2021
#[derive(Clone, Debug, PartialEq, Eq)]
2122
pub struct HoverConfig {
2223
pub implementations: bool,
24+
pub run: bool,
25+
pub debug: bool,
2326
}
2427

2528
impl Default for HoverConfig {
2629
fn default() -> Self {
27-
Self { implementations: true }
30+
Self { implementations: true, run: true, debug: true }
2831
}
2932
}
3033

3134
impl HoverConfig {
32-
pub const NO_ACTIONS: Self = Self { implementations: false };
35+
pub const NO_ACTIONS: Self = Self { implementations: false, run: false, debug: false };
3336

3437
pub fn any(&self) -> bool {
35-
self.implementations
38+
self.implementations || self.runnable()
3639
}
3740

3841
pub fn none(&self) -> bool {
3942
!self.any()
4043
}
44+
45+
pub fn runnable(&self) -> bool {
46+
self.run || self.debug
47+
}
4148
}
4249

4350
#[derive(Debug, Clone)]
4451
pub enum HoverAction {
52+
Runnable(Runnable),
4553
Implementaion(FilePosition),
4654
}
4755

@@ -125,6 +133,10 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
125133
res.push_action(action);
126134
}
127135

136+
if let Some(action) = runnable_action(&sema, name_kind, position.file_id) {
137+
res.push_action(action);
138+
}
139+
128140
return Some(RangeInfo::new(range, res));
129141
}
130142
}
@@ -175,6 +187,28 @@ fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<Hov
175187
}
176188
}
177189

190+
fn runnable_action(
191+
sema: &Semantics<RootDatabase>,
192+
def: Definition,
193+
file_id: FileId,
194+
) -> Option<HoverAction> {
195+
match def {
196+
Definition::ModuleDef(it) => match it {
197+
ModuleDef::Module(it) => match it.definition_source(sema.db).value {
198+
ModuleSource::Module(it) => runnable(&sema, it.syntax().clone(), file_id)
199+
.map(|it| HoverAction::Runnable(it)),
200+
_ => None,
201+
},
202+
ModuleDef::Function(it) => {
203+
runnable(&sema, it.source(sema.db).value.syntax().clone(), file_id)
204+
.map(|it| HoverAction::Runnable(it))
205+
}
206+
_ => None,
207+
},
208+
_ => None,
209+
}
210+
}
211+
178212
fn hover_text(
179213
docs: Option<String>,
180214
desc: Option<String>,
@@ -292,6 +326,7 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
292326
#[cfg(test)]
293327
mod tests {
294328
use super::*;
329+
use insta::assert_debug_snapshot;
295330

296331
use ra_db::FileLoader;
297332
use ra_syntax::TextRange;
@@ -309,6 +344,7 @@ mod tests {
309344
fn assert_impl_action(action: &HoverAction, position: u32) {
310345
let offset = match action {
311346
HoverAction::Implementaion(pos) => pos.offset,
347+
it => panic!("Unexpected hover action: {:#?}", it),
312348
};
313349
assert_eq!(offset, position.into());
314350
}
@@ -1176,4 +1212,89 @@ fn func(foo: i32) { if true { <|>foo; }; }
11761212
);
11771213
assert_impl_action(&actions[0], 5);
11781214
}
1215+
1216+
#[test]
1217+
fn test_hover_test_has_action() {
1218+
let (_, actions) = check_hover_result(
1219+
"
1220+
//- /lib.rs
1221+
#[test]
1222+
fn foo_<|>test() {}
1223+
",
1224+
&["fn foo_test()"],
1225+
);
1226+
assert_debug_snapshot!(actions,
1227+
@r###"
1228+
[
1229+
Runnable(
1230+
Runnable {
1231+
nav: NavigationTarget {
1232+
file_id: FileId(
1233+
1,
1234+
),
1235+
full_range: 0..24,
1236+
name: "foo_test",
1237+
kind: FN_DEF,
1238+
focus_range: Some(
1239+
11..19,
1240+
),
1241+
container_name: None,
1242+
description: None,
1243+
docs: None,
1244+
},
1245+
kind: Test {
1246+
test_id: Path(
1247+
"foo_test",
1248+
),
1249+
attr: TestAttr {
1250+
ignore: false,
1251+
},
1252+
},
1253+
cfg_exprs: [],
1254+
},
1255+
),
1256+
]
1257+
"###);
1258+
}
1259+
1260+
#[test]
1261+
fn test_hover_test_mod_has_action() {
1262+
let (_, actions) = check_hover_result(
1263+
"
1264+
//- /lib.rs
1265+
mod tests<|> {
1266+
#[test]
1267+
fn foo_test() {}
1268+
}
1269+
",
1270+
&["mod tests"],
1271+
);
1272+
assert_debug_snapshot!(actions,
1273+
@r###"
1274+
[
1275+
Runnable(
1276+
Runnable {
1277+
nav: NavigationTarget {
1278+
file_id: FileId(
1279+
1,
1280+
),
1281+
full_range: 0..46,
1282+
name: "tests",
1283+
kind: MODULE,
1284+
focus_range: Some(
1285+
4..9,
1286+
),
1287+
container_name: None,
1288+
description: None,
1289+
docs: None,
1290+
},
1291+
kind: TestMod {
1292+
path: "tests",
1293+
},
1294+
cfg_exprs: [],
1295+
},
1296+
),
1297+
]
1298+
"###);
1299+
}
11791300
}

crates/ra_ide/src/runnables.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ use ra_syntax::{
1111

1212
use crate::{display::ToNav, FileId, NavigationTarget};
1313

14-
#[derive(Debug)]
14+
#[derive(Debug, Clone)]
1515
pub struct Runnable {
1616
pub nav: NavigationTarget,
1717
pub kind: RunnableKind,
1818
pub cfg_exprs: Vec<CfgExpr>,
1919
}
2020

21-
#[derive(Debug)]
21+
#[derive(Debug, Clone)]
2222
pub enum TestId {
2323
Name(String),
2424
Path(String),
@@ -33,7 +33,7 @@ impl fmt::Display for TestId {
3333
}
3434
}
3535

36-
#[derive(Debug)]
36+
#[derive(Debug, Clone)]
3737
pub enum RunnableKind {
3838
Test { test_id: TestId, attr: TestAttr },
3939
TestMod { path: String },
@@ -95,7 +95,11 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
9595
source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect()
9696
}
9797

98-
fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> {
98+
pub(crate) fn runnable(
99+
sema: &Semantics<RootDatabase>,
100+
item: SyntaxNode,
101+
file_id: FileId,
102+
) -> Option<Runnable> {
99103
match_ast! {
100104
match item {
101105
ast::FnDef(it) => runnable_fn(sema, it, file_id),
@@ -171,7 +175,7 @@ fn runnable_fn(
171175
Some(Runnable { nav, kind, cfg_exprs })
172176
}
173177

174-
#[derive(Debug)]
178+
#[derive(Debug, Copy, Clone)]
175179
pub struct TestAttr {
176180
pub ignore: bool,
177181
}

crates/rust-analyzer/src/config.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,8 @@ impl Config {
285285
set(value, "/hoverActions/enable", &mut use_hover_actions);
286286
if use_hover_actions {
287287
set(value, "/hoverActions/implementations", &mut self.hover.implementations);
288+
set(value, "/hoverActions/run", &mut self.hover.run);
289+
set(value, "/hoverActions/debug", &mut self.hover.debug);
288290
} else {
289291
self.hover = HoverConfig::NO_ACTIONS;
290292
}

crates/rust-analyzer/src/main_loop/handlers.rs

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ use lsp_types::{
1818
TextDocumentIdentifier, Url, WorkspaceEdit,
1919
};
2020
use ra_ide::{
21-
FileId, FilePosition, FileRange, HoverAction, Query, RangeInfo, Runnable, RunnableKind, SearchScope,
22-
TextEdit,
21+
FileId, FilePosition, FileRange, HoverAction, Query, RangeInfo, Runnable, RunnableKind,
22+
SearchScope, TextEdit,
2323
};
2424
use ra_prof::profile;
2525
use ra_project_model::TargetKind;
@@ -403,12 +403,12 @@ pub fn handle_runnables(
403403
if !runnable.nav.full_range().contains_inclusive(offset) {
404404
continue;
405405
}
406-
}
406+
}
407407
if is_lib_target(&runnable, cargo_spec.as_ref()) {
408408
continue;
409409
}
410410

411-
res.push(to_proto::runnable(&snap, file_id, runnable)?);
411+
res.push(to_proto::runnable(&snap, file_id, &runnable)?);
412412
}
413413

414414
// Add `cargo check` and `cargo test` for the whole package
@@ -550,7 +550,7 @@ pub fn handle_hover(
550550
}),
551551
range: Some(range),
552552
},
553-
actions: prepare_hover_actions(&snap, info.info.actions()),
553+
actions: prepare_hover_actions(&snap, position.file_id, info.info.actions()),
554554
};
555555

556556
Ok(Some(hover))
@@ -818,7 +818,7 @@ pub fn handle_code_lens(
818818

819819
let action = runnable.action();
820820
let range = to_proto::range(&line_index, runnable.nav.range());
821-
let r = to_proto::runnable(&snap, file_id, runnable)?;
821+
let r = to_proto::runnable(&snap, file_id, &runnable)?;
822822
if snap.config.lens.run {
823823
let lens = CodeLens {
824824
range,
@@ -829,11 +829,8 @@ pub fn handle_code_lens(
829829
}
830830

831831
if action.debugee && snap.config.lens.debug {
832-
let debug_lens = CodeLens {
833-
range,
834-
command: Some(debug_single_command(r)),
835-
data: None,
836-
};
832+
let debug_lens =
833+
CodeLens { range, command: Some(debug_single_command(r)), data: None };
837834
lenses.push(debug_lens);
838835
}
839836
}
@@ -1183,8 +1180,33 @@ fn show_impl_command_link(
11831180
None
11841181
}
11851182

1183+
fn to_runnable_action(
1184+
snap: &GlobalStateSnapshot,
1185+
file_id: FileId,
1186+
runnable: &Runnable,
1187+
) -> Option<lsp_ext::CommandLinkGroup> {
1188+
to_proto::runnable(snap, file_id, runnable).ok().map(|r| {
1189+
let mut group = lsp_ext::CommandLinkGroup::default();
1190+
1191+
let action = runnable.action();
1192+
if snap.config.hover.run {
1193+
let run_command = run_single_command(&r, action.run_title);
1194+
group.commands.push(to_command_link(run_command, r.label.clone()));
1195+
}
1196+
1197+
if snap.config.hover.debug {
1198+
let hint = r.label.clone();
1199+
let dbg_command = debug_single_command(r);
1200+
group.commands.push(to_command_link(dbg_command, hint));
1201+
}
1202+
1203+
group
1204+
})
1205+
}
1206+
11861207
fn prepare_hover_actions(
11871208
snap: &GlobalStateSnapshot,
1209+
file_id: FileId,
11881210
actions: &[HoverAction],
11891211
) -> Vec<lsp_ext::CommandLinkGroup> {
11901212
if snap.config.hover.none() || !snap.config.client_caps.hover_actions {
@@ -1195,6 +1217,7 @@ fn prepare_hover_actions(
11951217
.iter()
11961218
.filter_map(|it| match it {
11971219
HoverAction::Implementaion(position) => show_impl_command_link(snap, position),
1220+
HoverAction::Runnable(r) => to_runnable_action(snap, file_id, r),
11981221
})
11991222
.collect()
12001223
}
@@ -1205,10 +1228,10 @@ fn is_lib_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>) -> b
12051228
if let Some(spec) = cargo_spec {
12061229
match spec.target_kind {
12071230
TargetKind::Bin => return true,
1208-
_ => ()
1231+
_ => (),
12091232
}
12101233
}
12111234
}
12121235

12131236
false
1214-
}
1237+
}

crates/rust-analyzer/src/to_proto.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -656,14 +656,14 @@ pub(crate) fn resolved_code_action(
656656
pub(crate) fn runnable(
657657
snap: &GlobalStateSnapshot,
658658
file_id: FileId,
659-
runnable: Runnable,
659+
runnable: &Runnable,
660660
) -> Result<lsp_ext::Runnable> {
661661
let spec = CargoTargetSpec::for_file(snap, file_id)?;
662662
let target = spec.as_ref().map(|s| s.target.clone());
663663
let (cargo_args, executable_args) =
664664
CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.cfg_exprs)?;
665665
let label = runnable.label(target);
666-
let location = location_link(snap, None, runnable.nav)?;
666+
let location = location_link(snap, None, runnable.nav.clone())?;
667667

668668
Ok(lsp_ext::Runnable {
669669
label,

editors/code/package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,16 @@
486486
"type": "boolean",
487487
"default": true
488488
},
489+
"rust-analyzer.hoverActions.run": {
490+
"markdownDescription": "Whether to show `Run` action. Only applies when `#rust-analyzer.hoverActions.enable#` is set.",
491+
"type": "boolean",
492+
"default": true
493+
},
494+
"rust-analyzer.hoverActions.debug": {
495+
"markdownDescription": "Whether to show `Debug` action. Only applies when `#rust-analyzer.hoverActions.enable#` is set.",
496+
"type": "boolean",
497+
"default": true
498+
},
489499
"rust-analyzer.linkedProjects": {
490500
"markdownDescription": [
491501
"Disable project auto-discovery in favor of explicitly specified set of projects.",

0 commit comments

Comments
 (0)