From b3683df0cd67ca97c83f5a7ea58a780dbe4e1b8e Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Sun, 31 Mar 2019 20:00:50 +0800 Subject: [PATCH] Improve cargo-watch usage --- editors/code/package.json | 12 -- editors/code/src/commands/cargo_watch.ts | 168 +++++++++++++++++++++++ editors/code/src/commands/runnables.ts | 29 +--- editors/code/src/extension.ts | 4 +- 4 files changed, 177 insertions(+), 36 deletions(-) create mode 100644 editors/code/src/commands/cargo_watch.ts diff --git a/editors/code/package.json b/editors/code/package.json index facb633d9ad..240aff6c9bc 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -223,18 +223,6 @@ "${workspaceRoot}" ], "pattern": "$rustc" - }, - { - "name": "rustc-watch", - "fileLocation": [ - "relative", - "${workspaceRoot}" - ], - "background": { - "beginsPattern": "^\\[Running\\b", - "endsPattern": "^\\[Finished running\\b" - }, - "pattern": "$rustc" } ] } diff --git a/editors/code/src/commands/cargo_watch.ts b/editors/code/src/commands/cargo_watch.ts new file mode 100644 index 00000000000..55a1909cb8f --- /dev/null +++ b/editors/code/src/commands/cargo_watch.ts @@ -0,0 +1,168 @@ +import * as child_process from 'child_process'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { setInterval } from 'timers'; + +const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; + +class StatusDisplay { + private i = 0; + private statusBarItem: vscode.StatusBarItem; + private timer?: NodeJS.Timeout; + + constructor(subscriptions: vscode.Disposable[]) { + this.statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10); + subscriptions.push(this.statusBarItem); + this.statusBarItem.hide(); + } + + public show() { + this.timer = this.timer || setInterval(() => { + this.statusBarItem!.text = "cargo check " + this.frame(); + }, 300); + + this.statusBarItem!.show(); + } + + public hide() { + if(this.timer) { + clearInterval(this.timer); + this.timer = undefined; + } + + this.statusBarItem!.hide(); + } + + frame() { + return spinnerFrames[this.i = ++this.i % spinnerFrames.length]; + } +} + +export class CargoWatchProvider { + private diagnosticCollection?: vscode.DiagnosticCollection; + private cargoProcess?: child_process.ChildProcess; + private outBuffer: string = ""; + private statusDisplay? : StatusDisplay; + + constructor() { + } + + public activate(subscriptions: vscode.Disposable[]) { + subscriptions.push(this); + this.diagnosticCollection = vscode.languages.createDiagnosticCollection("rustc"); + + this.statusDisplay = new StatusDisplay(subscriptions); + + // Start the cargo watch with json message + this.cargoProcess = child_process.spawn('cargo', + ["watch", "-x", "\"check --message-format json\""], + { + // stdio: ['ignore', 'pipe', 'ignore'], + shell: true, + cwd: vscode.workspace.rootPath, + }); + + + this.cargoProcess.stdout.on('data', (s: string) => { + this.processOutput(s); + }); + + this.cargoProcess.stderr.on('data', (s: string) => { + console.error('Error on cargo watch : ' + s); + }); + + this.cargoProcess.on('error', (err: Error) => { + console.error('Error on spawn cargo process : ' + err); + }); + } + + public dispose(): void { + if (this.diagnosticCollection) { + this.diagnosticCollection.clear(); + this.diagnosticCollection.dispose(); + } + + if (this.cargoProcess) { + this.cargoProcess.kill(); + } + } + + parseLine(line: string) { + if (line.startsWith("[Running")) { + this.diagnosticCollection!.clear(); + this.statusDisplay!.show(); + } + + if (line.startsWith("[Finished running")) { + this.statusDisplay!.hide(); + } + + function getLevel(s: string): vscode.DiagnosticSeverity { + if (s === "error") + return vscode.DiagnosticSeverity.Error; + + if (s.startsWith("warn")) + return vscode.DiagnosticSeverity.Warning; + + return vscode.DiagnosticSeverity.Information; + } + + // cargo-watch itself output non json format + // Ignore these lines + let data = null; + try { + data = JSON.parse(line.trim()); + } catch (error) { + return; + } + + // Only handle compiler-message now + if (data.reason !== "compiler-message") { + return; + } + + let spans: any[] = data.message.spans; + spans = spans.filter(o => o.is_primary); + let file_name = null; + + // We only handle primary span right now. + if (spans.length > 0) { + let o = spans[0]; + + console.log("o", o); + let rendered = data.message.rendered; + let level = getLevel(data.message.level); + let range = new vscode.Range( + new vscode.Position(o.line_start - 1, o.column_start - 1), + new vscode.Position(o.line_end - 1, o.column_end - 1) + ); + + file_name = path.join(vscode.workspace.rootPath!, o.file_name); + const diagnostic = new vscode.Diagnostic(range, rendered, level); + + diagnostic.source = 'rustc'; + diagnostic.code = data.message.code.code; + diagnostic.relatedInformation = []; + + let fileUrl = vscode.Uri.file(file_name!); + + let diagnostics: vscode.Diagnostic[] = [...(this.diagnosticCollection!.get(fileUrl) || [])]; + diagnostics.push(diagnostic); + + this.diagnosticCollection!.set(fileUrl, diagnostics); + } + } + + processOutput(chunk: string) { + // The stdout is not line based, convert it to line based for proceess. + this.outBuffer += chunk; + let eolIndex; + while ((eolIndex = this.outBuffer.indexOf('\n')) >= 0) { + // line includes the EOL + const line = this.outBuffer.slice(0, eolIndex + 1); + this.parseLine(line); + this.outBuffer = this.outBuffer.slice(eolIndex + 1); + } + } + +} diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts index 4187ef4d157..722db158aa0 100644 --- a/editors/code/src/commands/runnables.ts +++ b/editors/code/src/commands/runnables.ts @@ -1,9 +1,11 @@ import * as child_process from 'child_process'; + import * as util from 'util'; import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; import { Server } from '../server'; +import { CargoWatchProvider } from './cargo_watch'; interface RunnablesParams { textDocument: lc.TextDocumentIdentifier; @@ -127,32 +129,13 @@ export async function handleSingle(runnable: Runnable) { return vscode.tasks.executeTask(task); } -export const autoCargoWatchTask: vscode.Task = { - name: 'cargo watch', - source: 'rust-analyzer', - definition: { - type: 'watch' - }, - execution: new vscode.ShellExecution('cargo', ['watch'], { cwd: '.' }), - - isBackground: true, - problemMatchers: ['$rustc-watch'], - presentationOptions: { - clear: true - }, - // Not yet exposed in the vscode.d.ts - // https://github.com/Microsoft/vscode/blob/ea7c31d770e04b51d586b0d3944f3a7feb03afb9/src/vs/workbench/contrib/tasks/common/tasks.ts#L444-L456 - runOptions: ({ - runOn: 2 // RunOnOptions.folderOpen - } as unknown) as vscode.RunOptions -}; - /** * Interactively asks the user whether we should run `cargo check` in order to * provide inline diagnostics; the user is met with a series of dialog boxes * that, when accepted, allow us to `cargo install cargo-watch` and then run it. */ -export async function interactivelyStartCargoWatch() { +export async function interactivelyStartCargoWatch(context: vscode.ExtensionContext) { + if (Server.config.enableCargoWatchOnStartup === 'disabled') { return; } @@ -212,5 +195,7 @@ export async function interactivelyStartCargoWatch() { } } - vscode.tasks.executeTask(autoCargoWatchTask); + + let validater = new CargoWatchProvider(); + validater.activate(context.subscriptions); } diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts index 2e13c87de16..5cbf285e58d 100644 --- a/editors/code/src/extension.ts +++ b/editors/code/src/extension.ts @@ -2,7 +2,7 @@ import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; import * as commands from './commands'; -import { interactivelyStartCargoWatch } from './commands/runnables'; +import { interactivelyStartCargoWatch} from './commands/runnables'; import { SyntaxTreeContentProvider } from './commands/syntaxTree'; import * as events from './events'; import * as notifications from './notifications'; @@ -121,7 +121,7 @@ export function activate(context: vscode.ExtensionContext) { ); // Executing `cargo watch` provides us with inline diagnostics on save - interactivelyStartCargoWatch(); + interactivelyStartCargoWatch(context); // Start the language server, finally! Server.start(allNotifications);