aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src/commands/cargo_watch.ts
diff options
context:
space:
mode:
Diffstat (limited to 'editors/code/src/commands/cargo_watch.ts')
-rw-r--r--editors/code/src/commands/cargo_watch.ts235
1 files changed, 235 insertions, 0 deletions
diff --git a/editors/code/src/commands/cargo_watch.ts b/editors/code/src/commands/cargo_watch.ts
new file mode 100644
index 000000000..d45d0e7d1
--- /dev/null
+++ b/editors/code/src/commands/cargo_watch.ts
@@ -0,0 +1,235 @@
1import * as child_process from 'child_process';
2import * as fs from 'fs';
3import * as path from 'path';
4import * as vscode from 'vscode';
5import { Server } from '../server';
6import { terminate } from '../utils/processes';
7import { LineBuffer } from './line_buffer';
8import { StatusDisplay } from './watch_status';
9
10export class CargoWatchProvider {
11 private diagnosticCollection?: vscode.DiagnosticCollection;
12 private cargoProcess?: child_process.ChildProcess;
13 private outBuffer: string = '';
14 private statusDisplay?: StatusDisplay;
15 private outputChannel?: vscode.OutputChannel;
16
17 public activate(subscriptions: vscode.Disposable[]) {
18 let cargoExists = false;
19 const cargoTomlFile = path.join(
20 vscode.workspace.rootPath!,
21 'Cargo.toml'
22 );
23 // Check if the working directory is valid cargo root path
24 try {
25 if (fs.existsSync(cargoTomlFile)) {
26 cargoExists = true;
27 }
28 } catch (err) {
29 cargoExists = false;
30 }
31
32 if (!cargoExists) {
33 vscode.window.showErrorMessage(
34 `Couldn\'t find \'Cargo.toml\' in ${cargoTomlFile}`
35 );
36 return;
37 }
38
39 subscriptions.push(this);
40 this.diagnosticCollection = vscode.languages.createDiagnosticCollection(
41 'rustc'
42 );
43
44 this.statusDisplay = new StatusDisplay(subscriptions);
45 this.outputChannel = vscode.window.createOutputChannel(
46 'Cargo Watch Trace'
47 );
48
49 let args = '"check --message-format json';
50 if (Server.config.cargoWatchOptions.checkArguments.length > 0) {
51 // Excape the double quote string:
52 args += ' ' + Server.config.cargoWatchOptions.checkArguments;
53 }
54 args += '"';
55
56 // Start the cargo watch with json message
57 this.cargoProcess = child_process.spawn(
58 'cargo',
59 ['watch', '-x', args],
60 {
61 stdio: ['ignore', 'pipe', 'pipe'],
62 cwd: vscode.workspace.rootPath,
63 windowsVerbatimArguments: true
64 }
65 );
66
67 const stdoutData = new LineBuffer();
68 this.cargoProcess.stdout.on('data', (s: string) => {
69 stdoutData.processOutput(s, line => {
70 this.logInfo(line);
71 try {
72 this.parseLine(line);
73 } catch (err) {
74 this.logError(`Failed to parse: ${err}, content : ${line}`);
75 }
76 });
77 });
78
79 const stderrData = new LineBuffer();
80 this.cargoProcess.stderr.on('data', (s: string) => {
81 stderrData.processOutput(s, line => {
82 this.logError('Error on cargo-watch : {\n' + line + '}\n');
83 });
84 });
85
86 this.cargoProcess.on('error', (err: Error) => {
87 this.logError(
88 'Error on cargo-watch process : {\n' + err.message + '}\n'
89 );
90 });
91
92 this.logInfo('cargo-watch started.');
93 }
94
95 public dispose(): void {
96 if (this.diagnosticCollection) {
97 this.diagnosticCollection.clear();
98 this.diagnosticCollection.dispose();
99 }
100
101 if (this.cargoProcess) {
102 this.cargoProcess.kill();
103 terminate(this.cargoProcess);
104 }
105
106 if (this.outputChannel) {
107 this.outputChannel.dispose();
108 }
109 }
110
111 private logInfo(line: string) {
112 if (Server.config.cargoWatchOptions.trace === 'verbose') {
113 this.outputChannel!.append(line);
114 }
115 }
116
117 private logError(line: string) {
118 if (
119 Server.config.cargoWatchOptions.trace === 'error' ||
120 Server.config.cargoWatchOptions.trace === 'verbose'
121 ) {
122 this.outputChannel!.append(line);
123 }
124 }
125
126 private parseLine(line: string) {
127 if (line.startsWith('[Running')) {
128 this.diagnosticCollection!.clear();
129 this.statusDisplay!.show();
130 }
131
132 if (line.startsWith('[Finished running')) {
133 this.statusDisplay!.hide();
134 }
135
136 function getLevel(s: string): vscode.DiagnosticSeverity {
137 if (s === 'error') {
138 return vscode.DiagnosticSeverity.Error;
139 }
140 if (s.startsWith('warn')) {
141 return vscode.DiagnosticSeverity.Warning;
142 }
143 return vscode.DiagnosticSeverity.Information;
144 }
145
146 // Reference:
147 // https://github.com/rust-lang/rust/blob/master/src/libsyntax/json.rs
148 interface RustDiagnosticSpan {
149 line_start: number;
150 line_end: number;
151 column_start: number;
152 column_end: number;
153 is_primary: boolean;
154 file_name: string;
155 }
156
157 interface RustDiagnostic {
158 spans: RustDiagnosticSpan[];
159 rendered: string;
160 level: string;
161 code?: {
162 code: string;
163 };
164 }
165
166 interface CargoArtifact {
167 reason: string;
168 package_id: string;
169 }
170
171 // https://github.com/rust-lang/cargo/blob/master/src/cargo/util/machine_message.rs
172 interface CargoMessage {
173 reason: string;
174 package_id: string;
175 message: RustDiagnostic;
176 }
177
178 // cargo-watch itself output non json format
179 // Ignore these lines
180 let data: CargoMessage;
181 try {
182 data = JSON.parse(line.trim());
183 } catch (error) {
184 this.logError(`Fail to parse to json : { ${error} }`);
185 return;
186 }
187
188 if (data.reason === 'compiler-artifact') {
189 const msg = data as CargoArtifact;
190
191 // The format of the package_id is "{name} {version} ({source_id})",
192 // https://github.com/rust-lang/cargo/blob/37ad03f86e895bb80b474c1c088322634f4725f5/src/cargo/core/package_id.rs#L53
193 this.statusDisplay!.packageName = msg.package_id.split(' ')[0];
194 } else if (data.reason === 'compiler-message') {
195 const msg = data.message as RustDiagnostic;
196
197 const spans = msg.spans.filter(o => o.is_primary);
198
199 // We only handle primary span right now.
200 if (spans.length > 0) {
201 const o = spans[0];
202
203 const rendered = msg.rendered;
204 const level = getLevel(msg.level);
205 const range = new vscode.Range(
206 new vscode.Position(o.line_start - 1, o.column_start - 1),
207 new vscode.Position(o.line_end - 1, o.column_end - 1)
208 );
209
210 const fileName = path.join(
211 vscode.workspace.rootPath!,
212 o.file_name
213 );
214 const diagnostic = new vscode.Diagnostic(
215 range,
216 rendered,
217 level
218 );
219
220 diagnostic.source = 'rustc';
221 diagnostic.code = msg.code ? msg.code.code : undefined;
222 diagnostic.relatedInformation = [];
223
224 const fileUrl = vscode.Uri.file(fileName!);
225
226 const diagnostics: vscode.Diagnostic[] = [
227 ...(this.diagnosticCollection!.get(fileUrl) || [])
228 ];
229 diagnostics.push(diagnostic);
230
231 this.diagnosticCollection!.set(fileUrl, diagnostics);
232 }
233 }
234 }
235}