aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-06-08 12:29:35 +0100
committerGitHub <[email protected]>2020-06-08 12:29:35 +0100
commit6e4fca5882bdd6d00dfcc384473c1f612e00baa2 (patch)
tree07fd36fde432cc8d42d23408a67133422a94fedb /crates
parenta5f427ced3ee0ec27041f0b7632b4baae6a736b2 (diff)
parentb7db9f058ad51b7ba47db02b581a76b6756d74e8 (diff)
Merge #4773
4773: Run|Debug hover actions. r=matklad a=vsrs ![hover_actions_run](https://user-images.githubusercontent.com/62505555/83335644-dfc1f780-a2b6-11ea-820b-ccaa82290e7d.gif) This hover actions work exactly like corresponding lenses. Co-authored-by: vsrs <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_ide/src/hover.rs142
-rw-r--r--crates/ra_ide/src/runnables.rs92
-rw-r--r--crates/rust-analyzer/src/config.rs2
-rw-r--r--crates/rust-analyzer/src/main_loop/handlers.rs122
-rw-r--r--crates/rust-analyzer/src/to_proto.rs12
5 files changed, 302 insertions, 68 deletions
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index 846d8c69b..ad78b7671 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -14,34 +14,43 @@ use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffs
14 14
15use crate::{ 15use crate::{
16 display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav}, 16 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,
18}; 19};
20use test_utils::mark;
19 21
20#[derive(Clone, Debug, PartialEq, Eq)] 22#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct HoverConfig { 23pub struct HoverConfig {
22 pub implementations: bool, 24 pub implementations: bool,
25 pub run: bool,
26 pub debug: bool,
23} 27}
24 28
25impl Default for HoverConfig { 29impl Default for HoverConfig {
26 fn default() -> Self { 30 fn default() -> Self {
27 Self { implementations: true } 31 Self { implementations: true, run: true, debug: true }
28 } 32 }
29} 33}
30 34
31impl HoverConfig { 35impl HoverConfig {
32 pub const NO_ACTIONS: Self = Self { implementations: false }; 36 pub const NO_ACTIONS: Self = Self { implementations: false, run: false, debug: false };
33 37
34 pub fn any(&self) -> bool { 38 pub fn any(&self) -> bool {
35 self.implementations 39 self.implementations || self.runnable()
36 } 40 }
37 41
38 pub fn none(&self) -> bool { 42 pub fn none(&self) -> bool {
39 !self.any() 43 !self.any()
40 } 44 }
45
46 pub fn runnable(&self) -> bool {
47 self.run || self.debug
48 }
41} 49}
42 50
43#[derive(Debug, Clone)] 51#[derive(Debug, Clone)]
44pub enum HoverAction { 52pub enum HoverAction {
53 Runnable(Runnable),
45 Implementaion(FilePosition), 54 Implementaion(FilePosition),
46} 55}
47 56
@@ -125,6 +134,10 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
125 res.push_action(action); 134 res.push_action(action);
126 } 135 }
127 136
137 if let Some(action) = runnable_action(&sema, name_kind, position.file_id) {
138 res.push_action(action);
139 }
140
128 return Some(RangeInfo::new(range, res)); 141 return Some(RangeInfo::new(range, res));
129 } 142 }
130 } 143 }
@@ -175,6 +188,36 @@ fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<Hov
175 } 188 }
176} 189}
177 190
191fn runnable_action(
192 sema: &Semantics<RootDatabase>,
193 def: Definition,
194 file_id: FileId,
195) -> Option<HoverAction> {
196 match def {
197 Definition::ModuleDef(it) => match it {
198 ModuleDef::Module(it) => match it.definition_source(sema.db).value {
199 ModuleSource::Module(it) => runnable(&sema, it.syntax().clone(), file_id)
200 .map(|it| HoverAction::Runnable(it)),
201 _ => None,
202 },
203 ModuleDef::Function(it) => {
204 let src = it.source(sema.db);
205 if src.file_id != file_id.into() {
206 mark::hit!(hover_macro_generated_struct_fn_doc_comment);
207 mark::hit!(hover_macro_generated_struct_fn_doc_attr);
208
209 return None;
210 }
211
212 runnable(&sema, src.value.syntax().clone(), file_id)
213 .map(|it| HoverAction::Runnable(it))
214 }
215 _ => None,
216 },
217 _ => None,
218 }
219}
220
178fn hover_text( 221fn hover_text(
179 docs: Option<String>, 222 docs: Option<String>,
180 desc: Option<String>, 223 desc: Option<String>,
@@ -292,6 +335,7 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
292#[cfg(test)] 335#[cfg(test)]
293mod tests { 336mod tests {
294 use super::*; 337 use super::*;
338 use insta::assert_debug_snapshot;
295 339
296 use ra_db::FileLoader; 340 use ra_db::FileLoader;
297 use ra_syntax::TextRange; 341 use ra_syntax::TextRange;
@@ -309,6 +353,7 @@ mod tests {
309 fn assert_impl_action(action: &HoverAction, position: u32) { 353 fn assert_impl_action(action: &HoverAction, position: u32) {
310 let offset = match action { 354 let offset = match action {
311 HoverAction::Implementaion(pos) => pos.offset, 355 HoverAction::Implementaion(pos) => pos.offset,
356 it => panic!("Unexpected hover action: {:#?}", it),
312 }; 357 };
313 assert_eq!(offset, position.into()); 358 assert_eq!(offset, position.into());
314 } 359 }
@@ -1076,6 +1121,8 @@ fn func(foo: i32) { if true { <|>foo; }; }
1076 1121
1077 #[test] 1122 #[test]
1078 fn test_hover_macro_generated_struct_fn_doc_comment() { 1123 fn test_hover_macro_generated_struct_fn_doc_comment() {
1124 mark::check!(hover_macro_generated_struct_fn_doc_comment);
1125
1079 check_hover_result( 1126 check_hover_result(
1080 r#" 1127 r#"
1081 //- /lib.rs 1128 //- /lib.rs
@@ -1102,6 +1149,8 @@ fn func(foo: i32) { if true { <|>foo; }; }
1102 1149
1103 #[test] 1150 #[test]
1104 fn test_hover_macro_generated_struct_fn_doc_attr() { 1151 fn test_hover_macro_generated_struct_fn_doc_attr() {
1152 mark::check!(hover_macro_generated_struct_fn_doc_attr);
1153
1105 check_hover_result( 1154 check_hover_result(
1106 r#" 1155 r#"
1107 //- /lib.rs 1156 //- /lib.rs
@@ -1176,4 +1225,89 @@ fn func(foo: i32) { if true { <|>foo; }; }
1176 ); 1225 );
1177 assert_impl_action(&actions[0], 5); 1226 assert_impl_action(&actions[0], 5);
1178 } 1227 }
1228
1229 #[test]
1230 fn test_hover_test_has_action() {
1231 let (_, actions) = check_hover_result(
1232 "
1233 //- /lib.rs
1234 #[test]
1235 fn foo_<|>test() {}
1236 ",
1237 &["fn foo_test()"],
1238 );
1239 assert_debug_snapshot!(actions,
1240 @r###"
1241 [
1242 Runnable(
1243 Runnable {
1244 nav: NavigationTarget {
1245 file_id: FileId(
1246 1,
1247 ),
1248 full_range: 0..24,
1249 name: "foo_test",
1250 kind: FN_DEF,
1251 focus_range: Some(
1252 11..19,
1253 ),
1254 container_name: None,
1255 description: None,
1256 docs: None,
1257 },
1258 kind: Test {
1259 test_id: Path(
1260 "foo_test",
1261 ),
1262 attr: TestAttr {
1263 ignore: false,
1264 },
1265 },
1266 cfg_exprs: [],
1267 },
1268 ),
1269 ]
1270 "###);
1271 }
1272
1273 #[test]
1274 fn test_hover_test_mod_has_action() {
1275 let (_, actions) = check_hover_result(
1276 "
1277 //- /lib.rs
1278 mod tests<|> {
1279 #[test]
1280 fn foo_test() {}
1281 }
1282 ",
1283 &["mod tests"],
1284 );
1285 assert_debug_snapshot!(actions,
1286 @r###"
1287 [
1288 Runnable(
1289 Runnable {
1290 nav: NavigationTarget {
1291 file_id: FileId(
1292 1,
1293 ),
1294 full_range: 0..46,
1295 name: "tests",
1296 kind: MODULE,
1297 focus_range: Some(
1298 4..9,
1299 ),
1300 container_name: None,
1301 description: None,
1302 docs: None,
1303 },
1304 kind: TestMod {
1305 path: "tests",
1306 },
1307 cfg_exprs: [],
1308 },
1309 ),
1310 ]
1311 "###);
1312 }
1179} 1313}
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs
index f32ce0d22..fc57dc33d 100644
--- a/crates/ra_ide/src/runnables.rs
+++ b/crates/ra_ide/src/runnables.rs
@@ -11,14 +11,14 @@ use ra_syntax::{
11 11
12use crate::{display::ToNav, FileId, NavigationTarget}; 12use crate::{display::ToNav, FileId, NavigationTarget};
13 13
14#[derive(Debug)] 14#[derive(Debug, Clone)]
15pub struct Runnable { 15pub struct Runnable {
16 pub nav: NavigationTarget, 16 pub nav: NavigationTarget,
17 pub kind: RunnableKind, 17 pub kind: RunnableKind,
18 pub cfg_exprs: Vec<CfgExpr>, 18 pub cfg_exprs: Vec<CfgExpr>,
19} 19}
20 20
21#[derive(Debug)] 21#[derive(Debug, Clone)]
22pub enum TestId { 22pub enum TestId {
23 Name(String), 23 Name(String),
24 Path(String), 24 Path(String),
@@ -33,7 +33,7 @@ impl fmt::Display for TestId {
33 } 33 }
34} 34}
35 35
36#[derive(Debug)] 36#[derive(Debug, Clone)]
37pub enum RunnableKind { 37pub enum RunnableKind {
38 Test { test_id: TestId, attr: TestAttr }, 38 Test { test_id: TestId, attr: TestAttr },
39 TestMod { path: String }, 39 TestMod { path: String },
@@ -42,6 +42,42 @@ pub enum RunnableKind {
42 Bin, 42 Bin,
43} 43}
44 44
45#[derive(Debug, Eq, PartialEq)]
46pub struct RunnableAction {
47 pub run_title: &'static str,
48 pub debugee: bool,
49}
50
51const TEST: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Test", debugee: true };
52const DOCTEST: RunnableAction =
53 RunnableAction { run_title: "▶\u{fe0e} Run Doctest", debugee: false };
54const BENCH: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Bench", debugee: true };
55const BIN: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run", debugee: true };
56
57impl Runnable {
58 // test package::module::testname
59 pub fn label(&self, target: Option<String>) -> String {
60 match &self.kind {
61 RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
62 RunnableKind::TestMod { path } => format!("test-mod {}", path),
63 RunnableKind::Bench { test_id } => format!("bench {}", test_id),
64 RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id),
65 RunnableKind::Bin => {
66 target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t))
67 }
68 }
69 }
70
71 pub fn action(&self) -> &'static RunnableAction {
72 match &self.kind {
73 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => &TEST,
74 RunnableKind::DocTest { .. } => &DOCTEST,
75 RunnableKind::Bench { .. } => &BENCH,
76 RunnableKind::Bin => &BIN,
77 }
78 }
79}
80
45// Feature: Run 81// Feature: Run
46// 82//
47// Shows a popup suggesting to run a test/benchmark/binary **at the current cursor 83// Shows a popup suggesting to run a test/benchmark/binary **at the current cursor
@@ -59,7 +95,11 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
59 source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect() 95 source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect()
60} 96}
61 97
62fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> { 98pub(crate) fn runnable(
99 sema: &Semantics<RootDatabase>,
100 item: SyntaxNode,
101 file_id: FileId,
102) -> Option<Runnable> {
63 match_ast! { 103 match_ast! {
64 match item { 104 match item {
65 ast::FnDef(it) => runnable_fn(sema, it, file_id), 105 ast::FnDef(it) => runnable_fn(sema, it, file_id),
@@ -135,7 +175,7 @@ fn runnable_fn(
135 Some(Runnable { nav, kind, cfg_exprs }) 175 Some(Runnable { nav, kind, cfg_exprs })
136} 176}
137 177
138#[derive(Debug)] 178#[derive(Debug, Copy, Clone)]
139pub struct TestAttr { 179pub struct TestAttr {
140 pub ignore: bool, 180 pub ignore: bool,
141} 181}
@@ -207,6 +247,15 @@ mod tests {
207 247
208 use crate::mock_analysis::analysis_and_position; 248 use crate::mock_analysis::analysis_and_position;
209 249
250 use super::{Runnable, RunnableAction, BENCH, BIN, DOCTEST, TEST};
251
252 fn assert_actions(runnables: &[Runnable], actions: &[&RunnableAction]) {
253 assert_eq!(
254 actions,
255 runnables.into_iter().map(|it| it.action()).collect::<Vec<_>>().as_slice()
256 );
257 }
258
210 #[test] 259 #[test]
211 fn test_runnables() { 260 fn test_runnables() {
212 let (analysis, pos) = analysis_and_position( 261 let (analysis, pos) = analysis_and_position(
@@ -221,6 +270,9 @@ mod tests {
221 #[test] 270 #[test]
222 #[ignore] 271 #[ignore]
223 fn test_foo() {} 272 fn test_foo() {}
273
274 #[bench]
275 fn bench() {}
224 "#, 276 "#,
225 ); 277 );
226 let runnables = analysis.runnables(pos.file_id).unwrap(); 278 let runnables = analysis.runnables(pos.file_id).unwrap();
@@ -295,9 +347,32 @@ mod tests {
295 }, 347 },
296 cfg_exprs: [], 348 cfg_exprs: [],
297 }, 349 },
350 Runnable {
351 nav: NavigationTarget {
352 file_id: FileId(
353 1,
354 ),
355 full_range: 82..104,
356 name: "bench",
357 kind: FN_DEF,
358 focus_range: Some(
359 94..99,
360 ),
361 container_name: None,
362 description: None,
363 docs: None,
364 },
365 kind: Bench {
366 test_id: Path(
367 "bench",
368 ),
369 },
370 cfg_exprs: [],
371 },
298 ] 372 ]
299 "### 373 "###
300 ); 374 );
375 assert_actions(&runnables, &[&BIN, &TEST, &TEST, &BENCH]);
301 } 376 }
302 377
303 #[test] 378 #[test]
@@ -361,6 +436,7 @@ mod tests {
361 ] 436 ]
362 "### 437 "###
363 ); 438 );
439 assert_actions(&runnables, &[&BIN, &DOCTEST]);
364 } 440 }
365 441
366 #[test] 442 #[test]
@@ -427,6 +503,7 @@ mod tests {
427 ] 503 ]
428 "### 504 "###
429 ); 505 );
506 assert_actions(&runnables, &[&BIN, &DOCTEST]);
430 } 507 }
431 508
432 #[test] 509 #[test]
@@ -493,6 +570,7 @@ mod tests {
493 ] 570 ]
494 "### 571 "###
495 ); 572 );
573 assert_actions(&runnables, &[&TEST, &TEST]);
496 } 574 }
497 575
498 #[test] 576 #[test]
@@ -561,6 +639,7 @@ mod tests {
561 ] 639 ]
562 "### 640 "###
563 ); 641 );
642 assert_actions(&runnables, &[&TEST, &TEST]);
564 } 643 }
565 644
566 #[test] 645 #[test]
@@ -631,6 +710,7 @@ mod tests {
631 ] 710 ]
632 "### 711 "###
633 ); 712 );
713 assert_actions(&runnables, &[&TEST, &TEST]);
634 } 714 }
635 715
636 #[test] 716 #[test]
@@ -681,6 +761,7 @@ mod tests {
681 ] 761 ]
682 "### 762 "###
683 ); 763 );
764 assert_actions(&runnables, &[&TEST]);
684 } 765 }
685 766
686 #[test] 767 #[test]
@@ -739,6 +820,7 @@ mod tests {
739 ] 820 ]
740 "### 821 "###
741 ); 822 );
823 assert_actions(&runnables, &[&TEST]);
742 } 824 }
743 825
744 #[test] 826 #[test]
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 8d6efdbe8..17671f89e 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -285,6 +285,8 @@ impl Config {
285 set(value, "/hoverActions/enable", &mut use_hover_actions); 285 set(value, "/hoverActions/enable", &mut use_hover_actions);
286 if use_hover_actions { 286 if use_hover_actions {
287 set(value, "/hoverActions/implementations", &mut self.hover.implementations); 287 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);
288 } else { 290 } else {
289 self.hover = HoverConfig::NO_ACTIONS; 291 self.hover = HoverConfig::NO_ACTIONS;
290 } 292 }
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index 3ff779702..a41adf8b0 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -18,8 +18,8 @@ use lsp_types::{
18 TextDocumentIdentifier, Url, WorkspaceEdit, 18 TextDocumentIdentifier, Url, WorkspaceEdit,
19}; 19};
20use ra_ide::{ 20use ra_ide::{
21 FileId, FilePosition, FileRange, HoverAction, Query, RangeInfo, RunnableKind, SearchScope, 21 FileId, FilePosition, FileRange, HoverAction, Query, RangeInfo, Runnable, RunnableKind,
22 TextEdit, 22 SearchScope, TextEdit,
23}; 23};
24use ra_prof::profile; 24use ra_prof::profile;
25use ra_project_model::TargetKind; 25use ra_project_model::TargetKind;
@@ -404,15 +404,10 @@ pub fn handle_runnables(
404 continue; 404 continue;
405 } 405 }
406 } 406 }
407 // Do not suggest binary run on other target than binary 407 if should_skip_target(&runnable, cargo_spec.as_ref()) {
408 if let RunnableKind::Bin = runnable.kind { 408 continue;
409 if let Some(spec) = &cargo_spec {
410 match spec.target_kind {
411 TargetKind::Bin => {}
412 _ => continue,
413 }
414 }
415 } 409 }
410
416 res.push(to_proto::runnable(&snap, file_id, runnable)?); 411 res.push(to_proto::runnable(&snap, file_id, runnable)?);
417 } 412 }
418 413
@@ -555,7 +550,7 @@ pub fn handle_hover(
555 }), 550 }),
556 range: Some(range), 551 range: Some(range),
557 }, 552 },
558 actions: prepare_hover_actions(&snap, info.info.actions()), 553 actions: prepare_hover_actions(&snap, position.file_id, info.info.actions()),
559 }; 554 };
560 555
561 Ok(Some(hover)) 556 Ok(Some(hover))
@@ -817,55 +812,25 @@ pub fn handle_code_lens(
817 if snap.config.lens.runnable() { 812 if snap.config.lens.runnable() {
818 // Gather runnables 813 // Gather runnables
819 for runnable in snap.analysis().runnables(file_id)? { 814 for runnable in snap.analysis().runnables(file_id)? {
820 let (run_title, debugee) = match &runnable.kind { 815 if should_skip_target(&runnable, cargo_spec.as_ref()) {
821 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => { 816 continue;
822 ("▶\u{fe0e} Run Test", true) 817 }
823 }
824 RunnableKind::DocTest { .. } => {
825 // cargo does not support -no-run for doctests
826 ("▶\u{fe0e} Run Doctest", false)
827 }
828 RunnableKind::Bench { .. } => {
829 // Nothing wrong with bench debugging
830 ("Run Bench", true)
831 }
832 RunnableKind::Bin => {
833 // Do not suggest binary run on other target than binary
834 match &cargo_spec {
835 Some(spec) => match spec.target_kind {
836 TargetKind::Bin => ("Run", true),
837 _ => continue,
838 },
839 None => continue,
840 }
841 }
842 };
843 818
819 let action = runnable.action();
844 let range = to_proto::range(&line_index, runnable.nav.range()); 820 let range = to_proto::range(&line_index, runnable.nav.range());
845 let r = to_proto::runnable(&snap, file_id, runnable)?; 821 let r = to_proto::runnable(&snap, file_id, runnable)?;
846 if snap.config.lens.run { 822 if snap.config.lens.run {
847 let lens = CodeLens { 823 let lens = CodeLens {
848 range, 824 range,
849 command: Some(Command { 825 command: Some(run_single_command(&r, action.run_title)),
850 title: run_title.to_string(),
851 command: "rust-analyzer.runSingle".into(),
852 arguments: Some(vec![to_value(&r).unwrap()]),
853 }),
854 data: None, 826 data: None,
855 }; 827 };
856 lenses.push(lens); 828 lenses.push(lens);
857 } 829 }
858 830
859 if debugee && snap.config.lens.debug { 831 if action.debugee && snap.config.lens.debug {
860 let debug_lens = CodeLens { 832 let debug_lens =
861 range, 833 CodeLens { range, command: Some(debug_single_command(&r)), data: None };
862 command: Some(Command {
863 title: "Debug".into(),
864 command: "rust-analyzer.debugSingle".into(),
865 arguments: Some(vec![to_value(r).unwrap()]),
866 }),
867 data: None,
868 };
869 lenses.push(debug_lens); 834 lenses.push(debug_lens);
870 } 835 }
871 } 836 }
@@ -1169,6 +1134,22 @@ fn show_references_command(
1169 } 1134 }
1170} 1135}
1171 1136
1137fn run_single_command(runnable: &lsp_ext::Runnable, title: &str) -> Command {
1138 Command {
1139 title: title.to_string(),
1140 command: "rust-analyzer.runSingle".into(),
1141 arguments: Some(vec![to_value(runnable).unwrap()]),
1142 }
1143}
1144
1145fn debug_single_command(runnable: &lsp_ext::Runnable) -> Command {
1146 Command {
1147 title: "Debug".into(),
1148 command: "rust-analyzer.debugSingle".into(),
1149 arguments: Some(vec![to_value(runnable).unwrap()]),
1150 }
1151}
1152
1172fn to_command_link(command: Command, tooltip: String) -> lsp_ext::CommandLink { 1153fn to_command_link(command: Command, tooltip: String) -> lsp_ext::CommandLink {
1173 lsp_ext::CommandLink { tooltip: Some(tooltip), command } 1154 lsp_ext::CommandLink { tooltip: Some(tooltip), command }
1174} 1155}
@@ -1199,8 +1180,37 @@ fn show_impl_command_link(
1199 None 1180 None
1200} 1181}
1201 1182
1183fn to_runnable_action(
1184 snap: &GlobalStateSnapshot,
1185 file_id: FileId,
1186 runnable: Runnable,
1187) -> Option<lsp_ext::CommandLinkGroup> {
1188 let cargo_spec = CargoTargetSpec::for_file(&snap, file_id).ok()?;
1189 if should_skip_target(&runnable, cargo_spec.as_ref()) {
1190 return None;
1191 }
1192
1193 let action: &'static _ = runnable.action();
1194 to_proto::runnable(snap, file_id, runnable).ok().map(|r| {
1195 let mut group = lsp_ext::CommandLinkGroup::default();
1196
1197 if snap.config.hover.run {
1198 let run_command = run_single_command(&r, action.run_title);
1199 group.commands.push(to_command_link(run_command, r.label.clone()));
1200 }
1201
1202 if snap.config.hover.debug {
1203 let dbg_command = debug_single_command(&r);
1204 group.commands.push(to_command_link(dbg_command, r.label));
1205 }
1206
1207 group
1208 })
1209}
1210
1202fn prepare_hover_actions( 1211fn prepare_hover_actions(
1203 snap: &GlobalStateSnapshot, 1212 snap: &GlobalStateSnapshot,
1213 file_id: FileId,
1204 actions: &[HoverAction], 1214 actions: &[HoverAction],
1205) -> Vec<lsp_ext::CommandLinkGroup> { 1215) -> Vec<lsp_ext::CommandLinkGroup> {
1206 if snap.config.hover.none() || !snap.config.client_caps.hover_actions { 1216 if snap.config.hover.none() || !snap.config.client_caps.hover_actions {
@@ -1211,6 +1221,20 @@ fn prepare_hover_actions(
1211 .iter() 1221 .iter()
1212 .filter_map(|it| match it { 1222 .filter_map(|it| match it {
1213 HoverAction::Implementaion(position) => show_impl_command_link(snap, position), 1223 HoverAction::Implementaion(position) => show_impl_command_link(snap, position),
1224 HoverAction::Runnable(r) => to_runnable_action(snap, file_id, r.clone()),
1214 }) 1225 })
1215 .collect() 1226 .collect()
1216} 1227}
1228
1229fn should_skip_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>) -> bool {
1230 match runnable.kind {
1231 RunnableKind::Bin => {
1232 // Do not suggest binary run on other target than binary
1233 match &cargo_spec {
1234 Some(spec) => spec.target_kind != TargetKind::Bin,
1235 None => true,
1236 }
1237 }
1238 _ => false,
1239 }
1240}
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 1da4d80ec..710df1fbd 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -4,7 +4,7 @@ use ra_ide::{
4 Assist, CompletionItem, CompletionItemKind, Documentation, FileSystemEdit, Fold, FoldKind, 4 Assist, CompletionItem, CompletionItemKind, Documentation, FileSystemEdit, Fold, FoldKind,
5 FunctionSignature, Highlight, HighlightModifier, HighlightTag, HighlightedRange, Indel, 5 FunctionSignature, Highlight, HighlightModifier, HighlightTag, HighlightedRange, Indel,
6 InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, ReferenceAccess, 6 InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, ReferenceAccess,
7 ResolvedAssist, Runnable, RunnableKind, Severity, SourceChange, SourceFileEdit, TextEdit, 7 ResolvedAssist, Runnable, Severity, SourceChange, SourceFileEdit, TextEdit,
8}; 8};
9use ra_syntax::{SyntaxKind, TextRange, TextSize}; 9use ra_syntax::{SyntaxKind, TextRange, TextSize};
10use ra_vfs::LineEndings; 10use ra_vfs::LineEndings;
@@ -662,15 +662,7 @@ pub(crate) fn runnable(
662 let target = spec.as_ref().map(|s| s.target.clone()); 662 let target = spec.as_ref().map(|s| s.target.clone());
663 let (cargo_args, executable_args) = 663 let (cargo_args, executable_args) =
664 CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.cfg_exprs)?; 664 CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.cfg_exprs)?;
665 let label = match &runnable.kind { 665 let label = runnable.label(target);
666 RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
667 RunnableKind::TestMod { path } => format!("test-mod {}", path),
668 RunnableKind::Bench { test_id } => format!("bench {}", test_id),
669 RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id),
670 RunnableKind::Bin => {
671 target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t))
672 }
673 };
674 let location = location_link(snap, None, runnable.nav)?; 666 let location = location_link(snap, None, runnable.nav)?;
675 667
676 Ok(lsp_ext::Runnable { 668 Ok(lsp_ext::Runnable {