/**
* The toolbar in VS Code's title bar (`cmakeHelper.toolbar`: "titleBar").
* An extension can't put anything there, so media/titlebar.js is copied next
* to VS Code's workbench.html and added to it, and product.json's checksum of
* workbench.html is updated (else VS Code says its installation is corrupt).
* A VS Code update brings a new workbench.html without it, an update of this
* extension a new script: either is noticed on start and offered to patch again.
* The installation is usually root's: then the files go in with pkexec.
*/
import * as crypto from "node:crypto";
import * as fs from "node:fs";
import * as path from "node:path";
import * as vscode from "vscode";
import { errorText, exists, run, settings } from "./util";
const SCRIPT = "cmake-helper-titlebar.js";
const HTML = "vs/code/electron-browser/workbench/workbench.html";
const MARK = "";
const LINE = `\t${MARK}\n`;
type State = "absent" | "outdated" | "current" | "unsupported";
/** A file to put in place (`content`), or to remove. */
interface Change {
file: string;
content?: string;
}
export class TitleBar {
constructor(private readonly context: vscode.ExtensionContext) {}
private get files() {
const out = path.join(vscode.env.appRoot, "out");
return { html: path.join(out, HTML), script: path.join(out, path.dirname(HTML), SCRIPT), product: path.join(vscode.env.appRoot, "product.json") };
}
private get own(): string {
return fs.readFileSync(path.join(this.context.extensionPath, "media", "titlebar.js"), "utf8");
}
state(): State {
// A remote extension host has the server's installation, not the window's; the web has no workbench.html.
if (vscode.env.uiKind !== vscode.UIKind.Desktop || vscode.env.remoteName || !exists(this.files.html)) return "unsupported";
try {
if (!fs.readFileSync(this.files.html, "utf8").includes(MARK)) return "absent";
return fs.readFileSync(this.files.script, "utf8") === this.own ? "current" : "outdated";
} catch {
return "absent";
}
}
/** On start and when the setting changes: what's in VS Code against what the setting asks for. */
async reconcile(): Promise {
const wanted = settings().get("toolbar") === "titleBar";
const state = this.state();
if (wanted && state === "unsupported") {
void vscode.window.showWarningMessage("CMake's toolbar in the title bar works only in desktop VS Code with a local workspace; here it stays in the status bar.");
return;
}
if (wanted && state === "absent") {
const patchedFor = this.context.globalState.get("titleBar.vscodeVersion");
const message = patchedFor
? `VS Code was updated (${patchedFor} → ${vscode.version}) and its title bar lost the CMake toolbar. Patch VS Code again?`
: "The CMake toolbar in the title bar needs VS Code's workbench.html patched (you may be asked for your password). Patch it now?";
const a = await vscode.window.showInformationMessage(message, "Patch VS Code", "Use Status Bar");
if (a === "Patch VS Code") await this.patch();
if (a === "Use Status Bar") await settings().update("toolbar", "statusBar", vscode.ConfigurationTarget.Global);
} else if (wanted && state === "outdated") {
const a = await vscode.window.showInformationMessage("CMake Helper was updated: update its toolbar in VS Code's title bar too?", "Update");
if (a) await this.patch();
} else if (!wanted && (state === "current" || state === "outdated")) {
const a = await vscode.window.showInformationMessage("VS Code still has the CMake toolbar patched into its title bar. Remove it?", "Remove", "Use Title Bar");
if (a === "Remove") await this.unpatch();
if (a === "Use Title Bar") await settings().update("toolbar", "titleBar", vscode.ConfigurationTarget.Global);
}
}
async patch(): Promise {
if (this.state() === "unsupported") {
void vscode.window.showWarningMessage("Patching the title bar works only in desktop VS Code with a local workspace.");
return;
}
const html = fs.readFileSync(this.files.html, "utf8").replace(LINE, "");
const patched = html.replace("