mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-12 21:28:51 +00:00
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 <vit@conrlab.com>
This commit is contained in:
commit
7accf6bc37
13 changed files with 471 additions and 28 deletions
|
@ -49,7 +49,7 @@ impl fmt::Display for CfgAtom {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum CfgExpr {
|
||||
Invalid,
|
||||
Atom(CfgAtom),
|
||||
|
|
|
@ -447,6 +447,15 @@ impl Analysis {
|
|||
self.with_db(|db| runnables::runnables(db, file_id))
|
||||
}
|
||||
|
||||
/// Returns the set of tests for the given file position.
|
||||
pub fn related_tests(
|
||||
&self,
|
||||
position: FilePosition,
|
||||
search_scope: Option<SearchScope>,
|
||||
) -> Cancelable<Vec<Runnable>> {
|
||||
self.with_db(|db| runnables::related_tests(db, position, search_scope))
|
||||
}
|
||||
|
||||
/// Computes syntax highlighting for the given file
|
||||
pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HlRange>> {
|
||||
self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false))
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
use std::fmt;
|
||||
|
||||
use ast::NameOwner;
|
||||
use cfg::CfgExpr;
|
||||
use hir::{AsAssocItem, HasAttrs, HasSource, Semantics};
|
||||
use ide_assists::utils::test_related_attribute;
|
||||
use ide_db::{defs::Definition, RootDatabase, SymbolKind};
|
||||
use ide_db::{
|
||||
base_db::{FilePosition, FileRange},
|
||||
defs::Definition,
|
||||
search::SearchScope,
|
||||
RootDatabase, SymbolKind,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::FxHashSet;
|
||||
use syntax::{
|
||||
ast::{self, AstNode, AttrsOwner},
|
||||
match_ast, SyntaxNode,
|
||||
|
@ -12,17 +19,17 @@ use syntax::{
|
|||
|
||||
use crate::{
|
||||
display::{ToNav, TryToNav},
|
||||
FileId, NavigationTarget,
|
||||
references, FileId, NavigationTarget,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct Runnable {
|
||||
pub nav: NavigationTarget,
|
||||
pub kind: RunnableKind,
|
||||
pub cfg: Option<CfgExpr>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub enum TestId {
|
||||
Name(String),
|
||||
Path(String),
|
||||
|
@ -37,7 +44,7 @@ impl fmt::Display for TestId {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub enum RunnableKind {
|
||||
Test { test_id: TestId, attr: TestAttr },
|
||||
TestMod { path: String },
|
||||
|
@ -105,6 +112,105 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
|
|||
res
|
||||
}
|
||||
|
||||
// Feature: Related Tests
|
||||
//
|
||||
// Provides a sneak peek of all tests where the current item is used.
|
||||
//
|
||||
// The simplest way to use this feature is via the context menu:
|
||||
// - Right-click on the selected item. The context menu opens.
|
||||
// - Select **Peek related tests**
|
||||
//
|
||||
// |===
|
||||
// | Editor | Action Name
|
||||
//
|
||||
// | VS Code | **Rust Analyzer: Peek related tests**
|
||||
// |===
|
||||
pub(crate) fn related_tests(
|
||||
db: &RootDatabase,
|
||||
position: FilePosition,
|
||||
search_scope: Option<SearchScope>,
|
||||
) -> Vec<Runnable> {
|
||||
let sema = Semantics::new(db);
|
||||
let mut res: FxHashSet<Runnable> = FxHashSet::default();
|
||||
|
||||
find_related_tests(&sema, position, search_scope, &mut res);
|
||||
|
||||
res.into_iter().collect_vec()
|
||||
}
|
||||
|
||||
fn find_related_tests(
|
||||
sema: &Semantics<RootDatabase>,
|
||||
position: FilePosition,
|
||||
search_scope: Option<SearchScope>,
|
||||
tests: &mut FxHashSet<Runnable>,
|
||||
) {
|
||||
if let Some(refs) = references::find_all_refs(&sema, position, search_scope) {
|
||||
for (file_id, refs) in refs.references {
|
||||
let file = sema.parse(file_id);
|
||||
let file = file.syntax();
|
||||
let functions = refs.iter().filter_map(|(range, _)| {
|
||||
let token = file.token_at_offset(range.start()).next()?;
|
||||
let token = sema.descend_into_macros(token);
|
||||
let syntax = token.parent();
|
||||
syntax.ancestors().find_map(ast::Fn::cast)
|
||||
});
|
||||
|
||||
for fn_def in functions {
|
||||
if let Some(runnable) = as_test_runnable(&sema, &fn_def) {
|
||||
// direct test
|
||||
tests.insert(runnable);
|
||||
} else if let Some(module) = parent_test_module(&sema, &fn_def) {
|
||||
// indirect test
|
||||
find_related_tests_in_module(sema, &fn_def, &module, tests);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_related_tests_in_module(
|
||||
sema: &Semantics<RootDatabase>,
|
||||
fn_def: &ast::Fn,
|
||||
parent_module: &hir::Module,
|
||||
tests: &mut FxHashSet<Runnable>,
|
||||
) {
|
||||
if let Some(fn_name) = fn_def.name() {
|
||||
let mod_source = parent_module.definition_source(sema.db);
|
||||
let range = match mod_source.value {
|
||||
hir::ModuleSource::Module(m) => m.syntax().text_range(),
|
||||
hir::ModuleSource::BlockExpr(b) => b.syntax().text_range(),
|
||||
hir::ModuleSource::SourceFile(f) => f.syntax().text_range(),
|
||||
};
|
||||
|
||||
let file_id = mod_source.file_id.original_file(sema.db);
|
||||
let mod_scope = SearchScope::file_range(FileRange { file_id, range });
|
||||
let fn_pos = FilePosition { file_id, offset: fn_name.syntax().text_range().start() };
|
||||
find_related_tests(sema, fn_pos, Some(mod_scope), tests)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_test_runnable(sema: &Semantics<RootDatabase>, fn_def: &ast::Fn) -> Option<Runnable> {
|
||||
if test_related_attribute(&fn_def).is_some() {
|
||||
let function = sema.to_def(fn_def)?;
|
||||
runnable_fn(sema, function)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_test_module(sema: &Semantics<RootDatabase>, fn_def: &ast::Fn) -> Option<hir::Module> {
|
||||
fn_def.syntax().ancestors().find_map(|node| {
|
||||
let module = ast::Module::cast(node)?;
|
||||
let module = sema.to_def(&module)?;
|
||||
|
||||
if has_test_function_or_multiple_test_submodules(sema, &module) {
|
||||
Some(module)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn runnables_mod(sema: &Semantics<RootDatabase>, acc: &mut Vec<Runnable>, module: hir::Module) {
|
||||
acc.extend(module.declarations(sema.db).into_iter().filter_map(|def| {
|
||||
let runnable = match def {
|
||||
|
@ -256,7 +362,7 @@ fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Op
|
|||
Some(res)
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct TestAttr {
|
||||
pub ignore: bool,
|
||||
}
|
||||
|
@ -349,6 +455,12 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
fn check_tests(ra_fixture: &str, expect: Expect) {
|
||||
let (analysis, position) = fixture::position(ra_fixture);
|
||||
let tests = analysis.related_tests(position, None).unwrap();
|
||||
expect.assert_debug_eq(&tests);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runnables() {
|
||||
check(
|
||||
|
@ -1074,4 +1186,224 @@ mod tests {
|
|||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_no_tests() {
|
||||
check_tests(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
fn foo$0() { };
|
||||
"#,
|
||||
expect![[r#"
|
||||
[]
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_direct_fn_test() {
|
||||
check_tests(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
fn foo$0() { };
|
||||
|
||||
mod tests {
|
||||
#[test]
|
||||
fn foo_test() {
|
||||
super::foo()
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
[
|
||||
Runnable {
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
0,
|
||||
),
|
||||
full_range: 31..85,
|
||||
focus_range: 46..54,
|
||||
name: "foo_test",
|
||||
kind: Function,
|
||||
},
|
||||
kind: Test {
|
||||
test_id: Path(
|
||||
"tests::foo_test",
|
||||
),
|
||||
attr: TestAttr {
|
||||
ignore: false,
|
||||
},
|
||||
},
|
||||
cfg: None,
|
||||
},
|
||||
]
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_direct_struct_test() {
|
||||
check_tests(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
struct Fo$0o;
|
||||
fn foo(arg: &Foo) { };
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn foo_test() {
|
||||
foo(Foo);
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
[
|
||||
Runnable {
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
0,
|
||||
),
|
||||
full_range: 71..122,
|
||||
focus_range: 86..94,
|
||||
name: "foo_test",
|
||||
kind: Function,
|
||||
},
|
||||
kind: Test {
|
||||
test_id: Path(
|
||||
"tests::foo_test",
|
||||
),
|
||||
attr: TestAttr {
|
||||
ignore: false,
|
||||
},
|
||||
},
|
||||
cfg: None,
|
||||
},
|
||||
]
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_indirect_fn_test() {
|
||||
check_tests(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
fn foo$0() { };
|
||||
|
||||
mod tests {
|
||||
use super::foo;
|
||||
|
||||
fn check1() {
|
||||
check2()
|
||||
}
|
||||
|
||||
fn check2() {
|
||||
foo()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn foo_test() {
|
||||
check1()
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
[
|
||||
Runnable {
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
0,
|
||||
),
|
||||
full_range: 133..183,
|
||||
focus_range: 148..156,
|
||||
name: "foo_test",
|
||||
kind: Function,
|
||||
},
|
||||
kind: Test {
|
||||
test_id: Path(
|
||||
"tests::foo_test",
|
||||
),
|
||||
attr: TestAttr {
|
||||
ignore: false,
|
||||
},
|
||||
},
|
||||
cfg: None,
|
||||
},
|
||||
]
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tests_are_unique() {
|
||||
check_tests(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
fn foo$0() { };
|
||||
|
||||
mod tests {
|
||||
use super::foo;
|
||||
|
||||
#[test]
|
||||
fn foo_test() {
|
||||
foo();
|
||||
foo();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn foo2_test() {
|
||||
foo();
|
||||
foo();
|
||||
}
|
||||
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
[
|
||||
Runnable {
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
0,
|
||||
),
|
||||
full_range: 52..115,
|
||||
focus_range: 67..75,
|
||||
name: "foo_test",
|
||||
kind: Function,
|
||||
},
|
||||
kind: Test {
|
||||
test_id: Path(
|
||||
"tests::foo_test",
|
||||
),
|
||||
attr: TestAttr {
|
||||
ignore: false,
|
||||
},
|
||||
},
|
||||
cfg: None,
|
||||
},
|
||||
Runnable {
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
0,
|
||||
),
|
||||
full_range: 121..185,
|
||||
focus_range: 136..145,
|
||||
name: "foo2_test",
|
||||
kind: Function,
|
||||
},
|
||||
kind: Test {
|
||||
test_id: Path(
|
||||
"tests::foo2_test",
|
||||
),
|
||||
attr: TestAttr {
|
||||
ignore: false,
|
||||
},
|
||||
},
|
||||
cfg: None,
|
||||
},
|
||||
]
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,6 +86,10 @@ impl SearchScope {
|
|||
SearchScope::new(std::iter::once((file, None)).collect())
|
||||
}
|
||||
|
||||
pub fn file_range(range: FileRange) -> SearchScope {
|
||||
SearchScope::new(std::iter::once((range.file_id, Some(range.range))).collect())
|
||||
}
|
||||
|
||||
pub fn files(files: &[FileId]) -> SearchScope {
|
||||
SearchScope::new(files.iter().map(|f| (*f, None)).collect())
|
||||
}
|
||||
|
|
|
@ -555,7 +555,7 @@ pub(crate) fn handle_runnables(
|
|||
if should_skip_target(&runnable, cargo_spec.as_ref()) {
|
||||
continue;
|
||||
}
|
||||
let mut runnable = to_proto::runnable(&snap, file_id, runnable)?;
|
||||
let mut runnable = to_proto::runnable(&snap, runnable)?;
|
||||
if expect_test {
|
||||
runnable.label = format!("{} + expect", runnable.label);
|
||||
runnable.args.expect_test = Some(true);
|
||||
|
@ -607,6 +607,24 @@ pub(crate) fn handle_runnables(
|
|||
Ok(res)
|
||||
}
|
||||
|
||||
pub(crate) fn handle_related_tests(
|
||||
snap: GlobalStateSnapshot,
|
||||
params: lsp_types::TextDocumentPositionParams,
|
||||
) -> Result<Vec<lsp_ext::TestInfo>> {
|
||||
let _p = profile::span("handle_related_tests");
|
||||
let position = from_proto::file_position(&snap, params)?;
|
||||
|
||||
let tests = snap.analysis.related_tests(position, None)?;
|
||||
let mut res = Vec::new();
|
||||
for it in tests {
|
||||
if let Ok(runnable) = to_proto::runnable(&snap, it) {
|
||||
res.push(lsp_ext::TestInfo { runnable })
|
||||
}
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub(crate) fn handle_completion(
|
||||
snap: GlobalStateSnapshot,
|
||||
params: lsp_types::CompletionParams,
|
||||
|
@ -772,7 +790,7 @@ pub(crate) fn handle_hover(
|
|||
contents: HoverContents::Markup(to_proto::markup_content(info.info.markup)),
|
||||
range: Some(range),
|
||||
},
|
||||
actions: prepare_hover_actions(&snap, position.file_id, &info.info.actions),
|
||||
actions: prepare_hover_actions(&snap, &info.info.actions),
|
||||
};
|
||||
|
||||
Ok(Some(hover))
|
||||
|
@ -1440,17 +1458,16 @@ fn show_impl_command_link(
|
|||
|
||||
fn runnable_action_links(
|
||||
snap: &GlobalStateSnapshot,
|
||||
file_id: FileId,
|
||||
runnable: Runnable,
|
||||
) -> Option<lsp_ext::CommandLinkGroup> {
|
||||
let cargo_spec = CargoTargetSpec::for_file(&snap, file_id).ok()?;
|
||||
let cargo_spec = CargoTargetSpec::for_file(&snap, runnable.nav.file_id).ok()?;
|
||||
let hover_config = snap.config.hover();
|
||||
if !hover_config.runnable() || should_skip_target(&runnable, cargo_spec.as_ref()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let action: &'static _ = runnable.action();
|
||||
to_proto::runnable(snap, file_id, runnable).ok().map(|r| {
|
||||
to_proto::runnable(snap, runnable).ok().map(|r| {
|
||||
let mut group = lsp_ext::CommandLinkGroup::default();
|
||||
|
||||
if hover_config.run {
|
||||
|
@ -1489,7 +1506,6 @@ fn goto_type_action_links(
|
|||
|
||||
fn prepare_hover_actions(
|
||||
snap: &GlobalStateSnapshot,
|
||||
file_id: FileId,
|
||||
actions: &[HoverAction],
|
||||
) -> Vec<lsp_ext::CommandLinkGroup> {
|
||||
if snap.config.hover().none() || !snap.config.hover_actions() {
|
||||
|
@ -1500,7 +1516,7 @@ fn prepare_hover_actions(
|
|||
.iter()
|
||||
.filter_map(|it| match it {
|
||||
HoverAction::Implementation(position) => show_impl_command_link(snap, position),
|
||||
HoverAction::Runnable(r) => runnable_action_links(snap, file_id, r.clone()),
|
||||
HoverAction::Runnable(r) => runnable_action_links(snap, r.clone()),
|
||||
HoverAction::GoToType(targets) => goto_type_action_links(snap, targets),
|
||||
})
|
||||
.collect()
|
||||
|
|
|
@ -177,6 +177,19 @@ pub struct CargoRunnable {
|
|||
pub expect_test: Option<bool>,
|
||||
}
|
||||
|
||||
pub enum RelatedTests {}
|
||||
|
||||
impl Request for RelatedTests {
|
||||
type Params = lsp_types::TextDocumentPositionParams;
|
||||
type Result = Vec<TestInfo>;
|
||||
const METHOD: &'static str = "rust-analyzer/relatedTests";
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct TestInfo {
|
||||
pub runnable: Runnable,
|
||||
}
|
||||
|
||||
pub enum InlayHints {}
|
||||
|
||||
impl Request for InlayHints {
|
||||
|
|
|
@ -500,6 +500,7 @@ impl GlobalState {
|
|||
.on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro)
|
||||
.on::<lsp_ext::ParentModule>(handlers::handle_parent_module)
|
||||
.on::<lsp_ext::Runnables>(handlers::handle_runnables)
|
||||
.on::<lsp_ext::RelatedTests>(handlers::handle_related_tests)
|
||||
.on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints)
|
||||
.on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)
|
||||
.on::<lsp_ext::CodeActionResolveRequest>(handlers::handle_code_action_resolve)
|
||||
|
|
|
@ -838,11 +838,10 @@ pub(crate) fn resolved_code_action(
|
|||
|
||||
pub(crate) fn runnable(
|
||||
snap: &GlobalStateSnapshot,
|
||||
file_id: FileId,
|
||||
runnable: Runnable,
|
||||
) -> Result<lsp_ext::Runnable> {
|
||||
let config = snap.config.runnables();
|
||||
let spec = CargoTargetSpec::for_file(snap, file_id)?;
|
||||
let spec = CargoTargetSpec::for_file(snap, runnable.nav.file_id)?;
|
||||
let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone());
|
||||
let target = spec.as_ref().map(|s| s.target.clone());
|
||||
let (cargo_args, executable_args) =
|
||||
|
@ -875,7 +874,7 @@ pub(crate) fn code_lens(
|
|||
let annotation_range = range(&line_index, annotation.range);
|
||||
|
||||
let action = run.action();
|
||||
let r = runnable(&snap, run.nav.file_id, run)?;
|
||||
let r = runnable(&snap, run)?;
|
||||
|
||||
let command = if debug {
|
||||
command::debug_single(&r)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<!---
|
||||
lsp_ext.rs hash: d279d971d4f62cd7
|
||||
lsp_ext.rs hash: 4dfa8d7035f4aee7
|
||||
|
||||
If you need to change the above hash to make the test pass, please check if you
|
||||
need to adjust this doc as well and ping this issue:
|
||||
|
@ -579,3 +579,19 @@ This request is sent from client to server to open the current project's Cargo.t
|
|||
```
|
||||
|
||||
`experimental/openCargoToml` returns a single `Link` to the start of the `[package]` keyword.
|
||||
|
||||
## Related tests
|
||||
|
||||
This request is sent from client to server to get the list of tests for the specified position.
|
||||
|
||||
**Method:** `rust-analyzer/relatedTests`
|
||||
|
||||
**Request:** `TextDocumentPositionParams`
|
||||
|
||||
**Response:** `TestInfo[]`
|
||||
|
||||
```typescript
|
||||
interface TestInfo {
|
||||
runnable: Runnable;
|
||||
}
|
||||
```
|
||||
|
|
|
@ -203,6 +203,11 @@
|
|||
"command": "rust-analyzer.openCargoToml",
|
||||
"title": "Open Cargo.toml",
|
||||
"category": "Rust Analyzer"
|
||||
},
|
||||
{
|
||||
"command": "rust-analyzer.peekTests",
|
||||
"title": "Peek related tests",
|
||||
"category": "Rust Analyzer"
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
|
@ -1165,7 +1170,14 @@
|
|||
"command": "rust-analyzer.openCargoToml",
|
||||
"when": "inRustProject"
|
||||
}
|
||||
],
|
||||
"editor/context": [
|
||||
{
|
||||
"command": "rust-analyzer.peekTests",
|
||||
"when": "inRustProject",
|
||||
"group": "navigation@1000"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import { RunnableQuickPick, selectRunnable, createTask, createArgs } from './run
|
|||
import { AstInspector } from './ast_inspector';
|
||||
import { isRustDocument, sleep, isRustEditor } from './util';
|
||||
import { startDebugSession, makeDebugConfig } from './debug';
|
||||
import { LanguageClient } from 'vscode-languageclient/node';
|
||||
|
||||
export * from './ast_inspector';
|
||||
export * from './run';
|
||||
|
@ -455,17 +456,20 @@ export function reloadWorkspace(ctx: Ctx): Cmd {
|
|||
return async () => ctx.client.sendRequest(ra.reloadWorkspace);
|
||||
}
|
||||
|
||||
async function showReferencesImpl(client: LanguageClient, uri: string, position: lc.Position, locations: lc.Location[]) {
|
||||
if (client) {
|
||||
await vscode.commands.executeCommand(
|
||||
'editor.action.showReferences',
|
||||
vscode.Uri.parse(uri),
|
||||
client.protocol2CodeConverter.asPosition(position),
|
||||
locations.map(client.protocol2CodeConverter.asLocation),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function showReferences(ctx: Ctx): Cmd {
|
||||
return async (uri: string, position: lc.Position, locations: lc.Location[]) => {
|
||||
const client = ctx.client;
|
||||
if (client) {
|
||||
await vscode.commands.executeCommand(
|
||||
'editor.action.showReferences',
|
||||
vscode.Uri.parse(uri),
|
||||
client.protocol2CodeConverter.asPosition(position),
|
||||
locations.map(client.protocol2CodeConverter.asLocation),
|
||||
);
|
||||
}
|
||||
await showReferencesImpl(ctx.client, uri, position, locations);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -554,6 +558,36 @@ export function run(ctx: Ctx): Cmd {
|
|||
};
|
||||
}
|
||||
|
||||
export function peekTests(ctx: Ctx): Cmd {
|
||||
const client = ctx.client;
|
||||
|
||||
return async () => {
|
||||
const editor = ctx.activeRustEditor;
|
||||
if (!editor || !client) return;
|
||||
|
||||
await vscode.window.withProgress({
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: "Looking for tests...",
|
||||
cancellable: false,
|
||||
}, async (_progress, _token) => {
|
||||
const uri = editor.document.uri.toString();
|
||||
const position = client.code2ProtocolConverter.asPosition(
|
||||
editor.selection.active,
|
||||
);
|
||||
|
||||
const tests = await client.sendRequest(ra.relatedTests, {
|
||||
textDocument: { uri: uri },
|
||||
position: position,
|
||||
});
|
||||
const locations: lc.Location[] = tests.map(it =>
|
||||
lc.Location.create(it.runnable.location!.targetUri, it.runnable.location!.targetSelectionRange));
|
||||
|
||||
await showReferencesImpl(client, uri, position, locations);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function runSingle(ctx: Ctx): Cmd {
|
||||
return async (runnable: ra.Runnable) => {
|
||||
const editor = ctx.activeRustEditor;
|
||||
|
|
|
@ -72,6 +72,12 @@ export interface Runnable {
|
|||
}
|
||||
export const runnables = new lc.RequestType<RunnablesParams, Runnable[], void>("experimental/runnables");
|
||||
|
||||
export interface TestInfo {
|
||||
runnable: Runnable;
|
||||
}
|
||||
|
||||
export const relatedTests = new lc.RequestType<lc.TextDocumentPositionParams, TestInfo[], void>("rust-analyzer/relatedTests");
|
||||
|
||||
export type InlayHint = InlayHint.TypeHint | InlayHint.ParamHint | InlayHint.ChainingHint;
|
||||
|
||||
export namespace InlayHint {
|
||||
|
|
|
@ -113,6 +113,7 @@ async function tryActivate(context: vscode.ExtensionContext) {
|
|||
ctx.registerCommand('newDebugConfig', commands.newDebugConfig);
|
||||
ctx.registerCommand('openDocs', commands.openDocs);
|
||||
ctx.registerCommand('openCargoToml', commands.openCargoToml);
|
||||
ctx.registerCommand('peekTests', commands.peekTests);
|
||||
|
||||
defaultOnEnter.dispose();
|
||||
ctx.registerCommand('onEnter', commands.onEnter);
|
||||
|
|
Loading…
Reference in a new issue