diff options
Diffstat (limited to 'editors/code/src/commands')
-rw-r--r-- | editors/code/src/commands/cargo_watch.ts | 198 | ||||
-rw-r--r-- | editors/code/src/commands/watch_status.ts | 10 |
2 files changed, 147 insertions, 61 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'; | |||
4 | import * as vscode from 'vscode'; | 4 | import * as vscode from 'vscode'; |
5 | import { Server } from '../server'; | 5 | import { Server } from '../server'; |
6 | import { terminate } from '../utils/processes'; | 6 | import { terminate } from '../utils/processes'; |
7 | import { | ||
8 | mapRustDiagnosticToVsCode, | ||
9 | RustDiagnostic | ||
10 | } from '../utils/rust_diagnostics'; | ||
7 | import { LineBuffer } from './line_buffer'; | 11 | import { LineBuffer } from './line_buffer'; |
8 | import { StatusDisplay } from './watch_status'; | 12 | import { StatusDisplay } from './watch_status'; |
9 | 13 | ||
@@ -33,20 +37,39 @@ export function registerCargoWatchProvider( | |||
33 | return provider; | 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 | 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 | } |
diff --git a/editors/code/src/commands/watch_status.ts b/editors/code/src/commands/watch_status.ts index a3b0178f2..6c1f9041b 100644 --- a/editors/code/src/commands/watch_status.ts +++ b/editors/code/src/commands/watch_status.ts | |||
@@ -7,13 +7,15 @@ export class StatusDisplay implements vscode.Disposable { | |||
7 | 7 | ||
8 | private i = 0; | 8 | private i = 0; |
9 | private statusBarItem: vscode.StatusBarItem; | 9 | private statusBarItem: vscode.StatusBarItem; |
10 | private command: string; | ||
10 | private timer?: NodeJS.Timeout; | 11 | private timer?: NodeJS.Timeout; |
11 | 12 | ||
12 | constructor() { | 13 | constructor(command: string) { |
13 | this.statusBarItem = vscode.window.createStatusBarItem( | 14 | this.statusBarItem = vscode.window.createStatusBarItem( |
14 | vscode.StatusBarAlignment.Left, | 15 | vscode.StatusBarAlignment.Left, |
15 | 10 | 16 | 10 |
16 | ); | 17 | ); |
18 | this.command = command; | ||
17 | this.statusBarItem.hide(); | 19 | this.statusBarItem.hide(); |
18 | } | 20 | } |
19 | 21 | ||
@@ -24,11 +26,13 @@ export class StatusDisplay implements vscode.Disposable { | |||
24 | this.timer || | 26 | this.timer || |
25 | setInterval(() => { | 27 | setInterval(() => { |
26 | if (this.packageName) { | 28 | if (this.packageName) { |
27 | this.statusBarItem!.text = `cargo check [${ | 29 | this.statusBarItem!.text = `cargo ${this.command} [${ |
28 | this.packageName | 30 | this.packageName |
29 | }] ${this.frame()}`; | 31 | }] ${this.frame()}`; |
30 | } else { | 32 | } else { |
31 | this.statusBarItem!.text = `cargo check ${this.frame()}`; | 33 | this.statusBarItem!.text = `cargo ${ |
34 | this.command | ||
35 | } ${this.frame()}`; | ||
32 | } | 36 | } |
33 | }, 300); | 37 | }, 300); |
34 | 38 | ||