From bc560a2f5b323b6b3581b30e97cd8cca792ddb65 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Mon, 18 Mar 2019 17:53:07 +0100 Subject: Remove redundant Runnable.range --- editors/code/src/commands/runnables.ts | 1 - 1 file changed, 1 deletion(-) (limited to 'editors/code') diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts index c0f2ada76..28ad7a302 100644 --- a/editors/code/src/commands/runnables.ts +++ b/editors/code/src/commands/runnables.ts @@ -8,7 +8,6 @@ interface RunnablesParams { } interface Runnable { - range: lc.Range; label: string; bin: string; args: string[]; -- cgit v1.2.3 From 9f1ae658dbb0d091bd384efbab93d622e5fff49f Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Mon, 18 Mar 2019 20:47:52 +0100 Subject: Define a cargo watch task --- editors/code/src/commands/runnables.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) (limited to 'editors/code') diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts index 28ad7a302..74d664034 100644 --- a/editors/code/src/commands/runnables.ts +++ b/editors/code/src/commands/runnables.ts @@ -33,7 +33,7 @@ interface CargoTaskDefinition extends vscode.TaskDefinition { env?: { [key: string]: string }; } -function createTask(spec: Runnable): vscode.Task { +export function createTask(spec: Runnable): vscode.Task { const TASK_SOURCE = 'Rust'; const definition: CargoTaskDefinition = { type: 'cargo', @@ -123,3 +123,23 @@ export async function handleSingle(runnable: Runnable) { return vscode.tasks.executeTask(task); } + +export const autoCargoWatchTask: vscode.Task = { + name: 'cargo watch', + source: 'rust-analyzer', + definition: { + type: "dupa", + }, + execution: new vscode.ShellExecution('cargo', ['watch'], { cwd: '.' }), + + isBackground: true, + problemMatchers: ['$rustc-watch'], + presentationOptions: { + clear: true + }, + // Not yet exposed in the vscode.d.ts + runOptions: { + runOn: 2 // RunOnOptions.folderOpen, https://github.com/Microsoft/vscode/blob/ea7c31d770e04b51d586b0d3944f3a7feb03afb9/src/vs/workbench/contrib/tasks/common/tasks.ts#L444-L456 + } as unknown as vscode.RunOptions, + +}; -- cgit v1.2.3 From afe9cea6402ec7d2deb129e823c3a6d19aa2c906 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Mon, 18 Mar 2019 20:50:52 +0100 Subject: Ask the user to install and start cargo watch --- editors/code/src/extension.ts | 66 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 5 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts index 941beba18..d5b496b1b 100644 --- a/editors/code/src/extension.ts +++ b/editors/code/src/extension.ts @@ -1,11 +1,15 @@ +import { exec, spawn } from 'child_process'; +import * as util from 'util'; import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; import * as commands from './commands'; +import { autoCargoWatchTask, createTask } from './commands/runnables'; import { SyntaxTreeContentProvider } from './commands/syntaxTree'; import * as events from './events'; import * as notifications from './notifications'; import { Server } from './server'; +import { TextDecoder } from 'util'; export function activate(context: vscode.ExtensionContext) { function disposeOnDeactivation(disposable: vscode.Disposable) { @@ -89,11 +93,11 @@ export function activate(context: vscode.ExtensionContext) { const allNotifications: Iterable< [string, lc.GenericNotificationHandler] > = [ - [ - 'rust-analyzer/publishDecorations', - notifications.publishDecorations.handle - ] - ]; + [ + 'rust-analyzer/publishDecorations', + notifications.publishDecorations.handle + ] + ]; const syntaxTreeContentProvider = new SyntaxTreeContentProvider(); // The events below are plain old javascript events, triggered and handled by vscode @@ -119,6 +123,9 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions ); + // Attempts to run `cargo watch`, which provides inline diagnostics on save + askToCargoWatch(); + // Start the language server, finally! Server.start(allNotifications); } @@ -129,3 +136,52 @@ export function deactivate(): Thenable { } return Server.client.stop(); } + +async function askToCargoWatch() { + 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 util.promisify(exec)('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; + } + + try { + // await vscode.tasks.executeTask(createTask({label: '', bin: 'cargo', args: ['install', 'cargo-watch'], env: {}})); + + const channel = vscode.window.createOutputChannel('cargo-watch'); + channel.show(false); + const sup = spawn('cargo', ['install', 'cargo-watch']); + sup.stderr.on('data', chunk => { + const output = new TextDecoder().decode(chunk); + channel.append(output); + }); + await new Promise((resolve, reject) => { + sup.on('close', (code, signal) => { + if (code === 0) { + resolve(code); + } else { + reject(code); + } + }); + }); + channel.dispose(); + } catch (err) { + vscode.window.showErrorMessage( + `Couldn't install \`cargo-watch\`: ${err.message}` + ); + return; + } + } + + vscode.tasks.executeTask(autoCargoWatchTask); +} -- cgit v1.2.3 From 21b73f984433ca6ca1500826af59f8dd8bc22618 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Mon, 18 Mar 2019 21:04:33 +0100 Subject: Respect the user-provided label when creating task --- editors/code/src/commands/runnables.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'editors/code') diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts index 74d664034..c6d23a185 100644 --- a/editors/code/src/commands/runnables.ts +++ b/editors/code/src/commands/runnables.ts @@ -37,7 +37,7 @@ export 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 -- cgit v1.2.3 From 4bd61430622fcd289de1374c9e7c8d9260f38cf3 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Mon, 18 Mar 2019 21:13:49 +0100 Subject: Prefer installing `cargo-watch` via Task API This gives us much more fine-grained stdout buffering and ANSI terminal colors. --- editors/code/src/extension.ts | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts index d5b496b1b..f06c5445d 100644 --- a/editors/code/src/extension.ts +++ b/editors/code/src/extension.ts @@ -155,30 +155,21 @@ async function askToCargoWatch() { return; } - try { - // await vscode.tasks.executeTask(createTask({label: '', bin: 'cargo', args: ['install', 'cargo-watch'], env: {}})); - - const channel = vscode.window.createOutputChannel('cargo-watch'); - channel.show(false); - const sup = spawn('cargo', ['install', 'cargo-watch']); - sup.stderr.on('data', chunk => { - const output = new TextDecoder().decode(chunk); - channel.append(output); - }); - await new Promise((resolve, reject) => { - sup.on('close', (code, signal) => { - if (code === 0) { - resolve(code); - } else { - reject(code); - } - }); + const label = 'install-cargo-watch'; + const taskFinished = new Promise((resolve, reject) => { + let disposable = vscode.tasks.onDidEndTask(({ execution }) => { + if (execution.task.name === label) { + disposable.dispose(); + resolve(); + }; }); - channel.dispose(); - } catch (err) { - vscode.window.showErrorMessage( - `Couldn't install \`cargo-watch\`: ${err.message}` - ); + }); + + vscode.tasks.executeTask(createTask({ label, bin: 'cargo', args: ['install', 'cargo-watch'], env: {} })); + await taskFinished; + const { stderr } = await util.promisify(exec)('cargo watch --version').catch(e => e); + if (stderr !== '') { + vscode.window.showErrorMessage(`Couldn't install \`cargo-\`watch: ${stderr}`); return; } } -- cgit v1.2.3 From 7d2378ed7da7e2255e0ea155632a609909071ca7 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Mon, 18 Mar 2019 21:16:20 +0100 Subject: Remove unused imports --- editors/code/src/extension.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts index f06c5445d..442c9cd0d 100644 --- a/editors/code/src/extension.ts +++ b/editors/code/src/extension.ts @@ -1,4 +1,4 @@ -import { exec, spawn } from 'child_process'; +import { exec } from 'child_process'; import * as util from 'util'; import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; @@ -9,7 +9,6 @@ import { SyntaxTreeContentProvider } from './commands/syntaxTree'; import * as events from './events'; import * as notifications from './notifications'; import { Server } from './server'; -import { TextDecoder } from 'util'; export function activate(context: vscode.ExtensionContext) { function disposeOnDeactivation(disposable: vscode.Disposable) { -- cgit v1.2.3 From 5c3cc8c95f392c523bb638d78e0217780d6e8476 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Mon, 18 Mar 2019 22:15:03 +0100 Subject: Reformat using Prettier --- editors/code/src/commands/runnables.ts | 10 ++++---- editors/code/src/extension.ts | 42 ++++++++++++++++++++++++---------- 2 files changed, 35 insertions(+), 17 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts index c6d23a185..23fd280b4 100644 --- a/editors/code/src/commands/runnables.ts +++ b/editors/code/src/commands/runnables.ts @@ -128,7 +128,7 @@ export const autoCargoWatchTask: vscode.Task = { name: 'cargo watch', source: 'rust-analyzer', definition: { - type: "dupa", + type: 'watch' }, execution: new vscode.ShellExecution('cargo', ['watch'], { cwd: '.' }), @@ -138,8 +138,8 @@ export const autoCargoWatchTask: vscode.Task = { clear: true }, // Not yet exposed in the vscode.d.ts - runOptions: { - runOn: 2 // RunOnOptions.folderOpen, https://github.com/Microsoft/vscode/blob/ea7c31d770e04b51d586b0d3944f3a7feb03afb9/src/vs/workbench/contrib/tasks/common/tasks.ts#L444-L456 - } as unknown as vscode.RunOptions, - + // 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 }; diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts index 442c9cd0d..f915a5023 100644 --- a/editors/code/src/extension.ts +++ b/editors/code/src/extension.ts @@ -92,11 +92,11 @@ export function activate(context: vscode.ExtensionContext) { const allNotifications: Iterable< [string, lc.GenericNotificationHandler] > = [ - [ - 'rust-analyzer/publishDecorations', - notifications.publishDecorations.handle - ] - ]; + [ + 'rust-analyzer/publishDecorations', + notifications.publishDecorations.handle + ] + ]; const syntaxTreeContentProvider = new SyntaxTreeContentProvider(); // The events below are plain old javascript events, triggered and handled by vscode @@ -146,10 +146,17 @@ async function askToCargoWatch() { return; } - const { stderr } = await util.promisify(exec)('cargo watch --version').catch(e => e); + const { stderr } = await util + .promisify(exec)('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'); + 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; } @@ -160,15 +167,26 @@ async function askToCargoWatch() { if (execution.task.name === label) { disposable.dispose(); resolve(); - }; + } }); }); - vscode.tasks.executeTask(createTask({ label, bin: 'cargo', args: ['install', 'cargo-watch'], env: {} })); + vscode.tasks.executeTask( + createTask({ + label, + bin: 'cargo', + args: ['install', 'cargo-watch'], + env: {} + }) + ); await taskFinished; - const { stderr } = await util.promisify(exec)('cargo watch --version').catch(e => e); + const { stderr } = await util + .promisify(exec)('cargo watch --version') + .catch(e => e); if (stderr !== '') { - vscode.window.showErrorMessage(`Couldn't install \`cargo-\`watch: ${stderr}`); + vscode.window.showErrorMessage( + `Couldn't install \`cargo-\`watch: ${stderr}` + ); return; } } -- cgit v1.2.3 From 60cac299640912ad1ad75644bfa0088d7ba6e367 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Mon, 18 Mar 2019 22:30:23 +0100 Subject: Separate out the interactive cargo watch procedure --- editors/code/src/commands/runnables.ts | 68 +++++++++++++++++++++++++++++++++- editors/code/src/extension.ts | 66 ++------------------------------- 2 files changed, 70 insertions(+), 64 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts index 23fd280b4..285afaaf6 100644 --- a/editors/code/src/commands/runnables.ts +++ b/editors/code/src/commands/runnables.ts @@ -1,5 +1,8 @@ +import { exec } 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 { @@ -33,7 +36,7 @@ interface CargoTaskDefinition extends vscode.TaskDefinition { env?: { [key: string]: string }; } -export function createTask(spec: Runnable): vscode.Task { +function createTask(spec: Runnable): vscode.Task { const TASK_SOURCE = 'Rust'; const definition: CargoTaskDefinition = { type: 'cargo', @@ -143,3 +146,66 @@ export const autoCargoWatchTask: vscode.Task = { 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() { + const execAsync = util.promisify(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 execAsync('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) => { + let 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 { stderr } = await execAsync('cargo watch --version').catch( + e => e + ); + if (stderr !== '') { + vscode.window.showErrorMessage( + `Couldn't install \`cargo-\`watch: ${stderr}` + ); + return; + } + } + + vscode.tasks.executeTask(autoCargoWatchTask); +} diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts index f915a5023..2e13c87de 100644 --- a/editors/code/src/extension.ts +++ b/editors/code/src/extension.ts @@ -1,10 +1,8 @@ -import { exec } from 'child_process'; -import * as util from 'util'; import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; import * as commands from './commands'; -import { autoCargoWatchTask, createTask } from './commands/runnables'; +import { interactivelyStartCargoWatch } from './commands/runnables'; import { SyntaxTreeContentProvider } from './commands/syntaxTree'; import * as events from './events'; import * as notifications from './notifications'; @@ -122,8 +120,8 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions ); - // Attempts to run `cargo watch`, which provides inline diagnostics on save - askToCargoWatch(); + // Executing `cargo watch` provides us with inline diagnostics on save + interactivelyStartCargoWatch(); // Start the language server, finally! Server.start(allNotifications); @@ -135,61 +133,3 @@ export function deactivate(): Thenable { } return Server.client.stop(); } - -async function askToCargoWatch() { - 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 util - .promisify(exec)('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) => { - let 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 { stderr } = await util - .promisify(exec)('cargo watch --version') - .catch(e => e); - if (stderr !== '') { - vscode.window.showErrorMessage( - `Couldn't install \`cargo-\`watch: ${stderr}` - ); - return; - } - } - - vscode.tasks.executeTask(autoCargoWatchTask); -} -- cgit v1.2.3 From 7c2595c26820917fa9ad1b1c36f01fc6ac979287 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Mon, 18 Mar 2019 22:35:47 +0100 Subject: Guard auto cargo watch behind a config option --- editors/code/package.json | 5 +++++ editors/code/src/commands/runnables.ts | 4 ++++ editors/code/src/config.ts | 8 ++++++++ 3 files changed, 17 insertions(+) (limited to 'editors/code') diff --git a/editors/code/package.json b/editors/code/package.json index 3834f2847..3e8cde388 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 285afaaf6..74407dc3e 100644 --- a/editors/code/src/commands/runnables.ts +++ b/editors/code/src/commands/runnables.ts @@ -153,6 +153,10 @@ export const autoCargoWatchTask: vscode.Task = { * that, when accepted, allow us to `cargo install cargo-watch` and then run it. */ export async function interactivelyStartCargoWatch() { + if (!Server.config.enableCargoWatchOnStartup) { + return; + } + const execAsync = util.promisify(exec); const watch = await vscode.window.showInformationMessage( diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index afc5cc6af..d8795f3b0 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 + ); + } } } -- cgit v1.2.3 From 34b428cc5e4b37ecd44063547f73fd1b05bf2b9e Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Mon, 18 Mar 2019 22:51:01 +0100 Subject: Appease CI --- editors/code/src/commands/runnables.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts index 74407dc3e..ea2883ad4 100644 --- a/editors/code/src/commands/runnables.ts +++ b/editors/code/src/commands/runnables.ts @@ -1,4 +1,4 @@ -import { exec } from 'child_process'; +import * as child_process from 'child_process'; import * as util from 'util'; import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; @@ -157,7 +157,7 @@ export async function interactivelyStartCargoWatch() { return; } - const execAsync = util.promisify(exec); + const execPromise = util.promisify(child_process.exec); const watch = await vscode.window.showInformationMessage( 'Start watching changes with cargo? (Executes `cargo watch`, provides inline diagnostics)', @@ -168,7 +168,7 @@ export async function interactivelyStartCargoWatch() { return; } - const { stderr } = await execAsync('cargo watch --version').catch(e => e); + 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)'; @@ -183,7 +183,7 @@ export async function interactivelyStartCargoWatch() { const label = 'install-cargo-watch'; const taskFinished = new Promise((resolve, reject) => { - let disposable = vscode.tasks.onDidEndTask(({ execution }) => { + const disposable = vscode.tasks.onDidEndTask(({ execution }) => { if (execution.task.name === label) { disposable.dispose(); resolve(); @@ -200,12 +200,10 @@ export async function interactivelyStartCargoWatch() { }) ); await taskFinished; - const { stderr } = await execAsync('cargo watch --version').catch( - e => e - ); - if (stderr !== '') { + const output = await execPromise('cargo watch --version').catch(e => e); + if (output.stderr !== '') { vscode.window.showErrorMessage( - `Couldn't install \`cargo-\`watch: ${stderr}` + `Couldn't install \`cargo-\`watch: ${output.stderr}` ); return; } -- cgit v1.2.3