diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-03-13 13:50:35 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2021-03-13 13:50:35 +0000 |
commit | 7accf6bc37c059a83a58c82f463f02a02ed2226f (patch) | |
tree | 979c8ebe39bde9c989cb1a09f06cea4cf7cb04ac /crates | |
parent | 9674490b45a931399a9a05cd3a4c7e8fae9ec2a8 (diff) | |
parent | 49cdb2452a225dec3834e69fb011c3d7e68084f7 (diff) |
Merge #7799
7799: Related tests r=matklad a=vsrs
![tests](https://user-images.githubusercontent.com/62505555/109397453-a9013680-7947-11eb-8b11-ac03079f7645.gif)
This adds an ability to look for tests for the item under the cursor: function, constant, data type, etc
The LSP part is bound to change. But the feature itself already works and I'm looking for a feedback :)
Co-authored-by: vsrs <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r-- | crates/cfg/src/cfg_expr.rs | 2 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 9 | ||||
-rw-r--r-- | crates/ide/src/runnables.rs | 344 | ||||
-rw-r--r-- | crates/ide_db/src/search.rs | 4 | ||||
-rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 30 | ||||
-rw-r--r-- | crates/rust-analyzer/src/lsp_ext.rs | 13 | ||||
-rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 1 | ||||
-rw-r--r-- | crates/rust-analyzer/src/to_proto.rs | 5 |
8 files changed, 391 insertions, 17 deletions
diff --git a/crates/cfg/src/cfg_expr.rs b/crates/cfg/src/cfg_expr.rs index 42327f1e1..069fc01d0 100644 --- a/crates/cfg/src/cfg_expr.rs +++ b/crates/cfg/src/cfg_expr.rs | |||
@@ -49,7 +49,7 @@ impl fmt::Display for CfgAtom { | |||
49 | } | 49 | } |
50 | } | 50 | } |
51 | 51 | ||
52 | #[derive(Debug, Clone, PartialEq, Eq)] | 52 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
53 | pub enum CfgExpr { | 53 | pub enum CfgExpr { |
54 | Invalid, | 54 | Invalid, |
55 | Atom(CfgAtom), | 55 | Atom(CfgAtom), |
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 0a493d2f3..a8b169e87 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -447,6 +447,15 @@ impl Analysis { | |||
447 | self.with_db(|db| runnables::runnables(db, file_id)) | 447 | self.with_db(|db| runnables::runnables(db, file_id)) |
448 | } | 448 | } |
449 | 449 | ||
450 | /// Returns the set of tests for the given file position. | ||
451 | pub fn related_tests( | ||
452 | &self, | ||
453 | position: FilePosition, | ||
454 | search_scope: Option<SearchScope>, | ||
455 | ) -> Cancelable<Vec<Runnable>> { | ||
456 | self.with_db(|db| runnables::related_tests(db, position, search_scope)) | ||
457 | } | ||
458 | |||
450 | /// Computes syntax highlighting for the given file | 459 | /// Computes syntax highlighting for the given file |
451 | pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HlRange>> { | 460 | pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HlRange>> { |
452 | self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false)) | 461 | self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false)) |
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 280565563..faa91541e 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs | |||
@@ -1,10 +1,17 @@ | |||
1 | use std::fmt; | 1 | use std::fmt; |
2 | 2 | ||
3 | use ast::NameOwner; | ||
3 | use cfg::CfgExpr; | 4 | use cfg::CfgExpr; |
4 | use hir::{AsAssocItem, HasAttrs, HasSource, Semantics}; | 5 | use hir::{AsAssocItem, HasAttrs, HasSource, Semantics}; |
5 | use ide_assists::utils::test_related_attribute; | 6 | use ide_assists::utils::test_related_attribute; |
6 | use ide_db::{defs::Definition, RootDatabase, SymbolKind}; | 7 | use ide_db::{ |
8 | base_db::{FilePosition, FileRange}, | ||
9 | defs::Definition, | ||
10 | search::SearchScope, | ||
11 | RootDatabase, SymbolKind, | ||
12 | }; | ||
7 | use itertools::Itertools; | 13 | use itertools::Itertools; |
14 | use rustc_hash::FxHashSet; | ||
8 | use syntax::{ | 15 | use syntax::{ |
9 | ast::{self, AstNode, AttrsOwner}, | 16 | ast::{self, AstNode, AttrsOwner}, |
10 | match_ast, SyntaxNode, | 17 | match_ast, SyntaxNode, |
@@ -12,17 +19,17 @@ use syntax::{ | |||
12 | 19 | ||
13 | use crate::{ | 20 | use crate::{ |
14 | display::{ToNav, TryToNav}, | 21 | display::{ToNav, TryToNav}, |
15 | FileId, NavigationTarget, | 22 | references, FileId, NavigationTarget, |
16 | }; | 23 | }; |
17 | 24 | ||
18 | #[derive(Debug, Clone)] | 25 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] |
19 | pub struct Runnable { | 26 | pub struct Runnable { |
20 | pub nav: NavigationTarget, | 27 | pub nav: NavigationTarget, |
21 | pub kind: RunnableKind, | 28 | pub kind: RunnableKind, |
22 | pub cfg: Option<CfgExpr>, | 29 | pub cfg: Option<CfgExpr>, |
23 | } | 30 | } |
24 | 31 | ||
25 | #[derive(Debug, Clone)] | 32 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] |
26 | pub enum TestId { | 33 | pub enum TestId { |
27 | Name(String), | 34 | Name(String), |
28 | Path(String), | 35 | Path(String), |
@@ -37,7 +44,7 @@ impl fmt::Display for TestId { | |||
37 | } | 44 | } |
38 | } | 45 | } |
39 | 46 | ||
40 | #[derive(Debug, Clone)] | 47 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] |
41 | pub enum RunnableKind { | 48 | pub enum RunnableKind { |
42 | Test { test_id: TestId, attr: TestAttr }, | 49 | Test { test_id: TestId, attr: TestAttr }, |
43 | TestMod { path: String }, | 50 | TestMod { path: String }, |
@@ -105,6 +112,105 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { | |||
105 | res | 112 | res |
106 | } | 113 | } |
107 | 114 | ||
115 | // Feature: Related Tests | ||
116 | // | ||
117 | // Provides a sneak peek of all tests where the current item is used. | ||
118 | // | ||
119 | // The simplest way to use this feature is via the context menu: | ||
120 | // - Right-click on the selected item. The context menu opens. | ||
121 | // - Select **Peek related tests** | ||
122 | // | ||
123 | // |=== | ||
124 | // | Editor | Action Name | ||
125 | // | ||
126 | // | VS Code | **Rust Analyzer: Peek related tests** | ||
127 | // |=== | ||
128 | pub(crate) fn related_tests( | ||
129 | db: &RootDatabase, | ||
130 | position: FilePosition, | ||
131 | search_scope: Option<SearchScope>, | ||
132 | ) -> Vec<Runnable> { | ||
133 | let sema = Semantics::new(db); | ||
134 | let mut res: FxHashSet<Runnable> = FxHashSet::default(); | ||
135 | |||
136 | find_related_tests(&sema, position, search_scope, &mut res); | ||
137 | |||
138 | res.into_iter().collect_vec() | ||
139 | } | ||
140 | |||
141 | fn find_related_tests( | ||
142 | sema: &Semantics<RootDatabase>, | ||
143 | position: FilePosition, | ||
144 | search_scope: Option<SearchScope>, | ||
145 | tests: &mut FxHashSet<Runnable>, | ||
146 | ) { | ||
147 | if let Some(refs) = references::find_all_refs(&sema, position, search_scope) { | ||
148 | for (file_id, refs) in refs.references { | ||
149 | let file = sema.parse(file_id); | ||
150 | let file = file.syntax(); | ||
151 | let functions = refs.iter().filter_map(|(range, _)| { | ||
152 | let token = file.token_at_offset(range.start()).next()?; | ||
153 | let token = sema.descend_into_macros(token); | ||
154 | let syntax = token.parent(); | ||
155 | syntax.ancestors().find_map(ast::Fn::cast) | ||
156 | }); | ||
157 | |||
158 | for fn_def in functions { | ||
159 | if let Some(runnable) = as_test_runnable(&sema, &fn_def) { | ||
160 | // direct test | ||
161 | tests.insert(runnable); | ||
162 | } else if let Some(module) = parent_test_module(&sema, &fn_def) { | ||
163 | // indirect test | ||
164 | find_related_tests_in_module(sema, &fn_def, &module, tests); | ||
165 | } | ||
166 | } | ||
167 | } | ||
168 | } | ||
169 | } | ||
170 | |||
171 | fn find_related_tests_in_module( | ||
172 | sema: &Semantics<RootDatabase>, | ||
173 | fn_def: &ast::Fn, | ||
174 | parent_module: &hir::Module, | ||
175 | tests: &mut FxHashSet<Runnable>, | ||
176 | ) { | ||
177 | if let Some(fn_name) = fn_def.name() { | ||
178 | let mod_source = parent_module.definition_source(sema.db); | ||
179 | let range = match mod_source.value { | ||
180 | hir::ModuleSource::Module(m) => m.syntax().text_range(), | ||
181 | hir::ModuleSource::BlockExpr(b) => b.syntax().text_range(), | ||
182 | hir::ModuleSource::SourceFile(f) => f.syntax().text_range(), | ||
183 | }; | ||
184 | |||
185 | let file_id = mod_source.file_id.original_file(sema.db); | ||
186 | let mod_scope = SearchScope::file_range(FileRange { file_id, range }); | ||
187 | let fn_pos = FilePosition { file_id, offset: fn_name.syntax().text_range().start() }; | ||
188 | find_related_tests(sema, fn_pos, Some(mod_scope), tests) | ||
189 | } | ||
190 | } | ||
191 | |||
192 | fn as_test_runnable(sema: &Semantics<RootDatabase>, fn_def: &ast::Fn) -> Option<Runnable> { | ||
193 | if test_related_attribute(&fn_def).is_some() { | ||
194 | let function = sema.to_def(fn_def)?; | ||
195 | runnable_fn(sema, function) | ||
196 | } else { | ||
197 | None | ||
198 | } | ||
199 | } | ||
200 | |||
201 | fn parent_test_module(sema: &Semantics<RootDatabase>, fn_def: &ast::Fn) -> Option<hir::Module> { | ||
202 | fn_def.syntax().ancestors().find_map(|node| { | ||
203 | let module = ast::Module::cast(node)?; | ||
204 | let module = sema.to_def(&module)?; | ||
205 | |||
206 | if has_test_function_or_multiple_test_submodules(sema, &module) { | ||
207 | Some(module) | ||
208 | } else { | ||
209 | None | ||
210 | } | ||
211 | }) | ||
212 | } | ||
213 | |||
108 | fn runnables_mod(sema: &Semantics<RootDatabase>, acc: &mut Vec<Runnable>, module: hir::Module) { | 214 | fn runnables_mod(sema: &Semantics<RootDatabase>, acc: &mut Vec<Runnable>, module: hir::Module) { |
109 | acc.extend(module.declarations(sema.db).into_iter().filter_map(|def| { | 215 | acc.extend(module.declarations(sema.db).into_iter().filter_map(|def| { |
110 | let runnable = match def { | 216 | let runnable = match def { |
@@ -256,7 +362,7 @@ fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Op | |||
256 | Some(res) | 362 | Some(res) |
257 | } | 363 | } |
258 | 364 | ||
259 | #[derive(Debug, Copy, Clone)] | 365 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] |
260 | pub struct TestAttr { | 366 | pub struct TestAttr { |
261 | pub ignore: bool, | 367 | pub ignore: bool, |
262 | } | 368 | } |
@@ -349,6 +455,12 @@ mod tests { | |||
349 | ); | 455 | ); |
350 | } | 456 | } |
351 | 457 | ||
458 | fn check_tests(ra_fixture: &str, expect: Expect) { | ||
459 | let (analysis, position) = fixture::position(ra_fixture); | ||
460 | let tests = analysis.related_tests(position, None).unwrap(); | ||
461 | expect.assert_debug_eq(&tests); | ||
462 | } | ||
463 | |||
352 | #[test] | 464 | #[test] |
353 | fn test_runnables() { | 465 | fn test_runnables() { |
354 | check( | 466 | check( |
@@ -1074,4 +1186,224 @@ mod tests { | |||
1074 | "#]], | 1186 | "#]], |
1075 | ); | 1187 | ); |
1076 | } | 1188 | } |
1189 | |||
1190 | #[test] | ||
1191 | fn find_no_tests() { | ||
1192 | check_tests( | ||
1193 | r#" | ||
1194 | //- /lib.rs | ||
1195 | fn foo$0() { }; | ||
1196 | "#, | ||
1197 | expect![[r#" | ||
1198 | [] | ||
1199 | "#]], | ||
1200 | ); | ||
1201 | } | ||
1202 | |||
1203 | #[test] | ||
1204 | fn find_direct_fn_test() { | ||
1205 | check_tests( | ||
1206 | r#" | ||
1207 | //- /lib.rs | ||
1208 | fn foo$0() { }; | ||
1209 | |||
1210 | mod tests { | ||
1211 | #[test] | ||
1212 | fn foo_test() { | ||
1213 | super::foo() | ||
1214 | } | ||
1215 | } | ||
1216 | "#, | ||
1217 | expect![[r#" | ||
1218 | [ | ||
1219 | Runnable { | ||
1220 | nav: NavigationTarget { | ||
1221 | file_id: FileId( | ||
1222 | 0, | ||
1223 | ), | ||
1224 | full_range: 31..85, | ||
1225 | focus_range: 46..54, | ||
1226 | name: "foo_test", | ||
1227 | kind: Function, | ||
1228 | }, | ||
1229 | kind: Test { | ||
1230 | test_id: Path( | ||
1231 | "tests::foo_test", | ||
1232 | ), | ||
1233 | attr: TestAttr { | ||
1234 | ignore: false, | ||
1235 | }, | ||
1236 | }, | ||
1237 | cfg: None, | ||
1238 | }, | ||
1239 | ] | ||
1240 | "#]], | ||
1241 | ); | ||
1242 | } | ||
1243 | |||
1244 | #[test] | ||
1245 | fn find_direct_struct_test() { | ||
1246 | check_tests( | ||
1247 | r#" | ||
1248 | //- /lib.rs | ||
1249 | struct Fo$0o; | ||
1250 | fn foo(arg: &Foo) { }; | ||
1251 | |||
1252 | mod tests { | ||
1253 | use super::*; | ||
1254 | |||
1255 | #[test] | ||
1256 | fn foo_test() { | ||
1257 | foo(Foo); | ||
1258 | } | ||
1259 | } | ||
1260 | "#, | ||
1261 | expect![[r#" | ||
1262 | [ | ||
1263 | Runnable { | ||
1264 | nav: NavigationTarget { | ||
1265 | file_id: FileId( | ||
1266 | 0, | ||
1267 | ), | ||
1268 | full_range: 71..122, | ||
1269 | focus_range: 86..94, | ||
1270 | name: "foo_test", | ||
1271 | kind: Function, | ||
1272 | }, | ||
1273 | kind: Test { | ||
1274 | test_id: Path( | ||
1275 | "tests::foo_test", | ||
1276 | ), | ||
1277 | attr: TestAttr { | ||
1278 | ignore: false, | ||
1279 | }, | ||
1280 | }, | ||
1281 | cfg: None, | ||
1282 | }, | ||
1283 | ] | ||
1284 | "#]], | ||
1285 | ); | ||
1286 | } | ||
1287 | |||
1288 | #[test] | ||
1289 | fn find_indirect_fn_test() { | ||
1290 | check_tests( | ||
1291 | r#" | ||
1292 | //- /lib.rs | ||
1293 | fn foo$0() { }; | ||
1294 | |||
1295 | mod tests { | ||
1296 | use super::foo; | ||
1297 | |||
1298 | fn check1() { | ||
1299 | check2() | ||
1300 | } | ||
1301 | |||
1302 | fn check2() { | ||
1303 | foo() | ||
1304 | } | ||
1305 | |||
1306 | #[test] | ||
1307 | fn foo_test() { | ||
1308 | check1() | ||
1309 | } | ||
1310 | } | ||
1311 | "#, | ||
1312 | expect![[r#" | ||
1313 | [ | ||
1314 | Runnable { | ||
1315 | nav: NavigationTarget { | ||
1316 | file_id: FileId( | ||
1317 | 0, | ||
1318 | ), | ||
1319 | full_range: 133..183, | ||
1320 | focus_range: 148..156, | ||
1321 | name: "foo_test", | ||
1322 | kind: Function, | ||
1323 | }, | ||
1324 | kind: Test { | ||
1325 | test_id: Path( | ||
1326 | "tests::foo_test", | ||
1327 | ), | ||
1328 | attr: TestAttr { | ||
1329 | ignore: false, | ||
1330 | }, | ||
1331 | }, | ||
1332 | cfg: None, | ||
1333 | }, | ||
1334 | ] | ||
1335 | "#]], | ||
1336 | ); | ||
1337 | } | ||
1338 | |||
1339 | #[test] | ||
1340 | fn tests_are_unique() { | ||
1341 | check_tests( | ||
1342 | r#" | ||
1343 | //- /lib.rs | ||
1344 | fn foo$0() { }; | ||
1345 | |||
1346 | mod tests { | ||
1347 | use super::foo; | ||
1348 | |||
1349 | #[test] | ||
1350 | fn foo_test() { | ||
1351 | foo(); | ||
1352 | foo(); | ||
1353 | } | ||
1354 | |||
1355 | #[test] | ||
1356 | fn foo2_test() { | ||
1357 | foo(); | ||
1358 | foo(); | ||
1359 | } | ||
1360 | |||
1361 | } | ||
1362 | "#, | ||
1363 | expect![[r#" | ||
1364 | [ | ||
1365 | Runnable { | ||
1366 | nav: NavigationTarget { | ||
1367 | file_id: FileId( | ||
1368 | 0, | ||
1369 | ), | ||
1370 | full_range: 52..115, | ||
1371 | focus_range: 67..75, | ||
1372 | name: "foo_test", | ||
1373 | kind: Function, | ||
1374 | }, | ||
1375 | kind: Test { | ||
1376 | test_id: Path( | ||
1377 | "tests::foo_test", | ||
1378 | ), | ||
1379 | attr: TestAttr { | ||
1380 | ignore: false, | ||
1381 | }, | ||
1382 | }, | ||
1383 | cfg: None, | ||
1384 | }, | ||
1385 | Runnable { | ||
1386 | nav: NavigationTarget { | ||
1387 | file_id: FileId( | ||
1388 | 0, | ||
1389 | ), | ||
1390 | full_range: 121..185, | ||
1391 | focus_range: 136..145, | ||
1392 | name: "foo2_test", | ||
1393 | kind: Function, | ||
1394 | }, | ||
1395 | kind: Test { | ||
1396 | test_id: Path( | ||
1397 | "tests::foo2_test", | ||
1398 | ), | ||
1399 | attr: TestAttr { | ||
1400 | ignore: false, | ||
1401 | }, | ||
1402 | }, | ||
1403 | cfg: None, | ||
1404 | }, | ||
1405 | ] | ||
1406 | "#]], | ||
1407 | ); | ||
1408 | } | ||
1077 | } | 1409 | } |
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs index ddcfbd3f3..fa18703e1 100644 --- a/crates/ide_db/src/search.rs +++ b/crates/ide_db/src/search.rs | |||
@@ -86,6 +86,10 @@ impl SearchScope { | |||
86 | SearchScope::new(std::iter::once((file, None)).collect()) | 86 | SearchScope::new(std::iter::once((file, None)).collect()) |
87 | } | 87 | } |
88 | 88 | ||
89 | pub fn file_range(range: FileRange) -> SearchScope { | ||
90 | SearchScope::new(std::iter::once((range.file_id, Some(range.range))).collect()) | ||
91 | } | ||
92 | |||
89 | pub fn files(files: &[FileId]) -> SearchScope { | 93 | pub fn files(files: &[FileId]) -> SearchScope { |
90 | SearchScope::new(files.iter().map(|f| (*f, None)).collect()) | 94 | SearchScope::new(files.iter().map(|f| (*f, None)).collect()) |
91 | } | 95 | } |
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 6cc433cb8..706a39dab 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs | |||
@@ -555,7 +555,7 @@ pub(crate) fn handle_runnables( | |||
555 | if should_skip_target(&runnable, cargo_spec.as_ref()) { | 555 | if should_skip_target(&runnable, cargo_spec.as_ref()) { |
556 | continue; | 556 | continue; |
557 | } | 557 | } |
558 | let mut runnable = to_proto::runnable(&snap, file_id, runnable)?; | 558 | let mut runnable = to_proto::runnable(&snap, runnable)?; |
559 | if expect_test { | 559 | if expect_test { |
560 | runnable.label = format!("{} + expect", runnable.label); | 560 | runnable.label = format!("{} + expect", runnable.label); |
561 | runnable.args.expect_test = Some(true); | 561 | runnable.args.expect_test = Some(true); |
@@ -607,6 +607,24 @@ pub(crate) fn handle_runnables( | |||
607 | Ok(res) | 607 | Ok(res) |
608 | } | 608 | } |
609 | 609 | ||
610 | pub(crate) fn handle_related_tests( | ||
611 | snap: GlobalStateSnapshot, | ||
612 | params: lsp_types::TextDocumentPositionParams, | ||
613 | ) -> Result<Vec<lsp_ext::TestInfo>> { | ||
614 | let _p = profile::span("handle_related_tests"); | ||
615 | let position = from_proto::file_position(&snap, params)?; | ||
616 | |||
617 | let tests = snap.analysis.related_tests(position, None)?; | ||
618 | let mut res = Vec::new(); | ||
619 | for it in tests { | ||
620 | if let Ok(runnable) = to_proto::runnable(&snap, it) { | ||
621 | res.push(lsp_ext::TestInfo { runnable }) | ||
622 | } | ||
623 | } | ||
624 | |||
625 | Ok(res) | ||
626 | } | ||
627 | |||
610 | pub(crate) fn handle_completion( | 628 | pub(crate) fn handle_completion( |
611 | snap: GlobalStateSnapshot, | 629 | snap: GlobalStateSnapshot, |
612 | params: lsp_types::CompletionParams, | 630 | params: lsp_types::CompletionParams, |
@@ -772,7 +790,7 @@ pub(crate) fn handle_hover( | |||
772 | contents: HoverContents::Markup(to_proto::markup_content(info.info.markup)), | 790 | contents: HoverContents::Markup(to_proto::markup_content(info.info.markup)), |
773 | range: Some(range), | 791 | range: Some(range), |
774 | }, | 792 | }, |
775 | actions: prepare_hover_actions(&snap, position.file_id, &info.info.actions), | 793 | actions: prepare_hover_actions(&snap, &info.info.actions), |
776 | }; | 794 | }; |
777 | 795 | ||
778 | Ok(Some(hover)) | 796 | Ok(Some(hover)) |
@@ -1440,17 +1458,16 @@ fn show_impl_command_link( | |||
1440 | 1458 | ||
1441 | fn runnable_action_links( | 1459 | fn runnable_action_links( |
1442 | snap: &GlobalStateSnapshot, | 1460 | snap: &GlobalStateSnapshot, |
1443 | file_id: FileId, | ||
1444 | runnable: Runnable, | 1461 | runnable: Runnable, |
1445 | ) -> Option<lsp_ext::CommandLinkGroup> { | 1462 | ) -> Option<lsp_ext::CommandLinkGroup> { |
1446 | let cargo_spec = CargoTargetSpec::for_file(&snap, file_id).ok()?; | 1463 | let cargo_spec = CargoTargetSpec::for_file(&snap, runnable.nav.file_id).ok()?; |
1447 | let hover_config = snap.config.hover(); | 1464 | let hover_config = snap.config.hover(); |
1448 | if !hover_config.runnable() || should_skip_target(&runnable, cargo_spec.as_ref()) { | 1465 | if !hover_config.runnable() || should_skip_target(&runnable, cargo_spec.as_ref()) { |
1449 | return None; | 1466 | return None; |
1450 | } | 1467 | } |
1451 | 1468 | ||
1452 | let action: &'static _ = runnable.action(); | 1469 | let action: &'static _ = runnable.action(); |
1453 | to_proto::runnable(snap, file_id, runnable).ok().map(|r| { | 1470 | to_proto::runnable(snap, runnable).ok().map(|r| { |
1454 | let mut group = lsp_ext::CommandLinkGroup::default(); | 1471 | let mut group = lsp_ext::CommandLinkGroup::default(); |
1455 | 1472 | ||
1456 | if hover_config.run { | 1473 | if hover_config.run { |
@@ -1489,7 +1506,6 @@ fn goto_type_action_links( | |||
1489 | 1506 | ||
1490 | fn prepare_hover_actions( | 1507 | fn prepare_hover_actions( |
1491 | snap: &GlobalStateSnapshot, | 1508 | snap: &GlobalStateSnapshot, |
1492 | file_id: FileId, | ||
1493 | actions: &[HoverAction], | 1509 | actions: &[HoverAction], |
1494 | ) -> Vec<lsp_ext::CommandLinkGroup> { | 1510 | ) -> Vec<lsp_ext::CommandLinkGroup> { |
1495 | if snap.config.hover().none() || !snap.config.hover_actions() { | 1511 | if snap.config.hover().none() || !snap.config.hover_actions() { |
@@ -1500,7 +1516,7 @@ fn prepare_hover_actions( | |||
1500 | .iter() | 1516 | .iter() |
1501 | .filter_map(|it| match it { | 1517 | .filter_map(|it| match it { |
1502 | HoverAction::Implementation(position) => show_impl_command_link(snap, position), | 1518 | HoverAction::Implementation(position) => show_impl_command_link(snap, position), |
1503 | HoverAction::Runnable(r) => runnable_action_links(snap, file_id, r.clone()), | 1519 | HoverAction::Runnable(r) => runnable_action_links(snap, r.clone()), |
1504 | HoverAction::GoToType(targets) => goto_type_action_links(snap, targets), | 1520 | HoverAction::GoToType(targets) => goto_type_action_links(snap, targets), |
1505 | }) | 1521 | }) |
1506 | .collect() | 1522 | .collect() |
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index 0d2c8f7ff..efcdcd1d9 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs | |||
@@ -177,6 +177,19 @@ pub struct CargoRunnable { | |||
177 | pub expect_test: Option<bool>, | 177 | pub expect_test: Option<bool>, |
178 | } | 178 | } |
179 | 179 | ||
180 | pub enum RelatedTests {} | ||
181 | |||
182 | impl Request for RelatedTests { | ||
183 | type Params = lsp_types::TextDocumentPositionParams; | ||
184 | type Result = Vec<TestInfo>; | ||
185 | const METHOD: &'static str = "rust-analyzer/relatedTests"; | ||
186 | } | ||
187 | |||
188 | #[derive(Debug, Deserialize, Serialize)] | ||
189 | pub struct TestInfo { | ||
190 | pub runnable: Runnable, | ||
191 | } | ||
192 | |||
180 | pub enum InlayHints {} | 193 | pub enum InlayHints {} |
181 | 194 | ||
182 | impl Request for InlayHints { | 195 | impl Request for InlayHints { |
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index f0cb309e4..984790d35 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -500,6 +500,7 @@ impl GlobalState { | |||
500 | .on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro) | 500 | .on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro) |
501 | .on::<lsp_ext::ParentModule>(handlers::handle_parent_module) | 501 | .on::<lsp_ext::ParentModule>(handlers::handle_parent_module) |
502 | .on::<lsp_ext::Runnables>(handlers::handle_runnables) | 502 | .on::<lsp_ext::Runnables>(handlers::handle_runnables) |
503 | .on::<lsp_ext::RelatedTests>(handlers::handle_related_tests) | ||
503 | .on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints) | 504 | .on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints) |
504 | .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action) | 505 | .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action) |
505 | .on::<lsp_ext::CodeActionResolveRequest>(handlers::handle_code_action_resolve) | 506 | .on::<lsp_ext::CodeActionResolveRequest>(handlers::handle_code_action_resolve) |
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 1a8cdadad..9ca0915b9 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -838,11 +838,10 @@ pub(crate) fn resolved_code_action( | |||
838 | 838 | ||
839 | pub(crate) fn runnable( | 839 | pub(crate) fn runnable( |
840 | snap: &GlobalStateSnapshot, | 840 | snap: &GlobalStateSnapshot, |
841 | file_id: FileId, | ||
842 | runnable: Runnable, | 841 | runnable: Runnable, |
843 | ) -> Result<lsp_ext::Runnable> { | 842 | ) -> Result<lsp_ext::Runnable> { |
844 | let config = snap.config.runnables(); | 843 | let config = snap.config.runnables(); |
845 | let spec = CargoTargetSpec::for_file(snap, file_id)?; | 844 | let spec = CargoTargetSpec::for_file(snap, runnable.nav.file_id)?; |
846 | let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone()); | 845 | let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone()); |
847 | let target = spec.as_ref().map(|s| s.target.clone()); | 846 | let target = spec.as_ref().map(|s| s.target.clone()); |
848 | let (cargo_args, executable_args) = | 847 | let (cargo_args, executable_args) = |
@@ -875,7 +874,7 @@ pub(crate) fn code_lens( | |||
875 | let annotation_range = range(&line_index, annotation.range); | 874 | let annotation_range = range(&line_index, annotation.range); |
876 | 875 | ||
877 | let action = run.action(); | 876 | let action = run.action(); |
878 | let r = runnable(&snap, run.nav.file_id, run)?; | 877 | let r = runnable(&snap, run)?; |
879 | 878 | ||
880 | let command = if debug { | 879 | let command = if debug { |
881 | command::debug_single(&r) | 880 | command::debug_single(&r) |