Skip to content

Commit

Permalink
[vscode] Throttle diagnostics.
Browse files Browse the repository at this point in the history
  • Loading branch information
pfusik committed Mar 26, 2024
1 parent 58f9e14 commit dbf2983
Showing 1 changed file with 105 additions and 54 deletions.
159 changes: 105 additions & 54 deletions editors/vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,98 +23,149 @@ import { FuParser, FuProgram, FuSystem, FuSema, FuSemaHost } from "./fucheck.js"

class VsCodeHost extends FuSemaHost
{
#mutex = false;
#system = FuSystem.new();
#diagnostics: Record<string, vscode.Diagnostic[]> = {};
#hasErrors = false;

reportError(filename: string, startLine: number, startUtf16Column: number, endLine: number, endUtf16Column: number, message: string): void
{
this.#hasErrors = true;
const diagnostics = this.#diagnostics[filename];
if (diagnostics !== undefined)
diagnostics.push(new vscode.Diagnostic(new vscode.Range(startLine, startUtf16Column, endLine, endUtf16Column), message));
}

#parse(filename: string, input: Uint8Array, parser: FuParser): void
createParser(): FuParser
{
this.#hasErrors = false;
this.program = new FuProgram();
this.program.parent = this.#system;
this.program.system = this.#system;
const parser = new FuParser();
parser.setHost(this);
return parser;
}

parse(filename: string, input: Uint8Array, parser: FuParser): void
{
this.#diagnostics[filename] = [];
parser.parse(filename, input, input.length);
}

#parseDocument(document: vscode.TextDocument, parser: FuParser): void
parseDocument(document: vscode.TextDocument, parser: FuParser): void
{
this.parse(document.uri.toString(), new TextEncoder().encode(document.getText()), parser);
}

async parseFolder(files: vscode.Uri[], parser: FuParser): Promise<void>
{
this.#parse(document.uri.toString(), new TextEncoder().encode(document.getText()), parser);
const documents = vscode.workspace.textDocuments;
for (const uri of files) {
const filename = uri.toString();
const doc = documents.find(doc => doc.uri.toString() == filename);
if (doc === undefined)
this.parse(filename, await vscode.workspace.fs.readFile(uri), parser);
else
this.parseDocument(doc, parser);
}
}

async #process(document: vscode.TextDocument, parser: FuParser): Promise<void>
doSema(): void
{
if (this.#mutex)
if (this.#hasErrors)
return;
this.#mutex = true;
const sema = new FuSema();
sema.setHost(this);
sema.process();
}
}

class VsCodeDiagnostics extends VsCodeHost
{
#queue: Map<string, vscode.TextDocument> = new Map();
#timeoutId: number | null = null;
#diagnostics: Map<string, vscode.Diagnostic[]> = new Map();
#diagnosticCollection = vscode.languages.createDiagnosticCollection("fusion");

reportError(filename: string, startLine: number, startUtf16Column: number, endLine: number, endUtf16Column: number, message: string): void
{
super.reportError(filename, startLine, startUtf16Column, endLine, endUtf16Column, message);
const fileDiagnostics = this.#diagnostics.get(filename);
if (fileDiagnostics !== undefined)
fileDiagnostics .push(new vscode.Diagnostic(new vscode.Range(startLine, startUtf16Column, endLine, endUtf16Column), message));
}

parse(filename: string, input: Uint8Array, parser: FuParser): void
{
this.#diagnostics.set(filename, []);
parser.parse(filename, input, input.length);
}

async #process(): Promise<void>
{
this.#timeoutId = null;
const files = await vscode.workspace.findFiles("*.fu");
this.#diagnostics = {};
this.#hasErrors = false;
parser.setHost(this);
this.program = new FuProgram();
this.program.parent = this.#system;
this.program.system = this.#system;
const documentFilename = document.uri.toString();
if (files.some(uri => uri.toString() == documentFilename)) {
const documents = vscode.workspace.textDocuments;
for (const uri of files) {
const filename = uri.toString();
const doc = documents.find(doc => doc.uri.toString() == filename);
if (doc === undefined)
this.#parse(filename, await vscode.workspace.fs.readFile(uri), parser);
else
this.#parseDocument(doc, parser);
}
let hasFolder = false;
for (const uri of files) {
if (this.#queue.delete(uri.toString()))
hasFolder = true;
}
else
this.#parseDocument(document, parser);
if (!this.#hasErrors) {
const sema = new FuSema();
sema.setHost(this);
sema.process();
for (const doc of this.#queue.values()) {
this.parseDocument(doc, this.createParser());
this.doSema();
}
this.#queue.clear();
if (hasFolder) {
await this.parseFolder(files, this.createParser());
this.doSema();
}
this.#mutex = false;
for (const [filename, diagnostics] of this.#diagnostics)
this.#diagnosticCollection.set(vscode.Uri.parse(filename), diagnostics);
this.#diagnostics.clear();
}

async updateDiagnostics(document: vscode.TextDocument, diagnosticCollection: vscode.DiagnosticCollection): Promise<void>
check(document: vscode.TextDocument)
{
if (document.languageId != "fusion")
return;
await this.#process(document, new FuParser());
for (const [filename, diagnostics] of Object.entries(this.#diagnostics))
diagnosticCollection.set(vscode.Uri.parse(filename), diagnostics);
this.#queue.set(document.uri.toString(), document);
if (this.#timeoutId !== null)
clearTimeout(this.#timeoutId);
this.#timeoutId = setTimeout(() => this.#process(), 1000);
}

delete(document: vscode.TextDocument)
{
this.#queue.delete(document.uri.toString());
this.#diagnosticCollection.delete(document.uri);
}
}

class VsCodeDefinitionProvider extends VsCodeHost
{
async findDefinition(document: vscode.TextDocument, position: vscode.Position): Promise<vscode.Location | null>
{
const parser = new FuParser();
parser.findDefinition(document.uri.toString(), position.line, position.character);
await this.#process(document, parser);
const filename: string | null = parser.getFoundDefinitionFilename();
return filename == null ? null : new vscode.Location(vscode.Uri.parse(filename), new vscode.Position(parser.getFoundDefinitionLine(), parser.getFoundDefinitionColumn()));
const parser = this.createParser();
const filename = document.uri.toString();
parser.findDefinition(filename, position.line, position.character);
const files = await vscode.workspace.findFiles("*.fu");
if (files.some(uri => uri.toString() == filename))
await this.parseFolder(files, parser);
else
this.parseDocument(document, parser);
this.doSema();
const definitionFilename: string | null = parser.getFoundDefinitionFilename();
return definitionFilename == null ? null : new vscode.Location(vscode.Uri.parse(definitionFilename), new vscode.Position(parser.getFoundDefinitionLine(), parser.getFoundDefinitionColumn()));
}
}

export function activate(context: vscode.ExtensionContext): void
{
const host = new VsCodeHost();
const diagnosticCollection = vscode.languages.createDiagnosticCollection("fusion");
const diagnostics = new VsCodeDiagnostics();
if (vscode.window.activeTextEditor)
host.updateDiagnostics(vscode.window.activeTextEditor.document, diagnosticCollection);
diagnostics.check(vscode.window.activeTextEditor.document);
context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(editor => {
if (editor)
host.updateDiagnostics(editor.document, diagnosticCollection);
diagnostics.check(editor.document);
}));
context.subscriptions.push(vscode.workspace.onDidChangeTextDocument(e => host.updateDiagnostics(e.document, diagnosticCollection)));
context.subscriptions.push(vscode.workspace.onDidCloseTextDocument(document => diagnosticCollection.delete(document.uri)));
context.subscriptions.push(vscode.workspace.onDidChangeTextDocument(e => diagnostics.check(e.document)));
context.subscriptions.push(vscode.workspace.onDidCloseTextDocument(document => diagnostics.delete(document)));
vscode.languages.registerDefinitionProvider("fusion", {
provideDefinition(document, position, token) {
return host.findDefinition(document, position);
return new VsCodeDefinitionProvider().findDefinition(document, position);
}
});
}

0 comments on commit dbf2983

Please sign in to comment.