Skip to content

Commit

Permalink
lots of updates
Browse files Browse the repository at this point in the history
  • Loading branch information
guyutongxue committed Jan 30, 2024
1 parent 59350f0 commit 27572b3
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 24 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ I've publish a [GitHub Page](https://clangd.guyutongxue.site/) for demonstrating

Notice that clangd is a multi-thread program, so we need `SharedArrayBuffer` -- and more over, a strict context called `crossOriginIsolated`. GitHub Pages do not send COOP/COEP headers for us to enabling that context, so I served this site through CloudFlare with a custom rule adding those headers. If you want to deploy this project by yourself, make sure correct COOP/COEP header is set on the server side, or you can use [`coi-serviceworker`](https://github.com/gzuidhof/coi-serviceworker).

<!--By the way, you should be able to embed this page into your website as a `<iframe>`. I've enabled [iframe-resizer](https://github.com/davidjbradshaw/iframe-resizer) on this page for your convenience.-->
You can pass URL search parameters to control the initial state of this page ([see here](./docs/params.md)), or embed it in your website ([see here](./docs/embed.md), **your website needs to be cross-origin-isolated too**)

## Acknowledgement

Expand Down
24 changes: 24 additions & 0 deletions docs/embed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Embed to `<iframe>`

You can embed this project into your own website. But you should notice that, because of the use of `SharedArrayBuffer`, you need to set your host server's COOP/COEP headers, and use following `<iframe>` attribute:

```html
<iframe src="https://clangd.guyutongxue.site/?embed" allow="cross-origin-isolated" width="600" height="400">
```

It's recommended to pass `embed` url params, so you can control some behavior like setting editor value or trigger a compile request. You can do so by `contentWindow.postMessage`.

## Message format

Should be a JavaScript object, with following properties:

```ts
interface ClangdInBrowserMessage {
cdib: "1.0.0";
id?: any; // if presented, you will receive a reply
type: "setCode" | "setInput" | "runCode";
value?: string; // should provided when setCode / setInput
}
```

`setCode` message will set the editor value with given `value`. `setInput` will set the input textarea to your given `value` (for next time's compile). `runCode` will send a compile request just like pressing "Run code" button in the runner panel.
23 changes: 23 additions & 0 deletions docs/params.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# About URL Params

All options are case-insensitive and `true` `false` `1` `0` `on` `off` `yes` `no` `y` `n` are all valid boolean values.

### `code` (`string`)

Set the initial code when showing editor. Defaults to a Hello World program.

### `theme` (`"light"|"dark"`)

Whether use light theme or dark theme. Defaults to user's last time choice or system preference.

### `lsp` (`boolean`)

Whether enable Clangd and Language Client. Defaults to `true`.

### `run` (`boolean|"showOnly"`)

Whether to automatically run the code when editor is ready. If `"showOnly"` is provided, the runner panel is shown but won't run the code. Defaults to `false`.

### `embed` (`boolean`)

Whether enable embed `<iframe>` message handlers [see here](./embed.md). When passing `embed=true`, [iframe-resizer](https://github.com/davidjbradshaw/iframe-resizer) is also enabled so you can resize to your host container size freely. Defaults to `false`.
1 change: 1 addition & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export async function createClient(serverWorker: Worker) {
} finally {
retry++;
if (retry > 5 && !succeeded) {
setClangdStatus("disabled");
console.error("Failed to start clangd after 5 retries");
return;
}
Expand Down
17 changes: 15 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ export const COMPILE_ARGS = [
"-Wall",
];

export const editorValueGetter = {
get: () => ""
const editorValue = {
get: () => "",
set: (value: string) => {},
};

export function setEditorValueSource(getter: () => string, setter: (value: string) => void) {
editorValue.get = getter;
editorValue.set = setter;
}

export function getEditorValue() {
return editorValue.get();
}
export function setEditorValue(value: string) {
editorValue.set(value);
}
17 changes: 13 additions & 4 deletions src/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import EditorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
import { Uri } from "vscode";

import { LIGHT_THEME, DARK_THEME, createTheme } from "./theme";
import { FILE_PATH, LANGUAGE_ID, WORKSPACE_PATH, editorValueGetter } from "./config";
import {
FILE_PATH,
LANGUAGE_ID,
WORKSPACE_PATH,
setEditorValueSource,
} from "./config";

const self = globalThis as any;
self.MonacoEnvironment = { getWorker: () => new EditorWorker() };
Expand Down Expand Up @@ -45,13 +50,17 @@ export async function createEditor(element: HTMLElement, code: string) {
enabled: "offUnlessPressed",
},
});
editorValueGetter.get = () => editorInstance.getValue();
setEditorValueSource(
() => editorInstance.getValue(),
(value) => editorInstance.setValue(value)
);
return editorInstance;
}

function toggleEditorTheme() {
const isDark = document.body.classList.contains("dark");
editor.setTheme(isDark ? "dark" : "light");
}
document.querySelector("#toggleTheme")!.addEventListener("click", toggleEditorTheme);

document
.querySelector("#toggleTheme")!
.addEventListener("click", toggleEditorTheme);
55 changes: 55 additions & 0 deletions src/embed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { setEditorValue } from "./config";
import { setInput, compileAndRun } from "./ui";

console.log("!");
globalThis.addEventListener("message", async (e) => {
const data = e.data;
console.log(data);
if (typeof data !== "object" || data === null) {
return;
}
if (!("cdib" in data)) {
return;
}
if (!["1.0.0"].includes(data.cdib)) {
console.error("Unsupported message version.");
return;
}
const hasId = "id" in data;
const id = data.id;
switch (data.type) {
case void 0: {
console.error("Message type not specified.");
return;
}
case "setCode": {
if (typeof data.value !== "string") {
console.error("Invalid value type.");
return;
}
setEditorValue(data.value);
if (hasId) {
globalThis.postMessage({ cdib: "1.0.0", id, type: "reply:setCode" });
}
break;
}
case "setInput": {
if (typeof data.value !== "string") {
console.error("Invalid value type.");
return;
}
setInput(data.value);
if (hasId) {
globalThis.postMessage({ cdib: "1.0.0", id, type: "reply:setInput" });
}
break;
}
case "runCode": {
await compileAndRun();
if (hasId) {
globalThis.postMessage({ cdib: "1.0.0", id, type: "reply:runCode" });
}
break;
}
}
});
56 changes: 45 additions & 11 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,68 @@
import "./style.css";
import "./ui";
import { compileAndRun, setClangdStatus, toggleBuildPanel } from "./ui";
import { ExtendedSearchParams } from "./search_params";

if (!globalThis.crossOriginIsolated) {
document.body.innerHTML =
"This page requires cross-origin isolation to work properly. Page will reload in 3s.";
setTimeout(() => window.location.reload(), 3000);
"This page requires cross-origin isolation to work properly. You may forget to set server's COOP/COEP headers. If you are using this page as an <iframe>, you should also pass <code>allow=\"cross-origin\"</code> to the <code>iframe</code> element.";
throw new Error("Cross-origin isolation is not enabled");
}

const params = new ExtendedSearchParams(window.location.search);

let isDark = false;
const themeParam = params.get("theme");
const userTheme = localStorage.getItem("color-theme");
const systemDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
if (userTheme === "dark" || (userTheme !== "light" && systemDark)) {
if (
themeParam === "dark" ||
(themeParam !== "light" &&
(userTheme === "dark" || (userTheme !== "light" && systemDark)))
) {
document.body.classList.toggle("dark", true);
isDark = true;
}
// @ts-ignore
import("iframe-resizer/js/iframeResizer.contentWindow");

const code = `#include <iostream>
if (params.isTrue("embed")) {
// @ts-ignore
import("iframe-resizer/js/iframeResizer.contentWindow");
import("./embed");
}

const code =
params.get("code") ??
`#include <iostream>
#include <format>
int main() {
std::cout << std::format("Hello, {}!\\n", "world");
}
`;

const serverPromise = import("./server").then(({ createServer }) => createServer());
const runCodeNow = params.isTrue("run");
const showBuildPanel =
runCodeNow || params.get("run")?.toLowerCase() === "showonly";

if (showBuildPanel) {
toggleBuildPanel();
}

const disableLsp = params.isExplicitFalse("lsp");
let serverPromise: Promise<Worker>;
if (disableLsp) {
setClangdStatus("disabled");
serverPromise = new Promise<never>(() => {});
} else {
serverPromise = import("./server").then(({ createServer }) => {
return createServer();
});
}
import("./client").then(async ({ createEditor, createClient }) => {
await createEditor(document.getElementById("editor")!, code);
await createClient(await serverPromise);
})

if (!disableLsp) {
await createClient(await serverPromise);
}
if (runCodeNow) {
compileAndRun();
}
});
11 changes: 11 additions & 0 deletions src/search_params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const trueValues = ["", "true", "1", "yes", "y", "on"];
const falseValues = ["false", "0", "no", "n", "off"];

export class ExtendedSearchParams extends URLSearchParams {
isTrue(key: string): boolean {
return this.has(key) && trueValues.includes(this.get(key)!.toLowerCase());
}
isExplicitFalse(key: string): boolean {
return falseValues.includes(this.get(key)?.toLowerCase() ?? "");
}
}
19 changes: 19 additions & 0 deletions src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ body {
position: relative;
display: flex;
flex-direction: column;
font-size: 14px;
}

#buildPanelResize {
Expand Down Expand Up @@ -221,6 +222,7 @@ body {
padding: 0.5em 1em;
font-family: var(--font-monospace);
overflow: auto;
user-select: text;
}
}

Expand Down Expand Up @@ -274,6 +276,9 @@ body {
&[data-ready] {
--tooltip-text: "Clangd is ready";
}
&[data-disabled] {
--tooltip-text: "Clangd is disabled";
}
}

#progress {
Expand All @@ -284,6 +289,9 @@ body {
[data-ready] #progress {
display: none;
}
[data-disabled] #progress {
display: none;
}

.progress-bar {
height: 0.5em;
Expand Down Expand Up @@ -352,6 +360,17 @@ a {
width: 1.2em;
height: 1.2em;
}
[data-disabled] .icon-loading {
--un-icon: url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 24 24' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M1 21L12 2l11 19zm3.45-2h15.1L12 6zM12 18q.425 0 .713-.288T13 17q0-.425-.288-.712T12 16q-.425 0-.712.288T11 17q0 .425.288.713T12 18m-1-3h2v-5h-2zm1-2.5'/%3E%3C/svg%3E");
-webkit-mask: var(--un-icon) no-repeat;
mask: var(--un-icon) no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
background-color: currentColor;
color: inherit;
width: 1.2em;
height: 1.2em;
}
.icon-run {
--un-icon: url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 24 24' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M8 19V5l11 7z'/%3E%3C/svg%3E");
-webkit-mask: var(--un-icon) no-repeat;
Expand Down
28 changes: 22 additions & 6 deletions src/ui.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { editorValueGetter } from "./config";
import { getEditorValue } from "./config";
import { runCode } from "./runner";

const status = document.querySelector<HTMLElement>("#status")!;
const toggleThemeBtn = document.querySelector<HTMLButtonElement>("#toggleTheme")!;
const toggleThemeBtn =
document.querySelector<HTMLButtonElement>("#toggleTheme")!;
const buildPanel = document.querySelector<HTMLElement>("#buildPanel")!;
const showBuildPanel = document.querySelector<HTMLElement>("#showBuildPanel")!;
const resizeHandle = document.querySelector<HTMLElement>("#buildPanelResize")!;
Expand All @@ -16,7 +17,7 @@ const inputEl =
const outputEl = document.querySelector<HTMLPreElement>("#buildPanelOutput")!;
const closeBtn = document.querySelector<HTMLButtonElement>("#closeBuildPanel")!;

function toggleBuildPanel() {
export function toggleBuildPanel() {
buildPanel.classList.toggle("display-none");
showBuildPanel.classList.toggle("display-none");
}
Expand Down Expand Up @@ -88,7 +89,15 @@ let input = "";
let output = "";
let diagnostics = "";

async function compileAndRun() {
export function setInput(value: string) {
input = value;
inputEl.value = value;
}

export async function compileAndRun() {
if (buildPanel.classList.contains("display-none")) {
toggleBuildPanel();
}
const inner = [...runBtn.childNodes];
runBtn.setAttribute("disabled", "true");
runBtn.innerHTML = "Compiling...";
Expand All @@ -98,7 +107,7 @@ async function compileAndRun() {
stderr,
didExecute;
({ stdout, stderr, diagnostics, didExecute } = await runCode(
editorValueGetter.get(),
getEditorValue(),
{ stdin }
));
const outputContainer = document.createElement("pre");
Expand Down Expand Up @@ -126,19 +135,26 @@ function toggleTheme() {
}
toggleThemeBtn.addEventListener("click", toggleTheme);

export function setClangdStatus(status: "ready" | "indeterminate"): void;
export function setClangdStatus(status: "ready" | "indeterminate" | "disabled"): void;
export function setClangdStatus(value: number, max: number): void;
export function setClangdStatus(strOrVal: string | number, max?: number) {
if (typeof strOrVal === "number") {
status.removeAttribute("data-ready");
status.removeAttribute("data-disabled");
status.removeAttribute("data-indeterminate");
status.style.setProperty("--progress-value", `${strOrVal}`);
status.style.setProperty("--progress-max", `${max}`);
} else if (strOrVal === "ready") {
status.setAttribute("data-ready", "");
status.removeAttribute("data-disabled");
status.removeAttribute("data-indeterminate");
} else if (strOrVal === "disabled") {
status.removeAttribute("data-ready");
status.setAttribute("data-disabled", "");
status.removeAttribute("data-indeterminate");
} else if (strOrVal === "indeterminate") {
status.removeAttribute("data-ready");
status.removeAttribute("data-disabled");
status.setAttribute("data-indeterminate", "");
}
}

0 comments on commit 27572b3

Please sign in to comment.