const vscode = require("vscode"); const { execFile } = require("node:child_process"); const fs = require("node:fs/promises"); const path = require("node:path"); const { promisify } = require("node:util"); const exec = promisify(execFile); function activate(extension) { const folders = vscode.workspace.workspaceFolders ?? []; if (!vscode.workspace.isTrusted || folders.length !== 1) return; const folder = folders[0]; const output = vscode.window.createOutputChannel("Hubris"); const status = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); status.command = "hubris.refresh"; extension.subscriptions.push(output, status); const targetFile = path.join(extension.storageUri.fsPath, "target"); let applied; let requested; let running = false; let timer; let disposed = false; let child; function request() { const document = vscode.window.activeTextEditor?.document; const app = vscode.workspace.getConfiguration("hubris", folder.uri).get("app"); if (!app || !document || document.languageId !== "rust" || document.uri.scheme !== "file") return; const relative = path.relative(folder.uri.fsPath, document.uri.fsPath); if (relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) return; requested = { file: document.uri.fsPath, app }; clearTimeout(timer); timer = setTimeout(update, 250); } async function update() { if (running || disposed) return; running = true; try { while (requested && !disposed) { const { file, app } = requested; requested = undefined; const args = ["xtask", "lsp"]; if (applied?.app === app) args.push("-c", JSON.stringify({ toml: app, task: applied.task })); args.push(file); const env = { ...process.env, HUBRIS_APP: app }; delete env.HUBRIS_TASK; const job = exec("cargo", args, { cwd: folder.uri.fsPath, env, maxBuffer: 4 * 1024 * 1024 }); child = job.child; const { stdout } = await job; child = undefined; if (disposed || (requested && (requested.file !== file || requested.app !== app))) continue; const result = JSON.parse(stdout); if (result.Err !== undefined) { status.text = "$(warning) Hubris: no task"; status.tooltip = result.Err; status.show(); output.appendLine(`${file}: ${result.Err}`); continue; } const next = result.Ok; if (!next?.app || !next.task || !Array.isArray(next.buildOverrideCommand)) { throw new Error("Unexpected response from cargo xtask lsp"); } const label = `${next.app}:${next.task}`; status.text = `Hubris: ${label}`; status.tooltip = "Rust context for the active file; click to refresh"; status.show(); if (JSON.stringify(next) === JSON.stringify(applied)) continue; status.text = `$(sync~spin) Hubris: ${label}`; await vscode.commands.executeCommand("rust-analyzer.stopServer"); try { // Keep server.extraEnv stable: changing it prompts for a restart on every switch. await fs.mkdir(extension.storageUri.fsPath, { recursive: true }); await fs.writeFile(targetFile, label); const config = vscode.workspace.getConfiguration("rust-analyzer", folder.uri); const settings = { "server.path": extension.asAbsolutePath("rust-analyzer"), "server.extraEnv": { ...config.get("server.extraEnv"), HUBRIS_WORKSPACE_ROOT: folder.uri.fsPath, HUBRIS_CONTEXT_FILE: targetFile, }, "cargo.buildScripts.overrideCommand": next.buildOverrideCommand, }; for (const [key, value] of Object.entries(settings)) { if (JSON.stringify(config.get(key)) !== JSON.stringify(value)) { await config.update(key, value, vscode.ConfigurationTarget.Workspace); } } } finally { if (!disposed) await vscode.commands.executeCommand("rust-analyzer.startServer"); } applied = next; status.text = `Hubris: ${label}`; output.appendLine(`Context: ${label} (${file})`); } } catch (error) { if (!disposed) { output.appendLine(error.stack ?? String(error)); status.text = "$(warning) Hubris"; status.show(); vscode.window.showErrorMessage(`Hubris context switch failed: ${error.message}`); } } finally { running = false; if (requested && !disposed) timer = setTimeout(update, 250); } } extension.subscriptions.push( vscode.window.onDidChangeActiveTextEditor(request), vscode.workspace.onDidSaveTextDocument((document) => { if (document.uri.fsPath.endsWith(".toml") || path.basename(document.uri.fsPath) === "Cargo.lock") request(); }), vscode.workspace.onDidChangeConfiguration((event) => { if (event.affectsConfiguration("hubris.app")) request(); }), vscode.commands.registerCommand("hubris.refresh", request), { dispose() { disposed = true; clearTimeout(timer); child?.kill(); } }, ); request(); } module.exports = { activate };