diff options
Diffstat (limited to 'editors/code/src/commands/cargo_watch.ts')
-rw-r--r-- | editors/code/src/commands/cargo_watch.ts | 238 |
1 files changed, 238 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..32bd38a1c --- /dev/null +++ b/editors/code/src/commands/cargo_watch.ts | |||
@@ -0,0 +1,238 @@ | |||
1 | import * as child_process from 'child_process'; | ||
2 | import * as fs from 'fs'; | ||
3 | import * as path from 'path'; | ||
4 | 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'; | ||
9 | |||
10 | export 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 | // Windows handles arguments differently than the unix-likes, so we need to wrap the args in double quotes | ||
55 | if (process.platform === 'win32') { | ||
56 | args = '"' + args + '"'; | ||
57 | } | ||
58 | |||
59 | // Start the cargo watch with json message | ||
60 | this.cargoProcess = child_process.spawn( | ||
61 | 'cargo', | ||
62 | ['watch', '-x', args], | ||
63 | { | ||
64 | stdio: ['ignore', 'pipe', 'pipe'], | ||
65 | cwd: vscode.workspace.rootPath, | ||
66 | windowsVerbatimArguments: true | ||
67 | } | ||
68 | ); | ||
69 | |||
70 | const stdoutData = new LineBuffer(); | ||
71 | this.cargoProcess.stdout.on('data', (s: string) => { | ||
72 | stdoutData.processOutput(s, line => { | ||
73 | this.logInfo(line); | ||
74 | try { | ||
75 | this.parseLine(line); | ||
76 | } catch (err) { | ||
77 | this.logError(`Failed to parse: ${err}, content : ${line}`); | ||
78 | } | ||
79 | }); | ||
80 | }); | ||
81 | |||
82 | const stderrData = new LineBuffer(); | ||
83 | this.cargoProcess.stderr.on('data', (s: string) => { | ||
84 | stderrData.processOutput(s, line => { | ||
85 | this.logError('Error on cargo-watch : {\n' + line + '}\n'); | ||
86 | }); | ||
87 | }); | ||
88 | |||
89 | this.cargoProcess.on('error', (err: Error) => { | ||
90 | this.logError( | ||
91 | 'Error on cargo-watch process : {\n' + err.message + '}\n' | ||
92 | ); | ||
93 | }); | ||
94 | |||
95 | this.logInfo('cargo-watch started.'); | ||
96 | } | ||
97 | |||
98 | public dispose(): void { | ||
99 | if (this.diagnosticCollection) { | ||
100 | this.diagnosticCollection.clear(); | ||
101 | this.diagnosticCollection.dispose(); | ||
102 | } | ||
103 | |||
104 | if (this.cargoProcess) { | ||
105 | this.cargoProcess.kill(); | ||
106 | terminate(this.cargoProcess); | ||
107 | } | ||
108 | |||
109 | if (this.outputChannel) { | ||
110 | this.outputChannel.dispose(); | ||
111 | } | ||
112 | } | ||
113 | |||
114 | private logInfo(line: string) { | ||
115 | if (Server.config.cargoWatchOptions.trace === 'verbose') { | ||
116 | this.outputChannel!.append(line); | ||
117 | } | ||
118 | } | ||
119 | |||
120 | private logError(line: string) { | ||
121 | if ( | ||
122 | Server.config.cargoWatchOptions.trace === 'error' || | ||
123 | Server.config.cargoWatchOptions.trace === 'verbose' | ||
124 | ) { | ||
125 | this.outputChannel!.append(line); | ||
126 | } | ||
127 | } | ||
128 | |||
129 | private parseLine(line: string) { | ||
130 | if (line.startsWith('[Running')) { | ||
131 | this.diagnosticCollection!.clear(); | ||
132 | this.statusDisplay!.show(); | ||
133 | } | ||
134 | |||
135 | if (line.startsWith('[Finished running')) { | ||
136 | this.statusDisplay!.hide(); | ||
137 | } | ||
138 | |||
139 | function getLevel(s: string): vscode.DiagnosticSeverity { | ||
140 | if (s === 'error') { | ||
141 | return vscode.DiagnosticSeverity.Error; | ||
142 | } | ||
143 | if (s.startsWith('warn')) { | ||
144 | return vscode.DiagnosticSeverity.Warning; | ||
145 | } | ||
146 | return vscode.DiagnosticSeverity.Information; | ||
147 | } | ||
148 | |||
149 | // Reference: | ||
150 | // https://github.com/rust-lang/rust/blob/master/src/libsyntax/json.rs | ||
151 | interface RustDiagnosticSpan { | ||
152 | line_start: number; | ||
153 | line_end: number; | ||
154 | column_start: number; | ||
155 | column_end: number; | ||
156 | is_primary: boolean; | ||
157 | file_name: string; | ||
158 | } | ||
159 | |||
160 | interface RustDiagnostic { | ||
161 | spans: RustDiagnosticSpan[]; | ||
162 | rendered: string; | ||
163 | level: string; | ||
164 | code?: { | ||
165 | code: string; | ||
166 | }; | ||
167 | } | ||
168 | |||
169 | interface CargoArtifact { | ||
170 | reason: string; | ||
171 | package_id: string; | ||
172 | } | ||
173 | |||
174 | // https://github.com/rust-lang/cargo/blob/master/src/cargo/util/machine_message.rs | ||
175 | interface CargoMessage { | ||
176 | reason: string; | ||
177 | package_id: string; | ||
178 | message: RustDiagnostic; | ||
179 | } | ||
180 | |||
181 | // cargo-watch itself output non json format | ||
182 | // Ignore these lines | ||
183 | let data: CargoMessage; | ||
184 | try { | ||
185 | data = JSON.parse(line.trim()); | ||
186 | } catch (error) { | ||
187 | this.logError(`Fail to parse to json : { ${error} }`); | ||
188 | return; | ||
189 | } | ||
190 | |||
191 | if (data.reason === 'compiler-artifact') { | ||
192 | const msg = data as CargoArtifact; | ||
193 | |||
194 | // The format of the package_id is "{name} {version} ({source_id})", | ||
195 | // https://github.com/rust-lang/cargo/blob/37ad03f86e895bb80b474c1c088322634f4725f5/src/cargo/core/package_id.rs#L53 | ||
196 | this.statusDisplay!.packageName = msg.package_id.split(' ')[0]; | ||
197 | } else if (data.reason === 'compiler-message') { | ||
198 | const msg = data.message as RustDiagnostic; | ||
199 | |||
200 | const spans = msg.spans.filter(o => o.is_primary); | ||
201 | |||
202 | // We only handle primary span right now. | ||
203 | if (spans.length > 0) { | ||
204 | const o = spans[0]; | ||
205 | |||
206 | const rendered = msg.rendered; | ||
207 | const level = getLevel(msg.level); | ||
208 | const range = new vscode.Range( | ||
209 | new vscode.Position(o.line_start - 1, o.column_start - 1), | ||
210 | new vscode.Position(o.line_end - 1, o.column_end - 1) | ||
211 | ); | ||
212 | |||
213 | const fileName = path.join( | ||
214 | vscode.workspace.rootPath!, | ||
215 | o.file_name | ||
216 | ); | ||
217 | const diagnostic = new vscode.Diagnostic( | ||
218 | range, | ||
219 | rendered, | ||
220 | level | ||
221 | ); | ||
222 | |||
223 | diagnostic.source = 'rustc'; | ||
224 | diagnostic.code = msg.code ? msg.code.code : undefined; | ||
225 | diagnostic.relatedInformation = []; | ||
226 | |||
227 | const fileUrl = vscode.Uri.file(fileName!); | ||
228 | |||
229 | const diagnostics: vscode.Diagnostic[] = [ | ||
230 | ...(this.diagnosticCollection!.get(fileUrl) || []) | ||
231 | ]; | ||
232 | diagnostics.push(diagnostic); | ||
233 | |||
234 | this.diagnosticCollection!.set(fileUrl, diagnostics); | ||
235 | } | ||
236 | } | ||
237 | } | ||
238 | } | ||