Files
VS-Code-CMake-helper/media/titlebar.js
Tomáš ČerníkandClaude Opus 5.5 fe6fd8be69
Build / build (pull_request) Successful in 10m29s
VS Code CMake Helper: first implementation
CMake the way CLion does it (profiles and presets, targets, run and debug
configurations, CTest), moved out of personal-assistant's cmake-extension
and renamed from CMake Targets.

- Gitea Actions: typecheck, package the .vsix, release it on a v* tag
- Updates: the latest Gitea release is looked at every 10 minutes and
  offered for install; a private repository's token in the secret storage
- cmakeHelper.toolbar: the toolbar in the status bar, the editors' title,
  or VS Code's title bar (workbench.html patched, offered again after
  VS Code or extension updates)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 09:05:39 +01:00

150 lines
6.5 KiB
JavaScript

/*
* CLion's toolbar in VS Code's title bar: the CMake profile and target, then
* Build, Run and Debug, next to the command center. An extension can't put
* anything in the title bar, so CMake Helper copies this script next to VS
* Code's workbench.html and adds it there (src/titleBar.ts). It draws nothing
* of its own: it mirrors the extension's status bar items (text, icon,
* tooltip, colours) and a click on one clicks the item, so the extension
* keeps all the logic. While it's shown, those status bar items are hidden.
* Plain DOM, no innerHTML: the workbench requires trusted types.
*/
(() => {
const PREFIX = "cernik.vs-code-cmake-helper.cmakeHelper.";
/** The mirrored items, in the toolbar's order; `group` items share one bordered box, like CLion's run configuration. */
const ITEMS = [
{ id: "reload", kind: "text" },
{ id: "profile", kind: "text", group: true },
{ id: "target", kind: "text", group: true },
{ id: "build", kind: "icon" },
{ id: "run", kind: "icon", color: "run" },
{ id: "debug", kind: "icon", color: "run" },
];
const css = `
body.cmake-helper-toolbar ${ITEMS.map((i) => `#${CSS.escape(PREFIX + i.id)}`).join(", ")} { display: none !important; }
/* Above the title bar's drag region, as VS Code's own toolbars are. */
.cmake-helper-toolbar-bar { position: relative; z-index: 2500; display: flex; align-items: center; gap: 2px; height: 100%; padding: 0 6px; -webkit-app-region: no-drag; app-region: no-drag; }
.cmake-helper-toolbar-bar button { display: flex; align-items: center; gap: 4px; height: 22px; padding: 0 5px; border: 0; border-radius: 5px; background: none;
color: inherit; font: inherit; font-size: 12px; cursor: pointer; white-space: nowrap; max-width: 220px; }
.cmake-helper-toolbar-bar button:hover { background: var(--vscode-toolbar-hoverBackground); }
.cmake-helper-toolbar-bar button .label { overflow: hidden; text-overflow: ellipsis; }
.cmake-helper-toolbar-bar button.icon { width: 24px; justify-content: center; padding: 0; }
.cmake-helper-toolbar-bar button.run .codicon { color: var(--vscode-debugIcon-startForeground, #89d185); }
.cmake-helper-toolbar-bar button .chevron { opacity: .7; font-size: 12px; }
.cmake-helper-toolbar-bar .group { display: flex; align-items: center; height: 22px; margin-right: 4px; border: 1px solid var(--vscode-commandCenter-border, rgba(128,128,128,.35)); border-radius: 6px; }
.cmake-helper-toolbar-bar .group button { border-radius: 0; height: 20px; }
.cmake-helper-toolbar-bar .group button:first-child { border-radius: 5px 0 0 5px; }
.cmake-helper-toolbar-bar .group button:last-child { border-radius: 0 5px 5px 0; }
.cmake-helper-toolbar-bar .group button + button { border-left: 1px solid var(--vscode-commandCenter-border, rgba(128,128,128,.35)); }
`;
/** The status bar item's own clickable label, if the extension shows it now. */
const original = (id) => document.getElementById(PREFIX + id)?.querySelector("a.statusbar-item-label");
/** "play, Run test (Shift+F10)": the tooltip part of VS Code's aria-label (the part before is the icon's name). */
const tooltipOf = (a) => {
const label = a.getAttribute("aria-label") ?? "";
const i = label.indexOf(", ");
return i >= 0 ? label.slice(i + 2) : label;
};
function button(item, a) {
const b = document.createElement("button");
b.type = "button";
b.className = [item.kind, item.color ?? ""].join(" ").trim();
b.title = tooltipOf(a);
b.setAttribute("aria-label", b.title);
// The icon (a codicon span, VS Code's own font) and the text, as the item shows them.
const icon = a.querySelector(".codicon")?.cloneNode(true);
if (icon) b.append(icon);
const text = a.textContent.trim();
if (item.kind === "text" && text) {
const label = document.createElement("span");
label.className = "label";
label.textContent = text;
b.append(label);
if (item.group) {
const chevron = document.createElement("span");
chevron.className = "codicon codicon-chevron-down chevron";
b.append(chevron);
}
}
// A warning-coloured item (Reload CMake) keeps its colours.
const box = a.parentElement;
if (box?.style.backgroundColor) b.style.backgroundColor = box.style.backgroundColor;
if (box?.style.color || a.style.color) b.style.color = box.style.color || a.style.color;
b.addEventListener("click", (e) => {
e.preventDefault();
original(item.id)?.click();
});
return b;
}
let bar = null;
let last = "";
function render() {
const right = document.querySelector(".part.titlebar .titlebar-right");
const present = ITEMS.map((item) => ({ item, a: original(item.id) })).filter((x) => x.a);
// No project (or no title bar): the status bar shows whatever there is.
if (!right || !present.length) {
bar?.remove();
bar = null;
last = "";
document.body.classList.remove("cmake-helper-toolbar");
return;
}
// Rebuild only when something shown changed.
const key = present.map(({ item, a }) => `${item.id}\u0000${a.outerHTML}\u0000${a.parentElement?.getAttribute("style") ?? ""}`).join("\u0001");
if (bar && bar.isConnected && key === last) return;
last = key;
if (!bar || !bar.isConnected) {
bar = document.createElement("div");
bar.className = "cmake-helper-toolbar-bar";
right.prepend(bar);
}
bar.replaceChildren();
let group = null;
for (const { item, a } of present) {
const b = button(item, a);
if (item.group) {
if (!group) {
group = document.createElement("div");
group.className = "group";
bar.append(group);
}
group.append(b);
} else {
group = null;
bar.append(b);
}
}
document.body.classList.add("cmake-helper-toolbar");
}
let queued = false;
const schedule = () => {
if (queued) return;
queued = true;
requestAnimationFrame(() => {
queued = false;
render();
});
};
function start() {
const style = document.createElement("style");
style.textContent = css;
document.head.append(style);
// The status bar changes (items come, go and change text) and the title bar may be redrawn: follow both.
new MutationObserver((records) => {
if (bar && records.every((r) => bar.contains(r.target))) return;
schedule();
}).observe(document.body, { subtree: true, childList: true, characterData: true, attributes: true, attributeFilter: ["aria-label", "style", "class"] });
schedule();
}
if (document.body) start();
else document.addEventListener("DOMContentLoaded", start);
})();