diff --git a/editors/code/package.json b/editors/code/package.json index 3834f28473e..3e8cde38808 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -168,6 +168,11 @@ "default": "ra_lsp_server", "description": "Path to ra_lsp_server executable" }, + "rust-analyzer.enableCargoWatchOnStartup": { + "type": "boolean", + "default": "true", + "description": "When enabled, ask the user whether to run `cargo watch` on startup" + }, "rust-analyzer.trace.server": { "type": "string", "scope": "window", diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts index c0f2ada7608..ea2883ad4c4 100644 --- a/editors/code/src/commands/runnables.ts +++ b/editors/code/src/commands/runnables.ts @@ -1,5 +1,8 @@ +import * as child_process from 'child_process'; +import * as util from 'util'; import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; + import { Server } from '../server'; interface RunnablesParams { @@ -8,7 +11,6 @@ interface RunnablesParams { } interface Runnable { - range: lc.Range; label: string; bin: string; args: string[]; @@ -38,7 +40,7 @@ function createTask(spec: Runnable): vscode.Task { const TASK_SOURCE = 'Rust'; const definition: CargoTaskDefinition = { type: 'cargo', - label: 'cargo', + label: spec.label, command: spec.bin, args: spec.args, env: spec.env @@ -124,3 +126,88 @@ export async function handleSingle(runnable: Runnable) { return vscode.tasks.executeTask(task); } + +export const autoCargoWatchTask: vscode.Task = { + name: 'cargo watch', + source: 'rust-analyzer', + definition: { + type: 'watch' + }, + execution: new vscode.ShellExecution('cargo', ['watch'], { cwd: '.' }), + + isBackground: true, + problemMatchers: ['$rustc-watch'], + presentationOptions: { + clear: true + }, + // Not yet exposed in the vscode.d.ts + // https://github.com/Microsoft/vscode/blob/ea7c31d770e04b51d586b0d3944f3a7feb03afb9/src/vs/workbench/contrib/tasks/common/tasks.ts#L444-L456 + runOptions: ({ + runOn: 2 // RunOnOptions.folderOpen + } as unknown) as vscode.RunOptions +}; + +/** + * Interactively asks the user whether we should run `cargo check` in order to + * provide inline diagnostics; the user is met with a series of dialog boxes + * that, when accepted, allow us to `cargo install cargo-watch` and then run it. + */ +export async function interactivelyStartCargoWatch() { + if (!Server.config.enableCargoWatchOnStartup) { + return; + } + + const execPromise = util.promisify(child_process.exec); + + const watch = await vscode.window.showInformationMessage( + 'Start watching changes with cargo? (Executes `cargo watch`, provides inline diagnostics)', + 'yes', + 'no' + ); + if (watch === 'no') { + return; + } + + const { stderr } = await execPromise('cargo watch --version').catch(e => e); + if (stderr.includes('no such subcommand: `watch`')) { + const msg = + 'The `cargo-watch` subcommand is not installed. Install? (takes ~1-2 minutes)'; + const install = await vscode.window.showInformationMessage( + msg, + 'yes', + 'no' + ); + if (install === 'no') { + return; + } + + const label = 'install-cargo-watch'; + const taskFinished = new Promise((resolve, reject) => { + const disposable = vscode.tasks.onDidEndTask(({ execution }) => { + if (execution.task.name === label) { + disposable.dispose(); + resolve(); + } + }); + }); + + vscode.tasks.executeTask( + createTask({ + label, + bin: 'cargo', + args: ['install', 'cargo-watch'], + env: {} + }) + ); + await taskFinished; + const output = await execPromise('cargo watch --version').catch(e => e); + if (output.stderr !== '') { + vscode.window.showErrorMessage( + `Couldn't install \`cargo-\`watch: ${output.stderr}` + ); + return; + } + } + + vscode.tasks.executeTask(autoCargoWatchTask); +} diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index afc5cc6aff6..d8795f3b0f8 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -9,6 +9,7 @@ export class Config { public enableEnhancedTyping = true; public raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server'; public showWorkspaceLoadedNotification = true; + public enableCargoWatchOnStartup = true; private prevEnhancedTyping: null | boolean = null; @@ -68,5 +69,12 @@ export class Config { this.raLspServerPath = RA_LSP_DEBUG || (config.get('raLspServerPath') as string); } + + if (config.has('enableCargoWatchOnStartup')) { + this.enableCargoWatchOnStartup = config.get( + 'enableCargoWatchOnStartup', + true + ); + } } } diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts index 941beba1807..2e13c87de16 100644 --- a/editors/code/src/extension.ts +++ b/editors/code/src/extension.ts @@ -2,6 +2,7 @@ import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; import * as commands from './commands'; +import { interactivelyStartCargoWatch } from './commands/runnables'; import { SyntaxTreeContentProvider } from './commands/syntaxTree'; import * as events from './events'; import * as notifications from './notifications'; @@ -119,6 +120,9 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions ); + // Executing `cargo watch` provides us with inline diagnostics on save + interactivelyStartCargoWatch(); + // Start the language server, finally! Server.start(allNotifications); }