@@ -4,6 +4,10 @@ import * as path from 'path';
4
4
import * as vscode from 'vscode' ;
5
5
import { Server } from '../server' ;
6
6
import { terminate } from '../utils/processes' ;
7
+ import {
8
+ mapRustDiagnosticToVsCode ,
9
+ RustDiagnostic
10
+ } from '../utils/rust_diagnostics' ;
7
11
import { LineBuffer } from './line_buffer' ;
8
12
import { StatusDisplay } from './watch_status' ;
9
13
@@ -33,10 +37,17 @@ export function registerCargoWatchProvider(
33
37
return provider ;
34
38
}
35
39
36
- export class CargoWatchProvider implements vscode . Disposable {
40
+ export class CargoWatchProvider
41
+ implements vscode . Disposable , vscode . CodeActionProvider {
37
42
private readonly diagnosticCollection : vscode . DiagnosticCollection ;
38
43
private readonly statusDisplay : StatusDisplay ;
39
44
private readonly outputChannel : vscode . OutputChannel ;
45
+
46
+ private codeActions : {
47
+ [ fileUri : string ] : vscode . CodeAction [ ] ;
48
+ } ;
49
+ private readonly codeActionDispose : vscode . Disposable ;
50
+
40
51
private cargoProcess ?: child_process . ChildProcess ;
41
52
42
53
constructor ( ) {
@@ -49,6 +60,16 @@ export class CargoWatchProvider implements vscode.Disposable {
49
60
this . outputChannel = vscode . window . createOutputChannel (
50
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
75
public start ( ) {
@@ -127,6 +148,14 @@ export class CargoWatchProvider implements vscode.Disposable {
127
148
this . diagnosticCollection . dispose ( ) ;
128
149
this . outputChannel . dispose ( ) ;
129
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
161
private logInfo ( line : string ) {
@@ -147,41 +176,73 @@ export class CargoWatchProvider implements vscode.Disposable {
147
176
private parseLine ( line : string ) {
148
177
if ( line . startsWith ( '[Running' ) ) {
149
178
this . diagnosticCollection . clear ( ) ;
179
+ this . codeActions = { } ;
150
180
this . statusDisplay . show ( ) ;
151
181
}
152
182
153
183
if ( line . startsWith ( '[Finished running' ) ) {
154
184
this . statusDisplay . hide ( ) ;
155
185
}
156
186
157
- function getLevel ( s : string ) : vscode . DiagnosticSeverity {
158
- if ( s === 'error' ) {
159
- return vscode . DiagnosticSeverity . Error ;
187
+ function areDiagnosticsEqual (
188
+ left : vscode . Diagnostic ,
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' ) ) {
162
- return vscode . DiagnosticSeverity . Warning ;
211
+
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:
168
- // https://github.com/rust-lang/rust/blob/master/src/libsyntax/json.rs
169
- interface RustDiagnosticSpan {
170
- line_start : number ;
171
- line_end : number ;
172
- column_start : number ;
173
- column_end : number ;
174
- is_primary : boolean ;
175
- file_name : string ;
176
- }
219
+ for ( let i = 0 ; i < leftEditEntries . length ; i ++ ) {
220
+ const [ leftUri , leftEdits ] = leftEditEntries [ i ] ;
221
+ const [ rightUri , rightEdits ] = rightEditEntries [ i ] ;
222
+
223
+ if ( leftUri . toString ( ) !== rightUri . toString ( ) ) {
224
+ return false ;
225
+ }
177
226
178
- interface RustDiagnostic {
179
- spans : RustDiagnosticSpan [ ] ;
180
- rendered : string ;
181
- level : string ;
182
- code ?: {
183
- code : string ;
184
- } ;
227
+ if ( leftEdits . length !== rightEdits . length ) {
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
+ }
238
+
239
+ if ( leftEdit . newText !== rightEdit . newText ) {
240
+ return false ;
241
+ }
242
+ }
243
+ }
244
+
245
+ return true ;
185
246
}
186
247
187
248
interface CargoArtifact {
@@ -215,41 +276,58 @@ export class CargoWatchProvider implements vscode.Disposable {
215
276
} else if ( data . reason === 'compiler-message' ) {
216
277
const msg = data . message as RustDiagnostic ;
217
278
218
- const spans = msg . spans . filter ( o => o . is_primary ) ;
219
-
220
- // We only handle primary span right now.
221
- if ( spans . length > 0 ) {
222
- const o = spans [ 0 ] ;
279
+ const mapResult = mapRustDiagnosticToVsCode ( msg ) ;
280
+ if ( ! mapResult ) {
281
+ return ;
282
+ }
223
283
224
- const rendered = msg . rendered ;
225
- const level = getLevel ( msg . level ) ;
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
- ) ;
284
+ const { location, diagnostic, codeActions } = mapResult ;
285
+ const fileUri = location . uri ;
230
286
231
- const fileName = path . join (
232
- vscode . workspace . rootPath ! ,
233
- o . file_name
234
- ) ;
235
- const diagnostic = new vscode . Diagnostic (
236
- range ,
237
- rendered ,
238
- level
239
- ) ;
287
+ const diagnostics : vscode . Diagnostic [ ] = [
288
+ ...( this . diagnosticCollection ! . get ( fileUri ) || [ ] )
289
+ ] ;
240
290
241
- diagnostic . source = 'rustc' ;
242
- diagnostic . code = msg . code ? msg . code . code : undefined ;
243
- diagnostic . relatedInformation = [ ] ;
291
+ // If we're building multiple targets it's possible we've already seen this diagnostic
292
+ const isDuplicate = diagnostics . some ( d =>
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 [ ] = [
248
- ...( this . diagnosticCollection ! . get ( fileUrl ) || [ ] )
249
- ] ;
250
- diagnostics . push ( diagnostic ) ;
300
+ diagnostics . push ( diagnostic ) ;
301
+ this . diagnosticCollection ! . set ( fileUri , diagnostics ) ;
302
+
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
}
0 commit comments