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.ts198
1 files changed, 140 insertions, 58 deletions
diff --git a/editors/code/src/commands/cargo_watch.ts b/editors/code/src/commands/cargo_watch.ts
index 6ba794bb3..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,20 +37,39 @@ 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() {
43 this.diagnosticCollection = vscode.languages.createDiagnosticCollection( 54 this.diagnosticCollection = vscode.languages.createDiagnosticCollection(
44 'rustc' 55 'rustc'
45 ); 56 );
46 this.statusDisplay = new StatusDisplay(); 57 this.statusDisplay = new StatusDisplay(
58 Server.config.cargoWatchOptions.command
59 );
47 this.outputChannel = vscode.window.createOutputChannel( 60 this.outputChannel = vscode.window.createOutputChannel(
48 'Cargo Watch Trace' 61 'Cargo Watch Trace'
49 ); 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 );
50 } 73 }
51 74
52 public start() { 75 public start() {
@@ -57,10 +80,12 @@ export class CargoWatchProvider implements vscode.Disposable {
57 return; 80 return;
58 } 81 }
59 82
60 let args = 'check --all-targets --message-format json'; 83 let args =
61 if (Server.config.cargoWatchOptions.checkArguments.length > 0) { 84 Server.config.cargoWatchOptions.command +
85 ' --all-targets --message-format json';
86 if (Server.config.cargoWatchOptions.command.length > 0) {
62 // Excape the double quote string: 87 // Excape the double quote string:
63 args += ' ' + Server.config.cargoWatchOptions.checkArguments; 88 args += ' ' + Server.config.cargoWatchOptions.arguments;
64 } 89 }
65 // Windows handles arguments differently than the unix-likes, so we need to wrap the args in double quotes 90 // Windows handles arguments differently than the unix-likes, so we need to wrap the args in double quotes
66 if (process.platform === 'win32') { 91 if (process.platform === 'win32') {
@@ -123,6 +148,14 @@ export class CargoWatchProvider implements vscode.Disposable {
123 this.diagnosticCollection.dispose(); 148 this.diagnosticCollection.dispose();
124 this.outputChannel.dispose(); 149 this.outputChannel.dispose();
125 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 || [];
126 } 159 }
127 160
128 private logInfo(line: string) { 161 private logInfo(line: string) {
@@ -143,6 +176,7 @@ export class CargoWatchProvider implements vscode.Disposable {
143 private parseLine(line: string) { 176 private parseLine(line: string) {
144 if (line.startsWith('[Running')) { 177 if (line.startsWith('[Running')) {
145 this.diagnosticCollection.clear(); 178 this.diagnosticCollection.clear();
179 this.codeActions = {};
146 this.statusDisplay.show(); 180 this.statusDisplay.show();
147 } 181 }
148 182
@@ -150,34 +184,65 @@ export class CargoWatchProvider implements vscode.Disposable {
150 this.statusDisplay.hide(); 184 this.statusDisplay.hide();
151 } 185 }
152 186
153 function getLevel(s: string): vscode.DiagnosticSeverity { 187 function areDiagnosticsEqual(
154 if (s === 'error') { 188 left: vscode.Diagnostic,
155 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;
156 } 210 }
157 if (s.startsWith('warn')) { 211
158 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;
159 } 217 }
160 return vscode.DiagnosticSeverity.Information;
161 }
162 218
163 // Reference: 219 for (let i = 0; i < leftEditEntries.length; i++) {
164 // https://github.com/rust-lang/rust/blob/master/src/libsyntax/json.rs 220 const [leftUri, leftEdits] = leftEditEntries[i];
165 interface RustDiagnosticSpan { 221 const [rightUri, rightEdits] = rightEditEntries[i];
166 line_start: number; 222
167 line_end: number; 223 if (leftUri.toString() !== rightUri.toString()) {
168 column_start: number; 224 return false;
169 column_end: number; 225 }
170 is_primary: boolean; 226
171 file_name: string; 227 if (leftEdits.length !== rightEdits.length) {
172 } 228 return false;
229 }
230
231 for (let j = 0; j < leftEdits.length; j++) {
232 const leftEdit = leftEdits[j];
233 const rightEdit = rightEdits[j];
234
235 if (!leftEdit.range.isEqual(rightEdit.range)) {
236 return false;
237 }
173 238
174 interface RustDiagnostic { 239 if (leftEdit.newText !== rightEdit.newText) {
175 spans: RustDiagnosticSpan[]; 240 return false;
176 rendered: string; 241 }
177 level: string; 242 }
178 code?: { 243 }
179 code: string; 244
180 }; 245 return true;
181 } 246 }
182 247
183 interface CargoArtifact { 248 interface CargoArtifact {
@@ -211,41 +276,58 @@ export class CargoWatchProvider implements vscode.Disposable {
211 } else if (data.reason === 'compiler-message') { 276 } else if (data.reason === 'compiler-message') {
212 const msg = data.message as RustDiagnostic; 277 const msg = data.message as RustDiagnostic;
213 278
214 const spans = msg.spans.filter(o => o.is_primary); 279 const mapResult = mapRustDiagnosticToVsCode(msg);
215 280 if (!mapResult) {
216 // We only handle primary span right now. 281 return;
217 if (spans.length > 0) { 282 }
218 const o = spans[0];
219 283
220 const rendered = msg.rendered; 284 const { location, diagnostic, codeActions } = mapResult;
221 const level = getLevel(msg.level); 285 const fileUri = location.uri;
222 const range = new vscode.Range(
223 new vscode.Position(o.line_start - 1, o.column_start - 1),
224 new vscode.Position(o.line_end - 1, o.column_end - 1)
225 );
226 286
227 const fileName = path.join( 287 const diagnostics: vscode.Diagnostic[] = [
228 vscode.workspace.rootPath!, 288 ...(this.diagnosticCollection!.get(fileUri) || [])
229 o.file_name 289 ];
230 );
231 const diagnostic = new vscode.Diagnostic(
232 range,
233 rendered,
234 level
235 );
236 290
237 diagnostic.source = 'rustc'; 291 // If we're building multiple targets it's possible we've already seen this diagnostic
238 diagnostic.code = msg.code ? msg.code.code : undefined; 292 const isDuplicate = diagnostics.some(d =>
239 diagnostic.relatedInformation = []; 293 areDiagnosticsEqual(d, diagnostic)
294 );
240 295
241 const fileUrl = vscode.Uri.file(fileName!); 296 if (isDuplicate) {
297 return;
298 }
242 299
243 const diagnostics: vscode.Diagnostic[] = [ 300 diagnostics.push(diagnostic);
244 ...(this.diagnosticCollection!.get(fileUrl) || []) 301 this.diagnosticCollection!.set(fileUri, diagnostics);
245 ]; 302
246 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 }
247 323
248 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 );
249 } 331 }
250 } 332 }
251 } 333 }