diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index f198b1f252..53b0d3e413 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -607,6 +607,24 @@ pub(crate) fn handle_runnables( Ok(res) } +pub(crate) fn handle_related_tests( + snap: GlobalStateSnapshot, + params: lsp_ext::RelatedTestsParams, +) -> Result> { + let _p = profile::span("handle_related_tests"); + let position = from_proto::file_position(&snap, params.text_document_position)?; + + 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, diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index 0d2c8f7ffb..fe11903f94 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs @@ -177,6 +177,26 @@ pub struct CargoRunnable { pub expect_test: Option, } +pub enum RelatedTests {} + +impl Request for RelatedTests { + type Params = RelatedTestsParams; + type Result = Vec; + const METHOD: &'static str = "rust-analyzer/relatedTests"; +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct RelatedTestsParams { + #[serde(flatten)] + pub text_document_position: lsp_types::TextDocumentPositionParams, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct TestInfo { + pub runnable: Runnable, +} + pub enum InlayHints {} impl Request for InlayHints { diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 2829d5970e..9f86b8c0d5 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -500,6 +500,7 @@ impl GlobalState { .on::(handlers::handle_expand_macro) .on::(handlers::handle_parent_module) .on::(handlers::handle_runnables) + .on::(handlers::handle_related_tests) .on::(handlers::handle_inlay_hints) .on::(handlers::handle_code_action) .on::(handlers::handle_code_action_resolve) diff --git a/editors/code/package.json b/editors/code/package.json index e3e0ebff0f..43ea1225ab 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -202,6 +202,11 @@ "command": "rust-analyzer.openCargoToml", "title": "Open Cargo.toml", "category": "Rust Analyzer" + }, + { + "command": "rust-analyzer.peekTests", + "title": "Peek related tests", + "category": "Rust Analyzer" } ], "keybindings": [ diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 283b9a160c..3512fefdf5 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -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'; @@ -456,17 +457,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); }; } @@ -555,6 +559,35 @@ 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; + + 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 => { + return { + uri: it.runnable.location!.targetUri, + range: 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; diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 2de1e427d7..11d4d5f00e 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -72,6 +72,15 @@ export interface Runnable { } export const runnables = new lc.RequestType("experimental/runnables"); +export interface RelatedTestsParams extends lc.TextDocumentPositionParams { +} + +export interface TestInfo { + runnable: Runnable; +} + +export const relatedTests = new lc.RequestType("rust-analyzer/relatedTests"); + export type InlayHint = InlayHint.TypeHint | InlayHint.ParamHint | InlayHint.ChainingHint; export namespace InlayHint { diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 00393d6e8f..f1a2020aa7 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -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);