285 lines
6.5 KiB
JavaScript
285 lines
6.5 KiB
JavaScript
// @ts-check
|
|
"use strict";
|
|
|
|
const vscode = require("vscode");
|
|
|
|
const { formatWrn } = require("./formatter");
|
|
|
|
const { registerCompletionProvider } = require("./completion");
|
|
|
|
const { registerDefinitionProvider } = require("./definition");
|
|
|
|
const { registerDiagnostics } = require("./diagnostics");
|
|
|
|
/**
|
|
* The WRN compiler is bundled to CommonJS using:
|
|
*
|
|
* bun run build:compiler
|
|
*
|
|
* Loading is optional so highlighting, formatting, snippets,
|
|
* autocomplete and navigation continue working when the compiler
|
|
* bundle is temporarily unavailable.
|
|
*/
|
|
let compiler = null;
|
|
|
|
try {
|
|
compiler = require("./compiler.cjs");
|
|
} catch (error) {
|
|
console.warn(
|
|
"[wrnexus] compiler bundle not found; compiler diagnostics disabled.",
|
|
error instanceof Error ? error.message : String(error),
|
|
);
|
|
}
|
|
|
|
const WRN_LANGUAGE_ID = "wrn";
|
|
const COMPILER_DIAGNOSTIC_COLLECTION = "wrnexus-compiler";
|
|
|
|
/**
|
|
* Register the WRN document formatter.
|
|
*
|
|
* @param {vscode.ExtensionContext} context
|
|
*/
|
|
function registerFormatter(context) {
|
|
const provider = vscode.languages.registerDocumentFormattingEditProvider(
|
|
{
|
|
language: WRN_LANGUAGE_ID,
|
|
scheme: "file",
|
|
},
|
|
{
|
|
provideDocumentFormattingEdits(document, options) {
|
|
const configuration = vscode.workspace.getConfiguration("wrnexus", document.uri);
|
|
|
|
const enabled = configuration.get("format.enable", true);
|
|
|
|
if (!enabled) {
|
|
return [];
|
|
}
|
|
|
|
const source = document.getText();
|
|
|
|
const printWidth = configuration.get("formatting.printWidth", 100);
|
|
|
|
const formatted = formatWrn(source, {
|
|
tabSize: options.tabSize,
|
|
insertSpaces: options.insertSpaces,
|
|
printWidth,
|
|
});
|
|
|
|
if (formatted === source) {
|
|
return [];
|
|
}
|
|
|
|
const range = new vscode.Range(document.positionAt(0), document.positionAt(source.length));
|
|
|
|
return [vscode.TextEdit.replace(range, formatted)];
|
|
},
|
|
},
|
|
);
|
|
|
|
context.subscriptions.push(provider);
|
|
}
|
|
|
|
/**
|
|
* Convert a compiler ParseError into a VS Code diagnostic.
|
|
*
|
|
* Compiler errors normally contain:
|
|
*
|
|
* at offset 123
|
|
*
|
|
* When no offset exists, the diagnostic is attached to the last
|
|
* line of the document.
|
|
*
|
|
* @param {vscode.TextDocument} document
|
|
* @param {unknown} error
|
|
* @returns {vscode.Diagnostic}
|
|
*/
|
|
function toCompilerDiagnostic(document, error) {
|
|
const message =
|
|
error && typeof error === "object" && "message" in error
|
|
? String(error.message)
|
|
: "Failed to compile .wrn file.";
|
|
|
|
const offsetMatch = /offset\s+(\d+)/i.exec(message);
|
|
|
|
let range;
|
|
|
|
if (offsetMatch) {
|
|
const requestedOffset = Number(offsetMatch[1]);
|
|
|
|
const safeOffset = Math.max(0, Math.min(requestedOffset, document.getText().length));
|
|
|
|
const start = document.positionAt(safeOffset);
|
|
|
|
const wordRange = document.getWordRangeAtPosition(start);
|
|
|
|
range =
|
|
wordRange ||
|
|
new vscode.Range(
|
|
start,
|
|
document.positionAt(Math.min(safeOffset + 1, document.getText().length)),
|
|
);
|
|
} else {
|
|
const lastLine = document.lineAt(Math.max(0, document.lineCount - 1));
|
|
|
|
range = lastLine.range;
|
|
}
|
|
|
|
const diagnostic = new vscode.Diagnostic(range, message, vscode.DiagnosticSeverity.Error);
|
|
|
|
diagnostic.source = "WRNexus Compiler";
|
|
|
|
diagnostic.code = "wrn-compiler-error";
|
|
|
|
return diagnostic;
|
|
}
|
|
|
|
/**
|
|
* Register diagnostics produced by the actual WRN compiler.
|
|
*
|
|
* The lightweight diagnostics in diagnostics.js provide immediate
|
|
* editor feedback. Compiler diagnostics verify that the document
|
|
* can also be parsed and generated by the real framework compiler.
|
|
*
|
|
* @param {vscode.ExtensionContext} context
|
|
*/
|
|
function registerCompilerDiagnostics(context) {
|
|
const collection = vscode.languages.createDiagnosticCollection(COMPILER_DIAGNOSTIC_COLLECTION);
|
|
|
|
const timers = new Map();
|
|
|
|
/**
|
|
* @param {vscode.TextDocument} document
|
|
*/
|
|
const run = (document) => {
|
|
if (document.languageId !== WRN_LANGUAGE_ID) {
|
|
return;
|
|
}
|
|
|
|
const configuration = vscode.workspace.getConfiguration("wrnexus", document.uri);
|
|
|
|
const enabled = configuration.get("diagnostics.enable", true);
|
|
|
|
if (!enabled) {
|
|
collection.delete(document.uri);
|
|
return;
|
|
}
|
|
|
|
if (!compiler || typeof compiler.compileWireFile !== "function") {
|
|
collection.delete(document.uri);
|
|
return;
|
|
}
|
|
|
|
const source = document.getText();
|
|
|
|
if (!source.trim()) {
|
|
collection.delete(document.uri);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
compiler.compileWireFile(source);
|
|
|
|
collection.set(document.uri, []);
|
|
} catch (error) {
|
|
collection.set(document.uri, [toCompilerDiagnostic(document, error)]);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @param {vscode.TextDocument} document
|
|
*/
|
|
const schedule = (document) => {
|
|
if (document.languageId !== WRN_LANGUAGE_ID) {
|
|
return;
|
|
}
|
|
|
|
const key = document.uri.toString();
|
|
|
|
const existing = timers.get(key);
|
|
|
|
if (existing) {
|
|
clearTimeout(existing);
|
|
}
|
|
|
|
const timer = setTimeout(() => {
|
|
timers.delete(key);
|
|
run(document);
|
|
}, 250);
|
|
|
|
timers.set(key, timer);
|
|
};
|
|
|
|
for (const document of vscode.workspace.textDocuments) {
|
|
run(document);
|
|
}
|
|
|
|
context.subscriptions.push(
|
|
collection,
|
|
|
|
vscode.workspace.onDidOpenTextDocument(run),
|
|
|
|
vscode.workspace.onDidChangeTextDocument((event) => {
|
|
schedule(event.document);
|
|
}),
|
|
|
|
vscode.workspace.onDidSaveTextDocument(run),
|
|
|
|
vscode.workspace.onDidCloseTextDocument((document) => {
|
|
const key = document.uri.toString();
|
|
|
|
const timer = timers.get(key);
|
|
|
|
if (timer) {
|
|
clearTimeout(timer);
|
|
timers.delete(key);
|
|
}
|
|
|
|
collection.delete(document.uri);
|
|
}),
|
|
|
|
vscode.workspace.onDidChangeConfiguration((event) => {
|
|
if (!event.affectsConfiguration("wrnexus.diagnostics.enable")) {
|
|
return;
|
|
}
|
|
|
|
for (const document of vscode.workspace.textDocuments) {
|
|
run(document);
|
|
}
|
|
}),
|
|
|
|
{
|
|
dispose() {
|
|
for (const timer of timers.values()) {
|
|
clearTimeout(timer);
|
|
}
|
|
|
|
timers.clear();
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Activate the WRNexus VS Code extension.
|
|
*
|
|
* @param {vscode.ExtensionContext} context
|
|
*/
|
|
function activate(context) {
|
|
registerDiagnostics(context);
|
|
registerCompilerDiagnostics(context);
|
|
|
|
registerCompletionProvider(context);
|
|
registerDefinitionProvider(context);
|
|
registerFormatter(context);
|
|
}
|
|
|
|
function deactivate() {}
|
|
|
|
module.exports = {
|
|
activate,
|
|
deactivate,
|
|
registerCompilerDiagnostics,
|
|
registerFormatter,
|
|
toCompilerDiagnostic,
|
|
};
|