squiggle/packages/vscode-ext/src/squiggleEditor.ts

142 lines
4.4 KiB
TypeScript
Raw Normal View History

import * as vscode from "vscode";
function getNonce() {
let text = "";
const possible =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
export class SquiggleEditorProvider implements vscode.CustomTextEditorProvider {
public static register(context: vscode.ExtensionContext): vscode.Disposable {
const provider = new SquiggleEditorProvider(context);
const providerRegistration = vscode.window.registerCustomEditorProvider(
SquiggleEditorProvider.viewType,
provider
);
return providerRegistration;
}
private static readonly viewType = "squiggle.wysiwyg";
constructor(private readonly context: vscode.ExtensionContext) {}
/**
* Called when our custom editor is opened.
*
*
*/
public async resolveCustomTextEditor(
document: vscode.TextDocument,
webviewPanel: vscode.WebviewPanel
): Promise<void> {
// Setup initial content for the webview
webviewPanel.webview.options = {
enableScripts: true,
};
webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview);
function updateWebview() {
webviewPanel.webview.postMessage({
type: "update",
text: document.getText(),
});
}
// Hook up event handlers so that we can synchronize the webview with the text document.
//
// The text document acts as our model, so we have to sync change in the document to our
// editor and sync changes in the editor back to the document.
//
// Remember that a single text document can also be shared between multiple custom
// editors (this happens for example when you split a custom editor)
const changeDocumentSubscription = vscode.workspace.onDidChangeTextDocument(
(e) => {
if (e.document.uri.toString() === document.uri.toString()) {
updateWebview();
}
}
);
// Make sure we get rid of the listener when our editor is closed.
webviewPanel.onDidDispose(() => {
changeDocumentSubscription.dispose();
});
// Receive message from the webview.
webviewPanel.webview.onDidReceiveMessage((e) => {
switch (e.type) {
case "edit":
this.updateTextDocument(document, e.text);
return;
}
});
updateWebview();
}
/**
* Get the static html used for the editor webviews.
*/
private getHtmlForWebview(webview: vscode.Webview): string {
// Local path to main script run in the webview
const bundleUri = webview.asWebviewUri(
vscode.Uri.joinPath(
this.context.extensionUri,
"media",
"components-bundle.js"
)
);
const styleUri = webview.asWebviewUri(
vscode.Uri.joinPath(this.context.extensionUri, "media", "components.css")
);
const scriptUri = webview.asWebviewUri(
vscode.Uri.joinPath(this.context.extensionUri, "media", "wysiwyg.js")
);
// Use a nonce to whitelist which scripts can be run
const nonce = getNonce();
return /* html */ `
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--
Use a content security policy to only allow loading images from https or from our extension directory,
and only allow scripts that have a specific nonce.
-->
<meta http-equiv="Content-Security-Policy" content="script-src 'nonce-${nonce}' 'unsafe-eval';">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script nonce="${nonce}" src="https://unpkg.com/react@18.2.0/umd/react.production.min.js" crossorigin></script>
<script nonce="${nonce}" src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js" crossorigin></script>
<script nonce="${nonce}" src="${bundleUri}"></script>
<link href="${styleUri}" rel="stylesheet" />
<title>Squiggle Editor</title>
</head>
<body style="background-color: white;">
<div id="root"></div>
<script nonce="${nonce}" src="${scriptUri}"></script>
</body>
</html>`;
}
private updateTextDocument(document: vscode.TextDocument, text: string) {
const edit = new vscode.WorkspaceEdit();
// Just replace the entire document every time.
edit.replace(
document.uri,
new vscode.Range(0, 0, document.lineCount, 0),
text
);
return vscode.workspace.applyEdit(edit);
}
}