preview mode for vscode ext
This commit is contained in:
parent
65e55c74c5
commit
24f4143cda
|
@ -18,6 +18,7 @@
|
||||||
"vega": "^5.22.1",
|
"vega": "^5.22.1",
|
||||||
"vega-embed": "^6.21.0",
|
"vega-embed": "^6.21.0",
|
||||||
"vega-lite": "^5.2.0",
|
"vega-lite": "^5.2.0",
|
||||||
|
"vscode-uri": "^3.0.3",
|
||||||
"yup": "^0.32.11"
|
"yup": "^0.32.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
43
packages/vscode-ext/media/previewWebview.js
Normal file
43
packages/vscode-ext/media/previewWebview.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// based on https://github.com/microsoft/vscode-extension-samples/blob/main/custom-editor-sample/media/catScratch.js
|
||||||
|
(function () {
|
||||||
|
console.log("hello world");
|
||||||
|
const vscode = acquireVsCodeApi();
|
||||||
|
|
||||||
|
const container = document.getElementById("root");
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(container);
|
||||||
|
function updateContent(text) {
|
||||||
|
root.render(
|
||||||
|
React.createElement(squiggle_components.SquigglePlayground, {
|
||||||
|
code: text,
|
||||||
|
onCodeChange: (code) => {
|
||||||
|
vscode.postMessage({ type: "edit", text: code });
|
||||||
|
},
|
||||||
|
showEditor: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle messages sent from the extension to the webview
|
||||||
|
window.addEventListener("message", (event) => {
|
||||||
|
const message = event.data; // The json data that the extension sent
|
||||||
|
switch (message.type) {
|
||||||
|
case "update":
|
||||||
|
const text = message.text;
|
||||||
|
|
||||||
|
// Update our webview's content
|
||||||
|
updateContent(text);
|
||||||
|
|
||||||
|
// Then persist state information.
|
||||||
|
// This state is returned in the call to `vscode.getState` below when a webview is reloaded.
|
||||||
|
vscode.setState({ text });
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = vscode.getState();
|
||||||
|
if (state) {
|
||||||
|
updateContent(state.text);
|
||||||
|
}
|
||||||
|
})();
|
|
@ -18,10 +18,22 @@
|
||||||
"Visualization"
|
"Visualization"
|
||||||
],
|
],
|
||||||
"activationEvents": [
|
"activationEvents": [
|
||||||
"onCustomEditor:squiggle.wysiwyg"
|
"onCustomEditor:squiggle.wysiwyg",
|
||||||
|
"onCommand:squiggle.preview"
|
||||||
],
|
],
|
||||||
"main": "./out/extension.js",
|
"main": "./out/extension.js",
|
||||||
"contributes": {
|
"contributes": {
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"id": "squiggle",
|
||||||
|
"extensions": [
|
||||||
|
".squiggle"
|
||||||
|
],
|
||||||
|
"aliases": [
|
||||||
|
"Squiggle"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"customEditors": [
|
"customEditors": [
|
||||||
{
|
{
|
||||||
"viewType": "squiggle.wysiwyg",
|
"viewType": "squiggle.wysiwyg",
|
||||||
|
@ -31,10 +43,41 @@
|
||||||
"filenamePattern": "*.squiggle"
|
"filenamePattern": "*.squiggle"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"priority": "default"
|
"priority": "option"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"commands": []
|
"commands": [
|
||||||
|
{
|
||||||
|
"command": "squiggle.preview",
|
||||||
|
"title": "Open Preview",
|
||||||
|
"category": "Squiggle",
|
||||||
|
"when": "editorLangId == squiggle",
|
||||||
|
"icon": "$(open-preview)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"menus": {
|
||||||
|
"editor/title": [
|
||||||
|
{
|
||||||
|
"command": "squiggle.preview",
|
||||||
|
"when": "editorLangId == squiggle",
|
||||||
|
"group": "navigation"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"commandPalette": [
|
||||||
|
{
|
||||||
|
"command": "squiggle.preview",
|
||||||
|
"when": "editorLangId == squiggle"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"keybindings": [
|
||||||
|
{
|
||||||
|
"command": "squiggle.preview",
|
||||||
|
"key": "ctrl+k v",
|
||||||
|
"mac": "cmd+k v",
|
||||||
|
"when": "editorLangId == squiggle"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"vscode:prepublish": "yarn run compile",
|
"vscode:prepublish": "yarn run compile",
|
||||||
|
|
|
@ -1,14 +1,5 @@
|
||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
|
import { getWebviewContent } from "./utils";
|
||||||
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 {
|
export class SquiggleEditorProvider implements vscode.CustomTextEditorProvider {
|
||||||
public static register(context: vscode.ExtensionContext): vscode.Disposable {
|
public static register(context: vscode.ExtensionContext): vscode.Disposable {
|
||||||
|
@ -26,8 +17,6 @@ export class SquiggleEditorProvider implements vscode.CustomTextEditorProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when our custom editor is opened.
|
* Called when our custom editor is opened.
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public async resolveCustomTextEditor(
|
public async resolveCustomTextEditor(
|
||||||
document: vscode.TextDocument,
|
document: vscode.TextDocument,
|
||||||
|
@ -37,7 +26,12 @@ export class SquiggleEditorProvider implements vscode.CustomTextEditorProvider {
|
||||||
webviewPanel.webview.options = {
|
webviewPanel.webview.options = {
|
||||||
enableScripts: true,
|
enableScripts: true,
|
||||||
};
|
};
|
||||||
webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview);
|
webviewPanel.webview.html = getWebviewContent({
|
||||||
|
webview: webviewPanel.webview,
|
||||||
|
script: "media/wysiwygWebview.js",
|
||||||
|
title: "Squiggle Editor",
|
||||||
|
context: this.context,
|
||||||
|
});
|
||||||
|
|
||||||
function updateWebview() {
|
function updateWebview() {
|
||||||
webviewPanel.webview.postMessage({
|
webviewPanel.webview.postMessage({
|
||||||
|
@ -79,57 +73,6 @@ export class SquiggleEditorProvider implements vscode.CustomTextEditorProvider {
|
||||||
updateWebview();
|
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 styleUri = webview.asWebviewUri(
|
|
||||||
vscode.Uri.joinPath(
|
|
||||||
this.context.extensionUri,
|
|
||||||
"media/vendor/components.css"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const scriptUris = [
|
|
||||||
// vendor files are copied over by `yarn run compile`
|
|
||||||
"media/vendor/react.js",
|
|
||||||
"media/vendor/react-dom.js",
|
|
||||||
"media/vendor/components.js",
|
|
||||||
"media/wysiwyg.js",
|
|
||||||
].map((script) =>
|
|
||||||
webview.asWebviewUri(
|
|
||||||
vscode.Uri.joinPath(this.context.extensionUri, script)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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">
|
|
||||||
<link href="${styleUri}" rel="stylesheet" />
|
|
||||||
<title>Squiggle Editor</title>
|
|
||||||
</head>
|
|
||||||
<body style="background-color: white; color: black; padding: 12px">
|
|
||||||
<div id="root"></div>
|
|
||||||
${scriptUris
|
|
||||||
.map((uri) => `<script nonce="${nonce}" src="${uri}"></script>`)
|
|
||||||
.join("")}
|
|
||||||
</body>
|
|
||||||
</html>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateTextDocument(document: vscode.TextDocument, text: string) {
|
private updateTextDocument(document: vscode.TextDocument, text: string) {
|
||||||
const edit = new vscode.WorkspaceEdit();
|
const edit = new vscode.WorkspaceEdit();
|
||||||
|
|
|
@ -2,12 +2,15 @@
|
||||||
// Import the module and reference it with the alias vscode in your code below
|
// Import the module and reference it with the alias vscode in your code below
|
||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
|
|
||||||
import { SquiggleEditorProvider } from "./squiggleEditor";
|
import { SquiggleEditorProvider } from "./editor";
|
||||||
|
import { registerPreviewCommand } from "./preview";
|
||||||
|
|
||||||
// this method is called when your extension is activated
|
// this method is called when your extension is activated
|
||||||
// your extension is activated the very first time the command is executed
|
// your extension is activated the very first time the command is executed
|
||||||
export function activate(context: vscode.ExtensionContext) {
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
context.subscriptions.push(SquiggleEditorProvider.register(context));
|
context.subscriptions.push(SquiggleEditorProvider.register(context));
|
||||||
|
|
||||||
|
registerPreviewCommand(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this method is called when your extension is deactivated
|
// this method is called when your extension is deactivated
|
||||||
|
|
51
packages/vscode-ext/src/preview.ts
Normal file
51
packages/vscode-ext/src/preview.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import * as vscode from "vscode";
|
||||||
|
import * as uri from "vscode-uri";
|
||||||
|
import { getWebviewContent } from "./utils";
|
||||||
|
|
||||||
|
export const registerPreviewCommand = (context: vscode.ExtensionContext) => {
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerTextEditorCommand("squiggle.preview", (editor) => {
|
||||||
|
// Create and show a new webview
|
||||||
|
const title = `Preview ${uri.Utils.basename(editor.document.uri)}`;
|
||||||
|
|
||||||
|
const panel = vscode.window.createWebviewPanel(
|
||||||
|
"squigglePreview",
|
||||||
|
title,
|
||||||
|
vscode.ViewColumn.Beside,
|
||||||
|
{} // Webview options. More on these later.
|
||||||
|
);
|
||||||
|
|
||||||
|
panel.webview.options = {
|
||||||
|
enableScripts: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
panel.webview.html = getWebviewContent({
|
||||||
|
context,
|
||||||
|
webview: panel.webview,
|
||||||
|
title,
|
||||||
|
script: "media/previewWebview.js",
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateWebview = () => {
|
||||||
|
panel.webview.postMessage({
|
||||||
|
type: "update",
|
||||||
|
text: editor.document.getText(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updateWebview();
|
||||||
|
|
||||||
|
const changeDocumentSubscription =
|
||||||
|
vscode.workspace.onDidChangeTextDocument((e) => {
|
||||||
|
if (e.document.uri.toString() === editor.document.uri.toString()) {
|
||||||
|
updateWebview();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make sure we get rid of the listener when our editor is closed.
|
||||||
|
panel.onDidDispose(() => {
|
||||||
|
changeDocumentSubscription.dispose();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
59
packages/vscode-ext/src/utils.ts
Normal file
59
packages/vscode-ext/src/utils.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import * as vscode from "vscode";
|
||||||
|
|
||||||
|
const 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 const getWebviewContent = ({
|
||||||
|
webview,
|
||||||
|
title,
|
||||||
|
script,
|
||||||
|
context,
|
||||||
|
}: {
|
||||||
|
webview: vscode.Webview;
|
||||||
|
title: string;
|
||||||
|
script: string;
|
||||||
|
context: vscode.ExtensionContext;
|
||||||
|
}) => {
|
||||||
|
const nonce = getNonce();
|
||||||
|
|
||||||
|
const styleUri = webview.asWebviewUri(
|
||||||
|
vscode.Uri.joinPath(context.extensionUri, "media/vendor/components.css")
|
||||||
|
);
|
||||||
|
|
||||||
|
const scriptUris = [
|
||||||
|
// vendor files are copied over by `yarn run compile`
|
||||||
|
"media/vendor/react.js",
|
||||||
|
"media/vendor/react-dom.js",
|
||||||
|
"media/vendor/components.js",
|
||||||
|
script,
|
||||||
|
].map((script) =>
|
||||||
|
webview.asWebviewUri(vscode.Uri.joinPath(context.extensionUri, script))
|
||||||
|
);
|
||||||
|
|
||||||
|
return `
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<!-- Use a content security policy to 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">
|
||||||
|
<link href="${styleUri}" rel="stylesheet" />
|
||||||
|
<title>${title}</title>
|
||||||
|
</head>
|
||||||
|
<body style="background-color: white; color: black; padding: 8px 12px">
|
||||||
|
<div id="root"></div>
|
||||||
|
${scriptUris
|
||||||
|
.map((uri) => `<script nonce="${nonce}" src="${uri}"></script>`)
|
||||||
|
.join("")}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
};
|
13
yarn.lock
13
yarn.lock
|
@ -14791,10 +14791,10 @@ react-vega@^7.5.1:
|
||||||
prop-types "^15.8.1"
|
prop-types "^15.8.1"
|
||||||
vega-embed "^6.5.1"
|
vega-embed "^6.5.1"
|
||||||
|
|
||||||
react@^18.0.0, react@^18.1.0:
|
react@^18.1.0:
|
||||||
version "18.2.0"
|
version "18.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
resolved "https://registry.yarnpkg.com/react/-/react-18.1.0.tgz#6f8620382decb17fdc5cc223a115e2adbf104890"
|
||||||
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
|
integrity sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
|
|
||||||
|
@ -17894,6 +17894,11 @@ vm-browserify@^1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
||||||
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
|
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
|
||||||
|
|
||||||
|
vscode-uri@^3.0.3:
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.3.tgz#a95c1ce2e6f41b7549f86279d19f47951e4f4d84"
|
||||||
|
integrity sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA==
|
||||||
|
|
||||||
w3c-hr-time@^1.0.2:
|
w3c-hr-time@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"
|
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user