aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src/commands/cargo_watch.ts
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-06-25 13:37:07 +0100
committerbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-06-25 13:37:07 +0100
commit4b0c37bd6e4cb3d47614ec6b42fb1deef9bc9324 (patch)
tree31ac3e97ade900dd51bc39c007316aa20a2e85fc /editors/code/src/commands/cargo_watch.ts
parentba97a5fbd2e14f38c633948f0d1551d0cf086ca3 (diff)
parent5c6ab1145319414e897a8eaca2bf1ad5558ccf24 (diff)
Merge #1439
1439: Rich mapping of cargo watch output r=matklad a=etaoins Currently we depend on the ASCII rendering string that `rustc` provides to populate Visual Studio Code's diagnostic. This has a number of shortcomings: 1. It's not a very good use of space in the error list 2. We can't jump to secondary spans (e.g. where a called function is defined) 3. We can't use Code Actions aka Quick Fix This moves all of the low-level parsing and mapping to a `rust_diagnostics.ts`. This uses some heuristics to map Rust diagnostics to VsCode: 1. As before, the Rust diagnostic message and primary span is used for the root diagnostic. However, we now just use the message instead of the rendered version. 2. Every secondary span is converted to "related information". This shows as child in the error list and can be jumped to. 3. Every child diagnostic is categorised in to three buckets: 1. If they have no span they're treated as another line of the root messages 2. If they have replacement text they're treated as a Code Action 3. If they have a span but no replacement text they're treated as related information (same as secondary spans). Co-authored-by: Ryan Cumming <[email protected]>
Diffstat (limited to 'editors/code/src/commands/cargo_watch.ts')
-rw-r--r--editors/code/src/commands/cargo_watch.ts186
1 files changed, 132 insertions, 54 deletions
diff --git a/editors/code/src/commands/cargo_watch.ts b/editors/code/src/commands/cargo_watch.ts
index 13adf4c10..126a8b1b3 100644
--- a/editors/code/src/commands/cargo_watch.ts
+++ b/editors/code/src/commands/cargo_watch.ts
@@ -4,6 +4,10 @@ import * as path from 'path';
4import * as vscode from 'vscode'; 4import * as vscode from 'vscode';
5import { Server } from '../server'; 5import { Server } from '../server';
6import { terminate } from '../utils/processes'; 6import { terminate } from '../utils/processes';
7import {
8 mapRustDiagnosticToVsCode,
9 RustDiagnostic
10} from '../utils/rust_diagnostics';
7import { LineBuffer } from './line_buffer'; 11import { LineBuffer } from './line_buffer';
8import { StatusDisplay } from './watch_status'; 12import { StatusDisplay } from './watch_status';
9 13
@@ -33,10 +37,17 @@ export function registerCargoWatchProvider(
33 return provider; 37 return provider;
34} 38}
35 39
36export class CargoWatchProvider implements vscode.Disposable { 40export class CargoWatchProvider
41 implements vscode.Disposable, vscode.CodeActionProvider {
37 private readonly diagnosticCollection: vscode.DiagnosticCollection; 42 private readonly diagnosticCollection: vscode.DiagnosticCollection;
38 private readonly statusDisplay: StatusDisplay; 43 private readonly statusDisplay: StatusDisplay;
39 private readonly outputChannel: vscode.OutputChannel; 44 private readonly outputChannel: vscode.OutputChannel;
45
46 private codeActions: {
47 [fileUri: string]: vscode.CodeAction[];
48 };
49 private readonly codeActionDispose: vscode.Disposable;
50
40 private cargoProcess?: child_process.ChildProcess; 51 private cargoProcess?: child_process.ChildProcess;
41 52
42 constructor() { 53 constructor() {
@@ -49,6 +60,16 @@ export class CargoWatchProvider implements vscode.Disposable {
49 this.outputChannel = vscode.window.createOutputChannel( 60 this.outputChannel = vscode.window.createOutputChannel(
50 'Cargo Watch Trace' 61 'Cargo Watch Trace'
51 ); 62 );
63
64 // Register code actions for rustc's suggested fixes
65 this.codeActions = {};
66 this.codeActionDispose = vscode.languages.registerCodeActionsProvider(
67 [{ scheme: 'file', language: 'rust' }],
68 this,
69 {
70 providedCodeActionKinds: [vscode.CodeActionKind.QuickFix]
71 }
72 );
52 } 73 }
53 74
54 public start() { 75 public start() {
@@ -127,6 +148,14 @@ export class CargoWatchProvider implements vscode.Disposable {
127 this.diagnosticCollection.dispose(); 148 this.diagnosticCollection.dispose();
128 this.outputChannel.dispose(); 149 this.outputChannel.dispose();
129 this.statusDisplay.dispose(); 150 this.statusDisplay.dispose();
151 this.codeActionDispose.dispose();
152 }
153
154 public provideCodeActions(
155 document: vscode.TextDocument
156 ): vscode.ProviderResult<Array<vscode.Command | vscode.CodeAction>> {
157 const documentActions = this.codeActions[document.uri.toString()];
158 return documentActions || [];
130 } 159 }
131 160
132 private logInfo(line: string) { 161 private logInfo(line: string) {
@@ -147,6 +176,7 @@ export class CargoWatchProvider implements vscode.Disposable {
147 private parseLine(line: string) { 176 private parseLine(line: string) {
148 if (line.startsWith('[Running')) { 177 if (line.startsWith('[Running')) {
149 this.diagnosticCollection.clear(); 178 this.diagnosticCollection.clear();
179 this.codeActions = {};
150 this.statusDisplay.show(); 180 this.statusDisplay.show();
151 } 181 }
152 182
@@ -154,34 +184,65 @@ export class CargoWatchProvider implements vscode.Disposable {
154 this.statusDisplay.hide(); 184 this.statusDisplay.hide();
155 } 185 }
156 186
157 function getLevel(s: string): vscode.DiagnosticSeverity { 187 function areDiagnosticsEqual(
158 if (s === 'error') { 188 left: vscode.Diagnostic,
159 return vscode.DiagnosticSeverity.Error; 189 right: vscode.Diagnostic
190 ): boolean {
191 return (
192 left.source === right.source &&
193 left.severity === right.severity &&
194 left.range.isEqual(right.range) &&
195 left.message === right.message
196 );
197 }
198
199 function areCodeActionsEqual(
200 left: vscode.CodeAction,
201 right: vscode.CodeAction
202 ): boolean {
203 if (
204 left.kind !== right.kind ||
205 left.title !== right.title ||
206 !left.edit ||
207 !right.edit
208 ) {
209 return false;
160 } 210 }
161 if (s.startsWith('warn')) { 211
162 return vscode.DiagnosticSeverity.Warning; 212 const leftEditEntries = left.edit.entries();
213 const rightEditEntries = right.edit.entries();
214
215 if (leftEditEntries.length !== rightEditEntries.length) {
216 return false;
163 } 217 }
164 return vscode.DiagnosticSeverity.Information;
165 }
166 218
167 // Reference: 219 for (let i = 0; i < leftEditEntries.length; i++) {
168 // https://github.com/rust-lang/rust/blob/master/src/libsyntax/json.rs 220 const [leftUri, leftEdits] = leftEditEntries[i];
169 interface RustDiagnosticSpan { 221 const [rightUri, rightEdits] = rightEditEntries[i];
170 line_start: number; 222
171 line_end: number; 223 if (leftUri.toString() !== rightUri.toString()) {
172 column_start: number; 224 return false;
173 column_end: number; 225 }
174 is_primary: boolean;
175 file_name: string;
176 }
177 226
178 interface RustDiagnostic { 227 if (leftEdits.length !== rightEdits.length) {
179 spans: RustDiagnosticSpan[]; 228 return false;
180 rendered: string; 229 }
181 level: string; 230
182 code?: { 231 for (let j = 0; j < leftEdits.length; j++) {
183 code: string; 232 const leftEdit = leftEdits[j];
184 }; 233 const rightEdit = rightEdits[j];
234
235 if (!leftEdit.range.isEqual(rightEdit.range)) {
236 return false;
237 }
238
239 if (leftEdit.newText !== rightEdit.newText) {
240 return false;
241 }
242 }
243 }
244
245 return true;
185 } 246 }
186 247
187 interface CargoArtifact { 248 interface CargoArtifact {
@@ -215,41 +276,58 @@ export class CargoWatchProvider implements vscode.Disposable {
215 } else if (data.reason === 'compiler-message') { 276 } else if (data.reason === 'compiler-message') {
216 const msg = data.message as RustDiagnostic; 277 const msg = data.message as RustDiagnostic;
217 278
218 const spans = msg.spans.filter(o => o.is_primary); 279 const mapResult = mapRustDiagnosticToVsCode(msg);
219 280 if (!mapResult) {
220 // We only handle primary span right now. 281 return;
221 if (spans.length > 0) { 282 }
222 const o = spans[0];
223 283
224 const rendered = msg.rendered; 284 const { location, diagnostic, codeActions } = mapResult;
225 const level = getLevel(msg.level); 285 const fileUri = location.uri;
226 const range = new vscode.Range(
227 new vscode.Position(o.line_start - 1, o.column_start - 1),
228 new vscode.Position(o.line_end - 1, o.column_end - 1)
229 );
230 286
231 const fileName = path.join( 287 const diagnostics: vscode.Diagnostic[] = [
232 vscode.workspace.rootPath!, 288 ...(this.diagnosticCollection!.get(fileUri) || [])
233 o.file_name 289 ];
234 );
235 const diagnostic = new vscode.Diagnostic(
236 range,
237 rendered,
238 level
239 );
240 290
241 diagnostic.source = 'rustc'; 291 // If we're building multiple targets it's possible we've already seen this diagnostic
242 diagnostic.code = msg.code ? msg.code.code : undefined; 292 const isDuplicate = diagnostics.some(d =>
243 diagnostic.relatedInformation = []; 293 areDiagnosticsEqual(d, diagnostic)
294 );
244 295
245 const fileUrl = vscode.Uri.file(fileName!); 296 if (isDuplicate) {
297 return;
298 }
246 299
247 const diagnostics: vscode.Diagnostic[] = [ 300 diagnostics.push(diagnostic);
248 ...(this.diagnosticCollection!.get(fileUrl) || []) 301 this.diagnosticCollection!.set(fileUri, diagnostics);
249 ]; 302
250 diagnostics.push(diagnostic); 303 if (codeActions.length) {
304 const fileUriString = fileUri.toString();
305 const existingActions = this.codeActions[fileUriString] || [];
306
307 for (const newAction of codeActions) {
308 const existingAction = existingActions.find(existing =>
309 areCodeActionsEqual(existing, newAction)
310 );
311
312 if (existingAction) {
313 if (!existingAction.diagnostics) {
314 existingAction.diagnostics = [];
315 }
316 // This action also applies to this diagnostic
317 existingAction.diagnostics.push(diagnostic);
318 } else {
319 newAction.diagnostics = [diagnostic];
320 existingActions.push(newAction);
321 }
322 }
251 323
252 this.diagnosticCollection!.set(fileUrl, diagnostics); 324 // Have VsCode query us for the code actions
325 this.codeActions[fileUriString] = existingActions;
326 vscode.commands.executeCommand(
327 'vscode.executeCodeActionProvider',
328 fileUri,
329 diagnostic.range
330 );
253 } 331 }
254 } 332 }
255 } 333 }