Skip to content

Commit

Permalink
Support astgen diagnostics
Browse files Browse the repository at this point in the history
  • Loading branch information
Jarred-Sumner committed May 22, 2021
1 parent 1b6eacb commit fb9df87
Show file tree
Hide file tree
Showing 2 changed files with 226 additions and 111 deletions.
311 changes: 215 additions & 96 deletions src/zigCompilerProvider.ts
Original file line number Diff line number Diff line change
@@ -1,111 +1,230 @@
'use strict';
"use strict";

import * as path from 'path';
import * as cp from 'child_process';
import * as vscode from 'vscode';
import * as path from "path";
import * as cp from "child_process";
import * as vscode from "vscode";
// This will be treeshaked to only the debounce function
import { throttle } from "lodash-es";

export default class ZigCompilerProvider implements vscode.CodeActionProvider {
private diagnosticCollection: vscode.DiagnosticCollection;
private buildDiagnostics: vscode.DiagnosticCollection;
private astDiagnostics: vscode.DiagnosticCollection;
private dirtyChange = new WeakMap<vscode.Uri, boolean>();

public activate(subscriptions: vscode.Disposable[]) {
subscriptions.push(this);
this.buildDiagnostics = vscode.languages.createDiagnosticCollection("zig");
this.astDiagnostics = vscode.languages.createDiagnosticCollection("zig");

// vscode.workspace.onDidOpenTextDocument(this.doCompile, this, subscriptions);
// vscode.workspace.onDidCloseTextDocument(
// (textDocument) => {
// this.diagnosticCollection.delete(textDocument.uri);
// },
// null,
// subscriptions
// );

// vscode.workspace.onDidSaveTextDocument(this.doCompile, this);
vscode.workspace.onDidChangeTextDocument(
this.maybeDoASTGenErrorCheck,
this
);
}

maybeDoASTGenErrorCheck(change: vscode.TextDocumentChangeEvent) {
if (change.document.languageId !== "zig") return;
if (change.document.isClosed) {
this.astDiagnostics.delete(change.document.uri);
}

public activate(subscriptions: vscode.Disposable[]) {
subscriptions.push(this);
this.diagnosticCollection = vscode.languages.createDiagnosticCollection("zig");
this.doASTGenErrorCheck(change);

vscode.workspace.onDidOpenTextDocument(this.doCompile, this, subscriptions);
vscode.workspace.onDidCloseTextDocument((textDocument) => {
this.diagnosticCollection.delete(textDocument.uri);
}, null, subscriptions);
if (!change.document.isUntitled) {
let config = vscode.workspace.getConfiguration("zig");
if (
config.get<boolean>("buildOnSave") &&
this.dirtyChange.has(change.document.uri) &&
this.dirtyChange.get(change.document.uri) !== change.document.isDirty &&
!change.document.isDirty
) {
this.doCompile(change.document);
}

vscode.workspace.onDidSaveTextDocument(this.doCompile, this);
this.dirtyChange.set(change.document.uri, change.document.isDirty);
}

public dispose(): void {
this.diagnosticCollection.clear();
this.diagnosticCollection.dispose();
}

public dispose(): void {
this.buildDiagnostics.clear();
this.astDiagnostics.clear();
this.buildDiagnostics.dispose();
this.astDiagnostics.dispose();
}

private _doASTGenErrorCheck(change: vscode.TextDocumentChangeEvent) {
let config = vscode.workspace.getConfiguration("zig");
const textDocument = change.document;
if (textDocument.languageId !== "zig") {
return;
}
const zig_path = config.get("zigPath") || "zig";
const cwd = vscode.workspace.getWorkspaceFolder(textDocument.uri).uri
.fsPath;

let childProcess = cp.spawn(
zig_path as string,
["astgen", "--errors-only", "--stdin"],
{ cwd }
);

if (!childProcess.pid) {
return;
}

private doCompile(textDocument: vscode.TextDocument) {
let config = vscode.workspace.getConfiguration('zig');
let buildOnSave = config.get<boolean>("buildOnSave");
var stderr = "";
childProcess.stderr.on("data", (chunk) => {
stderr += chunk;
});

childProcess.stdin.end(change.document.getText(null));

childProcess.once("close", () => {
this.doASTGenErrorCheck.cancel();
this.astDiagnostics.delete(textDocument.uri);

if (stderr.length == 0) return;
var diagnostics: { [id: string]: vscode.Diagnostic[] } = {};
let regex = /(\S.*):(\d*):(\d*): ([^:]*): (.*)/g;

for (let match = regex.exec(stderr); match; match = regex.exec(stderr)) {
let path = textDocument.uri.fsPath;

let line = parseInt(match[2]) - 1;
let column = parseInt(match[3]) - 1;
let type = match[4];
let message = match[5];

let severity =
type.trim().toLowerCase() === "error"
? vscode.DiagnosticSeverity.Error
: vscode.DiagnosticSeverity.Information;
let range = new vscode.Range(line, column, line, Infinity);

if (diagnostics[path] == null) diagnostics[path] = [];
diagnostics[path].push(new vscode.Diagnostic(range, message, severity));
}

for (let path in diagnostics) {
let diagnostic = diagnostics[path];
this.astDiagnostics.set(textDocument.uri, diagnostic);
}
});
}

private _doCompile(textDocument: vscode.TextDocument) {
let config = vscode.workspace.getConfiguration("zig");

let buildOption = config.get<string>("buildOption");
let processArg: string[] = [buildOption];
let workspaceFolder = vscode.workspace.getWorkspaceFolder(textDocument.uri);
if (!workspaceFolder && vscode.workspace.workspaceFolders.length) {
workspaceFolder = vscode.workspace.workspaceFolders[0];
}
const cwd = workspaceFolder.uri.fsPath;

switch (buildOption) {
case "build":
let buildFilePath = config.get<string>("buildFilePath");
processArg.push("--build-file");
try {
processArg.push(
path.resolve(buildFilePath.replace("${workspaceFolder}", cwd))
);
} catch {}

break;
default:
processArg.push(textDocument.fileName);
break;
}

if (textDocument.languageId !== 'zig' || !buildOnSave) {
return;
let extraArgs = config.get<string[]>("buildArgs");
extraArgs.forEach((element) => {
processArg.push(element);
});

let decoded = "";
let childProcess = cp.spawn("zig", processArg, { cwd });
if (childProcess.pid) {
childProcess.stderr.on("data", (data: Buffer) => {
decoded += data;
});
childProcess.stdout.on("end", () => {
this.doCompile.cancel();
var diagnostics: { [id: string]: vscode.Diagnostic[] } = {};
let regex = /(\S.*):(\d*):(\d*): ([^:]*): (.*)/g;

this.buildDiagnostics.clear();
for (
let match = regex.exec(decoded);
match;
match = regex.exec(decoded)
) {
let path = match[1].trim();
try {
if (!path.includes(cwd)) {
path = require("path").resolve(workspaceFolder.uri.fsPath, path);
}
} catch {}

let line = parseInt(match[2]) - 1;
let column = parseInt(match[3]) - 1;
let type = match[4];
let message = match[5];

// De-dupe build errors with ast errors
if (this.astDiagnostics.has(textDocument.uri)) {
for (let diag of this.astDiagnostics.get(textDocument.uri)) {
if (
diag.range.start.line === line &&
diag.range.start.character === column
) {
continue;
}
}
}

let severity =
type.trim().toLowerCase() === "error"
? vscode.DiagnosticSeverity.Error
: vscode.DiagnosticSeverity.Information;
let range = new vscode.Range(line, column, line, Infinity);

if (diagnostics[path] == null) diagnostics[path] = [];
diagnostics[path].push(
new vscode.Diagnostic(range, message, severity)
);
}

let buildOption = config.get<string>("buildOption");
let processArg: string[] = [buildOption];
let workspaceFolder = vscode.workspace.getWorkspaceFolder(textDocument.uri);
if (!workspaceFolder && vscode.workspace.workspaceFolders.length) {
workspaceFolder = vscode.workspace.workspaceFolders[0];
for (let path in diagnostics) {
let diagnostic = diagnostics[path];
this.buildDiagnostics.set(vscode.Uri.file(path), diagnostic);
}
const cwd = workspaceFolder.uri.fsPath;

switch (buildOption) {
case "build":
let buildFilePath = config.get<string>("buildFilePath");
processArg.push("--build-file");
try {
processArg.push( path.resolve(buildFilePath.replace("${workspaceFolder}", cwd)));
} catch {

}

break;
default:
processArg.push(textDocument.fileName);
break;
}


let extraArgs = config.get<string[]>("buildArgs");
extraArgs.forEach(element => {
processArg.push(element);
});

let decoded = ''
let childProcess = cp.spawn('zig', processArg, {cwd});
if (childProcess.pid) {
childProcess.stderr.on('data', (data: Buffer) => {
decoded += data;
});
childProcess.stdout.on('end', () => {
var diagnostics: { [id: string]: vscode.Diagnostic[]; } = {};
let regex = /(\S.*):(\d*):(\d*): ([^:]*): (.*)/g;

this.diagnosticCollection.clear();
for (let match = regex.exec(decoded); match;
match = regex.exec(decoded)) {
let path = match[1].trim();
try {
if (!path.includes(cwd)) {
path = require("path").resolve(workspaceFolder.uri.fsPath, path);
}
} catch {

}

let line = parseInt(match[2]) - 1;
let column = parseInt(match[3]) - 1;
let type = match[4];
let message = match[5];

let severity = type.trim().toLowerCase() === "error" ? vscode.DiagnosticSeverity.Error : vscode.DiagnosticSeverity.Information;
let range = new vscode.Range(line, column,
line, Infinity);

if (diagnostics[path] == null) diagnostics[path] = [];
diagnostics[path].push(new vscode.Diagnostic(range, message, severity));
}

for (let path in diagnostics) {
let diagnostic = diagnostics[path];
this.diagnosticCollection.set(vscode.Uri.file(path), diagnostic);
}
});
}
}

public provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken): vscode.ProviderResult<vscode.Command[]> {
return [];
});
}
}

doASTGenErrorCheck = throttle(this._doASTGenErrorCheck, 16, {
trailing: true,
});
doCompile = throttle(this._doCompile, 60);
public provideCodeActions(
document: vscode.TextDocument,
range: vscode.Range,
context: vscode.CodeActionContext,
token: vscode.CancellationToken
): vscode.ProviderResult<vscode.Command[]> {
return [];
}
}
26 changes: 11 additions & 15 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "ESNext",
"outDir": "out",
"lib": [
"esnext",
],
"sourceMap": true,
"rootDir": "src"
},
"exclude": [
"node_modules",
".vscode-test"
]
}
"compilerOptions": {
"module": "commonjs",
"target": "ESNext",
"outDir": "out",
"esModuleInterop": true,
"lib": ["esnext"],
"sourceMap": true,
"rootDir": "src"
},
"exclude": ["node_modules", ".vscode-test"]
}

0 comments on commit fb9df87

Please sign in to comment.