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