aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
-rw-r--r--docs/dev/README.md35
-rw-r--r--editors/code/package.json10
-rw-r--r--xtask/src/lib.rs57
-rw-r--r--xtask/src/main.rs7
-rw-r--r--xtask/src/release.rs67
10 files changed, 420 insertions, 126 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 {
diff --git a/docs/dev/README.md b/docs/dev/README.md
index 903cb4055..46ee030fc 100644
--- a/docs/dev/README.md
+++ b/docs/dev/README.md
@@ -241,6 +241,34 @@ struct Foo {
241For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines. 241For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines.
242If the line is too long, you want to split the sentence in two :-) 242If the line is too long, you want to split the sentence in two :-)
243 243
244## Preconditions
245
246Function preconditions should generally be expressed in types and provided by the caller (rather than checked by callee):
247
248```rust
249// Good
250fn frbonicate(walrus: Walrus) {
251 ...
252}
253
254// Not as good
255fn frobnicate(walrus: Option<Walrus>) {
256 let walrus = match walrus {
257 Some(it) => it,
258 None => return,
259 };
260 ...
261}
262```
263
264## Commit Style
265
266We don't have specific rules around git history hygiene.
267Maintaining clean git history is encouraged, but not enforced.
268We use rebase workflow, it's OK to rewrite history during PR review process.
269
270Avoid @mentioning people in commit messages, as such messages create a lot of duplicate notification traffic during rebases.
271
244# Architecture Invariants 272# Architecture Invariants
245 273
246This section tries to document high-level design constraints, which are not 274This section tries to document high-level design constraints, which are not
@@ -268,6 +296,13 @@ IDE assumes that all information is available at all times.
268IDE should use only types from `ra_hir`, and should not depend on the underling compiler types. 296IDE should use only types from `ra_hir`, and should not depend on the underling compiler types.
269`ra_hir` is a facade. 297`ra_hir` is a facade.
270 298
299## IDE API
300
301The main IDE crate (`ra_ide`) uses "Plain Old Data" for the API.
302Rather than talking in definitions and references, it talks in Strings and textual offsets.
303In general, API is centered around UI concerns -- the result of the call is what the user sees in the editor, and not what the compiler sees underneath.
304The results are 100% Rust specific though.
305
271# Logging 306# Logging
272 307
273Logging is done by both rust-analyzer and VS Code, so it might be tricky to 308Logging is done by both rust-analyzer and VS Code, so it might be tricky to
diff --git a/editors/code/package.json b/editors/code/package.json
index 859ab4477..779d7e1b8 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -486,6 +486,16 @@
486 "type": "boolean", 486 "type": "boolean",
487 "default": true 487 "default": true
488 }, 488 },
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 },
489 "rust-analyzer.linkedProjects": { 499 "rust-analyzer.linkedProjects": {
490 "markdownDescription": "Disable project auto-discovery in favor of explicitly specified set of projects. \nElements must be paths pointing to Cargo.toml, rust-project.json, or JSON objects in rust-project.json format", 500 "markdownDescription": "Disable project auto-discovery in favor of explicitly specified set of projects. \nElements must be paths pointing to Cargo.toml, rust-project.json, or JSON objects in rust-project.json format",
491 "type": "array", 501 "type": "array",
diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs
index 739f49f7b..747654c1f 100644
--- a/xtask/src/lib.rs
+++ b/xtask/src/lib.rs
@@ -4,6 +4,7 @@
4 4
5pub mod not_bash; 5pub mod not_bash;
6pub mod install; 6pub mod install;
7pub mod release;
7pub mod dist; 8pub mod dist;
8pub mod pre_commit; 9pub mod pre_commit;
9 10
@@ -19,7 +20,7 @@ use walkdir::{DirEntry, WalkDir};
19 20
20use crate::{ 21use crate::{
21 codegen::Mode, 22 codegen::Mode,
22 not_bash::{date_iso, fs2, pushd, pushenv, rm_rf, run}, 23 not_bash::{fs2, pushd, pushenv, rm_rf, run},
23}; 24};
24 25
25pub use anyhow::{bail, Context as _, Result}; 26pub use anyhow::{bail, Context as _, Result};
@@ -153,60 +154,6 @@ pub fn run_pre_cache() -> Result<()> {
153 Ok(()) 154 Ok(())
154} 155}
155 156
156pub fn run_release(dry_run: bool) -> Result<()> {
157 if !dry_run {
158 run!("git switch release")?;
159 run!("git fetch upstream --tags --force")?;
160 run!("git reset --hard tags/nightly")?;
161 run!("git push")?;
162 }
163 codegen::generate_assists_docs(Mode::Overwrite)?;
164 codegen::generate_feature_docs(Mode::Overwrite)?;
165
166 let website_root = project_root().join("../rust-analyzer.github.io");
167 let changelog_dir = website_root.join("./thisweek/_posts");
168
169 let today = date_iso()?;
170 let commit = run!("git rev-parse HEAD")?;
171 let changelog_n = fs2::read_dir(changelog_dir.as_path())?.count();
172
173 let contents = format!(
174 "\
175= Changelog #{}
176:sectanchors:
177:page-layout: post
178
179Commit: commit:{}[] +
180Release: release:{}[]
181
182== New Features
183
184* pr:[] .
185
186== Fixes
187
188== Internal Improvements
189",
190 changelog_n, commit, today
191 );
192
193 let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n));
194 fs2::write(&path, &contents)?;
195
196 for &adoc in ["manual.adoc", "generated_features.adoc", "generated_assists.adoc"].iter() {
197 let src = project_root().join("./docs/user/").join(adoc);
198 let dst = website_root.join(adoc);
199 fs2::copy(src, dst)?;
200 }
201
202 let tags = run!("git tag --list"; echo = false)?;
203 let prev_tag = tags.lines().filter(|line| is_release_tag(line)).last().unwrap();
204
205 println!("\n git log {}..HEAD --merges --reverse", prev_tag);
206
207 Ok(())
208}
209
210fn is_release_tag(tag: &str) -> bool { 157fn is_release_tag(tag: &str) -> bool {
211 tag.len() == "2020-02-24".len() && tag.starts_with(|c: char| c.is_ascii_digit()) 158 tag.len() == "2020-02-24".len() && tag.starts_with(|c: char| c.is_ascii_digit())
212} 159}
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index 81bb3a33f..f7a79362d 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -16,8 +16,9 @@ use xtask::{
16 dist::run_dist, 16 dist::run_dist,
17 install::{ClientOpt, InstallCmd, ServerOpt}, 17 install::{ClientOpt, InstallCmd, ServerOpt},
18 not_bash::pushd, 18 not_bash::pushd,
19 pre_commit, project_root, run_clippy, run_fuzzer, run_pre_cache, run_release, run_rustfmt, 19 pre_commit, project_root,
20 Result, 20 release::ReleaseCmd,
21 run_clippy, run_fuzzer, run_pre_cache, run_rustfmt, Result,
21}; 22};
22 23
23fn main() -> Result<()> { 24fn main() -> Result<()> {
@@ -102,7 +103,7 @@ FLAGS:
102 "release" => { 103 "release" => {
103 let dry_run = args.contains("--dry-run"); 104 let dry_run = args.contains("--dry-run");
104 args.finish()?; 105 args.finish()?;
105 run_release(dry_run) 106 ReleaseCmd { dry_run }.run()
106 } 107 }
107 "dist" => { 108 "dist" => {
108 let nightly = args.contains("--nightly"); 109 let nightly = args.contains("--nightly");
diff --git a/xtask/src/release.rs b/xtask/src/release.rs
new file mode 100644
index 000000000..36c912184
--- /dev/null
+++ b/xtask/src/release.rs
@@ -0,0 +1,67 @@
1use crate::{
2 codegen, is_release_tag,
3 not_bash::{date_iso, fs2, run},
4 project_root, Mode, Result,
5};
6
7pub struct ReleaseCmd {
8 pub dry_run: bool,
9}
10
11impl ReleaseCmd {
12 pub fn run(self) -> Result<()> {
13 if !self.dry_run {
14 run!("git switch release")?;
15 run!("git fetch upstream --tags --force")?;
16 run!("git reset --hard tags/nightly")?;
17 run!("git push")?;
18 }
19 codegen::generate_assists_docs(Mode::Overwrite)?;
20 codegen::generate_feature_docs(Mode::Overwrite)?;
21
22 let website_root = project_root().join("../rust-analyzer.github.io");
23 let changelog_dir = website_root.join("./thisweek/_posts");
24
25 let today = date_iso()?;
26 let commit = run!("git rev-parse HEAD")?;
27 let changelog_n = fs2::read_dir(changelog_dir.as_path())?.count();
28
29 let contents = format!(
30 "\
31 = Changelog #{}
32 :sectanchors:
33 :page-layout: post
34
35 Commit: commit:{}[] +
36 Release: release:{}[]
37
38 == New Features
39
40 * pr:[] .
41
42 == Fixes
43
44 == Internal Improvements
45 ",
46 changelog_n, commit, today
47 );
48
49 let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n));
50 fs2::write(&path, &contents)?;
51
52 for &adoc in ["manual.adoc", "generated_features.adoc", "generated_assists.adoc"].iter() {
53 let src = project_root().join("./docs/user/").join(adoc);
54 let dst = website_root.join(adoc);
55 fs2::copy(src, dst)?;
56 }
57
58 let tags = run!("git tag --list"; echo = false)?;
59 let prev_tag = tags.lines().filter(|line| is_release_tag(line)).last().unwrap();
60
61 let git_log = run!("git log {}..HEAD --merges --reverse", prev_tag; echo = false)?;
62 let git_log_dst = website_root.join("git.log");
63 fs2::write(git_log_dst, &git_log)?;
64
65 Ok(())
66 }
67}