1 import * as child_process from 'child_process';
2 import * as path from 'path';
3 import * as vscode from 'vscode';
5 import { Server } from '../server';
6 import { terminate } from '../utils/processes';
7 import { LineBuffer } from './line_buffer';
8 import { StatusDisplay } from './watch_status';
11 mapRustDiagnosticToVsCode,
13 } from '../utils/diagnostics/rust';
14 import SuggestedFixCollection from '../utils/diagnostics/SuggestedFixCollection';
15 import { areDiagnosticsEqual } from '../utils/diagnostics/vscode';
17 export async function registerCargoWatchProvider(
18 subscriptions: vscode.Disposable[],
19 ): Promise<CargoWatchProvider | undefined> {
20 let cargoExists = false;
22 // Check if the working directory is valid cargo root path
23 const cargoTomlPath = path.join(vscode.workspace.rootPath!, 'Cargo.toml');
24 const cargoTomlUri = vscode.Uri.file(cargoTomlPath);
25 const cargoTomlFileInfo = await vscode.workspace.fs.stat(cargoTomlUri);
27 if (cargoTomlFileInfo) {
32 vscode.window.showErrorMessage(
33 `Couldn\'t find \'Cargo.toml\' at ${cargoTomlPath}`,
38 const provider = new CargoWatchProvider();
39 subscriptions.push(provider);
43 export class CargoWatchProvider implements vscode.Disposable {
44 private readonly diagnosticCollection: vscode.DiagnosticCollection;
45 private readonly statusDisplay: StatusDisplay;
46 private readonly outputChannel: vscode.OutputChannel;
48 private suggestedFixCollection: SuggestedFixCollection;
49 private codeActionDispose: vscode.Disposable;
51 private cargoProcess?: child_process.ChildProcess;
54 this.diagnosticCollection = vscode.languages.createDiagnosticCollection(
57 this.statusDisplay = new StatusDisplay(
58 Server.config.cargoWatchOptions.command,
60 this.outputChannel = vscode.window.createOutputChannel(
64 // Track `rustc`'s suggested fixes so we can convert them to code actions
65 this.suggestedFixCollection = new SuggestedFixCollection();
66 this.codeActionDispose = vscode.languages.registerCodeActionsProvider(
67 [{ scheme: 'file', language: 'rust' }],
68 this.suggestedFixCollection,
70 providedCodeActionKinds:
71 SuggestedFixCollection.PROVIDED_CODE_ACTION_KINDS,
77 if (this.cargoProcess) {
78 vscode.window.showInformationMessage(
79 'Cargo Watch is already running',
85 Server.config.cargoWatchOptions.command +
86 ' --message-format json';
87 if (Server.config.cargoWatchOptions.allTargets) {
88 args += ' --all-targets';
90 if (Server.config.cargoWatchOptions.command.length > 0) {
91 // Excape the double quote string:
92 args += ' ' + Server.config.cargoWatchOptions.arguments;
94 // Windows handles arguments differently than the unix-likes, so we need to wrap the args in double quotes
95 if (process.platform === 'win32') {
96 args = '"' + args + '"';
99 const ignoreFlags = Server.config.cargoWatchOptions.ignore.reduce(
100 (flags, pattern) => [...flags, '--ignore', pattern],
104 // Start the cargo watch with json message
105 this.cargoProcess = child_process.spawn(
107 ['watch', '-x', args, ...ignoreFlags],
109 stdio: ['ignore', 'pipe', 'pipe'],
110 cwd: vscode.workspace.rootPath,
111 windowsVerbatimArguments: true,
115 const stdoutData = new LineBuffer();
116 this.cargoProcess.stdout.on('data', (s: string) => {
117 stdoutData.processOutput(s, line => {
120 this.parseLine(line);
122 this.logError(`Failed to parse: ${err}, content : ${line}`);
127 const stderrData = new LineBuffer();
128 this.cargoProcess.stderr.on('data', (s: string) => {
129 stderrData.processOutput(s, line => {
130 this.logError('Error on cargo-watch : {\n' + line + '}\n');
134 this.cargoProcess.on('error', (err: Error) => {
136 'Error on cargo-watch process : {\n' + err.message + '}\n',
140 this.logInfo('cargo-watch started.');
144 if (this.cargoProcess) {
145 this.cargoProcess.kill();
146 terminate(this.cargoProcess);
147 this.cargoProcess = undefined;
149 vscode.window.showInformationMessage('Cargo Watch is not running');
153 public dispose(): void {
156 this.diagnosticCollection.clear();
157 this.diagnosticCollection.dispose();
158 this.outputChannel.dispose();
159 this.statusDisplay.dispose();
160 this.codeActionDispose.dispose();
163 private logInfo(line: string) {
164 if (Server.config.cargoWatchOptions.trace === 'verbose') {
165 this.outputChannel.append(line);
169 private logError(line: string) {
171 Server.config.cargoWatchOptions.trace === 'error' ||
172 Server.config.cargoWatchOptions.trace === 'verbose'
174 this.outputChannel.append(line);
178 private parseLine(line: string) {
179 if (line.startsWith('[Running')) {
180 this.diagnosticCollection.clear();
181 this.suggestedFixCollection.clear();
182 this.statusDisplay.show();
185 if (line.startsWith('[Finished running')) {
186 this.statusDisplay.hide();
189 interface CargoArtifact {
194 // https://github.com/rust-lang/cargo/blob/master/src/cargo/util/machine_message.rs
195 interface CargoMessage {
198 message: RustDiagnostic;
201 // cargo-watch itself output non json format
202 // Ignore these lines
203 let data: CargoMessage;
205 data = JSON.parse(line.trim());
207 this.logError(`Fail to parse to json : { ${error} }`);
211 if (data.reason === 'compiler-artifact') {
212 const msg = data as CargoArtifact;
214 // The format of the package_id is "{name} {version} ({source_id})",
215 // https://github.com/rust-lang/cargo/blob/37ad03f86e895bb80b474c1c088322634f4725f5/src/cargo/core/package_id.rs#L53
216 this.statusDisplay.packageName = msg.package_id.split(' ')[0];
217 } else if (data.reason === 'compiler-message') {
218 const msg = data.message as RustDiagnostic;
220 const mapResult = mapRustDiagnosticToVsCode(msg);
225 const { location, diagnostic, suggestedFixes } = mapResult;
226 const fileUri = location.uri;
228 const diagnostics: vscode.Diagnostic[] = [
229 ...(this.diagnosticCollection!.get(fileUri) || []),
232 // If we're building multiple targets it's possible we've already seen this diagnostic
233 const isDuplicate = diagnostics.some(d =>
234 areDiagnosticsEqual(d, diagnostic),
240 diagnostics.push(diagnostic);
241 this.diagnosticCollection!.set(fileUri, diagnostics);
243 if (suggestedFixes.length) {
244 for (const suggestedFix of suggestedFixes) {
245 this.suggestedFixCollection.addSuggestedFixForDiagnostic(
251 // Have VsCode query us for the code actions
252 vscode.commands.executeCommand(
253 'vscode.executeCodeActionProvider',