From faf003763516074c619cee7e43ca8bc365540c92 Mon Sep 17 00:00:00 2001 From: "Jeremy A. Kolb" Date: Fri, 11 Jan 2019 15:16:55 -0500 Subject: Code lens support for running tests --- crates/ra_lsp_server/src/caps.rs | 6 +- crates/ra_lsp_server/src/main_loop.rs | 1 + crates/ra_lsp_server/src/main_loop/handlers.rs | 202 +++++++++++++++---------- crates/ra_lsp_server/src/req.rs | 4 +- editors/code/src/commands/index.ts | 2 + editors/code/src/commands/run_single.ts | 63 ++++++++ editors/code/src/extension.ts | 3 + 7 files changed, 196 insertions(+), 85 deletions(-) create mode 100644 editors/code/src/commands/run_single.ts diff --git a/crates/ra_lsp_server/src/caps.rs b/crates/ra_lsp_server/src/caps.rs index 2599a4ca6..be6a6ead6 100644 --- a/crates/ra_lsp_server/src/caps.rs +++ b/crates/ra_lsp_server/src/caps.rs @@ -1,5 +1,5 @@ use languageserver_types::{ - CodeActionProviderCapability, CompletionOptions, DocumentOnTypeFormattingOptions, + CodeActionProviderCapability, CodeLensOptions, CompletionOptions, DocumentOnTypeFormattingOptions, ExecuteCommandOptions, FoldingRangeProviderCapability, RenameOptions, RenameProviderCapability, ServerCapabilities, SignatureHelpOptions, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, @@ -32,7 +32,9 @@ pub fn server_capabilities() -> ServerCapabilities { document_symbol_provider: Some(true), workspace_symbol_provider: Some(true), code_action_provider: Some(CodeActionProviderCapability::Simple(true)), - code_lens_provider: None, + code_lens_provider: Some(CodeLensOptions { + resolve_provider: None, + }), document_formatting_provider: Some(true), document_range_formatting_provider: None, document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions { diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index c43637351..726c758aa 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs @@ -300,6 +300,7 @@ fn on_request( .on::(handlers::handle_decorations)? .on::(handlers::handle_completion)? .on::(handlers::handle_code_action)? + .on::(handlers::handle_code_lens)? .on::(handlers::handle_folding_range)? .on::(handlers::handle_signature_help)? .on::(handlers::handle_hover)? diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index aad9d6568..f881bd703 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use gen_lsp_server::ErrorCode; use languageserver_types::{ - CodeActionResponse, Command, Diagnostic, DiagnosticSeverity, DocumentFormattingParams, + CodeActionResponse, Command, CodeLens, Diagnostic, DiagnosticSeverity, DocumentFormattingParams, DocumentHighlight, DocumentSymbol, Documentation, FoldingRange, FoldingRangeKind, FoldingRangeParams, Hover, HoverContents, Location, MarkedString, MarkupContent, MarkupKind, ParameterInformation, ParameterLabel, Position, PrepareRenameResponse, Range, RenameParams, @@ -291,97 +291,93 @@ pub fn handle_runnables( env: FxHashMap::default(), }); return Ok(res); +} - fn runnable_args( - world: &ServerWorld, - file_id: FileId, - kind: &RunnableKind, - ) -> Result> { - let spec = CargoTargetSpec::for_file(world, file_id)?; - let mut res = Vec::new(); - match kind { - RunnableKind::Test { name } => { - res.push("test".to_string()); - if let Some(spec) = spec { - spec.push_to(&mut res); - } - res.push("--".to_string()); - res.push(name.to_string()); - res.push("--nocapture".to_string()); +fn runnable_args(world: &ServerWorld, file_id: FileId, kind: &RunnableKind) -> Result> { + let spec = CargoTargetSpec::for_file(world, file_id)?; + let mut res = Vec::new(); + match kind { + RunnableKind::Test { name } => { + res.push("test".to_string()); + if let Some(spec) = spec { + spec.push_to(&mut res); } - RunnableKind::TestMod { path } => { - res.push("test".to_string()); - if let Some(spec) = spec { - spec.push_to(&mut res); - } - res.push("--".to_string()); - res.push(path.to_string()); - res.push("--nocapture".to_string()); + res.push("--".to_string()); + res.push(name.to_string()); + res.push("--nocapture".to_string()); + } + RunnableKind::TestMod { path } => { + res.push("test".to_string()); + if let Some(spec) = spec { + spec.push_to(&mut res); } - RunnableKind::Bin => { - res.push("run".to_string()); - if let Some(spec) = spec { - spec.push_to(&mut res); - } + res.push("--".to_string()); + res.push(path.to_string()); + res.push("--nocapture".to_string()); + } + RunnableKind::Bin => { + res.push("run".to_string()); + if let Some(spec) = spec { + spec.push_to(&mut res); } } - Ok(res) } + Ok(res) +} - struct CargoTargetSpec { - package: String, - target: String, - target_kind: TargetKind, - } +struct CargoTargetSpec { + package: String, + target: String, + target_kind: TargetKind, +} - impl CargoTargetSpec { - fn for_file(world: &ServerWorld, file_id: FileId) -> Result> { - let &crate_id = match world.analysis().crate_for(file_id)?.first() { - Some(crate_id) => crate_id, - None => return Ok(None), +impl CargoTargetSpec { + fn for_file(world: &ServerWorld, file_id: FileId) -> Result> { + let &crate_id = match world.analysis().crate_for(file_id)?.first() { + Some(crate_id) => crate_id, + None => return Ok(None), + }; + let file_id = world.analysis().crate_root(crate_id)?; + let path = world + .vfs + .read() + .file2path(ra_vfs::VfsFile(file_id.0.into())); + let res = world.workspaces.iter().find_map(|ws| { + let tgt = ws.cargo.target_by_root(&path)?; + let res = CargoTargetSpec { + package: tgt.package(&ws.cargo).name(&ws.cargo).to_string(), + target: tgt.name(&ws.cargo).to_string(), + target_kind: tgt.kind(&ws.cargo), }; - let file_id = world.analysis().crate_root(crate_id)?; - let path = world - .vfs - .read() - .file2path(ra_vfs::VfsFile(file_id.0.into())); - let res = world.workspaces.iter().find_map(|ws| { - let tgt = ws.cargo.target_by_root(&path)?; - let res = CargoTargetSpec { - package: tgt.package(&ws.cargo).name(&ws.cargo).to_string(), - target: tgt.name(&ws.cargo).to_string(), - target_kind: tgt.kind(&ws.cargo), - }; - Some(res) - }); - Ok(res) - } + Some(res) + }); + Ok(res) + } - fn push_to(self, buf: &mut Vec) { - buf.push("--package".to_string()); - buf.push(self.package); - match self.target_kind { - TargetKind::Bin => { - buf.push("--bin".to_string()); - buf.push(self.target); - } - TargetKind::Test => { - buf.push("--test".to_string()); - buf.push(self.target); - } - TargetKind::Bench => { - buf.push("--bench".to_string()); - buf.push(self.target); - } - TargetKind::Example => { - buf.push("--example".to_string()); - buf.push(self.target); - } - TargetKind::Lib => { - buf.push("--lib".to_string()); - } - TargetKind::Other => (), + fn push_to(self, buf: &mut Vec) { + buf.push("--package".to_string()); + buf.push(self.package); + match self.target_kind { + TargetKind::Bin => { + buf.push("--bin".to_string()); + buf.push(self.target); + } + TargetKind::Test => { + buf.push("--test".to_string()); + buf.push(self.target); + } + TargetKind::Bench => { + buf.push("--bench".to_string()); + buf.push(self.target); + } + TargetKind::Example => { + buf.push("--example".to_string()); + buf.push(self.target); + } + TargetKind::Lib => { + buf.push("--lib".to_string()); } + TargetKind::Other => (), } } } @@ -666,6 +662,50 @@ pub fn handle_code_action( Ok(Some(CodeActionResponse::Commands(res))) } +pub fn handle_code_lens( + world: ServerWorld, + params: req::CodeLensParams, +) -> Result>> { + let file_id = params.text_document.try_conv_with(&world)?; + let line_index = world.analysis().file_line_index(file_id); + + let mut lenses: Vec = Default::default(); + + for runnable in world.analysis().runnables(file_id)? { + match &runnable.kind { + RunnableKind::Test { name: _ } | RunnableKind::TestMod { path: _ } => { + let args = runnable_args(&world, file_id, &runnable.kind)?; + + let range = runnable.range.conv_with(&line_index); + + // This represents the actual command that will be run. + let r: req::Runnable = req::Runnable { + range, + label: Default::default(), + bin: "cargo".into(), + args, + env: Default::default(), + }; + + let lens = CodeLens { + range, + command: Some(Command { + title: "Run Test".into(), + command: "ra-lsp.run-single".into(), + arguments: Some(vec![to_value(r).unwrap()]), + }), + data: None, + }; + + lenses.push(lens); + } + _ => continue, + }; + } + + return Ok(Some(lenses)); +} + pub fn handle_document_highlight( world: ServerWorld, params: req::TextDocumentPositionParams, diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs index b41e90328..c2b16725b 100644 --- a/crates/ra_lsp_server/src/req.rs +++ b/crates/ra_lsp_server/src/req.rs @@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize}; use url_serde; pub use languageserver_types::{ - notification::*, request::*, ApplyWorkspaceEditParams, CodeActionParams, CompletionParams, - CompletionResponse, DocumentOnTypeFormattingParams, DocumentSymbolParams, + notification::*, request::*, ApplyWorkspaceEditParams, CodeActionParams, CodeLens, CodeLensParams, + CompletionParams, CompletionResponse, DocumentOnTypeFormattingParams, DocumentSymbolParams, DocumentSymbolResponse, ExecuteCommandParams, Hover, InitializeResult, PublishDiagnosticsParams, ReferenceParams, SignatureHelp, TextDocumentEdit, TextDocumentPositionParams, TextEdit, WorkspaceEdit, WorkspaceSymbolParams, diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts index 33e2b34a2..c8bb55591 100644 --- a/editors/code/src/commands/index.ts +++ b/editors/code/src/commands/index.ts @@ -4,6 +4,7 @@ import * as joinLines from './join_lines'; import * as matchingBrace from './matching_brace'; import * as onEnter from './on_enter'; import * as parentModule from './parent_module'; +import * as runSingle from './run_single'; import * as runnables from './runnables'; import * as syntaxTree from './syntaxTree'; @@ -13,6 +14,7 @@ export { joinLines, matchingBrace, parentModule, + runSingle, runnables, syntaxTree, onEnter diff --git a/editors/code/src/commands/run_single.ts b/editors/code/src/commands/run_single.ts new file mode 100644 index 000000000..855bcdb06 --- /dev/null +++ b/editors/code/src/commands/run_single.ts @@ -0,0 +1,63 @@ +import * as vscode from 'vscode'; +import * as lc from 'vscode-languageclient'; + +interface Runnable { + range: lc.Range; + label: string; + bin: string; + args: string[]; + env: { [index: string]: string }; +} + +interface CargoTaskDefinition extends vscode.TaskDefinition { + type: 'cargo'; + label: string; + command: string; + args: string[]; + env?: { [key: string]: string }; +} + +function createTask(spec: Runnable): vscode.Task { + const TASK_SOURCE = 'Rust'; + const definition: CargoTaskDefinition = { + type: 'cargo', + label: 'cargo', + command: spec.bin, + args: spec.args, + env: spec.env + }; + + const execOption: vscode.ShellExecutionOptions = { + cwd: '.', + env: definition.env + }; + const exec = new vscode.ShellExecution(definition.command, definition.args, execOption); + + const f = vscode.workspace.workspaceFolders![0]; + const t = new vscode.Task( + definition, + f, + definition.label, + TASK_SOURCE, + exec, + ['$rustc'] + ); + t.presentationOptions.clear = true + return t; +} + +export async function handle(runnable: Runnable) { + const editor = vscode.window.activeTextEditor; + if (editor == null || editor.document.languageId !== 'rust') { + return; + } + + const task = createTask(runnable); + task.group = vscode.TaskGroup.Build; + task.presentationOptions = { + reveal: vscode.TaskRevealKind.Always, + panel: vscode.TaskPanelKind.Dedicated, + }; + + return vscode.tasks.executeTask(task); +} \ No newline at end of file diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts index 4acd54d90..acbb1f734 100644 --- a/editors/code/src/extension.ts +++ b/editors/code/src/extension.ts @@ -55,6 +55,9 @@ export function activate(context: vscode.ExtensionContext) { ); overrideCommand('type', commands.onEnter.handle); + // Unlike the above this does not send requests to the language server + registerCommand('ra-lsp.run-single', commands.runSingle.handle); + // Notifications are events triggered by the language server const allNotifications: Iterable< [string, lc.GenericNotificationHandler] -- cgit v1.2.3