Files
WRNexusJS/editors/vscode/src/extension.js
T
2026-07-14 22:39:50 +05:30

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,
};