Skip to content

Commit 10eca94

Browse files
committed
readd compiler explorer integration
1 parent c930ce0 commit 10eca94

5 files changed

Lines changed: 277 additions & 5 deletions

File tree

README.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ Supported attributes are:
4141

4242
- `compiler`: Compiler Explorer compiler ID, defaulting to `clang2110`.
4343
- `args`: arguments sent unchanged to Compiler Explorer.
44+
- `ce-url`: Compiler Explorer base URL, defaulting to `https://godbolt.org/`.
45+
- `ce-language`: language stored in the Compiler Explorer client state, defaulting to `c++`.
46+
- `ce-compiler`: override the linked Compiler Explorer compiler without changing Run.
47+
- `ce-options`: override the linked Compiler Explorer arguments without changing Run.
48+
- `ce-filters`: JSON object overriding Compiler Explorer output filters, such as `'{"intel":false,"demangle":true}'`.
4449
- `theme`: `auto`, `light`, or `dark`.
4550
- `debug`: show the basic/full editor and light/dark switches. These are hidden by default.
4651
- `width`: any valid CSS width for the complete block.
@@ -73,9 +78,33 @@ Supported attributes are:
7378
</script>
7479
```
7580

76-
Call `CodeBlocks.configure(options)` before a block is upgraded to set defaults for later blocks. The available options are `theme`, `showDebugControls`, `compiler`, `args`, `compilerExplorerUrl`, `editorOptions`, `styles`, and `onStatus`.
81+
Call `CodeBlocks.configure(options)` before a block is upgraded to set defaults for later blocks. The available options are `theme`, `showDebugControls`, `compiler`, `args`, `compilerExplorer`, `editorOptions`, `styles`, and `onStatus`.
82+
83+
The Compiler Explorer link contains the active source, filename, compiler, arguments, and output filters in its `/clientstate/` URL. No upload or short-link request is needed. Client-state fields can be overridden globally or when creating an individual block:
84+
85+
```js
86+
CodeBlocks.configure({
87+
compilerExplorer: {
88+
baseUrl: "https://godbolt.org/",
89+
language: "c++",
90+
compiler: "gsnapshot",
91+
options: "-std=c++26 -O2",
92+
filters: {
93+
intel: false,
94+
demangle: true,
95+
commentOnly: false,
96+
},
97+
libs: [],
98+
specialoutputs: [],
99+
tools: [],
100+
overrides: [],
101+
},
102+
});
103+
```
104+
105+
The legacy `compilerExplorerUrl` JavaScript option remains available as an alias for `compilerExplorer.baseUrl`.
77106

78-
`CodeBlocks.get(element)` returns the upgraded block instance. It exposes `getValue`, `setValue`, `getTabs`, `selectTab`, `focus`, `run`, `setTheme`, `dispose`, `onDidChange`, `editorReady`, `monacoReady`, and `clangdReady`. `getValue` and `setValue` act on the active tab. `monacoReady` resolves to the underlying Monaco standalone editor for integrations that need the native editor API.
107+
`CodeBlocks.get(element)` returns the upgraded block instance. It exposes `getValue`, `setValue`, `getTabs`, `selectTab`, `getCompilerExplorerUrl`, `focus`, `run`, `setTheme`, `dispose`, `onDidChange`, `editorReady`, `monacoReady`, and `clangdReady`. `getValue` and `setValue` act on the active tab. `monacoReady` resolves to the underlying Monaco standalone editor for integrations that need the native editor API.
79108

80109
Styling uses CSS custom properties. Common properties include:
81110

public/index.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@
2626
<main>
2727
<h1>C++ code blocks</h1>
2828
<p>Edit and run this example in your browser.</p>
29-
<codeblock compiler="gsnapshot" args="-std=c++26 -freflection" debug>
29+
<codeblock
30+
compiler="gsnapshot"
31+
args="-std=c++26 -freflection"
32+
ce-filters='{"intel":false}'
33+
debug
34+
>
3035
<codeblock-tab name="main.cpp">
3136
#include &lt;print&gt;
3237
int main() {

src/codeblocks.css

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,33 @@ codeblock,
134134
.codeblocks-root button:disabled { opacity: .65; cursor: wait; }
135135
.codeblocks-root a { color: var(--codeblocks-link); text-decoration: none; }
136136
.codeblocks-root a:hover { text-decoration: underline; }
137+
.codeblocks-root .codeblocks-compiler-link {
138+
display: inline-flex;
139+
min-height: 32px;
140+
align-items: center;
141+
gap: 7px;
142+
margin-left: auto;
143+
border: 1px solid #57ae20;
144+
border-radius: 5px;
145+
padding: 5px 10px;
146+
background: #67c52a;
147+
color: #102600;
148+
font-size: 13px;
149+
font-weight: 650;
150+
line-height: 1;
151+
white-space: nowrap;
152+
}
153+
.codeblocks-root .codeblocks-compiler-link:hover {
154+
border-color: #71d832;
155+
background: #71d832;
156+
color: #0b1d00;
157+
text-decoration: none;
158+
}
159+
.codeblocks-compiler-link svg {
160+
width: 15px;
161+
height: 15px;
162+
flex: none;
163+
}
137164
.codeblocks-debug { display: inline-flex; gap: 8px; }
138165
.codeblocks-debug[hidden] { display: none; }
139166
.codeblocks-output {

src/codeblocks.ts

Lines changed: 171 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,39 @@ import "./codeblocks.css";
66

77
export type CodeBlockTheme = "auto" | "light" | "dark";
88

9+
export interface CompilerExplorerFilters {
10+
binary: boolean;
11+
binaryObject: boolean;
12+
commentOnly: boolean;
13+
demangle: boolean;
14+
directives: boolean;
15+
execute: boolean;
16+
intel: boolean;
17+
labels: boolean;
18+
libraryCode: boolean;
19+
trim: boolean;
20+
debugCalls: boolean;
21+
}
22+
23+
export interface CompilerExplorerConfiguration {
24+
baseUrl?: string;
25+
language?: string;
26+
compiler?: string;
27+
options?: string;
28+
filters?: Partial<CompilerExplorerFilters>;
29+
libs?: unknown[];
30+
specialoutputs?: string[];
31+
tools?: unknown[];
32+
overrides?: unknown[];
33+
}
34+
935
export interface CodeBlocksConfiguration {
1036
theme?: CodeBlockTheme;
1137
showDebugControls?: boolean;
1238
compiler?: string;
1339
args?: string;
40+
compilerExplorer?: CompilerExplorerConfiguration;
41+
/** @deprecated Use compilerExplorer.baseUrl. */
1442
compilerExplorerUrl?: string;
1543
editorOptions?: MonacoEditor.IStandaloneEditorConstructionOptions;
1644
styles?: Record<string, string>;
@@ -27,6 +55,7 @@ export interface CodeBlock {
2755
setValue(value: string): void;
2856
getTabs(): Array<{ name: string; value: string }>;
2957
selectTab(tab: string | number): void;
58+
getCompilerExplorerUrl(): string;
3059
focus(): void;
3160
run(): Promise<void>;
3261
setTheme(theme: CodeBlockTheme): Promise<void>;
@@ -111,10 +140,10 @@ export function createCodeBlock(options: CreateCodeBlockOptions): CodeBlock {
111140
themeToggle.dataset.themeToggle = "";
112141
debugControls.append(editorToggle, themeToggle);
113142
const compilerLink = document.createElement("a");
114-
compilerLink.href = options.compilerExplorerUrl ?? "https://compiler-explorer.com/";
143+
compilerLink.className = "codeblocks-compiler-link";
115144
compilerLink.target = "_blank";
116145
compilerLink.rel = "noopener";
117-
compilerLink.textContent = "Open in Compiler Explorer";
146+
compilerLink.append("View on Compiler Explorer", externalLinkIcon());
118147
toolbar.append(runButton, debugControls, compilerLink);
119148

120149
const outputDrawer = document.createElement("section");
@@ -157,6 +186,7 @@ export function createCodeBlock(options: CreateCodeBlockOptions): CodeBlock {
157186
let lastLoggedDownload = -1;
158187
const changeListeners = new Set<(value: string) => void>();
159188
let unsubscribeActive = fallback.onDidChange(notifyChange);
189+
compilerLink.href = getCompilerExplorerUrl();
160190

161191
let resolveClangd!: () => void;
162192
let rejectClangd!: (error: unknown) => void;
@@ -167,6 +197,9 @@ export function createCodeBlock(options: CreateCodeBlockOptions): CodeBlock {
167197
void clangdReady.catch(() => {});
168198

169199
runButton.addEventListener("click", run);
200+
compilerLink.addEventListener("click", updateCompilerExplorerLink);
201+
compilerLink.addEventListener("pointerdown", updateCompilerExplorerLink);
202+
compilerLink.addEventListener("focus", updateCompilerExplorerLink);
170203
editorToggle.addEventListener("click", toggleEditor);
171204
themeToggle.addEventListener("click", toggleTheme);
172205
const media = matchMedia("(prefers-color-scheme: dark)");
@@ -323,6 +356,18 @@ export function createCodeBlock(options: CreateCodeBlockOptions): CodeBlock {
323356
changeListeners.forEach((listener) => listener(value));
324357
}
325358

359+
function getCompilerExplorerUrl(): string {
360+
return createCompilerExplorerUrl(
361+
activeEditor.getValue(),
362+
tabs[activeTab].name,
363+
options,
364+
);
365+
}
366+
367+
function updateCompilerExplorerLink(): void {
368+
compilerLink.href = getCompilerExplorerUrl();
369+
}
370+
326371
function selectTab(tab: string | number): void {
327372
const index = typeof tab === "number"
328373
? tab
@@ -350,13 +395,17 @@ export function createCodeBlock(options: CreateCodeBlockOptions): CodeBlock {
350395
value: index === activeTab ? activeEditor.getValue() : tab.value,
351396
})),
352397
selectTab,
398+
getCompilerExplorerUrl,
353399
focus: () => activeEditor.focus(),
354400
run,
355401
setTheme,
356402
dispose() {
357403
if (disposed) return;
358404
disposed = true;
359405
runButton.removeEventListener("click", run);
406+
compilerLink.removeEventListener("click", updateCompilerExplorerLink);
407+
compilerLink.removeEventListener("pointerdown", updateCompilerExplorerLink);
408+
compilerLink.removeEventListener("focus", updateCompilerExplorerLink);
360409
editorToggle.removeEventListener("click", toggleEditor);
361410
themeToggle.removeEventListener("click", toggleTheme);
362411
media.removeEventListener("change", systemThemeChanged);
@@ -397,6 +446,10 @@ function upgradeWithin(root: ParentNode): void {
397446
showDebugControls: element.hasAttribute("debug") || configuration.showDebugControls,
398447
compiler: element.getAttribute("compiler") ?? configuration.compiler,
399448
args: element.getAttribute("args") ?? configuration.args,
449+
compilerExplorer: compilerExplorerAttributes(
450+
element,
451+
configuration.compilerExplorer,
452+
),
400453
});
401454
instances.set(element, instance);
402455
}
@@ -429,6 +482,43 @@ function attributeTheme(element: HTMLElement): CodeBlockTheme | undefined {
429482
return value === "auto" || value === "light" || value === "dark" ? value : undefined;
430483
}
431484

485+
function compilerExplorerAttributes(
486+
element: HTMLElement,
487+
configured: CompilerExplorerConfiguration | undefined,
488+
): CompilerExplorerConfiguration | undefined {
489+
const baseUrl = element.getAttribute("ce-url");
490+
const language = element.getAttribute("ce-language");
491+
const compiler = element.getAttribute("ce-compiler");
492+
const options = element.getAttribute("ce-options");
493+
const filters = jsonAttribute<Partial<CompilerExplorerFilters>>(
494+
element,
495+
"ce-filters",
496+
);
497+
if (!configured && !baseUrl && !language && !compiler && !options && !filters) {
498+
return undefined;
499+
}
500+
return {
501+
...configured,
502+
...(baseUrl && { baseUrl }),
503+
...(language && { language }),
504+
...(compiler && { compiler }),
505+
...(options && { options }),
506+
filters: { ...configured?.filters, ...filters },
507+
};
508+
}
509+
510+
function jsonAttribute<T>(element: HTMLElement, name: string): T | undefined {
511+
const value = element.getAttribute(name);
512+
if (!value) return undefined;
513+
try {
514+
return JSON.parse(value) as T;
515+
} catch (error) {
516+
throw new SyntaxError(
517+
`${name} must contain valid JSON: ${errorMessage(error)}`,
518+
);
519+
}
520+
}
521+
432522
function button(label: string, secondary = false): HTMLButtonElement {
433523
const element = document.createElement("button");
434524
element.type = "button";
@@ -437,6 +527,21 @@ function button(label: string, secondary = false): HTMLButtonElement {
437527
return element;
438528
}
439529

530+
function externalLinkIcon(): SVGSVGElement {
531+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
532+
svg.setAttribute("viewBox", "0 0 16 16");
533+
svg.setAttribute("aria-hidden", "true");
534+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
535+
path.setAttribute("d", "M9 2h5v5M14 2 7.5 8.5M12 9.5V14H2V4h4.5");
536+
path.setAttribute("fill", "none");
537+
path.setAttribute("stroke", "currentColor");
538+
path.setAttribute("stroke-linecap", "round");
539+
path.setAttribute("stroke-linejoin", "round");
540+
path.setAttribute("stroke-width", "1.5");
541+
svg.append(path);
542+
return svg;
543+
}
544+
440545
function resolveTheme(theme: CodeBlockTheme): "light" | "dark" {
441546
if (theme === "light" || theme === "dark") return theme;
442547
return matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
@@ -481,6 +586,70 @@ function formatMegabytes(bytes: number): string {
481586
return (bytes / (1024 * 1024)).toFixed(1);
482587
}
483588

589+
const DEFAULT_COMPILER_EXPLORER_FILTERS: CompilerExplorerFilters = {
590+
binary: false,
591+
binaryObject: false,
592+
commentOnly: true,
593+
demangle: true,
594+
directives: true,
595+
execute: false,
596+
intel: true,
597+
labels: true,
598+
libraryCode: false,
599+
trim: false,
600+
debugCalls: false,
601+
};
602+
603+
function createCompilerExplorerUrl(
604+
source: string,
605+
filename: string,
606+
options: CreateCodeBlockOptions,
607+
): string {
608+
const explorer = options.compilerExplorer ?? {};
609+
const compiler = explorer.compiler ?? options.compiler ?? "clang2110";
610+
const compilerOptions = explorer.options ?? options.args ??
611+
"-std=c++2c -Wall -Wextra -pedantic-errors";
612+
const state = {
613+
sessions: [{
614+
id: 1,
615+
language: explorer.language ?? "c++",
616+
source,
617+
filename,
618+
compilers: [{
619+
id: compiler,
620+
options: compilerOptions,
621+
filters: {
622+
...DEFAULT_COMPILER_EXPLORER_FILTERS,
623+
...explorer.filters,
624+
},
625+
libs: explorer.libs ?? [],
626+
specialoutputs: explorer.specialoutputs ?? [],
627+
tools: explorer.tools ?? [],
628+
overrides: explorer.overrides ?? [],
629+
}],
630+
executors: [],
631+
}],
632+
trees: [],
633+
};
634+
const baseUrl = new URL(
635+
explorer.baseUrl ?? options.compilerExplorerUrl ?? "https://godbolt.org/",
636+
);
637+
if (!baseUrl.pathname.endsWith("/")) baseUrl.pathname += "/";
638+
return new URL(`clientstate/${base64Url(JSON.stringify(state))}`, baseUrl).href;
639+
}
640+
641+
function base64Url(value: string): string {
642+
const bytes = new TextEncoder().encode(value);
643+
let binary = "";
644+
for (let offset = 0; offset < bytes.length; offset += 0x8000) {
645+
binary += String.fromCharCode(...bytes.subarray(offset, offset + 0x8000));
646+
}
647+
return btoa(binary)
648+
.replace(/\+/g, "-")
649+
.replace(/\//g, "_")
650+
.replace(/=+$/, "");
651+
}
652+
484653
interface CompilerLine { text: string }
485654
interface CompilerResult {
486655
didExecute: boolean;

0 commit comments

Comments
 (0)