char-slop/vscode-hubris

git clone https://git.t4t.associates/char-slop/vscode-hubris

Charlotte Sominitial commitf4210c2

main
5.1 KiB126 linesraw
1const vscode = require("vscode");
2const { execFile } = require("node:child_process");
3const fs = require("node:fs/promises");
4const path = require("node:path");
5const { promisify } = require("node:util");
6
7const exec = promisify(execFile);
8
9function activate(extension) {
10  const folders = vscode.workspace.workspaceFolders ?? [];
11  if (!vscode.workspace.isTrusted || folders.length !== 1) return;
12  const folder = folders[0];
13  const output = vscode.window.createOutputChannel("Hubris");
14  const status = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
15  status.command = "hubris.refresh";
16  extension.subscriptions.push(output, status);
17  const targetFile = path.join(extension.storageUri.fsPath, "target");
18  let applied;
19  let requested;
20  let running = false;
21  let timer;
22  let disposed = false;
23  let child;
24
25  function request() {
26    const document = vscode.window.activeTextEditor?.document;
27    const app = vscode.workspace.getConfiguration("hubris", folder.uri).get("app");
28    if (!app || !document || document.languageId !== "rust" || document.uri.scheme !== "file") return;
29    const relative = path.relative(folder.uri.fsPath, document.uri.fsPath);
30    if (relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) return;
31    requested = { file: document.uri.fsPath, app };
32    clearTimeout(timer);
33    timer = setTimeout(update, 250);
34  }
35
36  async function update() {
37    if (running || disposed) return;
38    running = true;
39    try {
40      while (requested && !disposed) {
41        const { file, app } = requested;
42        requested = undefined;
43        const args = ["xtask", "lsp"];
44        if (applied?.app === app) args.push("-c", JSON.stringify({ toml: app, task: applied.task }));
45        args.push(file);
46        const env = { ...process.env, HUBRIS_APP: app };
47        delete env.HUBRIS_TASK;
48        const job = exec("cargo", args, { cwd: folder.uri.fsPath, env, maxBuffer: 4 * 1024 * 1024 });
49        child = job.child;
50        const { stdout } = await job;
51        child = undefined;
52        if (disposed || (requested && (requested.file !== file || requested.app !== app))) continue;
53        const result = JSON.parse(stdout);
54        if (result.Err !== undefined) {
55          status.text = "$(warning) Hubris: no task";
56          status.tooltip = result.Err;
57          status.show();
58          output.appendLine(`${file}: ${result.Err}`);
59          continue;
60        }
61        const next = result.Ok;
62        if (!next?.app || !next.task || !Array.isArray(next.buildOverrideCommand)) {
63          throw new Error("Unexpected response from cargo xtask lsp");
64        }
65        const label = `${next.app}:${next.task}`;
66        status.text = `Hubris: ${label}`;
67        status.tooltip = "Rust context for the active file; click to refresh";
68        status.show();
69        if (JSON.stringify(next) === JSON.stringify(applied)) continue;
70
71        status.text = `$(sync~spin) Hubris: ${label}`;
72        await vscode.commands.executeCommand("rust-analyzer.stopServer");
73        try {
74          // Keep server.extraEnv stable: changing it prompts for a restart on every switch.
75          await fs.mkdir(extension.storageUri.fsPath, { recursive: true });
76          await fs.writeFile(targetFile, label);
77          const config = vscode.workspace.getConfiguration("rust-analyzer", folder.uri);
78          const settings = {
79            "server.path": extension.asAbsolutePath("rust-analyzer"),
80            "server.extraEnv": {
81              ...config.get("server.extraEnv"),
82              HUBRIS_WORKSPACE_ROOT: folder.uri.fsPath,
83              HUBRIS_CONTEXT_FILE: targetFile,
84            },
85            "cargo.buildScripts.overrideCommand": next.buildOverrideCommand,
86          };
87          for (const [key, value] of Object.entries(settings)) {
88            if (JSON.stringify(config.get(key)) !== JSON.stringify(value)) {
89              await config.update(key, value, vscode.ConfigurationTarget.Workspace);
90            }
91          }
92        } finally {
93          if (!disposed) await vscode.commands.executeCommand("rust-analyzer.startServer");
94        }
95        applied = next;
96        status.text = `Hubris: ${label}`;
97        output.appendLine(`Context: ${label} (${file})`);
98      }
99    } catch (error) {
100      if (!disposed) {
101        output.appendLine(error.stack ?? String(error));
102        status.text = "$(warning) Hubris";
103        status.show();
104        vscode.window.showErrorMessage(`Hubris context switch failed: ${error.message}`);
105      }
106    } finally {
107      running = false;
108      if (requested && !disposed) timer = setTimeout(update, 250);
109    }
110  }
111
112  extension.subscriptions.push(
113    vscode.window.onDidChangeActiveTextEditor(request),
114    vscode.workspace.onDidSaveTextDocument((document) => {
115      if (document.uri.fsPath.endsWith(".toml") || path.basename(document.uri.fsPath) === "Cargo.lock") request();
116    }),
117    vscode.workspace.onDidChangeConfiguration((event) => {
118      if (event.affectsConfiguration("hubris.app")) request();
119    }),
120    vscode.commands.registerCommand("hubris.refresh", request),
121    { dispose() { disposed = true; clearTimeout(timer); child?.kill(); } },
122  );
123  request();
124}
125
126module.exports = { activate };